| 名称 | 前端开发 |
| 描述 | 本仓库前端开发专用技能(管理端 ztjg-ui:Vue 3 + TS + Element Plus + Pinia + Vite;移动端 ztjg-app:uni-app)。核心覆盖本项目的招牌特性——多组织数据视角(X-Data-View-Id 透传、视角切换器、菜单按视角下发、VIEW_EXPIRED 降级)。当用户要求新增/修改页面、弹窗表单、表格列表、组件、API 请求封装、路由、Pinia 状态,或排查前端联调/端口/代理/视角切换问题时使用。触发示例:「用 frontend-dev 帮我加一个页面」「给管理端加一个 XX 管理模块」「移动端加一个页面」。 |
前端开发技能(frontend-dev)
依据:仓库只读扫描(2026-10-27)。所有路径均为仓库相对路径;未确认项标注「不确定」。 全局上下文见
.agent/PROJECT_CONTEXT.md。
真实场景入口(先读这几份,再动代码)
| 场景 | 读什么 | 为什么 |
|---|---|---|
| 第一次接触本前端 | docs/07-项目分析文档/01-项目介绍.md |
工程启动顺序、端口、排障决策树 |
| 页面数据不对 / 越权 | docs/07-项目分析文档/02-架构分析.md §4 |
权限模型、L1/L2、SQL 下推;理解「为什么前端传组织参数没用」 |
| 视角切换 / 菜单 / 40304 | docs/07-项目分析文档/05-接口清单与业务详解.md §1.3 + §九 |
Header 契约、40304、VIEW_EXPIRED、端到端调用链 |
| 接口是不是存在 / 前缀对不对 | docs/07-项目分析文档/05-接口清单与业务详解.md 总览 |
各服务真实路由前缀与接口清单 |
| 提交前自查 | docs/07-项目分析文档/04-代码规范排查.md §17 |
数据权限相关的联调检查点 |
| 动手新增页面 / 照抄范例 | docs/07-项目分析文档/06-典型开发任务实战.md §1、§7 |
列表页全链路与移动端页面的分步 recipe |
| 我的任务「立即处理」/ 项目工作台 | docs/07-项目分析文档/06-典型开发任务实战.md §6.1、§6.2 → 08 §6 |
myTask → bizDoc/form?todoId= → 保存后自动提交审批;bizWorkbench 双视图页面壳子;工作台/待办 2026-11-05 重构(types.ts、待审核/已办结列、表头吸顶、QuickConfigDialog) |
| 业务单据台账页 / 任务「联动单据」选择器 | docs/07-项目分析文档/06-典型开发任务实战.md §6.3 → 08 §6.8 |
单据 ↔ 审批流绑定(icdProcessId);下拉用 GET /bizDocMap/options,编辑回显用 /bizDocMap/optionsByIds 防 N+1 |
| 计划变更单 / 版本对比 / 编制向导 | docs/07-项目分析文档/06-典型开发任务实战.md §6.4 → 08 §7(§7.4.1 提交关口预演校验) |
变更单 change/ChangeWizard.vue 三步向导弹窗 + approve/useChangeApprove.ts;提交审批会跑 ChangePreviewGraph 预演校验,前置环/合同超期在提交时被拦;版本对比 bizPlanVersion/index.vue + useVersionCompare.ts;编制向导 wizard.vue 经 provide('planTableCtx', ...) 拆出 wizard/steps/、wizard/composables/(工期口径:任务侧=自然日、节点侧=施工日) |
| 做 / 看项目管理看板(只读聚合页) | docs/07-项目分析文档/06-典型开发任务实战.md §6.5 |
management/bizPlanBoard/index.vue 装配 + BoardFilterBar/BoardNodeTree/BoardStatCards/BoardTaskPanel + useBoardData/useBoardTree/useBoardTasks;结构取生效版本快照、执行态取待办口径,不可混用;带 planId 必有 PlanAccessGuard;菜单缓存 menu_details 改完须重勾角色 |
| 想确认后端表/字段语义 | docs/07-项目分析文档/07-数据模型与权限表速览.md |
前端字段含义与组织/任职数据来源 |
| 改移动端 / App 页面 / 推送 | docs/07-项目分析文档/09-移动端应用链路.md |
22 个页面、动态 Tabbar、三端 baseUrl、App 不带视角头、推送绑定 |
| 前端部署 / nginx / 环境变量 | docs/07-项目分析文档/10-部署与运维速查.md §6 |
VITE_*、/api 代理剥前缀、Dockerfile |
| 本地只联调改动服务 | docs/07-项目分析文档/10-部署与运维速查.md §8 |
VITE_GRAY_VERSION + VERSION 头 + 后端 VM 参数 |
| 消息中心 / 未读数 / 通知跳转 | docs/07-项目分析文档/11-消息与通知链路.md |
PC/App 消息接口、send_flag、推送白名单、实时红点现状 |
0. 项目最关键的特性:多组织数据视角(先读这一节)
后端权限模型是「用户 → 任职 → 组织/岗位 → 角色 → 功能权限 + 数据范围」,并支持 L1(任职/临时权限)/ L2(角色细分)两级数据视角。前端的职责只有两件:把当前视角带给后端、正确处理视角失效。
| 事实 | 结论 | 证据 |
|---|---|---|
| 视角头 | Session 存 DATA_VIEW_ID(storage key 为 dataViewId),请求拦截器自动注入 X-Data-View-Id,单请求可用 skipDataView 跳过 |
src/utils/request.ts L51-L55、src/utils/storage.ts L9 |
| 视角切换器 | 全局组件 DataViewSwitch(挂在 layout/navBars/breadcrumb/index.vue):拉 switch-options + current,切换成功后写 Session 并 window.location.reload() |
src/components/DataViewSwitch/index.vue L124-L166、src/layout/navBars/breadcrumb/index.vue |
| 视角接口 | GET /admin/data-view/switch-options、POST /admin/data-view/switch、GET /admin/data-view/current |
src/api/admin/dataView.ts |
| 菜单来源 | 动态菜单改走 GET /admin/data-view/menus(不再走旧菜单接口) |
src/router/backEnd.ts L121-L126 |
| 失效处理 | 响应 msg === 'VIEW_EXPIRED' → 清 DATA_VIEW_ID;40304 因 code !== 0 直接抛错,不会写入无效 switchId |
src/utils/request.ts L90-L106 |
| 退出登录 | 清 DATA_VIEW_ID |
src/views/login/index.vue L155 |
必须遵守:
- 不要在前端拼组织过滤参数(
orgId/deptId/orgIds)。数据范围由后端DataScopeInnerInterceptor下推 SQL,页面只展示后端返回结果。 - 请求一律走
src/utils/request.ts,视角头自动带;禁止手工设置X-Data-View-Id(调试除外)。 - 需要「视角下拉/切换」时复用
DataViewSwitch+src/api/admin/dataView.ts,不要重复实现切换器。 - 空视角是常态:
/current返回context=null时菜单降级为全来源并集,页面不得报错或白屏。 - 按钮权限码可能因视角收窄而变少(后端
PermissionService按视角权限码集判定),v-auth只控制显隐,不能当作数据安全边界。 - 同一列表在不同视角下返回的数据量不同;切换视角后必须整页刷新(当前实现即
window.location.reload()),不要在局部组件里手动重置分页。
0.1 前端真实场景排查(按现象定位)
| 现象 | 先查 | 再查 |
|---|---|---|
| 切换视角后列表没变化 | DATA_VIEW_ID 是否真的写进 Session 并随请求头 X-Data-View-Id 发出(Network) |
后端 Mapper 是否声明 DataScope(未声明则视角无效);切换后是否 reload |
| 切换视角后列表为空 | 该视角是否 L2 收窄(只含该角色的可见组织) | 后端 visible-orgs 是否返回空集(角色 sys_data_scope/组织闭包未配) |
| 某个菜单点进去 404 | 浏览器实际 path → 是否命中网关前缀(/admin、/task 等) |
后端路由种子是否缺该前缀(/management、/msproject 不在种子,需 sys_route_conf 补录) |
| 菜单空白 / 按钮变少 | 是否带着已失效的视角请求 /admin/data-view/menus |
响应 msg=VIEW_EXPIRED → 按规则清 DATA_VIEW_ID 后重拉,不要弹错 |
| 提示「视角不存在/非法」 | 是否手工伪造过 X-Data-View-Id |
switchId 是否来自 switch-options 且未过期(任职撤销/临时授权到期即失效) |
| 接口 403 | 当前是否 L2 收窄视角(后端按视角权限码集判定,收窄后 403 属预期) | 切回 L1 验证;仍 403 则问题在后端岗位→角色链路,不在前端 |
| 本地联调全 404 / 跨域 | VITE_ADMIN_PROXY_PATH 是否指向 http://127.0.0.1:9999、VITE_IS_MICRO 取值 |
网关是否启动、目标服务是否注册到 Nacos |
前端不修数据权限问题:数据多少由后端
DataScope决定,前端能做的只有「带对头、处理失效、切回视角对比」。
1. 先判断改哪个前端
| 目标 | 工程 | 判断依据 |
|---|---|---|
| PC 管理后台(表格/表单/权限菜单) | ztjg-ui/ |
默认选它 |
| H5 / App / 小程序 | ztjg-app/ |
需求提到移动端、uni-app、扫码确认、消息推送 |
两套工程互不共享代码,不要跨工程 import。移动端目前没有视角切换器,所有数据范围由后端按登录用户收敛。
2. 技术栈与目录地图
2.1 管理端 ztjg-ui/
| 维度 | 结论 | 证据 |
|---|---|---|
| 框架 | Vue 3.5 <script setup> + TypeScript |
ztjg-ui/package.json |
| UI | Element Plus 2.13 + Tailwind + DaisyUI | ztjg-ui/package.json、ztjg-ui/tailwind.config.js |
| 状态 | Pinia(含 persist 插件) | ztjg-ui/src/stores/ |
| 路由 | Vue Router 4,后端菜单驱动动态路由 | ztjg-ui/src/router/backEnd.ts |
| 请求 | Axios 统一封装,含请求/响应加解密、视角头 | ztjg-ui/src/utils/request.ts |
| 构建 | Vite 8,本地 8888 | ztjg-ui/vite.config.mts、ztjg-ui/.env.development |
| 包管理 | pnpm(pnpm-lock.yaml + pnpm-workspace.yaml) |
ztjg-ui/ |
目录职责:
ztjg-ui/src/
├── api/<业务域>/xxx.ts # 请求函数,按域分目录(admin/management/app/flow/pay/mp/gen/daemon)
├── views/<业务域>/<Feature>/
│ ├── index.vue # 列表页
│ └── form.vue # 新增/编辑弹窗
├── components/ # 全局组件(PascalCase 目录 + index.vue),含 DataViewSwitch
├── hooks/ # table.ts(useTable)、dict.ts(useDict)、tenant.ts、param.ts、message.ts
├── router/ # index.ts、route.ts、frontEnd.ts、backEnd.ts
├── stores/ # userInfo.ts、dict.ts、routesList.ts、tagsViewRoutes.ts、themeConfig.ts ...
├── directive/authDirective.ts # v-auth 按钮权限
├── utils/request.ts # 唯一请求出口(含视角头/加解密/错误码)
└── layout/ theme/ i18n/ types/
2.2 移动端 ztjg-app/
| 维度 | 结论 | 证据 |
|---|---|---|
| 框架 | uni-app(Vue 3.2.47)+ Pinia | ztjg-app/package.json |
| 构建 | Vite 4,H5 本地 8088 | ztjg-app/vite.config.ts |
| 页面注册 | src/pages.json(新增页面必须登记) |
ztjg-app/src/pages.json |
| 请求 | 自研 uni.request 封装 |
ztjg-app/src/utils/request/index.ts |
| 推送 | uni-push 2.0 + uniCloud 云函数 | ztjg-app/src/utils/push.ts、ztjg-app/uniCloud-modules/ |
ztjg-app/src/
├── pages/<page>/<page>.vue # 页面(与 pages.json 路径一致)
├── components/ # 业务组件 + widgets
├── api/*.ts # 请求函数(扁平文件)
├── stores/ # user.ts / message.ts / app.ts
├── utils/request/ # index.ts / http.ts / cancel.ts
├── enums/ hooks/ plugins/
└── pages.json / manifest.json
3. 修改前必须阅读
管理端:
ztjg-ui/AGENTS.md、ztjg-ui/CLAUDE.md(硬性规范)ztjg-ui/src/utils/request.ts(鉴权头、视角头、加解密、错误判定)ztjg-ui/src/hooks/table.ts、ztjg-ui/src/hooks/dict.tsztjg-ui/src/api/management/bizProject.ts(请求层模板)ztjg-ui/src/views/management/bizProject/index.vue+form.vue(页面模板)ztjg-ui/src/router/backEnd.ts(路由来自后端菜单)- 涉及视角:
ztjg-ui/src/api/admin/dataView.ts、ztjg-ui/src/components/DataViewSwitch/index.vue ztjg-ui/.prettierrc.js、ztjg-ui/.eslintrc.js
移动端:
ztjg-app/src/pages.json、ztjg-app/src/utils/request/index.tsztjg-app/src/pages/message/message.vue、ztjg-app/src/api/message.ts(页面 + 请求模板)ztjg-app/.eslintrc.js、ztjg-app/vite.config.ts
4. 标准工作流
4.1 管理端:新增一个列表页(最常见)
- 请求层:在
ztjg-ui/src/api/<域>/<feature>.ts新建文件,照抄src/api/management/bizProject.ts:import request from '/@/utils/request',导出fetchList / addObj / getObj / putObj / delObjs。 - 页面:
src/views/<域>/<feature>/index.vue,照抄views/management/bizProject/index.vue:useTable管理分页与查询(import { BasicTableProps, useTable } from '/@/hooks/table';)- 字典用
useDict - 按钮权限用
v-auth指令配合权限码
- 表单:
form.vue用el-dialog+el-form,父页面ref调用打开方法。 - 菜单:管理端路由由后端菜单下发(
/admin/data-view/menus),不要在前端手写该页面路由。需在后台「菜单管理」配置菜单并分配权限码(必要时配置module_code)。 - 数据范围:列表接口不要传组织参数,权限由后端视角下推;若接口返回空列表,先确认当前视角,而不是先怀疑前端。
- 自测:
pnpm dev→ 8888,切换视角验证数据量变化。
4.2 管理端:新增组件
- 放
src/components/<PascalCase>/index.vue,按需在src/components/index.ts注册(先读该文件确认现有注册方式,不确定是否全局自动注册)。 - i18n 目录(
i18n/zh-cn.ts、en.ts)按现有组件补齐。 - 不要为一次性逻辑新建公共组件,直接在页面内实现。
4.3 管理端:新增请求
- 一律写进
src/api/<域>/,通过request调用;禁止在.vue里直接axios/fetch。 - URL 必须以网关前缀开头:
/admin/...、/management/...、/auth/...、/app/...、/task/...(网关前缀见.agent/PROJECT_CONTEXT.md§8.2)。 - 需要跳过
code校验(文件流、非R结构)时,加headers: { 'Skip-Code-Check': true };登录类接口参考src/api/login/index.ts的skipToken/skipTenant/Enc-Flag。 - 不要给业务请求加
skipDataView,除非该接口明确与组织数据无关且后端不需要视角。
4.4 管理端:路由与状态
- 路由:默认不新增静态路由;确需固定页面时才改
src/router/frontEnd.ts/route.ts,并在backEnd.ts逻辑内兼容。 - 状态:新 store 放
src/stores/<name>.ts,defineStore+storeToRefs使用时按现有文件风格;持久化参考src/stores/userInfo.ts。 - 页面级临时状态不建 store。
- 视角偏好不要存进 Pinia,统一用
Session的DATA_VIEW_ID(请求拦截器直接读取)。
4.5 移动端:新增页面
- 新建
ztjg-app/src/pages/<name>/<name>.vue。 - 在
ztjg-app/src/pages.json的pages数组追加同路径条目(含navigationBarTitleText等),否则无法跳转。 - 请求写进
ztjg-app/src/api/<name>.ts,使用ztjg-app/src/utils/request。 - H5 走
/api代理(vite.config.ts剥离前缀);App/小程序urlPrefix为空、直连VITE_APP_BASE_URL,因此 API 路径不要硬编码/api(src/utils/request/index.ts)。 - 移动端无视角切换,后端按登录用户默认视角收敛数据;不要自行传组织过滤参数。
- 自测:
npm run dev:h5→ 8088。
5. 常用命令(均取自 package.json)
# 管理端(ztjg-ui/)
pnpm install
pnpm dev # vite --force,端口 8888
pnpm build
pnpm build:docker
pnpm lint:eslint
pnpm prettier
# 移动端(ztjg-app/)
npm install # postinstall 会执行 scripts/patch-esbuild.cjs
npm run dev:h5 # 端口 8088
npm run dev:app
npm run build:h5
npm run build:app:prod
npm run lint
- 前端无 test 脚本(两工程
package.json均无test),单测命令「待确认」。 - 移动端包管理器「不确定」(
package-lock.json与yarn.lock并存),沿用团队现有方式。
6. 代码规范与现有模式
| 规范 | 要求 | 证据 |
|---|---|---|
| 组件写法 | <script setup lang="ts"> + Composition API |
ztjg-ui/AGENTS.md |
| 命名 | 组件 PascalCase;页面目录 Feature/index.vue |
ztjg-ui/src/components/*、ztjg-ui/src/views/management/bizProject/ |
| 别名 | /@ → src/ |
ztjg-ui/AGENTS.md、vite.config.mts |
| 格式 | 缩进 tab、行宽 150、单引号 | ztjg-ui/.prettierrc.js |
| Lint | no-console 为 error |
ztjg-ui/.eslintrc.js |
| 列表页 | useTable + el-table + Pagination |
views/management/bizProject/index.vue |
| 字典 | useDict + DictTag 组件 |
src/hooks/dict.ts、src/components/DictTag/index.vue |
| 权限按钮 | v-auth 指令配合权限码 |
src/directive/authDirective.ts |
| 视角 | 请求头自动注入、切换器统一整页刷新 | src/utils/request.ts、src/components/DataViewSwitch/index.vue |
| 移动端页面 | kebab/snake 目录名与 pages.json 完全一致 |
src/pages/message/detail.vue、src/pages.json |
7. API 调用与联调约定
- baseURL:
VITE_API_URL=/api;开发期 Vite 代理/api→VITE_ADMIN_PROXY_PATH(默认http://127.0.0.1:9999)并剥掉/api(ztjg-ui/vite.config.mts)。 - 微服务模式:
VITE_IS_MICRO=true时adaptationUrl不改写路径(src/utils/other.ts);若改为false会拼/admin前缀。 - 自动附带头:
Authorization、TENANT-ID、X-Data-View-Id、VERSION(src/utils/request.ts)。 - 本地灰度调试:
.env.development.local写VITE_GRAY_VERSION=<个人唯一标记>(被 gitignore,不提交),重启 dev server 后请求自动带VERSION头,只有本地改动的后端服务会命中,其余落服务器;后端 VM 参数见 10 §8。 - 响应判定:
code !== 0直接抛错并提示msg(含40304视角非法);因此后端返回必须是R结构。 VIEW_EXPIRED:菜单接口降级返回,前端清本地视角偏好后按无视角兜底,不要弹错。- 加密:请求体默认加密,
Enc-Flag: false可关闭;密钥VITE_PWD_ENC_KEY(ztjg-ui/.env)。改动请求封装前必须先读request.ts全文。 - 联调排查顺序:前端 URL → 浏览器 Network 实际 path → 网关路由是否存在 → 目标服务 Controller 映射 → 当前视角头是否携带。
8. 禁止事项
- ❌ 绕过
src/utils/request.ts直接用axios/fetch/uni.request。 - ❌ 手工拼接/伪造
X-Data-View-Id,或把视角 ID 存进 Pinia、URL 参数、localStorage 以外的位置。 - ❌ 在前端实现组织/部门过滤参数来「模拟」数据权限。
- ❌ 重复实现视角切换器,或切换视角后不刷新菜单与列表。
- ❌ 在前端
.env里硬编码后端 IP 之外的新变量而不说明;不要提交个人本地环境值。 - ❌ 新增未使用的依赖;不得擅自升级 Vue / Element Plus / Vite 版本。
- ❌ 管理端手写菜单路由来替代后端菜单配置。
- ❌ 在
.vue中使用any泛滥、console.log(ESLint error)。 - ❌ 跨工程复制
ztjg-ui组件到ztjg-app(运行环境不同)。 - ❌ 修改
src/utils/request.ts的加解密/错误码/视角头逻辑而不评估全站影响。
9. 交付前检查清单
- [ ] 请求写在
src/api/<域>/,URL 带正确网关前缀 - [ ] 列表页使用
useTable,字典使用useDict,按钮有v-auth - [ ] 新增移动端页面已登记
src/pages.json - [ ] 未在业务请求里手工设置
X-Data-View-Id,未传组织过滤参数 - [ ] 已切换至少 2 个视角验证:菜单变化、列表数据量变化、无白屏/报错
- [ ] 视角失效(
VIEW_EXPIRED)时页面能兜底,不弹错 - [ ]
pnpm lint:eslint/npm run lint通过 - [ ]
pnpm build/npm run build:h5可构建 - [ ] 无
console.log、无未使用 import/依赖 - [ ] 已在浏览器验证:列表加载、分页、增删改、错误提示
- [ ] 权限码与后端
@HasPermission完全一致
10. 不确定点
- 管理端组件是否全局自动注册:
src/components/index.ts存在,但自动注册机制未逐行确认。 - 两个前端均无自动化测试脚本,测试基线「待确认」。
- 移动端包管理器以 npm 还是 yarn 为准「不确定」。
/management/**、/msproject/**已核对不在路由种子 SQL(ztjg/db/2ztjg.sqlL1338-L1353 仅 15 条,工作流前缀为/task/**);运行期是否由sys_route_conf补录需查数据库/Redis。- 管理端菜单与
management_bizproject_*权限码未在种子 SQL 中找到,实际由后台配置「推测」。 - 视角缓存后端 TTL 1h、进程内 Caffeine 30s;前端切换后整页刷新,但另一个已打开的标签页不会自动同步视角,「不确定」是否需要跨标签同步。
DataViewSwitch挂载在src/layout/navBars/breadcrumb/index.vue;是否有其他布局未挂载,未逐布局确认。