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/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/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..12e7d3b 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,23 +63,9 @@ export default function LetterProgressPage() { return (
- {/* Header */} -
-
- -
-

- USAF Feedback Tracking System -

-

- Enter your ticket ID to track progress -

-
-
-
- + {/* Main Content */}
{/* Search Section */} @@ -105,10 +92,7 @@ export default function LetterProgressPage() { icon={} loading={loading} onClick={mockLookup} - style={{ - backgroundColor: "#003366", - borderColor: "#003366", - }}> + > Track
diff --git a/apps/web/src/app/main/letter/write/WriteHeader.tsx b/apps/web/src/app/main/letter/write/WriteHeader.tsx index 8d84c6b..33b166a 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..c893211 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, @@ -46,7 +47,7 @@ export default function WriteLetterPage() { return (

- +
{/* Search and Filter Section */} 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 ( -