Merge branch 'main' of http://113.45.157.195:3003/insiinc/leader-mail
This commit is contained in:
commit
8f43808ea2
|
@ -269,15 +269,18 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChildSimpleTree(
|
async getChildSimpleTree(
|
||||||
staff: UserProfile,
|
staff: UserProfile | null | undefined, // 允许 staff 为 null 或 undefined
|
||||||
data: z.infer<typeof TermMethodSchema.getSimpleTree>,
|
data: z.infer<typeof TermMethodSchema.getSimpleTree>,
|
||||||
) {
|
) {
|
||||||
const { domainId = null, permissions } = staff;
|
// 处理 staff 不存在的情况,设置默认值
|
||||||
const hasAnyPerms =
|
const domainId = staff?.domainId ?? null;
|
||||||
staff?.permissions?.includes(RolePerms.MANAGE_ANY_TERM) ||
|
const permissions = staff?.permissions ?? [];
|
||||||
staff?.permissions?.includes(RolePerms.READ_ANY_TERM);
|
|
||||||
|
// 权限判断逻辑调整
|
||||||
|
const hasAnyPerms = permissions.includes(RolePerms.MANAGE_ANY_TERM) ||
|
||||||
|
permissions.includes(RolePerms.READ_ANY_TERM);
|
||||||
|
|
||||||
const { termIds, parentId, taxonomyId } = data;
|
const { termIds, parentId, taxonomyId } = data;
|
||||||
// 提取非空 deptIds
|
|
||||||
const validTermIds = termIds?.filter((id) => id !== null) ?? [];
|
const validTermIds = termIds?.filter((id) => id !== null) ?? [];
|
||||||
const hasNullTermId = termIds?.includes(null) ?? false;
|
const hasNullTermId = termIds?.includes(null) ?? false;
|
||||||
|
|
||||||
|
@ -286,23 +289,18 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
|
||||||
where: {
|
where: {
|
||||||
...(termIds && {
|
...(termIds && {
|
||||||
OR: [
|
OR: [
|
||||||
...(validTermIds.length
|
...(validTermIds.length ? [{ ancestorId: { in: validTermIds } }] : []),
|
||||||
? [{ ancestorId: { in: validTermIds } }]
|
|
||||||
: []),
|
|
||||||
...(hasNullTermId ? [{ ancestorId: null }] : []),
|
...(hasNullTermId ? [{ ancestorId: null }] : []),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
descendant: {
|
descendant: {
|
||||||
taxonomyId: taxonomyId,
|
taxonomyId: taxonomyId,
|
||||||
// 动态权限控制条件
|
// 当 staff 不存在时 hasAnyPerms 自动为 false,会应用域过滤
|
||||||
...(hasAnyPerms
|
...(hasAnyPerms ? {} : {
|
||||||
? {} // 当有全局权限时,不添加任何额外条件
|
|
||||||
: {
|
|
||||||
// 当无全局权限时,添加域ID过滤
|
|
||||||
OR: [
|
OR: [
|
||||||
{ domainId: null }, // 通用记录
|
{ domainId: null },
|
||||||
{ domainId: domainId }, // 特定域记录
|
...(domainId ? [{ domainId }] : []) // 如果 domainId 为 null 则不添加
|
||||||
],
|
].filter(Boolean) // 过滤空数组
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ancestorId: parentId,
|
ancestorId: parentId,
|
||||||
|
@ -318,21 +316,16 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
|
||||||
where: {
|
where: {
|
||||||
...(termIds && {
|
...(termIds && {
|
||||||
OR: [
|
OR: [
|
||||||
...(validTermIds.length
|
...(validTermIds.length ? [{ id: { in: validTermIds } }] : []),
|
||||||
? [{ id: { in: validTermIds } }]
|
|
||||||
: []),
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
taxonomyId: taxonomyId,
|
taxonomyId: taxonomyId,
|
||||||
// 动态权限控制条件
|
// 同理处理第二个查询的权限
|
||||||
...(hasAnyPerms
|
...(hasAnyPerms ? {} : {
|
||||||
? {} // 当有全局权限时,不添加任何额外条件
|
|
||||||
: {
|
|
||||||
// 当无全局权限时,添加域ID过滤
|
|
||||||
OR: [
|
OR: [
|
||||||
{ domainId: null }, // 通用记录
|
{ domainId: null },
|
||||||
{ domainId: domainId }, // 特定域记录
|
...(domainId ? [{ domainId }] : [])
|
||||||
],
|
].filter(Boolean)
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
include: { children: true },
|
include: { children: true },
|
||||||
|
@ -340,6 +333,8 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
|
||||||
})
|
})
|
||||||
: [],
|
: [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 后续处理保持不变...
|
||||||
const children = childrenData
|
const children = childrenData
|
||||||
.map(({ descendant }) => descendant)
|
.map(({ descendant }) => descendant)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
@ -349,77 +344,68 @@ export class TermService extends BaseTreeService<Prisma.TermDelegate> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getParentSimpleTree(
|
async getParentSimpleTree(
|
||||||
staff: UserProfile,
|
staff: UserProfile | null | undefined, // 允许 staff 为 null 或 undefined
|
||||||
data: z.infer<typeof TermMethodSchema.getSimpleTree>,
|
data: z.infer<typeof TermMethodSchema.getSimpleTree>,
|
||||||
) {
|
) {
|
||||||
const { domainId = null, permissions } = staff;
|
// 安全解构 staff 属性并设置默认值
|
||||||
|
const domainId = staff?.domainId ?? null;
|
||||||
|
const permissions = staff?.permissions ?? [];
|
||||||
|
|
||||||
|
// 权限判断逻辑(自动处理空权限数组)
|
||||||
const hasAnyPerms =
|
const hasAnyPerms =
|
||||||
permissions.includes(RolePerms.READ_ANY_TERM) ||
|
permissions.includes(RolePerms.READ_ANY_TERM) ||
|
||||||
permissions.includes(RolePerms.MANAGE_ANY_TERM);
|
permissions.includes(RolePerms.MANAGE_ANY_TERM);
|
||||||
// 解构输入参数
|
|
||||||
const { termIds, taxonomyId } = data;
|
const { termIds, taxonomyId } = data;
|
||||||
|
|
||||||
// 并行查询父级部门ancestry和自身部门数据
|
// 构建通用权限过滤条件
|
||||||
// 使用Promise.all提高查询效率,减少等待时间
|
const buildDomainFilter = () => ({
|
||||||
|
OR: [
|
||||||
|
{ domainId: null }, // 始终包含公共记录
|
||||||
|
...(domainId ? [{ domainId }] : []) // 动态添加域过滤
|
||||||
|
].filter(Boolean) // 确保数组有效性
|
||||||
|
});
|
||||||
|
|
||||||
const [parentData, selfData] = await Promise.all([
|
const [parentData, selfData] = await Promise.all([
|
||||||
// 查询指定部门的所有祖先节点,包含子节点和父节点信息
|
|
||||||
db.termAncestry.findMany({
|
db.termAncestry.findMany({
|
||||||
where: {
|
where: {
|
||||||
descendantId: { in: termIds }, // 查询条件:descendant在给定的部门ID列表中
|
descendantId: { in: termIds },
|
||||||
ancestor: {
|
ancestor: {
|
||||||
taxonomyId: taxonomyId,
|
taxonomyId,
|
||||||
...(hasAnyPerms
|
// 应用动态权限过滤
|
||||||
? {} // 当有全局权限时,不添加任何额外条件
|
...(hasAnyPerms ? {} : buildDomainFilter())
|
||||||
: {
|
}
|
||||||
// 当无全局权限时,添加域ID过滤
|
|
||||||
OR: [
|
|
||||||
{ domainId: null }, // 通用记录
|
|
||||||
{ domainId: domainId }, // 特定域记录
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
ancestor: {
|
ancestor: {
|
||||||
include: {
|
include: {
|
||||||
children: true, // 包含子节点信息
|
children: true,
|
||||||
parent: true, // 包含父节点信息
|
parent: true
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
orderBy: { ancestor: { order: 'asc' } }
|
||||||
},
|
|
||||||
orderBy: { ancestor: { order: 'asc' } }, // 按祖先节点顺序升序排序
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// 查询自身部门数据
|
|
||||||
db.term.findMany({
|
db.term.findMany({
|
||||||
where: {
|
where: {
|
||||||
id: { in: termIds },
|
id: { in: termIds },
|
||||||
taxonomyId: taxonomyId,
|
taxonomyId,
|
||||||
...(hasAnyPerms
|
// 应用相同的权限过滤逻辑
|
||||||
? {} // 当有全局权限时,不添加任何额外条件
|
...(hasAnyPerms ? {} : buildDomainFilter())
|
||||||
: {
|
|
||||||
// 当无全局权限时,添加域ID过滤
|
|
||||||
OR: [
|
|
||||||
{ domainId: null }, // 通用记录
|
|
||||||
{ domainId: domainId }, // 特定域记录
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
include: { children: true }, // 包含子节点信息
|
include: { children: true },
|
||||||
orderBy: { order: 'asc' }, // 按顺序升序排序
|
orderBy: { order: 'asc' }
|
||||||
}),
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 处理父级节点:过滤并映射为简单树结构
|
// 数据处理保持不变...
|
||||||
const parents = parentData
|
const parents = parentData
|
||||||
.map(({ ancestor }) => ancestor) // 提取祖先节点
|
.map(({ ancestor }) => ancestor)
|
||||||
.filter((ancestor) => ancestor) // 过滤有效且超出根节点层级的节点
|
.filter(Boolean)
|
||||||
.map(mapToTermSimpleTree); // 映射为简单树结构
|
.map(mapToTermSimpleTree);
|
||||||
|
|
||||||
// 处理自身节点:映射为简单树结构
|
|
||||||
const selfItems = selfData.map(mapToTermSimpleTree);
|
const selfItems = selfData.map(mapToTermSimpleTree);
|
||||||
|
|
||||||
// 合并并去重父级和自身节点,返回唯一项
|
|
||||||
return getUniqueItems([...parents, ...selfItems], 'id');
|
return getUniqueItems([...parents, ...selfItems], 'id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,12 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => {
|
||||||
label="用户名"
|
label="用户名"
|
||||||
name="username"
|
name="username"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: "Username is required" },
|
{ required: true, message: "请输入用户名" },
|
||||||
{ min: 2, message: "Username must be at least 2 characters" }
|
{ min: 2, message: "用户名至少需要2个字符" }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
placeholder="Username"
|
placeholder="请输入用户名"
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
@ -45,11 +45,11 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => {
|
||||||
label="密码"
|
label="密码"
|
||||||
name="password"
|
name="password"
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: "Password is required" }
|
{ required: true, message: "请输入密码" }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
placeholder="Password"
|
placeholder="请输入密码"
|
||||||
className="rounded-lg"
|
className="rounded-lg"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
|
@ -6,11 +6,6 @@ import { LoginForm } from "./login";
|
||||||
import { Card, Typography, Button, Spin, Divider } from "antd";
|
import { Card, Typography, Button, Spin, Divider } from "antd";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { useAuthForm } from "./useAuthForm";
|
import { useAuthForm } from "./useAuthForm";
|
||||||
import {
|
|
||||||
GithubOutlined,
|
|
||||||
GoogleOutlined,
|
|
||||||
LoadingOutlined
|
|
||||||
} from '@ant-design/icons';
|
|
||||||
|
|
||||||
const { Title, Text, Paragraph } = Typography;
|
const { Title, Text, Paragraph } = Typography;
|
||||||
|
|
||||||
|
@ -41,23 +36,22 @@ const AuthPage: React.FC = () => {
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.5 }}
|
transition={{ duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<Card
|
<div
|
||||||
className="w-full max-w-5xl shadow-xl rounded-3xl overflow-hidden transition-all duration-300 hover:shadow-3xl relative backdrop-blur-sm bg-white/90"
|
className="w-full max-w-5xl shadow-elegant border-2 border-white rounded-xl overflow-hidden transition-all duration-300 relative"
|
||||||
bodyStyle={{ padding: 0 }}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col md:flex-row min-h-[650px]">
|
<div className="flex flex-col md:flex-row min-h-[650px]">
|
||||||
{/* Left Panel - Welcome Section */}
|
{/* Left Panel - Welcome Section */}
|
||||||
<div className="w-full md:w-1/2 p-12 bg-gradient-to-br from-primary to-primary-400 text-white flex flex-col justify-center relative overflow-hidden">
|
<div className="w-full md:w-1/2 p-12 bg-gradient-to-br from-primary to-primary-600 text-white flex flex-col justify-center relative overflow-hidden">
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ delay: 0.2, duration: 0.5 }}
|
transition={{ delay: 0.2, duration: 0.5 }}
|
||||||
>
|
>
|
||||||
<div className=" text-4xl text-white mb-4">
|
<div className="text-4xl text-white mb-4 font-serif">
|
||||||
欢迎来到 Leader Mail
|
首长机关信箱
|
||||||
</div>
|
</div>
|
||||||
<Paragraph className="text-lg mb-8 text-blue-100">
|
<Paragraph className="text-lg mb-8 text-blue-100 leading-relaxed text-justify">
|
||||||
与领导者建立联系,分享见解,在专为职业发展设计的协作环境中共同成长。
|
倾听官兵心声,及时响应诉求,竭诚为每一位战友解难题、办实事
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
{showLogin && (
|
{showLogin && (
|
||||||
<Button
|
<Button
|
||||||
|
@ -67,7 +61,7 @@ const AuthPage: React.FC = () => {
|
||||||
onClick={toggleForm}
|
onClick={toggleForm}
|
||||||
className="w-fit hover:bg-white hover:text-blue-700 transition-all"
|
className="w-fit hover:bg-white hover:text-blue-700 transition-all"
|
||||||
>
|
>
|
||||||
创建新账户
|
注册账号
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@ -88,13 +82,13 @@ const AuthPage: React.FC = () => {
|
||||||
<motion.div className="mb-8">
|
<motion.div className="mb-8">
|
||||||
<Title level={3} className="mb-2">登录</Title>
|
<Title level={3} className="mb-2">登录</Title>
|
||||||
<Text type="secondary">
|
<Text type="secondary">
|
||||||
还没有账户?{' '}
|
首次使用?{' '}
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={toggleForm}
|
onClick={toggleForm}
|
||||||
className="p-0 font-medium"
|
className="p-0 font-medium"
|
||||||
>
|
>
|
||||||
立即注册
|
注册账号
|
||||||
</Button>
|
</Button>
|
||||||
</Text>
|
</Text>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@ -113,15 +107,15 @@ const AuthPage: React.FC = () => {
|
||||||
transition={{ duration: 0.3 }}
|
transition={{ duration: 0.3 }}
|
||||||
>
|
>
|
||||||
<motion.div className="mb-8">
|
<motion.div className="mb-8">
|
||||||
<Title level={3} className="mb-2">创建账户</Title>
|
<Title level={3} className="mb-2">注册账号</Title>
|
||||||
<Text type="secondary">
|
<Text type="secondary">
|
||||||
已有账户?{' '}
|
已有账号?{' '}
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
onClick={toggleForm}
|
onClick={toggleForm}
|
||||||
className="p-0 font-medium"
|
className="p-0 font-medium"
|
||||||
>
|
>
|
||||||
立即登录
|
登录平台
|
||||||
</Button>
|
</Button>
|
||||||
</Text>
|
</Text>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
@ -135,7 +129,7 @@ const AuthPage: React.FC = () => {
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => {
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
className="w-full h-10 rounded-lg"
|
className="w-full h-10 rounded-lg"
|
||||||
>
|
>
|
||||||
{isLoading ? "正在创建账户..." : "创建账户"}
|
{isLoading ? "正在注册..." : "注册"}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -9,7 +9,7 @@ export default function LetterEditorPage() {
|
||||||
const termId = searchParams.get("termId");
|
const termId = searchParams.get("termId");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen rounded-xl bg-gradient-to-b from-slate-100 to-slate-50 ">
|
<div className="min-h-screen rounded-xl shadow-elegant border-2 border-white bg-gradient-to-b from-slate-100 to-slate-50 ">
|
||||||
<WriteHeader></WriteHeader>
|
<WriteHeader></WriteHeader>
|
||||||
<LetterFormProvider receiverId={receiverId} termId={termId}>
|
<LetterFormProvider receiverId={receiverId} termId={termId}>
|
||||||
<LetterBasicForm />
|
<LetterBasicForm />
|
||||||
|
|
|
@ -1,20 +1,72 @@
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="bg-gradient-to-r from-primary to-primary-400 p-6">
|
<header className="bg-gradient-to-r from-primary to-primary-400 p-6 rounded-t-xl">
|
||||||
<h1 className="text-3xl font-bold text-white">公开信件列表</h1>
|
<div className="flex flex-col space-y-6">
|
||||||
<div className="mt-4 text-blue-50">
|
{/* 主标题区域 */}
|
||||||
<p className="text-base opacity-90">
|
<div>
|
||||||
服务宗旨:畅通诉求渠道 • 促进建设发展 • 提升单位战斗力
|
<h1 className="text-3xl font-bold tracking-wider text-white">公开信件列表</h1>
|
||||||
|
<p className="mt-2 text-blue-100 text-lg">
|
||||||
|
透明公开 • 及时反馈 • 有效解决
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-2 text-sm opacity-80 flex flex-wrap gap-x-6 gap-y-2">
|
</div>
|
||||||
<span>实时跟踪反馈进度</span>
|
|
||||||
<span>保障信息传递安全</span>
|
{/* 服务特点说明 */}
|
||||||
|
<div className="flex flex-wrap gap-6 text-sm text-white">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>公开透明处理流程</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>及时跟进反馈进度</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
<span>高效解决实际问题</span>
|
<span>高效解决实际问题</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 服务宗旨说明 */}
|
||||||
|
<div className="text-sm text-blue-100 border-t border-blue-400/30 pt-4">
|
||||||
|
<p className="leading-relaxed">
|
||||||
|
我们致力于建设开放透明的信件处理平台,通过公开信件处理过程,展示单位工作成效,
|
||||||
|
促进各项工作有序开展,切实解决实际问题,提升单位整体战斗力。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@ export default function LetterListPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// 添加 flex flex-col 使其成为弹性布局容器
|
// 添加 flex flex-col 使其成为弹性布局容器
|
||||||
<div className="min-h-screen rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col">
|
<div className="min-h-screen shadow-elegant border-2 border-white rounded-xl overflow-hidden bg-gradient-to-b from-slate-100 to-slate-50 flex flex-col">
|
||||||
<Header />
|
<Header />
|
||||||
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */}
|
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
export default function ProgressHeader() {
|
||||||
|
return <header className=" rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6">
|
||||||
|
<div className="flex flex-col space-y-6">
|
||||||
|
{/* 主标题 */}
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold tracking-wider">
|
||||||
|
查询处理进度
|
||||||
|
</h1>
|
||||||
|
<p className="mt-2 text-blue-100 text-lg">
|
||||||
|
实时跟踪您的信件处理状态
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 处理状态说明 */}
|
||||||
|
<div className="flex flex-wrap gap-6 text-sm">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>身份信息已加密</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>全程跟踪处理状态</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
className="w-5 h-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M8 11V7a4 4 0 118 0m-4 8v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<span>处理结果及时反馈</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 处理说明 */}
|
||||||
|
<div className="text-sm text-blue-100 border-t border-blue-400/30 pt-4">
|
||||||
|
<p>您可以随时查看问题的处理进度,我们会及时更新处理状态并通知您最新进展。所有信息都经过加密保护,确保处理过程的安全性。</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Input, Button, Card, Steps, Tag, Spin, message } from "antd";
|
import { Input, Button, Card, Steps, Tag, Spin, message } from "antd";
|
||||||
import { SearchOutlined, SafetyCertificateOutlined } from "@ant-design/icons";
|
import { SearchOutlined, SafetyCertificateOutlined } from "@ant-design/icons";
|
||||||
|
import ProgressHeader from "./ProgressHeader";
|
||||||
|
|
||||||
interface FeedbackStatus {
|
interface FeedbackStatus {
|
||||||
status: "pending" | "processing" | "resolved";
|
status: "pending" | "processing" | "resolved";
|
||||||
|
@ -62,61 +63,37 @@ export default function LetterProgressPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="min-h-screen bg-white"
|
className="min-h-screen shadow-elegant border-2 border-white rounded-xl bg-gradient-to-b from-slate-100 to-slate-50 "
|
||||||
style={{ fontFamily: "Arial, sans-serif" }}>
|
style={{ fontFamily: "Arial, sans-serif" }}>
|
||||||
{/* Header */}
|
<ProgressHeader></ProgressHeader>
|
||||||
<header className="bg-[#003366] p-8 text-white">
|
|
||||||
<div className="container mx-auto flex items-center">
|
|
||||||
<SafetyCertificateOutlined className="text-4xl mr-4" />
|
|
||||||
<div>
|
|
||||||
<h1 className="text-3xl font-bold mb-2">
|
|
||||||
USAF Feedback Tracking System
|
|
||||||
</h1>
|
|
||||||
<p className="text-lg">
|
|
||||||
Enter your ticket ID to track progress
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
{/* Main Content */}
|
||||||
<main className="container mx-auto px-4 py-8">
|
<main className="container mx-auto p-6">
|
||||||
{/* Search Section */}
|
|
||||||
<Card className="mb-8 border border-gray-200">
|
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<label className="block text-lg font-medium text-[#003366]">
|
|
||||||
Ticket ID
|
|
||||||
</label>
|
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<Input
|
<Input
|
||||||
prefix={
|
prefix={
|
||||||
<SearchOutlined className="text-[#003366]" />
|
<SearchOutlined className=" text-secondary-300" />
|
||||||
}
|
}
|
||||||
size="large"
|
size="large"
|
||||||
value={feedbackId}
|
value={feedbackId}
|
||||||
onChange={(e) => setFeedbackId(e.target.value)}
|
onChange={(e) => setFeedbackId(e.target.value)}
|
||||||
placeholder="e.g. USAF-2025-0123"
|
placeholder="请输入信件编码查询处理状态"
|
||||||
status={error ? "error" : ""}
|
status={error ? "error" : ""}
|
||||||
className="border border-gray-300"
|
className="border border-gray-300"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
size="large"
|
size="large"
|
||||||
icon={<SearchOutlined />}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClick={mockLookup}
|
onClick={mockLookup}
|
||||||
style={{
|
>
|
||||||
backgroundColor: "#003366",
|
查询
|
||||||
borderColor: "#003366",
|
|
||||||
}}>
|
|
||||||
Track
|
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{error && (
|
{error && (
|
||||||
<p className="text-red-600 text-sm">{error}</p>
|
<p className="text-red-600 text-sm">{error}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Results Section */}
|
{/* Results Section */}
|
||||||
{status && (
|
{status && (
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { StaffDto } from "@nice/common";
|
import { StaffDto } from "@nice/common";
|
||||||
import { Button, Tooltip, Badge } from 'antd';
|
import { Button, Tooltip, Badge } from 'antd';
|
||||||
import { SendOutlined } from '@ant-design/icons';
|
import { BankFilled, SendOutlined } from '@ant-design/icons';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
export interface SendCardProps {
|
export interface SendCardProps {
|
||||||
|
@ -13,15 +13,12 @@ export function SendCard({ staff, termId }: SendCardProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSendLetter = () => {
|
const handleSendLetter = () => {
|
||||||
navigate(`/editor?termId=${termId || ''}&receiverId=${staff.id}`);
|
window.open(`/editor?termId=${termId || ''}&receiverId=${staff.id}`, '_blank');
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
className="bg-white rounded-xl overflow-hidden border-2 hover:border-primary transition-all duration-300"
|
||||||
whileHover={{ scale: 1.005 }}
|
|
||||||
transition={{ duration: 0.2 }}
|
|
||||||
className="bg-white rounded-xl shadow-lg overflow-hidden border-2 border-gray-100 hover:border-primary-200 transition-all duration-300"
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col sm:flex-row">
|
<div className="flex flex-col sm:flex-row">
|
||||||
{/* Image Container */}
|
{/* Image Container */}
|
||||||
|
@ -61,11 +58,14 @@ export function SendCard({ staff, termId }: SendCardProps) {
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-3 mb-2">
|
<div className="flex items-center gap-3 mb-2">
|
||||||
<h3 className="text-2xl font-semibold text-gray-900">
|
<h3 className="text-2xl font-semibold text-gray-900">
|
||||||
{staff?.showname}
|
{staff?.showname || staff?.username}
|
||||||
</h3>
|
</h3>
|
||||||
<Badge status="success" />
|
|
||||||
</div>
|
</div>
|
||||||
<p className="text-gray-600 text-lg font-medium">{staff.department?.name || '未设置部门'}</p>
|
<p className="text-gray-600 text-lg font-medium flex items-center gap-2">
|
||||||
|
|
||||||
|
<BankFilled></BankFilled>
|
||||||
|
{staff.department?.name || '未设置部门'}</p>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip title="职级">
|
<Tooltip title="职级">
|
||||||
<span className="inline-flex items-center px-4 py-1.5 text-sm font-medium bg-gradient-to-r from-blue-50 to-blue-100 text-primary rounded-full hover:from-blue-100 hover:to-blue-200 transition-colors shadow-sm">
|
<span className="inline-flex items-center px-4 py-1.5 text-sm font-medium bg-gradient-to-r from-blue-50 to-blue-100 text-primary rounded-full hover:from-blue-100 hover:to-blue-200 transition-colors shadow-sm">
|
||||||
|
@ -112,6 +112,6 @@ export function SendCard({ staff, termId }: SendCardProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
export default function WriteHeader() {
|
import { TermDto } from "@nice/common";
|
||||||
|
|
||||||
|
export default function WriteHeader({ term }: { term?: TermDto }) {
|
||||||
return <header className=" rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6">
|
return <header className=" rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6">
|
||||||
<div className="flex flex-col space-y-6">
|
<div className="flex flex-col space-y-6">
|
||||||
{/* 主标题 */}
|
{/* 主标题 */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold tracking-wider">
|
<h1 className="text-3xl font-bold tracking-wider">
|
||||||
信件投递入口
|
{term?.name}信件投递入口
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 text-blue-100 text-lg">
|
<p className="mt-2 text-blue-100 text-lg">
|
||||||
保护您隐私的信件传输平台
|
保护您隐私的信件传输平台
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { SendCard } from './SendCard';
|
import { SendCard } from './SendCard';
|
||||||
import { Spin, Empty, Input, Alert, Pagination } from 'antd';
|
import { Spin, Empty, Input, Alert, Pagination } from 'antd';
|
||||||
import { api } from '@nice/client';
|
import { api, useTerm } from '@nice/client';
|
||||||
import DepartmentSelect from '@web/src/components/models/department/department-select';
|
import DepartmentSelect from '@web/src/components/models/department/department-select';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
|
@ -17,6 +17,7 @@ export default function WriteLetterPage() {
|
||||||
const [selectedDept, setSelectedDept] = useState<string>();
|
const [selectedDept, setSelectedDept] = useState<string>();
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const pageSize = 10;
|
const pageSize = 10;
|
||||||
|
const { getTerm } = useTerm()
|
||||||
|
|
||||||
const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({
|
const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
|
@ -45,8 +46,8 @@ export default function WriteLetterPage() {
|
||||||
}, [searchQuery, selectedDept, resetPage]);
|
}, [searchQuery, selectedDept, resetPage]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gradient-to-b from-slate-100 to-slate-50">
|
<div className="min-h-screen shadow-elegant border-2 border-white rounded-xl bg-gradient-to-b from-slate-100 to-slate-50">
|
||||||
<WriteHeader />
|
<WriteHeader term={getTerm(termId)} />
|
||||||
<div className="container mx-auto px-4 py-8">
|
<div className="container mx-auto px-4 py-8">
|
||||||
<div className="mb-8 space-y-4">
|
<div className="mb-8 space-y-4">
|
||||||
{/* Search and Filter Section */}
|
{/* Search and Filter Section */}
|
||||||
|
@ -120,7 +121,10 @@ export default function WriteLetterPage() {
|
||||||
current={currentPage}
|
current={currentPage}
|
||||||
total={data?.totalPages || 0}
|
total={data?.totalPages || 0}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
onChange={(page) => setCurrentPage(page)}
|
onChange={(page) => {
|
||||||
|
setCurrentPage(page);
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}}
|
||||||
showSizeChanger={false}
|
showSizeChanger={false}
|
||||||
showTotal={(total) => `共 ${total} 条记录`}
|
showTotal={(total) => `共 ${total} 条记录`}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface LogoProps {
|
||||||
|
size?: 'small' | 'medium' | 'large';
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InnovationLogo: React.FC<LogoProps> = ({
|
||||||
|
size = 'medium',
|
||||||
|
className = ''
|
||||||
|
}) => {
|
||||||
|
const sizeClasses = {
|
||||||
|
small: 'w-8 h-8',
|
||||||
|
medium: 'w-16 h-16',
|
||||||
|
large: 'w-24 h-24'
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 200 200"
|
||||||
|
className={`${sizeClasses[size]} ${className}`}
|
||||||
|
>
|
||||||
|
{/* 背景圆形 */}
|
||||||
|
<circle
|
||||||
|
cx="100"
|
||||||
|
cy="100"
|
||||||
|
r="90"
|
||||||
|
fill="#1A1A1A"
|
||||||
|
className="transition-colors duration-300"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 六边形原子核心 */}
|
||||||
|
<polygon
|
||||||
|
points="100,20 160,55 160,125 100,160 40,125 40,55"
|
||||||
|
fill="none"
|
||||||
|
stroke="#61DAFB"
|
||||||
|
strokeWidth="12"
|
||||||
|
className="transition-all duration-500 hover:stroke-blue-400"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 三个原子轨道 */}
|
||||||
|
<g stroke="#61DAFB" strokeWidth="4" fill="none">
|
||||||
|
{/* 外层轨道 */}
|
||||||
|
<circle
|
||||||
|
cx="100"
|
||||||
|
cy="100"
|
||||||
|
r="80"
|
||||||
|
className="animate-spin-slow opacity-50"
|
||||||
|
/>
|
||||||
|
{/* 中层轨道 */}
|
||||||
|
<circle
|
||||||
|
cx="100"
|
||||||
|
cy="100"
|
||||||
|
r="60"
|
||||||
|
className="animate-spin-reverse-slow opacity-50"
|
||||||
|
/>
|
||||||
|
{/* 内层轨道 */}
|
||||||
|
<circle
|
||||||
|
cx="100"
|
||||||
|
cy="100"
|
||||||
|
r="40"
|
||||||
|
className="animate-spin-slow opacity-50"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
|
||||||
|
{/* 闪电/创新元素 */}
|
||||||
|
<path
|
||||||
|
d="M80,110 L120,90 L100,140 Z"
|
||||||
|
fill="#FFD700"
|
||||||
|
className="transition-all duration-300 hover:scale-110"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InnovationLogo;
|
|
@ -1,46 +0,0 @@
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
|
|
||||||
export const Logo = () => (
|
|
||||||
<Link to="/" className="flex items-center space-x-3 group rounded-lg focus:outline-none focus:ring-2 focus:ring-[#8EADD4]" aria-label="Go to homepage">
|
|
||||||
<div className="relative h-12 w-12 transform transition-transform group-hover:scale-105">
|
|
||||||
<svg
|
|
||||||
viewBox="0 0 100 100"
|
|
||||||
className="h-full w-full transition-transform duration-300"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
{/* Mail envelope base */}
|
|
||||||
<rect
|
|
||||||
x="10"
|
|
||||||
y="25"
|
|
||||||
width="80"
|
|
||||||
height="50"
|
|
||||||
rx="4"
|
|
||||||
className="fill-[#8EADD4] transition-colors duration-300 group-hover:fill-[#6B8CB3] rounded-lg"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Envelope flap */}
|
|
||||||
<path
|
|
||||||
d="M10 29L50 55L90 29"
|
|
||||||
strokeWidth="3"
|
|
||||||
strokeLinecap="round"
|
|
||||||
className="stroke-white"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* People silhouette */}
|
|
||||||
<path
|
|
||||||
d="M40 45C40 45 35 50 30 50C25 50 20 45 20 45C20 45 25 55 30 55C35 55 40 45 40 45Z"
|
|
||||||
className="fill-white"
|
|
||||||
/>
|
|
||||||
<circle cx="30" cy="42" r="5" className="fill-white" />
|
|
||||||
|
|
||||||
{/* Leadership star */}
|
|
||||||
<path
|
|
||||||
d="M70 42L72.5 47L78 48L74 52L75 57L70 54.5L65 57L66 52L62 48L67.5 47L70 42Z"
|
|
||||||
className="fill-white transition-transform origin-center group-hover:rotate-45 duration-500"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</Link>
|
|
||||||
);
|
|
|
@ -1,78 +1,69 @@
|
||||||
import { PhoneIcon } from '@heroicons/react/24/outline'
|
import { PhoneOutlined, MailOutlined, CloudOutlined, HomeOutlined, FileSearchOutlined, FireTwoTone, FireOutlined } from '@ant-design/icons';
|
||||||
|
import Logo from '../../common/element/Logo';
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="bg-gradient-to-b from-[#13294B] to-[#0c1c33] text-gray-300">
|
<footer className="bg-gradient-to-b from-primary-600 to-primary-800 text-secondary-200">
|
||||||
<div className="container mx-auto px-6 py-10">
|
<div className="container mx-auto px-4 py-6">
|
||||||
{/* Main Footer Content */}
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-10 mb-8">
|
{/* 开发组织信息 */}
|
||||||
{/* Logo and Main Info */}
|
<div className="text-center md:text-left space-y-2">
|
||||||
<div className="text-center md:text-left">
|
<h3 className="text-white font-semibold text-sm flex items-center justify-center md:justify-start">
|
||||||
<div className="flex md:justify-start justify-center mb-4 group">
|
创新高地 软件小组
|
||||||
<img
|
|
||||||
src="/usaf-emblem.png"
|
|
||||||
alt="USAF Official Emblem"
|
|
||||||
className="h-16 w-auto transform transition-all duration-300
|
|
||||||
group-hover:scale-105 group-hover:brightness-110
|
|
||||||
drop-shadow-lg"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<h3 className="text-white font-bold text-lg tracking-wide mb-2
|
|
||||||
drop-shadow-md">
|
|
||||||
官方领导机关信箱
|
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-tertiary-300 text-sm">
|
<p className="text-gray-400 text-xs">
|
||||||
为提供安全可靠的通信服务
|
提供系统开发与技术支持
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Technical Support */}
|
{/* 联系方式 */}
|
||||||
<div className="text-center md:text-left md:pl-6 md:border-l border-gray-700">
|
<div className="text-center space-y-2">
|
||||||
<h4 className="text-white font-semibold mb-4 text-lg tracking-wide
|
<div className="flex items-center justify-center space-x-2">
|
||||||
drop-shadow-md">
|
<PhoneOutlined className="text-gray-400" />
|
||||||
技术支持
|
<span className="text-gray-300 text-xs">1-800-XXX-XXXX</span>
|
||||||
</h4>
|
</div>
|
||||||
<div className="space-y-4">
|
<div className="flex items-center justify-center space-x-2">
|
||||||
<div className="bg-gradient-to-br from-[#1a3a6a] to-[#234785]
|
<MailOutlined className="text-gray-400" />
|
||||||
p-4 rounded-lg shadow-lg hover:shadow-xl
|
<span className="text-gray-300 text-xs">support@example.com</span>
|
||||||
transition-all duration-300 transform hover:-translate-y-0.5">
|
|
||||||
<p className="text-white font-medium mb-2">创新高地 软件小组</p>
|
|
||||||
<p className="text-gray-300 text-sm">负责系统开发维护及技术支持</p>
|
|
||||||
<div className="mt-3 flex items-center justify-center md:justify-start gap-2
|
|
||||||
text-sm text-gray-300">
|
|
||||||
<PhoneIcon className="w-4 h-4" />
|
|
||||||
<span>1-800-XXX-XXXX</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-tertiary-300 text-sm italic">
|
|
||||||
由软件小组提供 24/7 专业技术支持
|
{/* 系统链接 */}
|
||||||
</p>
|
<div className="text-center md:text-right space-y-2">
|
||||||
|
<div className="flex items-center justify-center md:justify-end space-x-4">
|
||||||
|
<a
|
||||||
|
href="https://portal.example.com"
|
||||||
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
|
title="访问门户网站"
|
||||||
|
>
|
||||||
|
<HomeOutlined className="text-lg" />
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://nextcloud.example.com"
|
||||||
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
|
title="访问烽火青云"
|
||||||
|
>
|
||||||
|
<CloudOutlined className="text-lg" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href="https://regulation.example.com"
|
||||||
|
className="text-gray-400 hover:text-white transition-colors"
|
||||||
|
title="访问烽火律询"
|
||||||
|
>
|
||||||
|
<FileSearchOutlined className="text-lg" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Divider */}
|
{/* 版权信息 */}
|
||||||
<div className="border-t border-gray-700/50 my-6"></div>
|
<div className="border-t border-gray-700/50 mt-4 pt-4 text-center">
|
||||||
|
<p className="text-gray-400 text-xs">
|
||||||
{/* Bottom Section */}
|
© {new Date().getFullYear()} 南天烽火. All rights reserved.
|
||||||
<div className="text-center">
|
|
||||||
<div className="flex flex-wrap justify-center gap-x-6 gap-y-2 mb-4 text-sm
|
|
||||||
text-tertiary-300">
|
|
||||||
<span className="hover:text-gray-300 transition-colors duration-200">
|
|
||||||
美国国防部授权网站
|
|
||||||
</span>
|
|
||||||
<span className="hover:text-gray-300 transition-colors duration-200">
|
|
||||||
所有通信内容受联邦法律保护
|
|
||||||
</span>
|
|
||||||
<span className="hover:text-gray-300 transition-colors duration-200">
|
|
||||||
政府信息系统
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="text-tertiary-300 text-sm">
|
|
||||||
© {new Date().getFullYear()} United States Air Force. All rights reserved.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,21 @@ import Navigation from "./navigation";
|
||||||
import { useAuth } from "@web/src/providers/auth-provider";
|
import { useAuth } from "@web/src/providers/auth-provider";
|
||||||
import { UserOutlined } from "@ant-design/icons";
|
import { UserOutlined } from "@ant-design/icons";
|
||||||
import { UserMenu } from "../element/usermenu";
|
import { UserMenu } from "../element/usermenu";
|
||||||
import { Logo } from "../element/Logo";
|
import SineWavesCanvas from "../../animation/sine-wave";
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
onSearch?: (query: string) => void;
|
onSearch?: (query: string) => void;
|
||||||
}
|
}
|
||||||
export const Header = memo(function Header({ onSearch }: HeaderProps) {
|
export const Header = memo(function Header({ onSearch }: HeaderProps) {
|
||||||
const { isAuthenticated } = useAuth()
|
const { isAuthenticated } = useAuth()
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-50 bg-[#13294B] text-white shadow-lg">
|
<header className="sticky top-0 z-50 bg-gradient-to-br from-primary-500 to-primary-800 text-white shadow-lg">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
<div className="py-3">
|
<div className="py-3">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<Logo />
|
<div className=" text-xl font-bold">
|
||||||
|
|
||||||
|
领导机关信箱
|
||||||
|
</div>
|
||||||
<div className="flex-grow max-w-2xl">
|
<div className="flex-grow max-w-2xl">
|
||||||
<SearchBar onSearch={onSearch} />
|
<SearchBar onSearch={onSearch} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { NavLink } from "react-router-dom";
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
import { useNavItem } from "./useNavItem";
|
import { useNavItem } from "./useNavItem";
|
||||||
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
interface NavItem {
|
||||||
|
to: string;
|
||||||
|
label: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
interface NavigationProps {
|
interface NavigationProps {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
@ -7,46 +14,83 @@ interface NavigationProps {
|
||||||
|
|
||||||
export default function Navigation({ className }: NavigationProps) {
|
export default function Navigation({ className }: NavigationProps) {
|
||||||
const { navItems } = useNavItem();
|
const { navItems } = useNavItem();
|
||||||
|
const location = useLocation();
|
||||||
|
|
||||||
|
const isActive = (to: string) => {
|
||||||
|
const [pathname, search] = to.split('?');
|
||||||
|
return location.pathname === pathname &&
|
||||||
|
(!search ? !location.search : location.search === `?${search}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className={`mt-4 rounded-lg bg-[#0B1A32]/90 ${className}`}>
|
<nav className={twMerge(
|
||||||
|
"mt-4 rounded-xl bg-gradient-to-r from-primary-500 to-primary-600 shadow-lg",
|
||||||
|
className
|
||||||
|
)}>
|
||||||
<div className="flex flex-col md:flex-row items-stretch">
|
<div className="flex flex-col md:flex-row items-stretch">
|
||||||
{/* Desktop Navigation */}
|
{/* Desktop Navigation */}
|
||||||
<div className="hidden md:flex items-center px-6 py-1 w-full overflow-x-auto scrollbar-thin scrollbar-thumb-[#00308F] scrollbar-track-transparent">
|
<div className="hidden md:flex items-center px-6 py-2 w-full overflow-x-auto">
|
||||||
|
<div className="flex space-x-6 min-w-max">
|
||||||
|
{navItems.map((item) => (
|
||||||
|
<NavLink
|
||||||
|
key={item.to}
|
||||||
|
to={item.to}
|
||||||
|
className={({ isActive: active }) => twMerge(
|
||||||
|
"relative px-4 py-2.5 text-sm font-medium",
|
||||||
|
"text-gray-300 hover:text-white",
|
||||||
|
"transition-all duration-200 ease-out group",
|
||||||
|
active && "text-white"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="relative z-10 flex items-center gap-2 transition-transform group-hover:translate-y-[-1px]">
|
||||||
|
{item.icon}
|
||||||
|
<span className="tracking-wide">{item.label}</span>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
{/* Active Indicator */}
|
||||||
|
<span className={twMerge(
|
||||||
|
"absolute bottom-0 left-1/2 h-[2px] bg-blue-400",
|
||||||
|
"transition-all duration-300 ease-out",
|
||||||
|
"transform -translate-x-1/2",
|
||||||
|
isActive(item.to)
|
||||||
|
? "w-full opacity-100"
|
||||||
|
: "w-0 opacity-0 group-hover:w-1/2 group-hover:opacity-40"
|
||||||
|
)} />
|
||||||
|
|
||||||
|
{/* Hover Glow Effect */}
|
||||||
|
<span className={twMerge(
|
||||||
|
"absolute inset-0 rounded-lg bg-blue-400/0",
|
||||||
|
"transition-all duration-300",
|
||||||
|
"group-hover:bg-blue-400/5"
|
||||||
|
)} />
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Mobile Navigation */}
|
||||||
|
<div className="md:hidden flex overflow-x-auto scrollbar-none px-4 py-2">
|
||||||
<div className="flex space-x-4 min-w-max">
|
<div className="flex space-x-4 min-w-max">
|
||||||
{navItems.map((item) => (
|
{navItems.map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.to}
|
key={item.to}
|
||||||
to={item.to}
|
to={item.to}
|
||||||
className={({ isActive }) => `
|
className={({ isActive: active }) => twMerge(
|
||||||
group relative px-4 py-3
|
"px-3 py-1.5 text-sm font-medium rounded-full",
|
||||||
transition-all duration-300 ease-out
|
"transition-colors duration-200",
|
||||||
${isActive ? 'text-white font-medium' : 'text-[#8EADD4]'}
|
"text-gray-300 hover:text-white",
|
||||||
`}
|
active && "bg-blue-500/20 text-white"
|
||||||
>
|
|
||||||
{({ isActive }) => (
|
|
||||||
<>
|
|
||||||
<span className="relative z-10 transition-colors group-hover:text-white flex items-center gap-2">
|
|
||||||
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`
|
|
||||||
absolute bottom-1.5 left-1/2 h-[2px] bg-white
|
|
||||||
transition-all duration-300 ease-out
|
|
||||||
transform -translate-x-1/2
|
|
||||||
${isActive
|
|
||||||
? 'w-12 opacity-100'
|
|
||||||
: 'w-0 opacity-0 group-hover:w-8 group-hover:opacity-50'
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
>
|
||||||
|
<span className="flex items-center gap-1.5">
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>)
|
</nav>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,19 @@
|
||||||
import { api } from "@nice/client";
|
import { api } from "@nice/client";
|
||||||
import { TaxonomySlug } from "@nice/common";
|
import { TaxonomySlug } from "@nice/common";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
FileTextOutlined,
|
||||||
|
ScheduleOutlined,
|
||||||
|
QuestionCircleOutlined,
|
||||||
|
FolderOutlined,
|
||||||
|
TagsOutlined
|
||||||
|
} from "@ant-design/icons";
|
||||||
|
|
||||||
|
export interface NavItem {
|
||||||
|
to: string;
|
||||||
|
label: string;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
export function useNavItem() {
|
export function useNavItem() {
|
||||||
const { data } = api.term.findMany.useQuery({
|
const { data } = api.term.findMany.useQuery({
|
||||||
|
@ -12,9 +25,21 @@ export function useNavItem() {
|
||||||
const navItems = useMemo(() => {
|
const navItems = useMemo(() => {
|
||||||
// 定义固定的导航项
|
// 定义固定的导航项
|
||||||
const staticItems = {
|
const staticItems = {
|
||||||
letterList: { to: "/", label: "公开信件" },
|
letterList: {
|
||||||
letterProgress: { to: "/letter-progress", label: "进度查询" },
|
to: "/",
|
||||||
help: { to: "/help", label: "使用帮助" }
|
label: "公开信件",
|
||||||
|
icon: <FileTextOutlined className="text-base" />
|
||||||
|
},
|
||||||
|
letterProgress: {
|
||||||
|
to: "/letter-progress",
|
||||||
|
label: "进度查询",
|
||||||
|
icon: <ScheduleOutlined className="text-base" />
|
||||||
|
},
|
||||||
|
help: {
|
||||||
|
to: "/help",
|
||||||
|
label: "使用帮助",
|
||||||
|
icon: <QuestionCircleOutlined className="text-base" />
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -24,14 +49,15 @@ export function useNavItem() {
|
||||||
// 构建分类导航项
|
// 构建分类导航项
|
||||||
const categoryItems = data.map(term => ({
|
const categoryItems = data.map(term => ({
|
||||||
to: `/write-letter?termId=${term.id}`,
|
to: `/write-letter?termId=${term.id}`,
|
||||||
label: term.name
|
label: term.name,
|
||||||
|
icon: <TagsOutlined className="text-base"></TagsOutlined>
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 按照指定顺序返回导航项
|
// 按照指定顺序返回导航项
|
||||||
return [
|
return [
|
||||||
staticItems.letterList,
|
staticItems.letterList,
|
||||||
...categoryItems,
|
|
||||||
staticItems.letterProgress,
|
staticItems.letterProgress,
|
||||||
|
...categoryItems,
|
||||||
staticItems.help
|
staticItems.help
|
||||||
];
|
];
|
||||||
}, [data]);
|
}, [data]);
|
|
@ -67,7 +67,6 @@ export function LetterFormProvider({
|
||||||
},
|
},
|
||||||
state: PostState.PENDING,
|
state: PostState.PENDING,
|
||||||
isPublic: data?.isPublic,
|
isPublic: data?.isPublic,
|
||||||
|
|
||||||
resources: data.resources?.length
|
resources: data.resources?.length
|
||||||
? {
|
? {
|
||||||
connect: (
|
connect: (
|
||||||
|
@ -97,11 +96,7 @@ export function LetterFormProvider({
|
||||||
termId,
|
termId,
|
||||||
form,
|
form,
|
||||||
}}>
|
}}>
|
||||||
<Form<LetterFormData>
|
|
||||||
form={form}
|
|
||||||
initialValues={{ meta: { tags: [] } }}>
|
|
||||||
{children}
|
{children}
|
||||||
</Form>
|
|
||||||
</LetterEditorContext.Provider>
|
</LetterEditorContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { Form, Input, Button, Checkbox, Select } from "antd";
|
||||||
import { useLetterEditor } from "../context/LetterEditorContext";
|
import { useLetterEditor } from "../context/LetterEditorContext";
|
||||||
import { SendOutlined } from "@ant-design/icons";
|
import { SendOutlined } from "@ant-design/icons";
|
||||||
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
||||||
import { PostBadge } from "../../detail/badge/PostBadge";
|
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||||
import StaffSelect from "../../../staff/staff-select";
|
import StaffSelect from "../../../staff/staff-select";
|
||||||
import TermSelect from "../../../term/term-select";
|
import TermSelect from "../../../term/term-select";
|
||||||
|
@ -20,8 +19,8 @@ export function LetterBasicForm() {
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
meta: { tags: [] },
|
meta: { tags: [] },
|
||||||
receiverId,
|
receivers: [receiverId],
|
||||||
termId,
|
terms: [termId],
|
||||||
isPublic: true,
|
isPublic: true,
|
||||||
}}>
|
}}>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
@ -97,7 +96,7 @@ export function LetterBasicForm() {
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="isPublic"
|
name="isPublic"
|
||||||
valuePropName="checked"
|
valuePropName="checked"
|
||||||
initialValue={true}>
|
>
|
||||||
<Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm">
|
<Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm">
|
||||||
是否公开
|
是否公开
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|
|
@ -27,7 +27,6 @@ export default function TermSelect({
|
||||||
multiple = false,
|
multiple = false,
|
||||||
taxonomyId,
|
taxonomyId,
|
||||||
domainId,
|
domainId,
|
||||||
|
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
|
||||||
}: TermSelectProps) {
|
}: TermSelectProps) {
|
||||||
|
@ -87,7 +86,7 @@ export default function TermSelect({
|
||||||
|
|
||||||
setListTreeData(combinedDepts);
|
setListTreeData(combinedDepts);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching departments:", error);
|
console.error("Error fetching terms:", error);
|
||||||
}
|
}
|
||||||
}, [defaultValue, value, taxonomyId, utils, fetchParentTerms]);
|
}, [defaultValue, value, taxonomyId, utils, fetchParentTerms]);
|
||||||
|
|
||||||
|
@ -129,16 +128,6 @@ export default function TermSelect({
|
||||||
try {
|
try {
|
||||||
const allKeyIds =
|
const allKeyIds =
|
||||||
keys.map((key) => key.toString()).filter(Boolean) || [];
|
keys.map((key) => key.toString()).filter(Boolean) || [];
|
||||||
// const expandedNodes = await Promise.all(
|
|
||||||
// keys.map(async (key) => {
|
|
||||||
// return await utils.department.getChildSimpleTree.fetch({
|
|
||||||
// deptId: key.toString(),
|
|
||||||
// domain,
|
|
||||||
// });
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
//
|
|
||||||
//上面那样一个个拉会拉爆,必须直接拉deptIds
|
|
||||||
const expandedNodes = await utils.term.getChildSimpleTree.fetch({
|
const expandedNodes = await utils.term.getChildSimpleTree.fetch({
|
||||||
termIds: allKeyIds,
|
termIds: allKeyIds,
|
||||||
taxonomyId,
|
taxonomyId,
|
||||||
|
|
|
@ -77,15 +77,21 @@ export const NiceTailwindConfig: Config = {
|
||||||
solid: "2px 2px 0 0 rgba(0, 0, 0, 0.2)",
|
solid: "2px 2px 0 0 rgba(0, 0, 0, 0.2)",
|
||||||
glow: "0 0 8px rgba(230, 180, 0, 0.8)",
|
glow: "0 0 8px rgba(230, 180, 0, 0.8)",
|
||||||
inset: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.15)",
|
inset: "inset 0 2px 4px 0 rgba(0, 0, 0, 0.15)",
|
||||||
"elevation-1": "0 1px 2px 0 rgba(0, 0, 0, 0.1)",
|
"elevation-1": "0 1px 2px rgba(0, 48, 138, 0.05), 0 1px 1px rgba(0, 0, 0, 0.05)",
|
||||||
"elevation-2": "0 2px 4px 0 rgba(0, 0, 0, 0.15)",
|
"elevation-2": "0 2px 4px rgba(0, 48, 138, 0.1), 0 2px 2px rgba(0, 0, 0, 0.1)",
|
||||||
"elevation-3": "0 4px 8px 0 rgba(0, 0, 0, 0.2)",
|
"elevation-3": "0 4px 8px rgba(0, 48, 138, 0.15), 0 4px 4px rgba(0, 0, 0, 0.15)",
|
||||||
"elevation-4": "0 8px 16px 0 rgba(0, 0, 0, 0.25)",
|
"elevation-4": "0 8px 16px rgba(0, 48, 138, 0.2), 0 8px 8px rgba(0, 0, 0, 0.2)",
|
||||||
"elevation-5": "0 16px 32px 0 rgba(0, 0, 0, 0.3)",
|
"elevation-5": "0 16px 32px rgba(0, 48, 138, 0.25), 0 16px 16px rgba(0, 0, 0, 0.25)",
|
||||||
panel: "0 4px 6px -1px rgba(0, 48, 138, 0.1), 0 2px 4px -2px rgba(0, 48, 138, 0.1)",
|
panel: "0 4px 6px -1px rgba(0, 48, 138, 0.1), 0 2px 4px -2px rgba(0, 48, 138, 0.1), inset 0 -1px 2px rgba(255, 255, 255, 0.05)",
|
||||||
button: "0 2px 4px rgba(0, 48, 138, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.1)",
|
button: "0 2px 4px rgba(0, 48, 138, 0.2), inset 0 1px 2px rgba(255, 255, 255, 0.1), 0 1px 1px rgba(0, 0, 0, 0.05)",
|
||||||
card: "0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08)",
|
card: "0 4px 6px rgba(0, 48, 138, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08), inset 0 -1px 2px rgba(255, 255, 255, 0.05)",
|
||||||
modal: "0 8px 32px rgba(0, 0, 0, 0.2), 0 4px 16px rgba(0, 0, 0, 0.15)",
|
modal: "0 8px 32px rgba(0, 48, 138, 0.2), 0 4px 16px rgba(0, 0, 0, 0.15), inset 0 -2px 4px rgba(255, 255, 255, 0.05)",
|
||||||
|
"soft-primary": "0 2px 4px rgba(0, 48, 138, 0.1), 0 4px 8px rgba(0, 48, 138, 0.05)",
|
||||||
|
"soft-secondary": "0 2px 4px rgba(77, 77, 77, 0.1), 0 4px 8px rgba(77, 77, 77, 0.05)",
|
||||||
|
"soft-accent": "0 2px 4px rgba(230, 180, 0, 0.1), 0 4px 8px rgba(230, 180, 0, 0.05)",
|
||||||
|
"inner-glow": "inset 0 0 8px rgba(0, 48, 138, 0.1)",
|
||||||
|
"outer-glow": "0 0 16px rgba(0, 48, 138, 0.1)",
|
||||||
|
"elegant": "0 4px 24px rgba(0, 48, 138, 0.15), 0 2px 12px rgba(0, 48, 138, 0.1), 0 1px 6px rgba(0, 48, 138, 0.05), inset 0 -1px 2px rgba(255, 255, 255, 0.1)",
|
||||||
},
|
},
|
||||||
animation: {
|
animation: {
|
||||||
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
||||||
|
|
Loading…
Reference in New Issue