diff --git a/apps/server/src/models/term/term.service.ts b/apps/server/src/models/term/term.service.ts index 7f21041..6c86b49 100755 --- a/apps/server/src/models/term/term.service.ts +++ b/apps/server/src/models/term/term.service.ts @@ -269,15 +269,18 @@ export class TermService extends BaseTreeService { } async getChildSimpleTree( - staff: UserProfile, + staff: UserProfile | null | undefined, // 允许 staff 为 null 或 undefined data: z.infer, ) { - const { domainId = null, permissions } = staff; - const hasAnyPerms = - staff?.permissions?.includes(RolePerms.MANAGE_ANY_TERM) || - staff?.permissions?.includes(RolePerms.READ_ANY_TERM); + // 处理 staff 不存在的情况,设置默认值 + const domainId = staff?.domainId ?? null; + const permissions = staff?.permissions ?? []; + + // 权限判断逻辑调整 + const hasAnyPerms = permissions.includes(RolePerms.MANAGE_ANY_TERM) || + permissions.includes(RolePerms.READ_ANY_TERM); + const { termIds, parentId, taxonomyId } = data; - // 提取非空 deptIds const validTermIds = termIds?.filter((id) => id !== null) ?? []; const hasNullTermId = termIds?.includes(null) ?? false; @@ -286,24 +289,19 @@ export class TermService extends BaseTreeService { where: { ...(termIds && { OR: [ - ...(validTermIds.length - ? [{ ancestorId: { in: validTermIds } }] - : []), + ...(validTermIds.length ? [{ ancestorId: { in: validTermIds } }] : []), ...(hasNullTermId ? [{ ancestorId: null }] : []), ], }), descendant: { taxonomyId: taxonomyId, - // 动态权限控制条件 - ...(hasAnyPerms - ? {} // 当有全局权限时,不添加任何额外条件 - : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), + // 当 staff 不存在时 hasAnyPerms 自动为 false,会应用域过滤 + ...(hasAnyPerms ? {} : { + OR: [ + { domainId: null }, + ...(domainId ? [{ domainId }] : []) // 如果 domainId 为 null 则不添加 + ].filter(Boolean) // 过滤空数组 + }), }, ancestorId: parentId, relDepth: 1, @@ -315,31 +313,28 @@ export class TermService extends BaseTreeService { }), termIds ? db.term.findMany({ - where: { - ...(termIds && { - OR: [ - ...(validTermIds.length - ? [{ id: { in: validTermIds } }] - : []), - ], - }), - taxonomyId: taxonomyId, - // 动态权限控制条件 - ...(hasAnyPerms - ? {} // 当有全局权限时,不添加任何额外条件 - : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), - }, - include: { children: true }, - orderBy: { order: 'asc' }, - }) + where: { + ...(termIds && { + OR: [ + ...(validTermIds.length ? [{ id: { in: validTermIds } }] : []), + ], + }), + taxonomyId: taxonomyId, + // 同理处理第二个查询的权限 + ...(hasAnyPerms ? {} : { + OR: [ + { domainId: null }, + ...(domainId ? [{ domainId }] : []) + ].filter(Boolean) + }), + }, + include: { children: true }, + orderBy: { order: 'asc' }, + }) : [], ]); + + // 后续处理保持不变... const children = childrenData .map(({ descendant }) => descendant) .filter(Boolean) @@ -349,77 +344,68 @@ export class TermService extends BaseTreeService { } async getParentSimpleTree( - staff: UserProfile, + staff: UserProfile | null | undefined, // 允许 staff 为 null 或 undefined data: z.infer, ) { - const { domainId = null, permissions } = staff; + // 安全解构 staff 属性并设置默认值 + const domainId = staff?.domainId ?? null; + const permissions = staff?.permissions ?? []; + + // 权限判断逻辑(自动处理空权限数组) const hasAnyPerms = permissions.includes(RolePerms.READ_ANY_TERM) || permissions.includes(RolePerms.MANAGE_ANY_TERM); - // 解构输入参数 + const { termIds, taxonomyId } = data; - // 并行查询父级部门ancestry和自身部门数据 - // 使用Promise.all提高查询效率,减少等待时间 + // 构建通用权限过滤条件 + const buildDomainFilter = () => ({ + OR: [ + { domainId: null }, // 始终包含公共记录 + ...(domainId ? [{ domainId }] : []) // 动态添加域过滤 + ].filter(Boolean) // 确保数组有效性 + }); + const [parentData, selfData] = await Promise.all([ - // 查询指定部门的所有祖先节点,包含子节点和父节点信息 db.termAncestry.findMany({ where: { - descendantId: { in: termIds }, // 查询条件:descendant在给定的部门ID列表中 + descendantId: { in: termIds }, ancestor: { - taxonomyId: taxonomyId, - ...(hasAnyPerms - ? {} // 当有全局权限时,不添加任何额外条件 - : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), - }, + taxonomyId, + // 应用动态权限过滤 + ...(hasAnyPerms ? {} : buildDomainFilter()) + } }, include: { ancestor: { include: { - children: true, // 包含子节点信息 - parent: true, // 包含父节点信息 - }, - }, + children: true, + parent: true + } + } }, - orderBy: { ancestor: { order: 'asc' } }, // 按祖先节点顺序升序排序 + orderBy: { ancestor: { order: 'asc' } } }), - - // 查询自身部门数据 db.term.findMany({ where: { id: { in: termIds }, - taxonomyId: taxonomyId, - ...(hasAnyPerms - ? {} // 当有全局权限时,不添加任何额外条件 - : { - // 当无全局权限时,添加域ID过滤 - OR: [ - { domainId: null }, // 通用记录 - { domainId: domainId }, // 特定域记录 - ], - }), + taxonomyId, + // 应用相同的权限过滤逻辑 + ...(hasAnyPerms ? {} : buildDomainFilter()) }, - include: { children: true }, // 包含子节点信息 - orderBy: { order: 'asc' }, // 按顺序升序排序 - }), + include: { children: true }, + orderBy: { order: 'asc' } + }) ]); - // 处理父级节点:过滤并映射为简单树结构 + // 数据处理保持不变... const parents = parentData - .map(({ ancestor }) => ancestor) // 提取祖先节点 - .filter((ancestor) => ancestor) // 过滤有效且超出根节点层级的节点 - .map(mapToTermSimpleTree); // 映射为简单树结构 + .map(({ ancestor }) => ancestor) + .filter(Boolean) + .map(mapToTermSimpleTree); - // 处理自身节点:映射为简单树结构 const selfItems = selfData.map(mapToTermSimpleTree); - // 合并并去重父级和自身节点,返回唯一项 return getUniqueItems([...parents, ...selfItems], 'id'); } } diff --git a/apps/web/src/app/auth/login.tsx b/apps/web/src/app/auth/login.tsx index aee4144..2e97e75 100644 --- a/apps/web/src/app/auth/login.tsx +++ b/apps/web/src/app/auth/login.tsx @@ -31,12 +31,12 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => { label="用户名" name="username" rules={[ - { required: true, message: "Username is required" }, - { min: 2, message: "Username must be at least 2 characters" } + { required: true, message: "请输入用户名" }, + { min: 2, message: "用户名至少需要2个字符" } ]} > @@ -45,11 +45,11 @@ export const LoginForm = ({ onSubmit, isLoading }: LoginFormProps) => { label="密码" name="password" rules={[ - { required: true, message: "Password is required" } + { required: true, message: "请输入密码" } ]} > diff --git a/apps/web/src/app/auth/page.tsx b/apps/web/src/app/auth/page.tsx index c4a3d27..ee304df 100644 --- a/apps/web/src/app/auth/page.tsx +++ b/apps/web/src/app/auth/page.tsx @@ -6,11 +6,6 @@ import { LoginForm } from "./login"; import { Card, Typography, Button, Spin, Divider } from "antd"; import { AnimatePresence, motion } from "framer-motion"; import { useAuthForm } from "./useAuthForm"; -import { - GithubOutlined, - GoogleOutlined, - LoadingOutlined -} from '@ant-design/icons'; const { Title, Text, Paragraph } = Typography; @@ -41,23 +36,22 @@ const AuthPage: React.FC = () => { animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} > -
{/* Left Panel - Welcome Section */} -
+
-
- 欢迎来到 Leader Mail +
+ 首长机关信箱
- - 与领导者建立联系,分享见解,在专为职业发展设计的协作环境中共同成长。 + + 倾听官兵心声,及时响应诉求,竭诚为每一位战友解难题、办实事 {showLogin && ( )} @@ -88,13 +82,13 @@ const AuthPage: React.FC = () => { 登录 - 还没有账户?{' '} + 首次使用?{' '} @@ -113,15 +107,15 @@ const AuthPage: React.FC = () => { transition={{ duration: 0.3 }} > - 创建账户 + 注册账号 - 已有账户?{' '} + 已有账号?{' '} @@ -135,7 +129,7 @@ const AuthPage: React.FC = () => {
- +
diff --git a/apps/web/src/app/auth/register.tsx b/apps/web/src/app/auth/register.tsx index aa96c1a..57c4b0b 100644 --- a/apps/web/src/app/auth/register.tsx +++ b/apps/web/src/app/auth/register.tsx @@ -122,7 +122,7 @@ export const RegisterForm = ({ onSubmit, isLoading }: RegisterFormProps) => { loading={isLoading} className="w-full h-10 rounded-lg" > - {isLoading ? "正在创建账户..." : "创建账户"} + {isLoading ? "正在注册..." : "注册"} diff --git a/apps/web/src/app/main/letter/editor/page.tsx b/apps/web/src/app/main/letter/editor/page.tsx index 61bd7bc..4caa91f 100644 --- a/apps/web/src/app/main/letter/editor/page.tsx +++ b/apps/web/src/app/main/letter/editor/page.tsx @@ -9,7 +9,7 @@ export default function LetterEditorPage() { const termId = searchParams.get("termId"); return ( -
+
diff --git a/apps/web/src/app/main/letter/list/Header.tsx b/apps/web/src/app/main/letter/list/Header.tsx index 382272a..c052de8 100644 --- a/apps/web/src/app/main/letter/list/Header.tsx +++ b/apps/web/src/app/main/letter/list/Header.tsx @@ -1,20 +1,72 @@ - export function Header() { - return ( -
-

公开信件列表

-
-

- 服务宗旨:畅通诉求渠道 • 促进建设发展 • 提升单位战斗力 -

-
- 实时跟踪反馈进度 - 保障信息传递安全 - 高效解决实际问题 +
+
+ {/* 主标题区域 */} +
+

公开信件列表

+

+ 透明公开 • 及时反馈 • 有效解决 +

+
+ + {/* 服务特点说明 */} +
+
+ + + + 公开透明处理流程 +
+
+ + + + 及时跟进反馈进度 +
+
+ + + + 高效解决实际问题 +
+
+ + {/* 服务宗旨说明 */} +
+

+ 我们致力于建设开放透明的信件处理平台,通过公开信件处理过程,展示单位工作成效, + 促进各项工作有序开展,切实解决实际问题,提升单位整体战斗力。 +

-
); -} +} \ No newline at end of file diff --git a/apps/web/src/app/main/letter/list/page.tsx b/apps/web/src/app/main/letter/list/page.tsx index 6d9fe18..9af1f9f 100644 --- a/apps/web/src/app/main/letter/list/page.tsx +++ b/apps/web/src/app/main/letter/list/page.tsx @@ -5,7 +5,7 @@ export default function LetterListPage() { return ( // 添加 flex flex-col 使其成为弹性布局容器 -
+
{/* 添加 flex-grow 使内容区域自动填充剩余空间 */} diff --git a/apps/web/src/app/main/letter/progress/ProgressHeader.tsx b/apps/web/src/app/main/letter/progress/ProgressHeader.tsx new file mode 100644 index 0000000..6a563f7 --- /dev/null +++ b/apps/web/src/app/main/letter/progress/ProgressHeader.tsx @@ -0,0 +1,70 @@ +export default function ProgressHeader() { + return
+
+ {/* 主标题 */} +
+

+ 查询处理进度 +

+

+ 实时跟踪您的信件处理状态 +

+
+ + {/* 处理状态说明 */} +
+
+ + + + 身份信息已加密 +
+
+ + + + 全程跟踪处理状态 +
+
+ + + + 处理结果及时反馈 +
+
+ + {/* 处理说明 */} +
+

您可以随时查看问题的处理进度,我们会及时更新处理状态并通知您最新进展。所有信息都经过加密保护,确保处理过程的安全性。

+
+
+
+ +} \ No newline at end of file diff --git a/apps/web/src/app/main/letter/progress/page.tsx b/apps/web/src/app/main/letter/progress/page.tsx index c9e03ad..7fb0805 100644 --- a/apps/web/src/app/main/letter/progress/page.tsx +++ b/apps/web/src/app/main/letter/progress/page.tsx @@ -1,6 +1,7 @@ import { useState } from "react"; import { Input, Button, Card, Steps, Tag, Spin, message } from "antd"; import { SearchOutlined, SafetyCertificateOutlined } from "@ant-design/icons"; +import ProgressHeader from "./ProgressHeader"; interface FeedbackStatus { status: "pending" | "processing" | "resolved"; @@ -62,61 +63,37 @@ export default function LetterProgressPage() { return (
- {/* Header */} -
-
- -
-

- USAF Feedback Tracking System -

-

- Enter your ticket ID to track progress -

-
-
-
- + {/* Main Content */} -
- {/* Search Section */} - -
- -
- - } - size="large" - value={feedbackId} - onChange={(e) => setFeedbackId(e.target.value)} - placeholder="e.g. USAF-2025-0123" - status={error ? "error" : ""} - className="border border-gray-300" - /> - -
- {error && ( -

{error}

- )} +
+
+
+ + } + size="large" + value={feedbackId} + onChange={(e) => setFeedbackId(e.target.value)} + placeholder="请输入信件编码查询处理状态" + status={error ? "error" : ""} + className="border border-gray-300" + /> +
- + {error && ( +

{error}

+ )} +
{/* Results Section */} {status && ( diff --git a/apps/web/src/app/main/letter/write/SendCard.tsx b/apps/web/src/app/main/letter/write/SendCard.tsx index 1099264..02d8a83 100644 --- a/apps/web/src/app/main/letter/write/SendCard.tsx +++ b/apps/web/src/app/main/letter/write/SendCard.tsx @@ -1,7 +1,7 @@ import { motion } from 'framer-motion'; import { StaffDto } from "@nice/common"; 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'; export interface SendCardProps { @@ -13,15 +13,12 @@ export function SendCard({ staff, termId }: SendCardProps) { const navigate = useNavigate(); const handleSendLetter = () => { - navigate(`/editor?termId=${termId || ''}&receiverId=${staff.id}`); + window.open(`/editor?termId=${termId || ''}&receiverId=${staff.id}`, '_blank'); }; return ( -
{/* Image Container */} @@ -61,11 +58,14 @@ export function SendCard({ staff, termId }: SendCardProps) {

- {staff?.showname} + {staff?.showname || staff?.username}

- +
-

{staff.department?.name || '未设置部门'}

+

+ + + {staff.department?.name || '未设置部门'}

@@ -112,6 +112,6 @@ export function SendCard({ staff, termId }: SendCardProps) {
- +
); } diff --git a/apps/web/src/app/main/letter/write/WriteHeader.tsx b/apps/web/src/app/main/letter/write/WriteHeader.tsx index 8d84c6b..15d3598 100644 --- a/apps/web/src/app/main/letter/write/WriteHeader.tsx +++ b/apps/web/src/app/main/letter/write/WriteHeader.tsx @@ -1,10 +1,12 @@ -export default function WriteHeader() { +import { TermDto } from "@nice/common"; + +export default function WriteHeader({ term }: { term?: TermDto }) { return
{/* 主标题 */}

- 信件投递入口 + {term?.name}信件投递入口

保护您隐私的信件传输平台 diff --git a/apps/web/src/app/main/letter/write/page.tsx b/apps/web/src/app/main/letter/write/page.tsx index f4d8c8a..cb8ffba 100644 --- a/apps/web/src/app/main/letter/write/page.tsx +++ b/apps/web/src/app/main/letter/write/page.tsx @@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router-dom'; import { SendCard } from './SendCard'; 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 debounce from 'lodash/debounce'; import { SearchOutlined } from '@ant-design/icons'; @@ -17,6 +17,7 @@ export default function WriteLetterPage() { const [selectedDept, setSelectedDept] = useState(); const [currentPage, setCurrentPage] = useState(1); const pageSize = 10; + const { getTerm } = useTerm() const { data, isLoading, error } = api.staff.findManyWithPagination.useQuery({ page: currentPage, @@ -45,8 +46,8 @@ export default function WriteLetterPage() { }, [searchQuery, selectedDept, resetPage]); return ( -

- +
+
{/* Search and Filter Section */} @@ -120,7 +121,10 @@ export default function WriteLetterPage() { current={currentPage} total={data?.totalPages || 0} pageSize={pageSize} - onChange={(page) => setCurrentPage(page)} + onChange={(page) => { + setCurrentPage(page); + window.scrollTo(0, 0); + }} showSizeChanger={false} showTotal={(total) => `共 ${total} 条记录`} /> diff --git a/apps/web/src/components/common/element/Logo.tsx b/apps/web/src/components/common/element/Logo.tsx new file mode 100644 index 0000000..8152f6e --- /dev/null +++ b/apps/web/src/components/common/element/Logo.tsx @@ -0,0 +1,77 @@ +import React from 'react'; + +interface LogoProps { + size?: 'small' | 'medium' | 'large'; + className?: string; +} + +const InnovationLogo: React.FC = ({ + size = 'medium', + className = '' +}) => { + const sizeClasses = { + small: 'w-8 h-8', + medium: 'w-16 h-16', + large: 'w-24 h-24' + }; + + return ( + + {/* 背景圆形 */} + + + {/* 六边形原子核心 */} + + + {/* 三个原子轨道 */} + + {/* 外层轨道 */} + + {/* 中层轨道 */} + + {/* 内层轨道 */} + + + + {/* 闪电/创新元素 */} + + + ); +}; + +export default InnovationLogo; diff --git a/apps/web/src/components/layout/element/Logo.tsx b/apps/web/src/components/layout/element/Logo.tsx deleted file mode 100644 index 5145970..0000000 --- a/apps/web/src/components/layout/element/Logo.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { Link } from "react-router-dom"; - -export const Logo = () => ( - -
- - {/* Mail envelope base */} - - - {/* Envelope flap */} - - - {/* People silhouette */} - - - - {/* Leadership star */} - - -
- - -); diff --git a/apps/web/src/components/layout/main/Footer.tsx b/apps/web/src/components/layout/main/Footer.tsx index a78ede2..3031cec 100644 --- a/apps/web/src/components/layout/main/Footer.tsx +++ b/apps/web/src/components/layout/main/Footer.tsx @@ -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() { return ( -