From b8c84ed9afa2526817856426837ac569f1be612e Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 21 May 2026 08:13:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(health):=20=E8=8F=9C=E5=8D=95=E6=96=B9?= =?UTF-8?q?=E6=A1=88B=E9=87=8D=E7=BB=84=20=E2=80=94=20=E6=82=A3=E8=80=85?= =?UTF-8?q?=E4=B8=AD=E5=BF=83+=E9=9A=8F=E8=AE=BF=E5=85=B3=E6=80=80+?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E5=BD=92=E5=85=A5=E7=B3=BB=E7=BB=9F=E7=AE=A1?= =?UTF-8?q?=E7=90=86+=E6=96=87=E7=AB=A0=E6=A0=87=E7=AD=BE=E5=90=88?= =?UTF-8?q?=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 方案B业务流程导向菜单优化: - "患者管理" → "患者中心",吸收日常监测/诊断/知情同意/咨询 - "诊疗服务" → "随访关怀",只保留随访相关 - 告警规则/危急值阈值 → 系统管理 - 文章分类/标签菜单软删除,合并为文章管理页内 Tab 变更文件: - 迁移 164: 重命名目录+移动叶子菜单+重建 menu_roles - ArticleManageList.tsx: 分类/标签管理合并为页内 Tab - 讨论记录 + 可视化原型 HTML Co-Authored-By: Claude Opus 4.7 --- .../src/pages/health/ArticleManageList.tsx | 319 ++++- ...260521_000164_reorganize_menus_scheme_b.rs | 493 +++++++ ...sidebar-menu-optimization-brainstorming.md | 204 +++ docs/discussions/menu-prototype.html | 1183 +++++++++++++++++ 4 files changed, 2141 insertions(+), 58 deletions(-) create mode 100644 crates/erp-server/migration/src/m20260521_000164_reorganize_menus_scheme_b.rs create mode 100644 docs/discussions/2026-05-21-sidebar-menu-optimization-brainstorming.md create mode 100644 docs/discussions/menu-prototype.html diff --git a/apps/web/src/pages/health/ArticleManageList.tsx b/apps/web/src/pages/health/ArticleManageList.tsx index d776020..6048404 100644 --- a/apps/web/src/pages/health/ArticleManageList.tsx +++ b/apps/web/src/pages/health/ArticleManageList.tsx @@ -1,10 +1,11 @@ -import { useState, useEffect, useMemo } from 'react'; -import { useNavigate } from 'react-router-dom'; +import { useState, useEffect, useMemo, useCallback } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; import { Table, Button, Space, Input, + InputNumber, Select, Tag, Tabs, @@ -26,13 +27,20 @@ import { import { articleApi, articleCategoryApi, + articleTagApi, type ArticleListItem, type ArticleStatus, type ArticleTagItem, + type ArticleCategory, + type CreateCategoryReq, + type UpdateCategoryReq, + type CreateTagReq, } from '../../api/health/articles'; +import { handleApiError } from '../../api/client'; import { AuthButton } from '../../components/AuthButton'; import { PageContainer } from '../../components/PageContainer'; import { usePaginatedData } from '../../hooks/usePaginatedData'; +import { useThemeMode } from '../../hooks/useThemeMode'; import { formatDateTime } from '../../utils/format'; // --- 常量 --- @@ -66,12 +74,21 @@ const DEFAULT_FILTERS: ArticleFilters = { category_id: '', }; +const PAGE_TABS = [ + { key: 'articles', label: '文章' }, + { key: 'categories', label: '分类管理' }, + { key: 'tags', label: '标签管理' }, +]; + export default function ArticleManageList() { const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const activePageTab = searchParams.get('tab') || 'articles'; const [categories, setCategories] = useState<{ id: string; name: string }[]>([]); const [rejectModalOpen, setRejectModalOpen] = useState(false); const [rejectingArticle, setRejectingArticle] = useState(null); const [rejectForm] = Form.useForm(); + const isDark = useThemeMode(); // ---- 分页数据 Hook ---- const { @@ -96,15 +113,12 @@ export default function ArticleManageList() { { pageSize: 20, defaultFilters: { ...DEFAULT_FILTERS } }, ); - // ---- 分类列表 ---- useEffect(() => { articleCategoryApi.list() .then((cats) => setCategories(cats.map((c) => ({ id: c.id, name: c.name })))) .catch(() => {}); }, []); - // ---- 操作 ---- - const handleDelete = async (id: string, version: number) => { try { await articleApi.delete(id, version); @@ -240,8 +254,6 @@ export default function ArticleManageList() { ); - // ---- 列定义 ---- - const columns = useMemo(() => [ { title: '标题', @@ -353,74 +365,211 @@ export default function ArticleManageList() { }, ], [navigate, renderActions]); + // ---- 分类管理内联 ---- + const [catList, setCatList] = useState([]); + const [catLoading, setCatLoading] = useState(false); + const [catModalOpen, setCatModalOpen] = useState(false); + const [catEditing, setCatEditing] = useState(null); + const [catForm] = Form.useForm(); + + const fetchCategories = useCallback(async () => { + setCatLoading(true); + try { + const result = await articleCategoryApi.list(); + setCatList(result); + } catch { + message.error('加载分类列表失败'); + } finally { + setCatLoading(false); + } + }, []); + + useEffect(() => { + if (activePageTab === 'categories') fetchCategories(); + }, [activePageTab, fetchCategories]); + + const catParentOptions = catList + .filter((c) => !catEditing || c.id !== catEditing.id) + .map((c) => ({ label: c.name, value: c.id })); + + const catColumns = useMemo(() => [ + { title: '分类名称', dataIndex: 'name', key: 'name', render: (v: string) => {v} }, + { title: '别名', dataIndex: 'slug', key: 'slug', width: 180, render: (v?: string) => v || '-' }, + { title: '父分类', dataIndex: 'parent_name', key: 'parent_name', width: 140, + render: (_v: string | undefined, record: ArticleCategory) => { + if (!record.parent_id) return '-'; + return catList.find((c) => c.id === record.parent_id)?.name || record.parent_id; + }, + }, + { title: '排序', dataIndex: 'sort_order', key: 'sort_order', width: 80, render: (v: number) => v ?? 0 }, + { title: '描述', dataIndex: 'description', key: 'description', ellipsis: true, render: (v?: string) => v || '-' }, + { title: '操作', key: 'actions', width: 120, + render: (_: unknown, record: ArticleCategory) => ( + + + + + + + + +
+ +
+ + + + + +
+ +
+ 三级折叠 +
+ 侧边栏 +
+
+ + + +
+ + +
+
+ + + +