Browse Source

新增工作台页面并接入首页看板接口

- 新增 src/views/workbench/index.vue:6 项 KPI + 近7日趋势柱状图 + 待办与预警表 + 最新上报前5条

- 新增 src/api/analysis/home.js:overview / todo-warning / trend-latest 三个接口封装

- 平台同步失败无接口展示占位、风控处理禁用;视觉沿用 prototype-theme

- 同步更新 PAGES_TODO / CURRENT_STATUS / DEVELOPMENT_LOG;提交前 eslint 与 build:prod 通过
master
DU 4 days ago
parent
commit
fbacb9cf61
  1. 3
      docs/development/CURRENT_STATUS.md
  2. 9
      docs/development/DEVELOPMENT_LOG.md
  3. 215
      docs/development/PAGES_TODO.md
  4. 16
      src/api/analysis/home.js
  5. 363
      src/views/workbench/index.vue

3
docs/development/CURRENT_STATUS.md

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
# 当前开发状态
更新时间:2026-10-08
更新时间:2026-10-15
## 已完成的基础改造
@ -34,6 +34,7 @@ @@ -34,6 +34,7 @@
| 页面 | 文件 | 菜单组件路径 | 当前状态 |
| --- | --- | --- | --- |
| 工作台 | `src/views/workbench/index.vue` | `workbench/index` | 已开发(2026-10-15),待测试。6 项 KPI + 近 7 日趋势柱状图 + 待办与预警表 + 最新上报前 5 条;接口 `analysis/home/overview`、`todo-warning`、`trend-latest`。“平台同步失败”无接口,展示 `—`、重试禁用;风控预警“处理”目标页未开发,禁用 |
| 隐患审核 | `src/views/hazard/audit/index.vue` | `hazard/audit/index` | 已完成隐患详情/附件展示、平台字段补录、通过/驳回/等级切换,并接入 `getProcessDetail` 自动补齐审批 `taskId`;已在左侧菜单隐藏,仅由台账“去审核”等入口携带参数进入 |
| 隐患台账 | `src/views/hazard/ledger/index.vue` | `hazard/ledger/index` | 已完成新版 Card Table、五态筛选/Tag、分页、详情弹窗和审核入口;列表已切换为 `/flowable/hazard/todoList`;单位筛选按角色使用 `factoryList`/`myFactory`;附件九宫格支持大图预览;平台同步列暂时隐藏;同步重试等待后端接口 |
| 积分管理 | `src/views/points/management/index.vue` | `points/management/index` | 已完成积分规则 Tab、统一 Card 列表、厂区列、分页、详情、新增、启停和删除;超管可筛选/选择厂区,厂区管理员可管理本厂区规则;其余 Tab 暂禁用 |

9
docs/development/DEVELOPMENT_LOG.md

@ -1,5 +1,14 @@ @@ -1,5 +1,14 @@
# 开发日志
## 2026-10-15:新增 Web 工作台页面
- 新增 `src/views/workbench/index.vue`:6 项 KPI(待审核 / 本月已闭环 / 平台同步失败 / 今日新增上报 / 今日兑换单 / 今日发放积分)、近 7 日上报趋势柱状图、待办与预警表、最新上报(前 5 条)。
- 新增 `src/api/analysis/home.js`:`getHomeOverview()`、`getHomeTodoWarning()`、`getHomeTrendLatest()`,对应 `analysis/home/overview|todo-warning|trend-latest`。
- 交互约定:“平台同步失败”KPI 与待办表“重试”后端无接口,展示 `—` / 禁用并 tooltip 说明;风控预警“处理”目标页未开发,禁用并提示;KPI 与待办跳转隐患台账 / 积分管理;最新上报表“审核”按 `id` 跳 `/hazard/audit/detail`。
- 视觉沿用 `prototype-theme.scss` 变量与既有卡片风格;图表用 `echarts` + `views/dashboard/mixins/resize`(含销毁 dispose)。
- 菜单(需后端配置):一级菜单「工作台」,路由地址 `workbench`,组件路径 `workbench/index`,访问地址 `/workbench`。
- 验证:`eslint --no-ignore` 通过、`npm run build:prod` 通过(仅既有 warning)、dev server 编译成功。
## 2026-10-08:隐患审核积分档位按厂区查询
- 问题:审核页“积分档位”下拉为空且显示裸规则ID;根因是 `rulesByLevel` 未按隐患所属厂区查询——超管无厂区上下文返回空 `children`,而 `trialCalculate` 带了 `deptId` 故能命中。

215
docs/development/PAGES_TODO.md

