From ba328e02bb9d9af5201188ce308e7d6bf3771ba6 Mon Sep 17 00:00:00 2001 From: hongawen <83944980@qq.com> Date: Thu, 21 May 2026 21:42:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor(projects):=201=E3=80=81=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=A7=E8=A1=8C=E4=BB=BB=E5=8A=A1=EF=BC=8C=E8=A1=A8?= =?UTF-8?q?=E5=8D=95=E4=BC=98=E5=8C=96=EF=BC=9B2=E3=80=81=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E9=80=BB=E8=BE=91=E4=B8=B0=E5=AF=8C=E3=80=823?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E6=94=B9=E5=B7=B2=E7=9F=A5=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 14 + README.md | 35 -- ...-04-23-product-overview-homepage-design.md | 292 --------------- .../custom/business-attachment-uploader.vue | 8 +- src/components/custom/dict-select.vue | 67 +++- src/components/custom/dict-tag.vue | 49 ++- src/components/custom/table-search-fields.vue | 4 + src/constants/dict.ts | 26 +- src/enum/index.ts | 3 +- src/service/api/project-shared.ts | 30 +- src/service/api/project.ts | 17 + src/store/modules/dict/index.ts | 12 +- src/store/modules/workbench/index.ts | 11 + src/typings/api/dict.d.ts | 6 + src/typings/api/project.d.ts | 54 +++ src/views/product/list/index.vue | 2 +- .../product/list/modules/product-search.vue | 2 +- src/views/project/list/index.vue | 2 +- .../project/list/modules/project-search.vue | 2 +- .../components/requirement-tree-picker.vue | 334 ++++++++++++++++++ .../use-project-requirement-options.ts | 100 ++++++ .../composables/use-task-board-columns.ts | 2 +- .../composables/use-task-permissions.ts | 7 +- src/views/project/project/execution/index.vue | 51 ++- src/views/project/project/execution/mock.ts | 4 + .../execution-assignee-current-panel.vue | 2 +- .../modules/execution-assignee-log-panel.vue | 2 +- .../modules/execution-list-panel.vue | 100 +++++- .../modules/execution-operate-dialog.vue | 47 ++- .../modules/object-delete-dialog.vue | 7 +- .../modules/task-assignee-current-panel.vue | 2 +- .../modules/task-assignee-log-panel.vue | 2 +- .../execution/modules/task-board-view.vue | 18 +- .../execution/modules/task-info-readonly.vue | 12 +- .../execution/modules/task-operate-dialog.vue | 38 +- .../project/execution/modules/task-search.vue | 9 + .../execution/modules/task-table-view.vue | 7 + .../modules/task-worklog-form-dialog.vue | 44 ++- .../execution/modules/task-worklog-panel.vue | 148 +++++++- .../execution/modules/task-workspace.vue | 73 +++- .../project/project/requirement/index.vue | 21 +- .../composables/layout-storage-local.ts | 28 ++ .../workbench/composables/layout-storage.ts | 6 + .../composables/use-workbench-layout.ts | 158 +++++++++ .../composables/use-workbench-modules.ts | 160 +++++++++ .../composables/workbench-layout-default.ts | 30 ++ .../composables/workbench-layout-reconcile.ts | 31 ++ .../composables/workbench-layout-types.ts | 22 ++ src/views/workbench/homepage.ts | 135 +++++++ src/views/workbench/index.vue | 157 ++++++-- src/views/workbench/mock.ts | 148 ++++++++ .../modules/workbench-activity-panel.vue | 58 ++- .../workbench/modules/workbench-banner.vue | 45 --- .../workbench/modules/workbench-column.vue | 61 ++++ .../modules/workbench-edit-overlay.vue | 53 +++ .../workbench/modules/workbench-favorite.vue | 72 ++++ src/views/workbench/modules/workbench-kpi.vue | 68 ++-- .../modules/workbench-module-card.vue | 150 ++++++++ .../modules/workbench-module-library.vue | 80 +++++ .../modules/workbench-my-requirement.vue | 101 ++++++ .../workbench/modules/workbench-my-task.vue | 105 ++++++ .../modules/workbench-progress-chart.vue | 78 ++++ .../modules/workbench-project-grid.vue | 70 ++-- .../modules/workbench-project-health.vue | 97 +++++ .../modules/workbench-shortcut-picker.vue | 102 ++++++ .../workbench/modules/workbench-shortcut.vue | 132 +++++++ .../workbench/modules/workbench-team-todo.vue | 47 +++ .../modules/workbench-todo-panel.vue | 113 +++--- 68 files changed, 3329 insertions(+), 644 deletions(-) delete mode 100644 README.md delete mode 100644 docs/superpowers/specs/2026-04-23-product-overview-homepage-design.md create mode 100644 src/store/modules/workbench/index.ts create mode 100644 src/views/project/project/execution/components/requirement-tree-picker.vue create mode 100644 src/views/project/project/execution/composables/use-project-requirement-options.ts create mode 100644 src/views/workbench/composables/layout-storage-local.ts create mode 100644 src/views/workbench/composables/layout-storage.ts create mode 100644 src/views/workbench/composables/use-workbench-layout.ts create mode 100644 src/views/workbench/composables/use-workbench-modules.ts create mode 100644 src/views/workbench/composables/workbench-layout-default.ts create mode 100644 src/views/workbench/composables/workbench-layout-reconcile.ts create mode 100644 src/views/workbench/composables/workbench-layout-types.ts create mode 100644 src/views/workbench/modules/workbench-column.vue create mode 100644 src/views/workbench/modules/workbench-edit-overlay.vue create mode 100644 src/views/workbench/modules/workbench-favorite.vue create mode 100644 src/views/workbench/modules/workbench-module-card.vue create mode 100644 src/views/workbench/modules/workbench-module-library.vue create mode 100644 src/views/workbench/modules/workbench-my-requirement.vue create mode 100644 src/views/workbench/modules/workbench-my-task.vue create mode 100644 src/views/workbench/modules/workbench-progress-chart.vue create mode 100644 src/views/workbench/modules/workbench-project-health.vue create mode 100644 src/views/workbench/modules/workbench-shortcut-picker.vue create mode 100644 src/views/workbench/modules/workbench-shortcut.vue create mode 100644 src/views/workbench/modules/workbench-team-todo.vue diff --git a/CLAUDE.md b/CLAUDE.md index 165da22..c94345b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -415,3 +415,17 @@ pnpm preview # preview server (9725) - 新建写接口 → 不用管,默认兜底;只有明确"允许短时间连发"才传 `dedupe: false` - 新建上传 / 下载流程 → FormData / Blob 天然不在去重范围,按原方式写即可 - 用户报"双击双发"复现不出来 → 检查目标按钮是否走了 `business-form-dialog`;若用的是 `ElMessageBox.confirm`,靠第二层就该挡住 + +--- + +## 20. 我生成文档的输出格式(强约束) + +- **superpowers 工作流(`docs/superpowers/plans/`、`docs/superpowers/specs/`)下输出的文档继续用 `.md`**——工作流以 markdown 为前提。 +- **其他**我生成的文档(设计方案、复盘、规约、技术经验沉淀等)**默认用 `.html`**,沿用 `docs/debt/` 现有 HTML 文档(参考 `token-刷新机制对齐分析.html`、`技术负债台账.html`)的样式骨架: + - 单文件、内联 CSS + - `max-width: 980px` 居中容器、`padding: 32px 28px 80px` + - 14px / `line-height: 1.7`、`PingFang SC` / `Microsoft YaHei` 中文字体优先 + - 模块化区块:`section` + 编号 h2、`card`、`table.cmp`、`pre`、`tag-ok/warn/bad/crit` + - 配色用 `--bg / --panel / --border / --text / --primary` 一套 CSS 变量 +- **`README.md`** 是目录索引约定文件,**保持 `.md`**(不强行 `.html`)。 +- **已有 `.md` 文档不主动改写**,等用户明确要求再转。 diff --git a/README.md b/README.md deleted file mode 100644 index 9207638..0000000 --- a/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# cn-rdms-web - -这是当前项目的前端工程仓库。 - -原开源模板项目的介绍内容已移除,这个 README 现在只保留当前项目自身所需的信息。 - -## 项目说明 - -待补充。 - -建议后续在这里补充: - -- 项目背景 -- 技术栈 -- 目录结构 -- 本地启动方式 -- 环境变量说明 -- 构建与发布流程 - -## 本地开发 - -```bash -pnpm install -pnpm dev -``` - -## 常用命令 - -```bash -pnpm dev -pnpm build -pnpm build:dev -pnpm typecheck -pnpm lint -``` diff --git a/docs/superpowers/specs/2026-04-23-product-overview-homepage-design.md b/docs/superpowers/specs/2026-04-23-product-overview-homepage-design.md deleted file mode 100644 index 58da0c6..0000000 --- a/docs/superpowers/specs/2026-04-23-product-overview-homepage-design.md +++ /dev/null @@ -1,292 +0,0 @@ -# 产品对象首页改版设计说明 - -日期:2026-04-23 - -## 1. 目标 - -本设计用于收敛 RDMS 产品对象上下文默认首页的改版方向。 - -本轮目标不是继续做“说明型占位页”,而是明确把当前 `/product/dashboard?objectId=...` 改成一个真正可用的产品对象首页: - -- 第一眼先让用户知道当前看的是什么产品 -- 第二眼能快速判断对象最近发生了什么 -- 第三眼能看出需求池现在的经营状态和最近变化 -- 底部为后续业务模块保留正式挂载位,而不是临时拼接入口 - -## 2. 已确认诉求 - -基于本轮对话,已确认以下用户诉求: - -1. 首页顶部必须先展示产品基础概述,而不是先铺统计卡片 -2. 基础概述至少包含:名称、编号、团队、产品经理等对象基础信息 -3. 页面需要一块明显的时间线,用于承接产品对象与团队变更动态 -4. 页面需要承接需求池管理情况,重点看总量、状态、待处理等统计信息 -5. 需求相关事件不要混入对象时间线,应单独作为需求池最近变化区域 -6. 快捷入口不要保留 -7. 底部允许保留后续扩展区,重点预留给里程碑、风险点管理、产品资料等模块 -8. 能接真实接口就接真实接口,当前没有稳定接口的区域允许先用假数据,但结构必须按正式首页来设计 - -## 3. 首页定位结论 - -本页定位不是: - -- 纯报表看板 -- 纯审计日志页 -- 设置页搬运版 -- 导航入口集合页 - -本页定位应当是: - -- 产品对象首页 -- 偏统计,也带审计 -- 但页面主语始终是“当前产品对象” - -换句话说,这个页面要同时回答三个问题: - -1. 我现在看的是什么产品? -2. 这个产品对象最近发生了什么? -3. 这个产品的需求池现在处于什么状态? - -## 4. 页面结构 - -### 4.1 桌面端结构 - -桌面端建议采用三层结构: - -1. 顶部 `对象基础概述横幅` -2. 中部 `左时间线 + 右需求池双模块` -3. 底部 `扩展信息区` - -推荐布局比例: - -- 顶部横幅:`24 / 24` -- 中部主区:左 `16 / 24`,右 `8 / 24` -- 底部扩展区:`24 / 24` - -中部左侧时间线高度应明显高于右侧任一单模块,形成首页主阅读区。 - -### 4.2 移动端结构 - -移动端统一退化为单列纵向布局,顺序为: - -1. 对象基础概述横幅 -2. 对象 / 团队动态时间线 -3. 需求池管理概览 -4. 需求池最近变化 -5. 扩展信息区 - -移动端不强撑左右栏并排,不做卡片墙式压缩。 - -## 5. 模块设计 - -### 5.1 对象基础概述横幅 - -顶部采用“档案横幅型”,不采用纯指标卡片型。 - -横幅左侧承接对象身份信息: - -- 产品名称 -- 产品编号 -- 当前状态标签 -- 产品经理 -- 团队规模 -- 团队角色摘要 -- 简短描述或备注 - -横幅右侧承接 4 个摘要指标: - -- 团队人数 -- 需求总量 -- 待处理需求 -- 最近动态时间 - -设计原则: - -- 左侧负责建立对象识别 -- 右侧负责快速判断当前概况 -- 右侧指标只保留 4 项,不堆成报表卡片墙 - -### 5.2 对象 / 团队动态时间线 - -该区域位于中部左侧,是首页的主阅读区。 - -这条时间线只承接对象与团队变化,不承接需求事件。 - -第一版事件范围收敛为: - -- 产品创建 -- 产品状态变更 -- 产品经理变更 -- 成员加入 -- 成员移出 -- 成员角色调整 - -每条时间线建议展示: - -- 事件标题 -- 事件类型标签 -- 发生时间 -- 操作摘要 -- 必要时展示原因或备注 - -表达目标是“业务时间线”,不是后台审计表格。 - -### 5.3 需求池管理概览 - -该区域位于中部右侧上半块,用于表达需求池的经营状态。 - -第一版首页需要优先看到的内容: - -- 需求总量 -- 各状态数量 -- 待处理数量 -- 高优先级待处理数量 - -展示方式建议为“摘要指标 + 状态分布列表”,不直接在首页展开完整需求表格。 - -这一块回答的是: - -- 需求池是否健康 -- 当前待处理压力大不大 -- 是否存在需要优先关注的积压 - -### 5.4 需求池最近变化 - -该区域位于中部右侧下半块,与需求池管理概览上下分层,但属于同一侧栏语义。 - -该区域不重复展示总量,而是展示需求池最近发生的变化。 - -第一版建议承接: - -- 最近新增需求 -- 最近状态流转 -- 最近关闭或完成 - -每条记录建议至少展示: - -- 需求标题 -- 动作类型 -- 时间 -- 当前状态或状态变更摘要 - -若当前没有真实数据,仍保留正式模块壳,不退化成“待开发”一句话。 - -### 5.5 扩展信息区 - -底部不再保留快捷入口,改为正式扩展信息区。 - -当前优先预留 3 类模块位: - -- 里程碑 -- 风险点管理 -- 产品资料 - -这一层的作用是: - -- 为后续对象级信息继续扩展留下稳定挂载位 -- 不把中部主结构挤成信息大杂烩 -- 避免为了未来模块提前做假导航入口 - -如果当前没有稳定接口,可先保留正式卡片结构与空态说明。 - -## 6. 数据策略 - -### 6.1 真实接口优先 - -当前首页优先消费现有真实接口: - -- `fetchGetProduct` -- `fetchGetProductSettings` -- `fetchGetProductMembers` - -这些接口足以支撑: - -- 对象基础概述中的名称、编号、状态、产品经理、描述 -- 团队人数与角色摘要 -- 最近动态中的产品创建、状态变化、成员加入/移出 - -### 6.2 假数据使用边界 - -当前没有稳定真实接口的区域,允许先用假数据,但边界必须明确: - -- 需求池管理概览 -- 需求池最近变化 -- 扩展信息区中的里程碑、风险点管理、产品资料摘要 - -假数据的使用原则: - -1. 只补“当前没有稳定接口”的区域 -2. 不反向污染对象基础信息 -3. 不把假数据混入对象上下文 store -4. 数据源要集中放在概览页自己的 mock 模块中,方便后续替换 - -### 6.3 不推荐的做法 - -以下做法应避免: - -- 把需求假数据散落写进页面组件 -- 用对象 demo 数据冒充真实产品详情 -- 把对象时间线和需求时间线混成一条 -- 用快捷入口伪装成首页内容 - -## 7. 空态规则 - -首页至少要区分三种状态: - -1. 能力未接入,只能先显示正式占位信息 -2. 能力已接入,但当前该产品暂无业务数据 -3. 当前用户无权限查看该模块 - -这三种状态不能共用一套模糊文案。 - -对需求池和扩展信息区,当前阶段更推荐“正式空态”而不是“待开发”。 - -## 8. 页面边界 - -首页明确不承接以下内容: - -- 快捷入口导航区 -- 完整团队成员表格 -- 完整需求列表表格 -- 设置页重表单 -- 完整审计日志明细页 - -首页要做的是概述、判断与阅读,不是重操作页。 - -## 9. 实施建议 - -第一阶段建议先完成结构性改造: - -1. 重做顶部横幅,建立对象档案感 -2. 保留中部左高右双块结构 -3. 用真实接口接通对象概述与对象 / 团队时间线 -4. 用局部 mock 数据先接通需求池两块和底部扩展区 - -第二阶段再逐步替换需求池与扩展区数据源: - -- 接真实需求池统计接口 -- 接真实需求动态接口 -- 接里程碑、风险点、产品资料摘要接口 - -## 10. 验证标准 - -本设计是否成立,可按以下标准判断: - -1. 进入首页后,第一眼能认出当前产品对象 -2. 用户能自然读到对象 / 团队最近发生了什么 -3. 右侧能快速判断需求池当前压力与最近变化 -4. 页面看起来像“对象首页”,而不是“普通后台卡片堆叠页” -5. 当前没有真实接口的区域也保留正式结构,不显得像临时占位 -6. 后续新增里程碑、风险点管理、产品资料等能力时,不需要推翻整页结构 - -## 11. 本轮设计结论 - -本轮最终设计结论如下: - -- 首页定位为“产品对象首页”,偏统计,也带审计,但不做纯报表页 -- 顶部采用档案横幅型,先立住对象身份信息 -- 中部左侧是高权重的对象 / 团队动态时间线 -- 中部右侧拆为“需求池管理概览 + 需求池最近变化”上下两块 -- 底部去掉快捷入口,改为正式扩展信息区 -- 当前有真实接口的模块优先接真实接口 -- 当前没有稳定接口的区域允许先用假数据,但必须隔离在概览页局部 mock 数据源中 diff --git a/src/components/custom/business-attachment-uploader.vue b/src/components/custom/business-attachment-uploader.vue index 0cff3f3..0cc1767 100644 --- a/src/components/custom/business-attachment-uploader.vue +++ b/src/components/custom/business-attachment-uploader.vue @@ -470,7 +470,7 @@ onBeforeUnmount(() => { { {{ item.name }} {{ formatSize(item.size) }} - 下载 + 下载 @@ -509,7 +509,7 @@ onBeforeUnmount(() => { { {{ item.name }} {{ formatSize(item.size) }} - 下载 + 下载 diff --git a/src/components/custom/dict-select.vue b/src/components/custom/dict-select.vue index ded9e89..7892093 100644 --- a/src/components/custom/dict-select.vue +++ b/src/components/custom/dict-select.vue @@ -14,6 +14,8 @@ interface Props { multiple?: boolean; collapseTags?: boolean; collapseTagsTooltip?: boolean; + /** 下拉项右侧追加字典 remark 中文释义(优先级等需要"P0 → 紧急"对照的场景) */ + showRemark?: boolean; } const props = withDefaults(defineProps(), { @@ -24,7 +26,8 @@ const props = withDefaults(defineProps(), { onlyEnabled: true, multiple: false, collapseTags: false, - collapseTagsTooltip: false + collapseTagsTooltip: false, + showRemark: false }); const model = defineModel | null | undefined>({ @@ -35,18 +38,27 @@ const { enabledDictData, dictData } = useDict(() => props.dictCode); const dictOptions = computed(() => { const source = props.onlyEnabled ? enabledDictData.value : dictData.value; - return source.map(item => ({ label: item.label, - value: item.value + value: item.value, + colorType: item.colorType ?? null, + remark: item.remark ?? null })); }); + +// 单选时取当前选中项的 colorType,用于触发器 prefix 色块 +const selectedColorType = computed(() => { + if (props.multiple) return null; + const value = model.value; + if (value === null || value === undefined || value === '') return null; + return dictOptions.value.find(opt => opt.value === value)?.colorType ?? null; +}); - + diff --git a/src/components/custom/dict-tag.vue b/src/components/custom/dict-tag.vue index 94b4ba4..e8aadc1 100644 --- a/src/components/custom/dict-tag.vue +++ b/src/components/custom/dict-tag.vue @@ -1,4 +1,6 @@