From 1aa75ef58e3586e4a1851da67c12378df571c0b1 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 11:18:46 +0800 Subject: [PATCH 1/7] chore(deps): remove 25 unused dependencies Removed dependencies flagged by react-doctor as unused (verified zero imports across source): @dnd-kit/*, @ffmpeg/*, @tiptap/*, @mantine/tiptap, @monaco-editor/react, monaco-editor, mapvgl, libav.js, lucide-react, classnames, geojson, html-react-parser, js-base64, msgpack-lite, paper, @tmcw/togeojson, and identity-obj-proxy (dev). Co-Authored-By: Claude Opus 4.8 (1M context) --- package.json | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/package.json b/package.json index d27b2a1..b9b1b6a 100644 --- a/package.json +++ b/package.json @@ -19,11 +19,6 @@ "format": "prettier --write \"**/*.{ts,tsx,css}\"" }, "dependencies": { - "@dnd-kit/core": "^6.3.1", - "@dnd-kit/sortable": "^10.0.0", - "@dnd-kit/utilities": "^3.2.2", - "@ffmpeg/ffmpeg": "^0.12.15", - "@ffmpeg/util": "^0.12.2", "@fingerprintjs/fingerprintjs": "^5.0.1", "@mantine/core": "^8.3.1", "@mantine/dates": "^8.3.18", @@ -31,35 +26,15 @@ "@mantine/hooks": "^8.3.1", "@mantine/modals": "^8.3.10", "@mantine/notifications": "^8.3.1", - "@mantine/tiptap": "^8.3.11", - "@monaco-editor/react": "^4.7.0", "@tabler/icons-react": "^3.35.0", - "@tiptap/extension-image": "^3.15.3", - "@tiptap/extension-link": "^3.15.3", - "@tiptap/extension-placeholder": "^3.15.3", - "@tiptap/extension-underline": "^3.15.3", - "@tiptap/pm": "^3.15.3", - "@tiptap/react": "^3.15.3", - "@tiptap/starter-kit": "^3.15.3", - "@tmcw/togeojson": "^7.1.2", "avvvatars-react": "^0.4.2", - "classnames": "^2.5.1", "clsx": "^2.1.1", "dayjs": "^1.11.18", "env-cmd": "^11.0.0", - "geojson": "^0.5.0", - "html-react-parser": "^5.2.11", "idb-keyval": "6.2.2", "ioredis": "^5.8.0", - "js-base64": "^3.7.8", - "libav.js": "^6.8.8", - "lucide-react": "^0.562.0", "mantine-datatable": "^8.2.0", - "mapvgl": "1.0.0-beta.194", - "monaco-editor": "^0.55.1", - "msgpack-lite": "^0.1.26", "next": "15.5.7", - "paper": "^0.12.18", "react": "19.2.1", "react-dom": "19.2.1", "react-window": "^2.2.3", @@ -82,7 +57,6 @@ "eslint-config-next": "15.5.7", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-react-hooks": "^7.0.1", - "identity-obj-proxy": "^3.0.0", "postcss": "^8.5.6", "postcss-preset-mantine": "^1.18.0", "postcss-simple-vars": "^7.0.1", From 0908af2e969fda2b3ec0afcfa50aaa19d95c52f0 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 11:18:46 +0800 Subject: [PATCH 2/7] chore: remove unused files flagged by react-doctor Deleted modules with no remaining references: - app/person/dashboard/components/Icon.tsx - components/label/hooks/useAuth.ts - components/layout/components/icons/custom.tsx - components/tree-select/index.tsx Co-Authored-By: Claude Opus 4.8 (1M context) --- app/person/dashboard/components/Icon.tsx | 282 ------------------ components/label/hooks/useAuth.ts | 115 ------- components/layout/components/icons/custom.tsx | 86 ------ components/tree-select/index.tsx | 1 - 4 files changed, 484 deletions(-) delete mode 100644 app/person/dashboard/components/Icon.tsx delete mode 100644 components/label/hooks/useAuth.ts delete mode 100644 components/layout/components/icons/custom.tsx delete mode 100644 components/tree-select/index.tsx diff --git a/app/person/dashboard/components/Icon.tsx b/app/person/dashboard/components/Icon.tsx deleted file mode 100644 index f3bf826..0000000 --- a/app/person/dashboard/components/Icon.tsx +++ /dev/null @@ -1,282 +0,0 @@ -"use client" - -export const DataIcon = () => ( - - - - - - - - - - - - - - -) - -export const HourIcon = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -) - -export const ReviewIcon = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -) diff --git a/components/label/hooks/useAuth.ts b/components/label/hooks/useAuth.ts deleted file mode 100644 index 16febc5..0000000 --- a/components/label/hooks/useAuth.ts +++ /dev/null @@ -1,115 +0,0 @@ -"use client" - -import { usePermissionStore } from "@/components/label/store/auth" -import { usePathname } from "next/navigation" - -interface PermissionItem { - [x: string]: [boolean, null, string] -} - -type PermissionMap = { - [x: string]: PermissionItem -} - -const mappingList: { [x: string]: [string, string] } = { - "/person/dashboard": ["task", "view"], - "/person/report": ["daily_work", "self_view"], - "/person/workload": ["workload", "self_view"], -} - -export const headerBarCheckItems = (data: PermissionMap) => { - const projectTitle = [ - ["daily_work", "self_view"], - ["label_schema", "view"], - ["project", "view"], - ["task", "view"], - ] as const - - let projectVisible = false - projectTitle.forEach(([prop, subProp]) => { - if (data[prop] && data[prop][subProp] && data[prop][subProp][0]) { - projectVisible = true - } - }) - - const staffTitle = [ - ["daily_work", "team_view"], - ["user", "view"], - ["user", "group_view"], - ] as const - - let staffVisible = false - staffTitle.forEach(([prop, subProp]) => { - if (data[prop] && data[prop][subProp] && data[prop][subProp][0]) { - staffVisible = true - } - }) - - return [projectVisible, staffVisible] -} - -const useAuth = () => { - const pathname = usePathname() - const detailInfo = usePermissionStore((s) => s.detailInfo) - const permission = detailInfo?.permissions as PermissionMap | undefined - const roleName: string[] = detailInfo?.role_name ?? [] - - return { - hasPermission: () => { - if ( - [ - "/", - "/login", - "/management", - "/management/person/dashboard", - "/management/team", - ].includes(pathname) - ) { - return true - } - - if (!permission) return false - if (mappingList[pathname]) { - const [prop, subProp] = mappingList[pathname] - return ( - !!permission[prop] && - !!permission[prop][subProp] && - !!permission[prop][subProp][0] - ) - } - return false - }, - // path 跳转是否可见 - canNv: (path: string, type = "view") => { - if (!permission) return false - if (mappingList[path]) { - const [prop] = mappingList[path] - return ( - !!permission[prop] && - !!permission[prop][type] && - !!permission[prop][type][0] - ) - } - return false - }, - // 当前页面是否可见某个操作 - isShow: (type = "view") => { - if (!permission) return false - if (mappingList[pathname]) { - const [prop] = mappingList[pathname] - return ( - !!permission[prop] && - !!permission[prop][type] && - !!permission[prop][type][0] - ) - } - return false - }, - checkRole: (name: string) => { - if (!name) return false - return roleName.includes(name) - }, - } -} - -export default useAuth diff --git a/components/layout/components/icons/custom.tsx b/components/layout/components/icons/custom.tsx deleted file mode 100644 index a1850c6..0000000 --- a/components/layout/components/icons/custom.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import { SVGProps } from "react" - -export function SystemIcon(props: SVGProps) { - return ( - - - - - ) -} - -export const PersonalMenuIcon = (props: SVGProps) => ( - - - -) - -export const TaskMenuIcon = (props: SVGProps) => ( - - - - - - - - - - -) - -export const AuthMenuIcon = (props: SVGProps) => ( - - - - - - - - - - - -) diff --git a/components/tree-select/index.tsx b/components/tree-select/index.tsx deleted file mode 100644 index 517ad6a..0000000 --- a/components/tree-select/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { VirtualTreeSelect as default, VirtualTreeSelect } from "./tree" From cf5044e1eb8526b9c071cb4d81db2a693f22b0f4 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 11:18:46 +0800 Subject: [PATCH 3/7] fix(icons): use valid React DOM props and trim SVG precision - digital.tsx: stop-color -> stopColor (React camelCase SVG attr) - Icon.tsx: drop invalid iconfont `p-id` attributes - both: round 4+ decimal path/transform coordinates to 2 decimals (no visible change; Icon.tsx -22%, digital.tsx -14%) Co-Authored-By: Claude Opus 4.8 (1M context) --- components/label/components/Icon.tsx | 168 ++++++++------------ components/login/resource/icons/digital.tsx | 42 ++--- 2 files changed, 91 insertions(+), 119 deletions(-) diff --git a/components/label/components/Icon.tsx b/components/label/components/Icon.tsx index 404c552..743ad67 100644 --- a/components/label/components/Icon.tsx +++ b/components/label/components/Icon.tsx @@ -36,7 +36,7 @@ export const Logo: React.FC = () => ( = () => ( width="20" height="20"> @@ -63,7 +63,7 @@ export const Logo: React.FC = () => ( @@ -120,7 +120,7 @@ export const LoginUserIcon = () => ( width="16" height="16" fill="#4E596999"> - + ) @@ -129,16 +129,11 @@ export const LoginPasswordIcon = () => ( viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" - p-id="6040" width="16" height="16" fill="#4E596999"> - - + + ) @@ -147,13 +142,10 @@ export const LoginCodeIcon = () => ( viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" - p-id="6188" width="16" height="16" fill="#4E596999"> - + ) @@ -166,7 +158,7 @@ export const DashboardMenuIcon = () => ( xmlns="http://www.w3.org/2000/svg"> @@ -187,7 +179,7 @@ export const TaskMenuIcon = () => ( xmlns="http://www.w3.org/2000/svg"> @@ -208,15 +200,15 @@ export const DeviceMenuIcon = () => ( xmlns="http://www.w3.org/2000/svg"> @@ -236,7 +228,7 @@ export const CountMenuIcon = () => ( fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -251,7 +243,7 @@ export const UserMenuIcon = () => ( xmlns="http://www.w3.org/2000/svg"> @@ -272,11 +264,11 @@ export const AuthMenuIcon = () => ( xmlns="http://www.w3.org/2000/svg"> @@ -293,13 +285,10 @@ export const PersonalMenuIcon = () => ( viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" - p-id="1927" width="14" height="14" fill="currentColor"> - + ) @@ -312,93 +301,93 @@ export const DevLogo = ({ ...props }) => ( xmlns="http://www.w3.org/2000/svg" {...props}> ( cy="16.5649" rx="1.28491" ry="4.70438" - transform="rotate(-47.9891 19.5156 16.5649)" + transform="rotate(-47.99 19.52 16.56)" fill="#2D67F5" /> ( cy="3.63681" rx="1.13456" ry="3.63681" - transform="matrix(-0.731719 -0.681606 -0.681606 0.731719 37.6641 13.9609)" + transform="matrix(-0.73 -0.68 -0.68 0.73 37.66 13.96)" fill="#2D67F5" /> ( y="31.2773" width="6.23302" height="1.17604" - transform="rotate(90 28.5312 31.2773)" + transform="rotate(90 28.53 31.28)" fill="#2D67F5" /> @@ -462,12 +451,10 @@ export const ExportIcon = () => ( viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" - p-id="2301" width="14" height="14"> ) @@ -482,7 +469,7 @@ export const BasicInfoIcon = () => ( @@ -496,14 +483,11 @@ export const BankCardIcon = () => ( fill="none" xmlns="http://www.w3.org/2000/svg"> - + ) @@ -515,20 +499,12 @@ export const BusinessCardIcon = () => ( fill="none" xmlns="http://www.w3.org/2000/svg"> - - + + ( @@ -594,7 +570,7 @@ export const BillRecordCardIcon = () => ( strokeWidth="1.3" /> @@ -609,7 +585,6 @@ export const ToolTipIcon = () => ( fill="currentColor"> ) @@ -619,16 +594,13 @@ export const IDLogoIcon = () => ( viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" - p-id="2219" width="18" height="18"> ) @@ -642,7 +614,7 @@ export const ChevronLeftIcon: React.FC = ({ ...props }) => ( xmlns="http://www.w3.org/2000/svg" {...props}> @@ -658,7 +630,7 @@ export const DesktopIcon: React.FC = ({ ...props }) => ( xmlns="http://www.w3.org/2000/svg" {...props}> @@ -676,7 +648,7 @@ export const LabelStatusIcon: React.FC = ({ ...props }) => ( @@ -693,7 +665,7 @@ export const LabelStatusWaitIcon = ({ ...props }) => ( @@ -711,7 +683,7 @@ export const LabelStatusAcceptanceIcon = ({ ...props }) => ( @@ -729,7 +701,7 @@ export const LabelStatusFailIcon = ({ ...props }) => ( @@ -746,7 +718,7 @@ export const LabelPretestIcon = () => ( @@ -763,7 +735,7 @@ export const LabelStatusStopIcon = ({ ...props }) => ( @@ -780,7 +752,7 @@ export const InfoCircleIcon = ({ ...props }) => ( @@ -817,7 +789,7 @@ export const TaskIcon2 = ({ ...props }) => ( @@ -835,7 +807,7 @@ export const TaskIcon3 = ({ ...props }) => ( @@ -853,7 +825,7 @@ export const TaskIcon4 = ({ ...props }) => ( @@ -871,7 +843,7 @@ export const TaskIcon5 = ({ ...props }) => ( @@ -889,7 +861,7 @@ export const TaskIcon6 = ({ ...props }) => ( @@ -907,7 +879,7 @@ export const TaskIcon7 = ({ ...props }) => ( @@ -923,7 +895,7 @@ export const DispatchTaskIcon = () => ( @@ -939,7 +911,7 @@ export const FreeTaskIcon = () => ( diff --git a/components/login/resource/icons/digital.tsx b/components/login/resource/icons/digital.tsx index 6a21544..abf8be2 100644 --- a/components/login/resource/icons/digital.tsx +++ b/components/login/resource/icons/digital.tsx @@ -8,15 +8,15 @@ export const DigitalIcon = () => { xmlns="http://www.w3.org/2000/svg"> { width="23" height="23"> @@ -92,27 +92,27 @@ export const HeaderIcon = () => { fill="none" xmlns="http://www.w3.org/2000/svg"> @@ -123,8 +123,8 @@ export const HeaderIcon = () => { x2="19.7265" y2="13.0418" gradientUnits="userSpaceOnUse"> - - + + { x2="19.7265" y2="13.0418" gradientUnits="userSpaceOnUse"> - - + + { x2="10.7851" y2="28.7987" gradientUnits="userSpaceOnUse"> - - + + { x2="10.7851" y2="28.7987" gradientUnits="userSpaceOnUse"> - - + + From dbd01ee15b10f09d15ca71b8aeee3234585baaea Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 11:18:46 +0800 Subject: [PATCH 4/7] docs: record react-doctor false positives Log confirmed false positives for server-auth-actions (genAccessToken, the session-validation endpoint itself) and exhaustive-deps (intentional minimal-deps effects where adding deps would cause re-render loops) so future triage skips them. Co-Authored-By: Claude Opus 4.8 (1M context) --- .react-doctor/false-positives.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .react-doctor/false-positives.md diff --git a/.react-doctor/false-positives.md b/.react-doctor/false-positives.md new file mode 100644 index 0000000..59eef50 --- /dev/null +++ b/.react-doctor/false-positives.md @@ -0,0 +1,26 @@ +# React Doctor — 已确认的误报 (false positives) + +本文件记录经人工确认的误报。React Doctor 在分诊时会读取此文件并跳过匹配的诊断。 +每条注明:规则、位置、代码形状、为何是误报。 + +--- + +## `react-doctor/server-auth-actions` + +- **位置**: `components/login/libs/session.ts` — `genAccessToken` +- **代码形状**: 文件顶部含 `"use server"`,导出的 async 函数本身就是**会话校验 / 凭证建立端点**——读取会话 cookie (`SESSION_COOKIE_NAME`)、查 redis、校验客户端 IP,失败时返回 `ok:false` 重定向到登录页。 +- **为何是误报**: 该函数*就是*鉴权逻辑本身,不存在"调用前的会话"可供校验。规则的 validation prompt 明确把"action 即认证/凭证建立端点(login/signup/OTP/session 校验)"列为典型误报——这里因函数名为 `genAccessToken`(不在规则的 `auth()`/`validateSession()` 等识别名单内)而误触发。给它再加 `auth()` 会构成循环逻辑。 +- **判定**: 跳过此规则在该函数上的诊断。 + +--- + +## `react-doctor/exhaustive-deps` — 有意排除的依赖(加了会引 bug) + +以下 useEffect 均**有意**保持最小依赖数组,且都已带 `// eslint-disable-next-line react-hooks/exhaustive-deps`(react-doctor 用的是 oxlint 同名规则,故未被该注释覆盖)。逐条确认:补齐缺失依赖会引入真实 bug,故不改逻辑,登记为误报。 + +- **`app/person/report/components/DailyReportModal.tsx:190`** — 缺 `resetFormState` 等。`resetFormState` 每次渲染重建,加入依赖会导致 effect 每次渲染都执行(反复重置表单)。effect 设计为仅在 `[opened, record, user_name]` 变化时运行。 +- **`app/person/report/components/DailyReportModal.tsx:201` / `:227`** — 缺 `form.setFieldValue`。Mantine form 方法引用稳定;effect 有意仅在对应 `form.values.*` 变化时联动计算字段,补 setter 无意义且违背设计意图。 +- **`app/person/workload/WorkloadMetricsTable.tsx:124`** — 缺 `refresh`。这是 `[]` 挂载时一次性拉取数据的 effect;`refresh` 为每次渲染重建的普通 async 函数,加入依赖会导致每次渲染重复请求。 +- **`components/tree-select/tree.tsx:122`** — 缺 `tree.initialize`。`useTree()` 每次渲染返回新的 controller 对象(文件内注释亦说明此点),依赖它会造成无限重渲染;effect 有意仅在 `[data]` 变化时初始化。 + +- **判定**: 以上位置跳过 `exhaustive-deps`。如需让 react-doctor 扫描层面也静默,可在各行改用 `// oxlint-disable-next-line react-doctor/exhaustive-deps` 并附理由(本表已记录理由)。 From f930432ffeb2f4ed2f99e27d2fac2cb353111521 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 11:32:18 +0800 Subject: [PATCH 5/7] fix(tree-select): hoist Row to module scope to prevent row remounts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The row renderer was defined inside VirtualizedTree, giving it a new component identity on every parent render — react-window then remounted every row, destroying row DOM/state. Move Row to module scope and pass flattened/tree/renderNode through react-window's rowProps instead of the closure. Verified at runtime: rows render, expand/collapse and select work, and selection state survives re-renders, with no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- components/tree-select/VirtualizedTree.tsx | 136 ++++++++++++--------- 1 file changed, 76 insertions(+), 60 deletions(-) diff --git a/components/tree-select/VirtualizedTree.tsx b/components/tree-select/VirtualizedTree.tsx index 898a982..7c5af10 100644 --- a/components/tree-select/VirtualizedTree.tsx +++ b/components/tree-select/VirtualizedTree.tsx @@ -4,8 +4,8 @@ import { TreeNodeData, UseTreeReturnType, } from "@mantine/core" -import React, { CSSProperties, useMemo } from "react" -import { List } from "react-window" +import React, { useMemo } from "react" +import { List, RowComponentProps } from "react-window" import "./mantine-scrollbar.css" export interface VirtualizedTreeProps { @@ -51,6 +51,78 @@ function flattenByExpanded( return res } +/** 行渲染所需的闭包数据,通过 react-window 的 rowProps 传入 */ +interface RowData { + flattened: { node: TreeNodeData; level: number }[] + tree: UseTreeReturnType + renderNode: (payload: RenderTreeNodePayload) => React.ReactNode +} + +/** + * 单行渲染组件 —— 提到模块作用域,避免每次父组件渲染都生成新的组件实例 + * (否则 react-window 会在每次渲染时 remount 所有行,销毁行内状态) + */ +function Row({ + index, + style, + flattened, + tree, + renderNode, +}: RowComponentProps) { + const { node, level } = flattened[index] + + // 构造 Mantine 的 RenderTreeNodePayload + const nodeValue = String(node.value) + const hasChildren = Array.isArray(node.children) && node.children.length > 0 + const expanded = !!(tree && (tree as any).expandedState?.[nodeValue]) + const selected = !!( + tree && (tree as any).selectedState?.includes?.(nodeValue) + ) + const hovered = tree ? (tree as any).hoveredNode === nodeValue : false + + // elementProps: 与 Mantine Tree 内部的 props shape 对齐,外部的 renderNode 会 spread 这个对象 + const elementProps = { + className: "", + style: { + display: "flex", + alignItems: "center", + width: "100%", + } as React.CSSProperties, + onClick: (e: React.MouseEvent) => { + // 保持与 Mantine Tree 一致的基础行为:选中、展开切换由 tree 控制 + // 点击行为在业务端通常由 renderNode 决定(例如中间区域用于 toggleExpanded) + // 这里我们调用 tree.toggleExpanded 以保证基础交互一致(不会阻止外部覆盖) + if (tree && typeof tree.toggleExpanded === "function") { + tree.toggleExpanded(nodeValue) + } + false && console.log(e) + }, + "data-selected": selected as boolean | undefined, + "data-value": nodeValue, + "data-hovered": hovered as boolean | undefined, + } + + const payload: RenderTreeNodePayload = { + level, + expanded, + hasChildren, + selected, + node, + tree, + elementProps, + } + + // paddingLeft 模拟 Mantine 的 levelOffset 行为(renderNode 可使用 elementProps.style 覆盖) + const paddedStyle: React.CSSProperties = { + ...style, + boxSizing: "border-box", + overflow: "hidden", + } + + // 将 style 作为容器传入,Mantine 的 renderNode 也会接受 elementProps.style + return
{renderNode(payload)}
+} + /** * VirtualizedTree - Mantine v8 compatible virtualized Tree * - Accepts tree (useTree() result) and data: TreeNodeData[] @@ -72,62 +144,6 @@ export function VirtualizedTree({ flattenByExpanded(data, (tree && (tree as any).expandedState) || {}, 0), [data, tree] ) - // react-window 的行渲染 - /** 单行渲染组件 */ - const Row = ({ index, style }: { index: number; style: CSSProperties }) => { - const { node, level } = flattened[index] - - // 构造 Mantine 的 RenderTreeNodePayload - const nodeValue = String(node.value) - const hasChildren = Array.isArray(node.children) && node.children.length > 0 - const expanded = !!(tree && (tree as any).expandedState?.[nodeValue]) - const selected = !!( - tree && (tree as any).selectedState?.includes?.(nodeValue) - ) - const hovered = tree ? (tree as any).hoveredNode === nodeValue : false - - // elementProps: 与 Mantine Tree 内部的 props shape 对齐,外部的 renderNode 会 spread 这个对象 - const elementProps = { - className: "", - style: { - display: "flex", - alignItems: "center", - width: "100%", - } as React.CSSProperties, - onClick: (e: React.MouseEvent) => { - // 保持与 Mantine Tree 一致的基础行为:选中、展开切换由 tree 控制 - // 点击行为在业务端通常由 renderNode 决定(例如中间区域用于 toggleExpanded) - // 这里我们调用 tree.toggleExpanded 以保证基础交互一致(不会阻止外部覆盖) - if (tree && typeof tree.toggleExpanded === "function") { - tree.toggleExpanded(nodeValue) - } - false && console.log(e) - }, - "data-selected": selected as boolean | undefined, - "data-value": nodeValue, - "data-hovered": hovered as boolean | undefined, - } - - const payload: RenderTreeNodePayload = { - level, - expanded, - hasChildren, - selected, - node, - tree, - elementProps, - } - - // paddingLeft 模拟 Mantine 的 levelOffset 行为(renderNode 可使用 elementProps.style 覆盖) - const paddedStyle: React.CSSProperties = { - ...style, - boxSizing: "border-box", - overflow: "hidden", - } - - // 将 style 作为容器传入,Mantine 的 renderNode 也会接受 elementProps.style - return
{renderNode(payload)}
- } return ( } - rowProps={{}} + rowComponent={Row} + rowProps={{ flattened, tree, renderNode }} overscanCount={overscanCount}> ) } From fb3ffe3dfdccaefee3b01bd7845d89c608bc8a4f Mon Sep 17 00:00:00 2001 From: zhangheng Date: Fri, 5 Jun 2026 12:39:27 +0800 Subject: [PATCH 6/7] refactor: remove unused dead-code exports flagged by react-doctor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Low-risk subset only — each verified to have zero external references (tsc + eslint green); API clients, stores, icons and constants left untouched. - libs/util.ts: unexport `is` (still used internally by isNumber) - components/tree-select/libs/util.ts: drop unused recursive `findLabel` - app/person/workload/config.ts: drop unused `dimensionOpts` - components/login/libs/session-cache.ts: drop unused `getCachedSession` - components/setting/PageSurface.tsx: drop 3 unused layout components (SettingTableViewport, SettingSectionHeader, SettingFilterStack) Co-Authored-By: Claude Opus 4.8 (1M context) --- app/person/workload/config.ts | 8 --- components/login/libs/session-cache.ts | 3 -- components/setting/PageSurface.tsx | 72 -------------------------- components/tree-select/libs/util.ts | 18 ------- libs/util.ts | 2 +- 5 files changed, 1 insertion(+), 102 deletions(-) diff --git a/app/person/workload/config.ts b/app/person/workload/config.ts index 607093e..42489ad 100644 --- a/app/person/workload/config.ts +++ b/app/person/workload/config.ts @@ -5,14 +5,6 @@ export type DimensionType = | "order_by_project_name" | "order_by_project_type" -export const dimensionOpts = [ - { label: "全部", value: "all" }, - { label: "用户名", value: "order_by_user_name" }, - { label: "用户组", value: "order_by_group_name" }, - { label: "项目名称", value: "order_by_project_name" }, - { label: "项目类型", value: "order_by_project_type" }, -] - export type MetricColumn = { key: string title: string diff --git a/components/login/libs/session-cache.ts b/components/login/libs/session-cache.ts index 3aa017b..af9b3ee 100644 --- a/components/login/libs/session-cache.ts +++ b/components/login/libs/session-cache.ts @@ -27,9 +27,6 @@ const getValidCachedSession = (redisKey: string) => { return cacheEntry.data as T } -export const getCachedSession = (redisKey: string) => - getValidCachedSession(redisKey) - export const setCachedSession = (redisKey: string, data: T) => { sessionCache.set(redisKey, { data, diff --git a/components/setting/PageSurface.tsx b/components/setting/PageSurface.tsx index 84fcfe3..aaab2e2 100644 --- a/components/setting/PageSurface.tsx +++ b/components/setting/PageSurface.tsx @@ -386,12 +386,6 @@ export function SettingCustomPageSizeControl({ ) } -type SettingTableViewportProps = PropsWithChildren<{ - maxHeight: number | string - className?: string - style?: CSSProperties -}> - export function SettingPage({ className, ...props @@ -428,21 +422,6 @@ export function SettingPanel({ ) } -export function SettingTableViewport({ - maxHeight, - className, - style, - children, -}: SettingTableViewportProps) { - return ( -
- {children} -
- ) -} - export function SettingFilterPanel({ className, ...props @@ -467,43 +446,6 @@ export function SettingContentPanel({ ) } -type SettingSectionHeaderProps = GroupProps & { - eyebrow?: string - title: ReactNode - description?: ReactNode - actions?: ReactNode -} - -export function SettingSectionHeader({ - eyebrow, - title, - description, - actions, - className, - ...props -}: SettingSectionHeaderProps) { - return ( - - - {eyebrow ? ( - {eyebrow} - ) : null} - {title} - {description ? ( - {description} - ) : null} - - {actions} - - ) -} - type SettingListHeaderProps = { title: ReactNode count?: ReactNode @@ -569,20 +511,6 @@ export function SettingFilterActions({ ) } -export function SettingFilterStack({ - className, - gap = 12, - ...props -}: PropsWithChildren) { - return ( - - ) -} - type SettingInlineFilterFieldProps = { label: ReactNode children: ReactNode diff --git a/components/tree-select/libs/util.ts b/components/tree-select/libs/util.ts index 7fd4661..bf8c8cd 100644 --- a/components/tree-select/libs/util.ts +++ b/components/tree-select/libs/util.ts @@ -75,24 +75,6 @@ export const buildTreeValueMap = ( return acc } -export const findLabel = ( - nodes: TreeNodeData[], - targetValue: string, - pathArr: string[] = [] // 内部用,调用端无需传入 -): FindResult => { - for (const node of nodes) { - const curPath = [...pathArr, node.label as string] // 复制一份,避免污染上层 - if (node.value === targetValue) { - return { path: curPath.join(TREE_PATH_SEPARATOR), node } - } - if (node.children) { - const found = findLabel(node.children, targetValue, curPath) - if (found.node) return found - } - } - return { path: "", node: null } -} - export const findNodesByValues = ( valueMap: Record, targetValues: string[] diff --git a/libs/util.ts b/libs/util.ts index caf42f1..0c42ee3 100644 --- a/libs/util.ts +++ b/libs/util.ts @@ -1,7 +1,7 @@ /** * @description: 判断值是否为某个类型 */ -export function is(val: unknown, type: string) { +function is(val: unknown, type: string) { return toString.call(val) === `[object ${type}]` } From e82cfc43ed6f0a5b742ecd241a20c4271aa6b567 Mon Sep 17 00:00:00 2001 From: zhangheng Date: Sat, 6 Jun 2026 14:05:46 +0800 Subject: [PATCH 7/7] feat(own): add filter --- .../components/PersonalTaskTable.tsx | 49 +++++++++++++++++-- app/person/dashboard/page.module.css | 19 +++++++ components/label/api/task/index.ts | 1 + components/setting/PageSurface.tsx | 8 ++- 4 files changed, 73 insertions(+), 4 deletions(-) diff --git a/app/person/dashboard/components/PersonalTaskTable.tsx b/app/person/dashboard/components/PersonalTaskTable.tsx index 4d7262a..802bd76 100644 --- a/app/person/dashboard/components/PersonalTaskTable.tsx +++ b/app/person/dashboard/components/PersonalTaskTable.tsx @@ -18,13 +18,27 @@ import { getPersistedPageSizeOptions, resolvePageSizeWithinOptions, } from "@/components/setting/pageSize" -import { Button, Group, Stack, Text, UnstyledButton } from "@mantine/core" +import { + Button, + Group, + Select, + Stack, + Text, + UnstyledButton, +} from "@mantine/core" import { IconRefresh } from "@tabler/icons-react" import { DataTableColumn } from "mantine-datatable" import { useCallback, useEffect, useMemo, useState } from "react" import classes from "../page.module.css" const DEFAULT_RECORDS_PER_PAGE_OPTIONS = [10, 15, 20, 50] +const REJECTED_FILTER_OPTIONS = [ + { value: "all", label: "全部" }, + { value: "true", label: "是" }, + { value: "false", label: "否" }, +] as const + +type RejectedFilterValue = (typeof REJECTED_FILTER_OPTIONS)[number]["value"] export default function PersonalTaskTable() { const user_name = usePermissionStore((s) => s.user_name) @@ -47,6 +61,8 @@ export default function PersonalTaskTable() { const [totalItems, setTotalItems] = useState(0) const [records, setRecords] = useState([]) const [loading, setLoading] = useState(false) + const [rejectedFilter, setRejectedFilter] = + useState("all") const totalPages = useMemo(() => { return Math.max(1, Math.ceil(totalItems / pageSize)) @@ -59,13 +75,15 @@ export default function PersonalTaskTable() { const res = await getUserTaskList({ page_number: page, page_size: pageSize, + rejected: + rejectedFilter === "all" ? undefined : rejectedFilter === "true", }) setRecords(res?.simple_task_list ?? []) setTotalItems(res?.total_items ?? 0) } finally { setLoading(false) } - }, [page, pageSize, user_name]) + }, [page, pageSize, rejectedFilter, user_name]) useEffect(() => { load() @@ -198,7 +216,32 @@ export default function PersonalTaskTable() { + + 共 {totalItems} 条记录 + + + + 是否返工 + +