@ -0,0 +1,215 @@ @@ -0,0 +1,215 @@
# 页面开发总览 · Web 管理后台 + APP 员工端
> 更新时间:2026-10-15
> 数据来源:代码现状(`zhongyou_web/src`、`zhongyouApp/src`)+ 最新接口文档([docs/api](../api/README.md),api.json 282 个接口)+ 甲方功能表 + 两份高保真原型。
>
> **图例**
> - 状态:`✅ 已完成` / `⚠️ 部分完成` / `❌ 未开始`
> - 可开工:`✅ 可直接开工`(接口与布局均就绪)/ `⚠️ 需确认`(部分接口或设计待定)/ `⛔ 不可开工`(缺关键接口或第三方方案)
>
> 本文件为页面级总览;具体接口字段见 [page-api-map.md](../api/page-api-map.md) 与 [docs/api/modules](../api/modules)。开发约定见 [AGENTS.md](../../AGENTS.md)。
## 一、总览统计
| 端 | 页面总量 | 已完成 | 部分完成 | 未开始 |
| --- | --- | --- | --- | --- |
| Web 管理后台 | 34(含系统监控/个人中心) | 25 | 2(积分管理、切换日志) | 7(工作台、商城运营+7子页、安全兑换管理、数据分析、风控与审计) |
| APP 员工端 | 15(14 屏 + 通知提醒卡片) | 10 | 0 | 5(设置、商城 5 屏)+ 通知提醒卡片 |
---
## 二、Web 管理后台
| # | 一级导航 | 页面 | 状态 | 接口就绪 | 可开工 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | — | 登录 | ✅ | — | — | [login.vue](../../src/views/login.vue) |
| 2 | 工作台 | 工作台(KPI 6 卡 + 近7日趋势 + 待办预警 + 最新上报) | ⚠️ 待测试 | ✅ | ✅ | **已开发(2026-10-15)**:`src/views/workbench/index.vue` + `src/api/analysis/home.js`,接口 `analysis/home/overview`、`todo-warning`、`trend-latest`。“平台同步失败”无接口(展示 `—`、重试禁用);风控预警“处理”目标页未开发(禁用) |
| 3 | 隐患治理 | 隐患审核 | ✅ | ✅ | — | [hazard/audit](../../src/views/hazard/audit/index.vue) |
| 4 | 隐患治理 | 隐患台账 | ✅ | ⚠️ | — | [hazard/ledger](../../src/views/hazard/ledger/index.vue);“重试同步”缺平台同步接口,列已隐藏 |
| 5 | 积分与商城 | 积分管理 · Tab1 积分规则 | ✅ | ✅ | — | [points/management](../../src/views/points/management/index.vue) |
| 6 | 积分与商城 | 积分管理 · Tab2 申诉处理 | ❌ | ✅ | ✅ | 受理/处理/审批:`appeal/todoList`、`accept/{id}`、`handle/{id}`、`completeFlow`、`detail/{id}`([积分申诉管理](../api/modules/07-points-appeal.md)) |
| 7 | 积分与商城 | 积分管理 · Tab3 手动调整 | ❌ | ✅ | ✅ | `balance/adjust`(补发/扣减 + 自动生成流水与审计)、`balance/getByUserId/{userId}`([用户积分账户](../api/modules/04-points-balance.md)) |
| 8 | 积分与商城 | 积分管理 · Tab4 流水冲正 | ❌ | ✅ | ✅ | `transaction/reverse/{logId}`、`transaction/list`([积分流水明细](../api/modules/05-points-transaction.md)) |
| 9 | 积分与商城 | 商城运营(主页:KPI + 兑换订单表 + 订单详情时间线 + 退款/导出) | ❌ | ⚠️ | ⚠️ | 订单/追踪/物品/日志接口齐全([兑换订单](../api/modules/08-exchange-order.md)、[订单追踪](../api/modules/10-exchange-order-track.md)、[兑换物品](../api/modules/09-exchange-item.md)、[操作日志](../api/modules/12-exchange-operation-log.md));**“惠服对接状态卡”(Token 状态/同步时间/全量·增量·单品同步/刷新 Token)无接口**,该卡片需等后端 |
| 10 | 积分与商城 | 商城运营 › 分类管理 | ❌ | ⛔ | ⛔ | 页面本体由惠服平台提供,需甲方给出嵌入方案(URL/SSO) |
| 11 | 积分与商城 | 商城运营 › 轮播图管理 | ❌ | ⛔ | ⛔ | 同上 |
| 12 | 积分与商城 | 商城运营 › 楼层管理 | ❌ | ⛔ | ⛔ | 同上 |
| 13 | 积分与商城 | 商城运营 › 纯积分兑换专区 | ❌ | ⛔ | ⛔ | 同上 |
| 14 | 积分与商城 | 商城运营 › 纯积分兑换管理 | ❌ | ⛔ | ⛔ | 同上 |
| 15 | 积分与商城 | 商城运营 › 日常积分兑换管理 | ❌ | ⛔ | ⛔ | 同上 |
| 16 | 积分与商城 | 商城运营 › 日常积分兑换专区 | ❌ | ⛔ | ⛔ | 同上 |
| 17 | 积分与商城 | 安全兑换管理(限额/大额审批/防刷 4 维度/校验) | ❌ | ✅ | ✅ | `exchange/config/active`、`validate`、`switch/status`、`switch/toggle`、`updateById`([兑换安全配置](../api/modules/11-exchange-config.md));防刷 `anti/farm/active`、`detectAll`、`detectSelfAudit`([防刷分管控](../api/modules/13-anti-farm-config.md)) |
| 18 | 分析与风控 | 数据分析(时效漏斗/排行/分类分布/导出;**无热力图、无顶部筛选列**) | ❌ | ✅ | ✅ | 20 个接口齐全([数据分析](../api/modules/15-data-analysis.md));“超期未处理表”可用隐患待办列表拼接。**页面调整(2026-10-15 甲方)**:去掉隐患热力图、去掉顶部筛选列;“处理时效漏斗”与“积分排行”改为左右布局占一行 |
| 19 | 分析与风控 | 风控与审计(Tab1 风控预警 + Tab2 审计日志) | ❌ | ⚠️ | ⚠️ | 审计日志 ✅:`audit/log/getListByPage`、`audit/log/biz/{bizId}`([操作审计日志](../api/modules/16-audit-log.md));**风控预警列表 + 处置(放行/冻结 24h/黑名单)无接口**,Tab1 需等后端 |
| 20 | 流程管理 | 隐患流程配置 | ✅ | ✅ | — | [flowable/hazardConfig](../../src/views/flowable/hazardConfig/index.vue) |
| 21 | 流程管理 | 隐患上报(流程发起) | ✅ | ✅ | — | [flowable/task/myProcess](../../src/views/flowable/task/myProcess) |
| 22 | 流程管理 | 隐患审批(待办/已办) | ✅ | ✅ | — | [flowable/task/todo](../../src/views/flowable/task/todo) |
| 23 | 流程管理 | 切换日志 | ⚠️ 空页 | ⛔ | ⛔ | **无切换日志列表/详情接口**,仅有 `POST /flowable/hazard/switchLevel` 操作接口(见 [CURRENT_STATUS.md](./CURRENT_STATUS.md) 已知风险) |
| 24 | 流程管理 | 流程监听 | ✅ | ✅ | — | [system/listener](../../src/views/system/listener/index.vue) |
| 25 | 流程管理 | 流程定义 | ✅ | ✅ | — | [flowable/definition](../../src/views/flowable/definition/index.vue) |
| 26 | 流程管理 | 审批流程设计 | ✅ | ✅ | — | [flowable/definition/model.vue](../../src/views/flowable/definition/model.vue) |
| 27 | 流程管理 | 表单配置 | ✅ | ✅ | — | [flowable/task/form](../../src/views/flowable/task/form/index.vue) |
| 28 | 流程管理 | 流程表达式 | ✅ | ✅ | — | [system/expression](../../src/views/system/expression/index.vue) |
| 29 | 系统管理 | 用户管理 | ✅ | ✅ | — | [system/user](../../src/views/system/user/index.vue) |
| 30 | 系统管理 | 角色管理 | ✅ | ✅ | — | [system/role](../../src/views/system/role/index.vue) |
| 31 | 系统管理 | 菜单管理 | ✅ | ✅ | — | [system/menu](../../src/views/system/menu/index.vue) |
| 32 | 系统管理 | 部门管理 | ✅ | ✅ | — | [system/dept](../../src/views/system/dept/index.vue) |
| 33 | 系统管理 | 岗位管理 | ✅ | ✅ | — | [system/post](../../src/views/system/post/index.vue) |
| 34 | 系统管理 | 字典管理 | ✅ | ✅ | — | [system/dict](../../src/views/system/dict/index.vue) |
| — | 系统监控 | 在线用户/定时任务/调度日志/缓存/服务/操作日志/登录日志 | ✅ | ✅ | — | [monitor/](../../src/views/monitor)(旧模块对接) |
| — | — | 个人中心 / 账号退出 | ✅ | ✅ | — | 已实现 |
**Web 可直接开工(5 项)**:工作台、积分管理 Tab2/3/4、安全兑换管理、数据分析。
**Web 需确认后开工(2 项)**:商城运营(订单部分可先做,惠服对接卡待接口)、风控与审计(审计日志可先做,风控预警待接口)。
**Web 阻塞(1 + 7 项)**:切换日志(无接口);商城运营 7 个三方子页(待惠服方案)。
---
## 三、APP 员工端
| # | 一级导航 | 页面/组件 | 状态 | 接口就绪 | 可开工 | 说明 |
| --- | --- | --- | --- | --- | --- | --- |
| 1 | — | 登录 | ✅ | ✅ | — | [LoginScreen](../../../zhongyouApp/src/screens/auth/LoginScreen.tsx) |
| 2 | 首页 | 首页(用户区/四宫格/公告条/今日概览/最近上报) | ✅ | ✅ | — | [HomeDashboardScreen](../../../zhongyouApp/src/screens/home/HomeDashboardScreen.tsx);缺“通知提醒卡片”(见 #14) |
| 3 | 上报 | 隐患录入(含照片≤8、草稿、校验、提交) | ✅ | ✅ | — | [ReportFormV2Screen](../../../zhongyouApp/src/screens/home/ReportFormV2Screen.tsx) |
| 4 | 上报 | 草稿箱 | ✅ | ✅ | — | [HazardDraftsScreen](../../../zhongyouApp/src/screens/home/HazardDraftsScreen.tsx) |
| 5 | 记录 | 隐患台账(卡片列表 + 审批弹层) | ✅ | ✅ | — | [RecordsLedgerScreen](../../../zhongyouApp/src/screens/home/RecordsLedgerScreen.tsx) |
| 6 | 我的 | 个人中心 | ⚠️ | ✅ | — | [ProfileScreen](../../../zhongyouApp/src/screens/home/ProfileScreen.tsx);“兑换”统计无值、设置入口为占位 |
| 7 | 我的 | 积分明细 | ✅ | ✅ | — | [PointsScreen](../../../zhongyouApp/src/screens/home/PointsScreen.tsx) |
| 8 | 我的 | 我的申诉(含撤回/查看) | ✅ | ✅ | — | [AppealsScreen](../../../zhongyouApp/src/screens/home/AppealsScreen.tsx) |
| 9 | 我的 | 发起申诉 | ✅ | ✅ | — | [AppealFormScreen](../../../zhongyouApp/src/screens/home/AppealFormScreen.tsx) |
| 10 | 我的 | **设置**(消息推送/声音/震动/关于我们/清除缓存/退出登录) | ❌ | ✅(纯本地) | ✅ | 目前是 `Alert('设置功能待接入')`;开关/缓存用 AsyncStorage,无后端依赖,参考原型 `scr-settings` |
| 11 | 我的 | **我的兑换** | ❌ | ✅ | ✅ | `exchange/order/myList`([积分兑换订单管理](../api/modules/08-exchange-order.md))现成,可直接做 |
| 12 | 公共组件 | **通知提醒卡片**(首页顶部未读提醒,可关闭/跳消息中心) | ❌ | ✅ | ✅ | `message/unread-count`、`message/list`([用户消息中心](../api/modules/23-user-message.md)) |
| 13 | 我的 | 消息中心(列表/详情/已读) | ✅ | ✅ | — | [MessagesScreen](../../../zhongyouApp/src/screens/home/MessagesScreen.tsx) |
| 14 | 商城 | 商城-首页 | ❌ | ⛔ | ⛔ | 惠服商城页面本体(三方),见下方原因 |
| 15 | 商城 | 商城-商品详情 / 购物车 / 确认订单 / 商城个人中心 | ❌ | ⛔ | ⛔ | 同上(4 屏) |
> 备注:APP 现状已含“隐患录入照片上传≤8 张”(`uploadHazardAttachment`),[zhongyouApp/开发任务清单.md](../../../zhongyouApp/开发任务清单.md) 中“照片未做”已过时。
**APP 可直接开工(3 项)**:设置、我的兑换、通知提醒卡片。
**APP 阻塞(5 屏 + 待确认)**:商城 5 屏(惠服 URL/SSO);个人信息修改(`api.json` 未收录系统用户 CRUD,需确认)。
---
## 四、不可开工 / 需确认的原因汇总
| 页面 | 阻塞原因 | 需要谁提供 |
| --- | --- | --- |
| 切换日志(Web) | 无切换日志列表/详情接口,仅有切换操作接口 | 后端补接口 |
| 商城运营 · 惠服对接状态卡(Web) | 无 Token 状态 / 同步时间 / 全量·增量·单品同步 / 刷新 Token 接口 | 后端补接口 |
| 风控预警(Web 风控与审计 Tab1) | 无预警列表与处置(放行/冻结 24h/黑名单)接口 | 后端补接口 |
| 隐患台账“重试同步” / 数据分析“平台同步失败”(Web) | 无平台同步状态 / 重试接口 | 后端补接口 |
| 商城运营 7 个三方子页(Web) | 页面本体由惠服平台提供 | 甲方/惠服:嵌入方案、URL、SSO |
| 商城 5 屏(APP) | 惠服商城正式地址 + SSO + 回调协议未定 | 甲方/惠服 |
| 个人信息修改(APP) | 最新 `api.json` 未收录系统用户 CRUD(RuoYi 原生 `/system/user/profile` 是否可用待确认) | 后端确认 |
| 我的兑换的下单/退款(APP,若需在端内下单) | 下单属惠服商城能力,端内仅能查记录 | 甲方/惠服 |
---
## 五、UI 风格统一要求(写页面前必读)
**通用原则**:新页面必须复用现有组件与主题变量,不得自造一套视觉;列表页沿用现有 Card 列表模式。**明确:是。**
### Web(Vue2 + Element UI 2.15)
- 布局:212px 左侧导航 + 52px Header + `#F0F2F5` 背景;主色 `#1890ff`;表格字号 12px。
- 统一主题:复用 [prototype-theme.scss](../../src/assets/styles/prototype-theme.scss),不要局部覆盖 Element 变量。
- 列表页:统一使用共享组件 [DataListPanel](../../src/components/DataListPanel/index.vue),开启 `modern-table`(无竖线、表头可拖列宽);筛选/批量操作/分页/状态 Tag 与既有页面(用户管理、隐患台账、积分规则)保持一致。
- 状态与操作:状态用 `el-tag`(启用绿/停用灰,等级/结果按既定色);破坏性操作二次确认;空态/加载态/错误态齐备。
- 弹窗:沿用现有 `el-dialog` 尺寸与按钮主次(主蓝次白、删除红)。
### APP(React Native)
- 主色 `#D93B48`;页面背景 `#F0F1F3`;卡片白底圆角;分隔线 `#F5F5F7`;正文色 `#1A1A2E`、次要 `#5A5A72`、弱化 `#9898AE`。
- 布局模式沿用现有 screen:顶部 hero 卡 + 白色 `crd` 卡片 + `li-it` 列表行 + `tg` 标签;图标统一用 `@ant-design/icons-react-native` 的 `IconOutline`。
- 交互:设置页开关样式参考原型 `scr-settings`;通知提醒卡片参考原型 `ntf`(顶部条、可关闭、点击跳消息中心)。
- 数据态:加载/空态/错误提示沿用现有 `ActivityIndicator` + 文案风格,不伪造数据。
---
## 六、开工顺序建议
1. **Web**:工作台 → 积分管理 Tab2/3/4 → 安全兑换管理 → 数据分析(均接口就绪)。
2. **APP**:设置 → 我的兑换 → 通知提醒卡片(均接口就绪)。
3. **待接口/第三方**:商城运营(订单部分先做)、风控与审计(审计日志先做)、商城 5 屏、切换日志。
> 每完成一页,同步更新本文件状态、[CURRENT_STATUS.md](./CURRENT_STATUS.md)、[DEVELOPMENT_LOG.md](./DEVELOPMENT_LOG.md) 及 [page-api-map.md](../api/page-api-map.md)。
---
## 七、备注与提醒(待跟进)
- [ ] **触发条件**:第二节 / 第三节的「可直接开工」项(Web 6 项 + APP 3 项)全部完成后。
- [ ] **动作**:提醒甲方/负责人决定 **是否进一步梳理「积分申诉」功能**。
- **梳理范围**(届时对齐):
1. Web「积分管理 → 申诉处理」Tab 的完整交互与字段(列表、受理、处理、流程审批、详情)。
2. APP「我的申诉」处理时间线 / 流程进度展示(`appeal/detail/{id}` 已返回流程进度,可直接补)。
3. 待确认项:申诉是否支持补充材料附件;`appeal` 流程 Key 是否已在「隐患流程配置」中配置(否则走全局默认 `appeal.flow.key`)。
---
## 八、积分申诉 · 流程梳理与待确认(疑问清单)
> 来源:后端提供的申诉流程说明图(提交 / 审批 / 查询列表)+ 文字补充(2026-10-15)。
### 8.1 流程关系(已明确)
- **状态机**:`0 待受理` →(`accept` 旧)`1 处理中` →(`handle` 旧)`2 已通过` / `3 已驳回`;`0` →(`submitFlow`)启动 Flowable →(`completeFlow` 逐节点)流程结束;`0` →(`withdraw`,仅 state=0)`4 已撤回`。
- **新旧接口分工**:新流程走 `submitFlow + completeFlow`;`accept`、`handle` 是**不依赖流程引擎的旧管理端接口**,Web 审批默认走 `completeFlow`,`accept/handle` 仅作无流程时兜底。
- **流程结束时后端自动处理**:通过 → 状态置 `2` 并按 `adjustPoints` 补发/扣减积分;驳回 → 状态置 `3`;两者都给申请人推送消息。调分会写入积分流水(`APPEAL_ADD` / `APPEAL_DEDUCT`)与审计日志。
- **申诉对象**:针对“某一条自己上报的隐患”的积分发起申诉。
### 8.2 待确认疑问清单
**A. 需后端确认(接口 / 字段 / 入参)**
- [ ] `completeFlow` 请求体结构:`taskId`、通过/驳回标识、`adjustPoints` 传参位置(每节点 or 结束节点)、审批意见字段名。
- [ ] `todoList` 筛选参数(待办/已办、类型、状态、厂区)与返回字段是否含 `canApprove`、`isInitiator`(现有 api.json 文档未列出)。
- [ ] `myList` 筛选参数(`appealType` / `status`)。
- [ ] `detail/{id}` 返回的“流程进度信息”结构(时间线需要:节点名 / 处理人 / 处理时间 / 审批意见)。
- [ ] 管理端“全部申诉列表”是否为 `appeal/getListByPage`,筛选字段有哪些(申请人 / 厂区 / 类型 / 状态 / 时间)。
- [ ] 消息推送的 `msgType` 与文案模板(是否 `appeal_result`)。
- [ ] 流程走中间节点时状态是否置 `1`;撤回(`withdraw`)后能否重新提交。
- [ ] 申诉是否支持附件/补充材料(`Appeal.attachmentUrls` 字段是否生效)。
- [ ] 申诉是否**必须关联一条隐患**,还是允许无关联的“积分异议 / 其他”。
**B. 需客户确认(需求)**
- [ ] PC「查询所有用户积分」界面:查询字段与筛选条件(客户待定)。
- [ ] Web 申诉台账的列表列与筛选项(类比隐患台账)。
- [ ] “申诉台账”是**独立菜单页**还是放在积分管理里的 **Tab**。
**C. 现状差异(待补代码 / 文档)**
- [ ] APP:[appeal.ts](../../../zhongyouApp/src/api/appeal.ts) 的 `myList` 未接 `appealType/status` 筛选;[AppealsScreen](../../../zhongyouApp/src/screens/home/AppealsScreen.tsx) 详情弹窗缺流程时间线;附件上传未做。
- [ ] `docs/api` 中 `completeFlow` / `todoList` / `myList` / `detail` 的说明比后端图简略,需按后端补全(否则会漏 `adjustPoints`、`canApprove` 等)。
- [ ] Web:积分管理「申诉处理」Tab 未开发;可能还需独立“申诉台账”页。
---
## 九、可开发功能 TODO 表
> 只列**当前可开工**的功能(接口就绪)。状态:`☐ 未开始` / `◐ 进行中` / `☑ 已完成`。
> 建议文件为推荐落位,可按实际调整;接口路径均省略 `/gd_dataserver` 前缀。
### 9.1 可直接开工(接口齐全)
| 编号 | 端 | 功能 / 页面 | 关键接口 | 建议文件(页面 / 封装) | 优先级 | 状态 | 依赖 / 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| W1 | Web | 工作台 | `analysis/home/overview`、`home/todo-warning`、`home/trend-latest` | `src/views/workbench/index.vue` + `src/api/analysis/home.js` | 高 | ◐ 待测试 | **已开发(2026-10-15)**,待测试。菜单:一级「工作台」,路由 `workbench`,组件 `workbench/index`(`/workbench`)。“平台同步失败”KPI 无接口,展示 `—` |
| W2 | Web | 积分管理 · 申诉处理 Tab | `appeal/todoList`、`appeal/detail/{id}`、`appeal/completeFlow` | `src/views/points/management/index.vue` + `src/api/points/appeal.js` | 高 | ☐ | 默认走 `completeFlow`;开工前按第八节 A 确认入参 |
| W3 | Web | 积分管理 · 手动调整 Tab | `balance/adjust`、`balance/getByUserId/{userId}` | 同上 + `src/api/points/balance.js` | 高 | ☐ | 员工选择用 `system/getOrgUsers` |
| W4 | Web | 积分管理 · 流水冲正 Tab | `transaction/reverse/{logId}`、`transaction/list` | 同上 + `src/api/points/transaction.js` | 中 | ☐ | 冲正需二次确认 |
| W5 | Web | 安全兑换管理 | `exchange/config/active`·`validate`·`switch/status`·`switch/toggle`·`updateById`、`anti/farm/active`·`detectAll`·`detectSelfAudit` | `src/views/exchange/security/index.vue` + `src/api/exchange/config.js` | 中 | ☐ | 字段见 `ExchangeConfig` / `AntiFarmConfig` |
| W6 | Web | 数据分析 | `analysis/stats/*`、`ranking/*`、`timeliness/level`、`report/monthly`(+`/export`)、`stats/category`·`stats/level` | `src/views/analysis/index.vue` + `src/api/analysis/index.js` | 中 | ☐ | **页面调整(2026-10-15)**:① 去掉隐患热力图(`heatmap/*` 接口暂不接);② 去掉顶部筛选列;③「处理时效漏斗」与「积分排行」改为左右布局占一行。图表用已装 `echarts` |
| A1 | APP | 设置(推送/声音/震动/关于/清缓存/退出) | 纯本地(AsyncStorage) | `src/screens/home/SettingsScreen.tsx` | 中 | ☐ | 参考原型 `scr-settings` |
| A2 | APP | 我的兑换 | `exchange/order/myList` | `src/screens/home/MyExchangeScreen.tsx` + `src/api/exchange.ts` | 中 | ☐ | 入口:我的 → 我的兑换 |
| A3 | APP | 通知提醒卡片 | `message/unread-count`、`message/list` | `src/components/NoticeReminderCard.tsx` | 中 | ☐ | 首页顶部条,点击跳 `Messages` |
### 9.2 部分可开工(先做主体,其余等接口)
| 编号 | 端 | 功能 / 页面 | 关键接口 | 建议文件(页面 / 封装) | 优先级 | 状态 | 依赖 / 备注 |
| --- | --- | --- | --- | --- | --- | --- | --- |
| W7 | Web | 商城运营(订单主体) | `exchange/order/getListByPage`·`info`·`refund/{orderId}`、`points/order/track/list/{orderId}`、`exchange/item/*` | `src/views/exchange/mall/index.vue` + `src/api/exchange/order.js` | 中 | ☐ | “惠服对接状态卡”无接口,后续再补,不阻塞订单部分 |
| W8 | Web | 风控与审计(审计日志 Tab) | `audit/log/getListByPage`、`audit/log/biz/{bizId}` | `src/views/analysis/risk/index.vue` + `src/api/audit/log.js` | 中 | ☐ | 风控预警 Tab 待后端接口 |
> 约定:每完成一项 → 勾选状态 → 同步更新第三节状态、[DEVELOPMENT_LOG.md](./DEVELOPMENT_LOG.md) 与 [page-api-map.md](../api/page-api-map.md)。

16
src/api/analysis/home.js

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
import request from '@/utils/request'
// 首页-核心指标概览:待审核数 / 本月已闭环数 / 今日新增上报数 / 今日兑换单数 / 今日发放积分数
export function getHomeOverview() {
return request({ url: '/business/points/analysis/home/overview', method: 'get' })
}
// 首页-待办与预警:待审核超 24h / 风控预警(集中兑换) / 申诉待处理
export function getHomeTodoWarning() {
return request({ url: '/business/points/analysis/home/todo-warning', method: 'get' })
}
// 首页-近 7 日趋势与最新上报:trend7d[] / latestReports[]
export function getHomeTrendLatest() {
return request({ url: '/business/points/analysis/home/trend-latest', method: 'get' })
}

363
src/views/workbench/index.vue

@ -0,0 +1,363 @@ @@ -0,0 +1,363 @@
<template>
<div v-loading="loading" class="app-container workbench">
<!-- KPI -->
<div class="kpi-grid">
<div
v-for="card in kpiCards"
:key="card.key"
class="kpi-card"
:class="{ 'is-clickable': card.clickable }"
@click="handleKpi(card)"
>
<div class="kpi-label">{{ card.label }}</div>
<div class="kpi-value" :style="{ color: card.color }">{{ card.value }}</div>
</div>
</div>
<!-- 趋势 + 待办与预警 -->
<div class="two-col">
<div class="panel">
<div class="panel-title">近 7 日上报趋势</div>
<div v-if="trendList.length" ref="trendChart" class="trend-chart" />
<div v-else class="panel-empty">暂无趋势数据</div>
<div class="panel-note">{{ trendRangeText }}</div>
</div>
<div class="panel">
<div class="panel-title">待办与预警</div>
<el-table :data="todoRows" size="mini" :show-header="false" class="todo-table">
<el-table-column prop="label" min-width="150" />
<el-table-column width="90" align="center">
<template slot-scope="scope">
<span class="count-tag" :class="scope.row.tagClass">{{ scope.row.countText }}</span>
</template>
</el-table-column>
<el-table-column width="96" align="right">
<template slot-scope="scope">
<el-tooltip
:disabled="!scope.row.disabled"
:content="scope.row.tip"
placement="top"
>
<span>
<el-button
size="mini"
:type="scope.row.buttonType"
:disabled="scope.row.disabled"
@click="handleTodoAction(scope.row)"
>{{ scope.row.actionText }}</el-button>
</span>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</div>
</div>
<!-- 最新上报 -->
<div class="panel">
<div class="panel-title">最新上报(前 5 条)</div>
<el-table :data="latestReports" size="mini" class="latest-table">
<el-table-column label="单号" prop="reportNo" min-width="120" show-overflow-tooltip />
<el-table-column label="描述" prop="hazardName" min-width="200" show-overflow-tooltip />
<el-table-column label="分级" width="90" align="center">
<template slot-scope="scope">
<el-tag :type="levelTagType(scope.row)" size="small">{{ levelLabel(scope.row) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="单位/区域" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.factoryDeptName || '—' }}</template>
</el-table-column>
<el-table-column label="上报人" prop="reporterName" width="110" show-overflow-tooltip>
<template slot-scope="scope">{{ scope.row.reporterName || '—' }}</template>
</el-table-column>
<el-table-column label="时间" width="150">
<template slot-scope="scope">{{ formatTime(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center">
<template slot-scope="scope">
<el-button type="text" size="mini" @click="goAudit(scope.row)">审核</el-button>
</template>
</el-table-column>
</el-table>
<div v-if="!latestReports.length && !loading" class="panel-empty">暂无上报数据</div>
</div>
</div>
</template>
<script>
import * as echarts from 'echarts'
import resize from '@/views/dashboard/mixins/resize'
import { getHomeOverview, getHomeTodoWarning, getHomeTrendLatest } from '@/api/analysis/home'
export default {
name: 'Workbench',
mixins: [resize],
data() {
return {
loading: false,
chart: null,
overview: {},
warning: {},
trendList: [],
latestReports: []
}
},
computed: {
kpiCards() {
return [
{ key: 'pendingAudit', label: '待审核', value: this.num(this.overview.pendingAuditCount), color: '#d64550', clickable: true, route: '/hazard/ledger' },
{ key: 'monthClosed', label: '本月已闭环', value: this.num(this.overview.monthClosedCount), color: '#2d8a4e', clickable: true, route: '/hazard/ledger' },
{ key: 'syncFail', label: '平台同步失败', value: '—', color: '#d64550', clickable: false, tip: '后端暂未提供平台同步统计接口' },
{ key: 'todayReport', label: '今日新增上报', value: this.num(this.overview.todayReportCount), color: '#1a1a2e', clickable: true, route: '/hazard/ledger' },
{ key: 'todayExchange', label: '今日兑换单', value: this.num(this.overview.todayExchangeCount), color: '#1a1a2e', clickable: false, tip: '商城运营页面开发中' },
{ key: 'todayPoints', label: '今日发放积分', value: this.plus(this.overview.todayPointsIssued), color: '#2d8a4e', clickable: true, route: '/points/management' }
]
},
todoRows() {
return [
{ key: 'auditOver24h', label: '待审核超 24h', countText: this.countText(this.warning.pendingAuditOver24hCount), tagClass: 'tag-orange', actionText: '去审核', buttonType: 'default', disabled: false, route: '/hazard/ledger' },
{ key: 'syncFail', label: '平台同步失败', countText: '—', tagClass: 'tag-red', actionText: '重试', buttonType: 'default', disabled: true, tip: '后端暂未提供重试接口' },
{ key: 'riskWarning', label: '风控预警(集中兑换)', countText: this.countText(this.warning.riskWarningCount), tagClass: 'tag-orange', actionText: '处理', buttonType: 'default', disabled: true, tip: '风控页面开发中' },
{ key: 'appealPending', label: '申诉待处理', countText: this.countText(this.warning.pendingAppealCount), tagClass: 'tag-blue', actionText: '查看', buttonType: 'default', disabled: false, route: '/points/management' }
]
},
trendRangeText() {
if (!this.trendList.length) return '单位:条'
const first = this.trendList[0].date || ''
const last = this.trendList[this.trendList.length - 1].date || ''
const range = first && last ? `${this.shortDate(first)} ~ ${this.shortDate(last)} · ` : ''
return `${range}单位:条`
}
},
created() {
this.loadData()
},
beforeDestroy() {
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
loadData() {
this.loading = true
Promise.all([getHomeOverview(), getHomeTodoWarning(), getHomeTrendLatest()])
.then(([overviewRes, warningRes, trendRes]) => {
this.overview = (overviewRes && overviewRes.data) || {}
this.warning = (warningRes && warningRes.data) || {}
const trend = (trendRes && trendRes.data) || {}
this.trendList = Array.isArray(trend.trend7d) ? trend.trend7d : []
this.latestReports = Array.isArray(trend.latestReports) ? trend.latestReports : []
this.$nextTick(this.renderChart)
})
.catch(() => {
this.overview = {}
this.warning = {}
this.trendList = []
this.latestReports = []
this.$modal.msgError('工作台数据加载失败,请检查后端服务和登录状态')
})
.finally(() => {
this.loading = false
})
},
renderChart() {
const el = this.$refs.trendChart
if (!el) return
if (!this.chart) {
this.chart = echarts.init(el)
}
this.chart.setOption({
grid: { top: 24, left: 8, right: 12, bottom: 4, containLabel: true },
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' }},
xAxis: {
type: 'category',
data: this.trendList.map(item => this.shortDate(item.date)),
axisTick: { alignWithLabel: true },
axisLine: { lineStyle: { color: '#e5e7eb' }},
axisLabel: { color: '#5a5a72', fontSize: 11 }
},
yAxis: {
type: 'value',
minInterval: 1,
axisTick: { show: false },
axisLine: { show: false },
splitLine: { lineStyle: { color: '#f0f0f2' }},
axisLabel: { color: '#9898ae', fontSize: 11 }
},
series: [{
type: 'bar',
data: this.trendList.map(item => Number(item.count || 0)),
barWidth: '46%',
itemStyle: { color: '#1890ff', borderRadius: [4, 4, 0, 0] },
label: { show: true, position: 'top', color: '#5a5a72', fontSize: 11 }
}]
}, true)
this.chart.resize()
},
handleKpi(card) {
if (card.route) {
this.$router.push(card.route)
} else if (card.tip) {
this.$modal.msgWarning(card.tip)
}
},
handleTodoAction(row) {
if (row.disabled) return
if (row.route) this.$router.push(row.route)
},
goAudit(row) {
if (row.id === undefined || row.id === null) {
this.$modal.msgWarning('该记录缺少主键,无法进入审核')
return
}
this.$router.push({ path: '/hazard/audit/detail', query: { id: String(row.id) }})
},
num(value) {
return value === undefined || value === null || value === '' ? '—' : String(value)
},
plus(value) {
if (value === undefined || value === null || value === '') return '—'
const n = Number(value)
return Number.isNaN(n) ? String(value) : `${n >= 0 ? '+' : ''}${n.toLocaleString('en-US')}`
},
countText(value) {
if (value === undefined || value === null || value === '') return '—'
return `${value} 条`
},
shortDate(value) {
const text = String(value || '')
return text.length >= 5 ? text.slice(-5) : text
},
formatTime(value) {
if (!value) return '—'
const text = String(value).replace('T', ' ')
return text.length >= 16 ? text.slice(5, 16) : text
},
levelLabel(row) {
return row.hazardLevelName || ({ general: '一般', major: '较大', critical: '重大' }[String(row.hazardLevel || '').toLowerCase()] || '—')
},
levelTagType(row) {
const level = String(row.hazardLevel || '').toLowerCase()
if (level === 'critical') return 'danger'
if (level === 'major') return 'warning'
return 'info'
}
}
}
</script>
<style lang="scss" scoped>
.workbench {
min-height: calc(100vh - 52px);
}
.kpi-grid {
display: grid;
grid-template-columns: repeat(6, 1fr);
gap: 12px;
}
.kpi-card {
padding: 16px 18px;
background: var(--prototype-panel, #fff);
border: 1px solid var(--prototype-divider, #f0f0f2);
border-radius: var(--prototype-radius, 8px);
}
.kpi-card.is-clickable {
cursor: pointer;
transition: border-color .2s, box-shadow .2s;
}
.kpi-card.is-clickable:hover {
border-color: var(--prototype-primary, #1890ff);
box-shadow: 0 4px 14px rgba(24, 144, 255, .12);
}
.kpi-label {
color: var(--prototype-text-secondary, #5a5a72);
font-size: 12px;
}
.kpi-value {
margin-top: 10px;
font-size: 26px;
font-weight: 800;
line-height: 1;
}
.two-col {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 14px;
margin-top: 14px;
}
.panel {
margin-top: 14px;
padding: 16px 18px;
background: var(--prototype-panel, #fff);
border: 1px solid var(--prototype-divider, #f0f0f2);
border-radius: var(--prototype-radius, 8px);
}
.two-col .panel {
margin-top: 0;
}
.panel-title {
margin-bottom: 12px;
color: var(--prototype-text, #1a1a2e);
font-size: 14px;
font-weight: 600;
}
.panel-note {
margin-top: 8px;
color: var(--prototype-text-muted, #9898ae);
font-size: 11px;
}
.panel-empty {
padding: 28px 0;
color: var(--prototype-text-muted, #9898ae);
font-size: 12px;
text-align: center;
}
.trend-chart {
width: 100%;
height: 236px;
}
.count-tag {
display: inline-block;
padding: 1px 8px;
border-radius: 8px;
font-size: 11px;
line-height: 18px;
}
.tag-orange { color: #c97e24; background: #fff4ec; }
.tag-red { color: #d64550; background: #fef0f0; }
.tag-blue { color: #1890ff; background: #eaf4ff; }
.todo-table,
.latest-table {
width: 100%;
}
@media (max-width: 1400px) {
.kpi-grid { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 1100px) {
.two-col { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
.kpi-grid { grid-template-columns: repeat(2, 1fr); }
}
</style>
Loading…
Cancel
Save