diff --git a/next/.env.example b/next/.env.example new file mode 100644 index 0000000..e8c61bc --- /dev/null +++ b/next/.env.example @@ -0,0 +1,2 @@ +# 仅供本地 Vite 代理读取,不会通过 VITE_ 前缀打包到浏览器。 +QHSE_API_TOKEN='AF17168B-87BD-4GLY-1111-F0A0A1158F9B' diff --git a/next/.gitignore b/next/.gitignore new file mode 100644 index 0000000..869e9d4 --- /dev/null +++ b/next/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.env +.env.local +.env.*.local diff --git a/next/README.md b/next/README.md new file mode 100644 index 0000000..55e7ad9 --- /dev/null +++ b/next/README.md @@ -0,0 +1,148 @@ +# 数据治理看板 Vue3 版 + +本目录是原版数据治理看板的 Vue3 升级版本。原版 `indexV2.html`、`res/` 和 `server.js` 保持不变,Vue3 版可独立开发和构建,并保持既有接口参数、业务口径、外部页签参数和 1920×1080 大屏布局。 + +## 技术栈 + +- Vue 3 + TypeScript + Vite:组件化开发、类型检查和静态构建。 +- Pinia:集中管理筛选条件、监控数据、项目详情和穿透弹窗状态。 +- Vue Router:集团、公司、项目三级哈希路由。 +- ECharts:柱状图和折线图按需加载,Canvas 渲染。 +- Vitest:数据适配和业务口径单元测试。 + +## 项目架构 + +### 分层关系 + +```mermaid +flowchart LR + A[App 应用壳层] --> B[Router 路由页面] + B --> C[Pinia Dashboard Store] + C --> D[HTTP Service] + D --> E[同源网关 /qhse_webapi] + E --> F[数据治理接口] + C --> G[Adapter 数据适配] + G --> H[稳定 ViewModel] + H --> B + B --> I[业务组件] + I --> J[ECharts 生命周期组件] +``` + +数据读取遵循单向链路:页面触发 Store Action,Service 请求接口,Adapter 将原始响应转换为 ViewModel,Store 更新状态后由 Vue 响应式刷新页面。页面组件不直接兼容后端字段别名,也不直接持有认证令牌。 + +### 目录职责 + +```text +next/ +├─ src/ +│ ├─ adapters/ 接口原始数据到 ViewModel 的转换及口径测试 +│ ├─ components/ KPI、指标列表、ECharts 和全局穿透弹窗 +│ ├─ config/ 接口路径、外链、系统枚举和报告文件映射 +│ ├─ router/ 集团、公司、项目三级路由 +│ ├─ services/ 请求参数、超时、取消、请求 ID 和错误处理 +│ ├─ stores/ 筛选、监控数据、项目数据和弹窗状态 +│ ├─ styles/ Vue3 版增量样式 +│ ├─ types/ API、ViewModel、筛选和弹窗类型 +│ ├─ views/ 集团级、公司级、项目级页面 +│ ├─ App.vue 大屏壳层、筛选、面包屑和缩放 +│ └─ main.ts Vue、Pinia、Router 和样式入口 +├─ vite.config.ts 开发服务器和接口代理 +├─ tsconfig.json TypeScript 配置 +└─ package.json 依赖和工程命令 +``` + +`main.ts` 会引入上级 `res/css/` 中的原版模块化样式作为视觉基线,Vite 构建时会将这些 CSS 合并进 `dist/assets/`。Vue3 专属修正放在 `src/styles/next.css`,禁止修改或重新聚合原版 CSS 文件。 + +### 页面与路由 + +| 层级 | 路由 | 页面职责 | +|------|------|----------| +| 集团级 | `#/` | 集团 KPI、重点指标、三率趋势、企业分析、报告入口、指标穿透 | +| 公司级 | `#/enterprise/:unitId` | 公司 KPI、三率趋势、项目分析、公司指标穿透 | +| 项目级 | `#/project/:unitId/:projectId` | 项目信息、指标历史值、异常状态 | + +外部系统的页签参数位于哈希前,例如 `?type=1,2#/`。参数映射保持原规则:`1` 为安全、`2` 为质量、`3` 为应急;未传或传入非法值时展示全部系统。 + +### 状态管理 + +`stores/dashboard.ts` 是唯一业务状态入口,主要状态分为: + +- 筛选状态:`system`、`dimension`、`allowedSystems`。 +- 监控状态:集团或当前公司的 `monitoring`、`loading`、`error`。 +- 项目状态:`projectData`、`projectLoading`、`projectError`。 +- 穿透状态:`dialog`、`selectedMetric`、`metricAnalysis`、`metricLoading`。 + +集团和公司监控共用 `loadMonitoring`。新请求开始时会取消旧请求,并通过递增请求序号阻止旧响应覆盖最新筛选结果。项目详情和指标穿透分别使用独立 Action,避免页面组件自行维护请求状态。 + +### 接口与数据适配 + +所有接口使用同源前缀 `/qhse_webapi/api/DataGovernance`: + +- `GetMetricMonitoringSituation`:集团监控。 +- `GetUnitMetricMonitoringSituation`:公司监控。 +- `GetGroupMetricAnalysis`:集团指标穿透。 +- `GetUnitMetricAnalysis`:公司指标穿透。 +- `GetProjectMonitoringSituation`:项目详情。 + +集团监控接口要求查询字符串始终包含 `unitId=`,即使值为空也不能省略。`services/http.ts` 因此只过滤 `undefined`,显式空字符串会被保留。 + +`adapters/dashboard.ts` 负责兼容 PascalCase/camelCase 字段、非法数字、布尔字符串和空数组,并集中维护上线率口径: + +```text +(已关联项目数 + 预立项项目数) / (主数据项目数 + 预立项项目数 - 申请不用项目数) +``` + +组件只能读取适配后的 ViewModel。新增或调整接口字段时,应先修改 `types/` 和 `adapters/`,不要在 Vue 模板中增加字段兼容分支。 + +### 图表与大屏布局 + +`components/EChart.vue` 统一负责 ECharts 初始化、响应式更新、`ResizeObserver` 监听和 `dispose`。当前只注册柱状图、折线图、网格、图例、提示框和 Canvas 渲染器。 + +页面按照 1920×1080 设计尺寸渲染,由 `App.vue` 等比缩放到实际窗口。趋势图宽度跟随面板;项目和企业数量较多的柱状图使用动态最小宽度和横向滚动,避免柱体被强制压缩。 + +### 安全与部署边界 + +- 浏览器只访问同源 `/qhse_webapi/`,前端构建产物不包含接口 token。 +- 本地 token 仅写入 `.env.local`,由 Vite 开发代理注入。 +- 生产环境由网关反向代理 `https://qhse.cncecoa.com/qhse_webapi` 并注入 token。 +- 网关应配置上游超时、请求 ID 透传、访问日志和 5xx 告警。 + +## 本地开发 + +复制 `.env.example` 为 `.env.local`,填写仅供本地代理使用的 `QHSE_API_TOKEN`,然后执行: + +```powershell +npm install +npm run dev +``` + +访问 `http://127.0.0.1:18081/`。外部页签参数仍支持 `?type=1`、`?type=2`、`?type=3` 和 `?type=1,2`。 + +## 测试 + +```powershell +npm run test +npm run typecheck +``` + +当前单元测试重点覆盖接口字段归一化、上线率口径、空数据、非法数字和布尔字符串。页面结构或交互调整后,还需要在浏览器验证集团、公司、项目三级路由以及统计和指标穿透弹窗。 + +## 构建与部署 + +```powershell +npm run build +``` + +产物位于 `dist/`,可部署到任意静态 Web 服务。生产环境需要由同源网关代理 `/qhse_webapi/`,并在服务端注入接口 token;token 不进入浏览器构建产物。 + +建议网关同时配置 12 秒上游超时、请求 ID 透传、访问日志和 5xx 告警。应用采用哈希路由,无需 Web 服务器配置 SPA history fallback。 + +## 开发约定 + +- 新接口先在 `config/dashboard.ts` 注册,再通过 `services/http.ts` 请求。 +- 后端响应必须先经过 `adapters/`,页面不得直接依赖原始字段命名。 +- 跨页面状态进入 Pinia;仅组件内部使用的展示状态保留在组件中。 +- 通用展示提取到 `components/`,集团、公司、项目编排保留在 `views/`。 +- 图表必须复用 `EChart.vue`,不要在页面中直接创建实例。 +- 样式优先复用原版 CSS 类;Vue3 增量样式只添加到 `styles/next.css`。 +- 修改后至少执行 `npm run test` 和 `npm run build`。 diff --git a/next/index.html b/next/index.html new file mode 100644 index 0000000..ad8f120 --- /dev/null +++ b/next/index.html @@ -0,0 +1,13 @@ + + +
+ + + +| 企业简称 | +上线项目数 | +上线率 | +应使用项目数 | +申请不用 | +主数据项目数 | +||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 自动取值 | +定期填报 | +总数 | +已关联项目 | +预立项项目 | +待建项目 | +条件受限 | +总数 | +已完工 | +停缓建 | +条件受限 | +|||
| + {{ row.name }}{{ row.name }} + | +{{ row.onlineAutoTotal }} | +{{ row.onlineReportedTotal }} | +{{ row.onlineRate.toFixed(2) }}% | +{{ row.total }} | +{{ row.linkedTotal }} | +{{ row.preProjectTotal }} | +{{ row.pendingTotal }} | +{{ restricted(row) }} | +{{ row.noNeedTotal }} | +{{ row.noNeedCompletedTotal }} | +{{ row.noNeedStoppedTotal }} | +{{ row.noNeedRestrictedTotal }} | +{{ row.masterProjectCount }} | +
| 合计 | +{{ sum("onlineAutoTotal") }} | +{{ sum("onlineReportedTotal") }} | ++ {{ store.monitoring.summary.onlineRate.toFixed(2) }}% + | +{{ sum("total") }} | +{{ sum("linkedTotal") }} | +{{ sum("preProjectTotal") }} | +{{ sum("pendingTotal") }} | +{{ restrictedTotal }} | +{{ sum("noNeedTotal") }} | +{{ sum("noNeedCompletedTotal") }} | +{{ sum("noNeedStoppedTotal") }} | +{{ sum("noNeedRestrictedTotal") }} | +{{ sum("masterProjectCount") }} | +
| 单位 | +项目名称 | +指标使用率 | +使用情况 | +
|---|---|---|---|
| {{ row.abbreviation || row.unitName }} | +{{ row.projectName || row.shortName }} | +{{ row.usageRate.toFixed(1) }}% | +{{ row.usage }} | +
| 单位简称 | +QHSE在建项目数 | +活跃项目数 | +使用率 | +日期 | +
|---|---|---|---|---|
| {{ row.abbreviation || row.unitName }} | +{{ row.qhseTotal }} | +{{ row.activeNum }} | +{{ row.usageRate.toFixed(1) }}% | +{{ row.date }} | +
| 项目名称 | +是否异常 | +今日 | +昨日 | +三天前 | +一周前 | +一个月前 | +三个月前 | +
|---|---|---|---|---|---|---|---|
| {{ row.ProjectName || row.projectName }} | ++ {{ + row.IsAbnormal || row.isAbnormal ? "异常" : "正常" + }} + | +{{ row.NowData ?? "-" }} | +{{ row.YesterdayData ?? "-" }} | +{{ row.ThreeDaysAgoData ?? "-" }} | +{{ row.WeekAgoData ?? "-" }} | +{{ row.MonthAgoData ?? "-" }} | +{{ row.ThreeMonthsAgoData ?? "-" }} | +
| 指标名称 | +是否异常 | +对比 | +今日 | +昨日 | +三天前 | +一周前 | +一个月前 | +三个月前 | +
|---|---|---|---|---|---|---|---|---|
| {{ row.Name || row.name }} | ++ {{ + row.IsAbnormal || row.isAbnormal ? "异常" : "正常" + }} + | +{{ row.Compare || row.compare || "-" }} | +{{ row.NowData ?? "-" }} | +{{ row.YesterdayData ?? "-" }} | +{{ row.ThreeDaysAgoData ?? "-" }} | +{{ row.WeekAgoData ?? "-" }} | +{{ row.MonthAgoData ?? "-" }} | +{{ row.ThreeMonthsAgoData ?? "-" }} | +