⚡

前端开发Skill frontend-dev

本技能面向前端开发,专注 ztjg-ui 管理端(Vue 3 + TypeScript + Element Plus + Pinia + Vite)与 ztjg-app 移动端(uni-app)的开发规范、页面/组件/表单/列表实现与联调排障;核心覆盖多组织数据视角(X-Data-View-Id 透传、视角切换器、菜单按视角下发、VIEW_EXPIRED 降级)、API 请求封装、动态路由、Pinia 状态、本地代理与灰度联调。关键词:前端开发, Vue3, TypeScript, Element Plus, Pinia, Vite, uni-app, 管理后台, 移动端, 多组织数据视角, X-Data-View-Id, 数据权限, 动态路由, API封装, 联调排障, 前端规范, SEO。

前端开发 0 次安装 0 次浏览 更新于 11/11/2026
名称 前端开发
描述 本仓库前端开发专用技能(管理端 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

必须遵守:

  1. 不要在前端拼组织过滤参数(orgId/deptId/orgIds)。数据范围由后端 DataScopeInnerInterceptor 下推 SQL,页面只展示后端返回结果。
  2. 请求一律走 src/utils/request.ts,视角头自动带;禁止手工设置 X-Data-View-Id(调试除外)。
  3. 需要「视角下拉/切换」时复用 DataViewSwitch + src/api/admin/dataView.ts,不要重复实现切换器。
  4. 空视角是常态:/current 返回 context=null 时菜单降级为全来源并集,页面不得报错或白屏。
  5. 按钮权限码可能因视角收窄而变少(后端 PermissionService 按视角权限码集判定),v-auth 只控制显隐,不能当作数据安全边界。
  6. 同一列表在不同视角下返回的数据量不同;切换视角后必须整页刷新(当前实现即 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. 修改前必须阅读

管理端:

  1. ztjg-ui/AGENTS.md、ztjg-ui/CLAUDE.md(硬性规范)
  2. ztjg-ui/src/utils/request.ts(鉴权头、视角头、加解密、错误判定)
  3. ztjg-ui/src/hooks/table.ts、ztjg-ui/src/hooks/dict.ts
  4. ztjg-ui/src/api/management/bizProject.ts(请求层模板)
  5. ztjg-ui/src/views/management/bizProject/index.vue + form.vue(页面模板)
  6. ztjg-ui/src/router/backEnd.ts(路由来自后端菜单)
  7. 涉及视角:ztjg-ui/src/api/admin/dataView.ts、ztjg-ui/src/components/DataViewSwitch/index.vue
  8. ztjg-ui/.prettierrc.js、ztjg-ui/.eslintrc.js

移动端:

  1. ztjg-app/src/pages.json、ztjg-app/src/utils/request/index.ts
  2. ztjg-app/src/pages/message/message.vue、ztjg-app/src/api/message.ts(页面 + 请求模板)
  3. ztjg-app/.eslintrc.js、ztjg-app/vite.config.ts

4. 标准工作流

4.1 管理端:新增一个列表页(最常见)

  1. 请求层:在 ztjg-ui/src/api/<域>/<feature>.ts 新建文件,照抄 src/api/management/bizProject.ts:import request from '/@/utils/request',导出 fetchList / addObj / getObj / putObj / delObjs。
  2. 页面:src/views/<域>/<feature>/index.vue,照抄 views/management/bizProject/index.vue:
    • useTable 管理分页与查询(import { BasicTableProps, useTable } from '/@/hooks/table';)
    • 字典用 useDict
    • 按钮权限用 v-auth 指令配合权限码
  3. 表单:form.vue 用 el-dialog + el-form,父页面 ref 调用打开方法。
  4. 菜单:管理端路由由后端菜单下发(/admin/data-view/menus),不要在前端手写该页面路由。需在后台「菜单管理」配置菜单并分配权限码(必要时配置 module_code)。
  5. 数据范围:列表接口不要传组织参数,权限由后端视角下推;若接口返回空列表,先确认当前视角,而不是先怀疑前端。
  6. 自测: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 移动端:新增页面

  1. 新建 ztjg-app/src/pages/<name>/<name>.vue。
  2. 在 ztjg-app/src/pages.json 的 pages 数组追加同路径条目(含 navigationBarTitleText 等),否则无法跳转。
  3. 请求写进 ztjg-app/src/api/<name>.ts,使用 ztjg-app/src/utils/request。
  4. H5 走 /api 代理(vite.config.ts 剥离前缀);App/小程序 urlPrefix 为空、直连 VITE_APP_BASE_URL,因此 API 路径不要硬编码 /api(src/utils/request/index.ts)。
  5. 移动端无视角切换,后端按登录用户默认视角收敛数据;不要自行传组织过滤参数。
  6. 自测: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. 不确定点

  1. 管理端组件是否全局自动注册:src/components/index.ts 存在,但自动注册机制未逐行确认。
  2. 两个前端均无自动化测试脚本,测试基线「待确认」。
  3. 移动端包管理器以 npm 还是 yarn 为准「不确定」。
  4. /management/**、/msproject/** 已核对不在路由种子 SQL(ztjg/db/2ztjg.sql L1338-L1353 仅 15 条,工作流前缀为 /task/**);运行期是否由 sys_route_conf 补录需查数据库/Redis。
  5. 管理端菜单与 management_bizproject_* 权限码未在种子 SQL 中找到,实际由后台配置「推测」。
  6. 视角缓存后端 TTL 1h、进程内 Caffeine 30s;前端切换后整页刷新,但另一个已打开的标签页不会自动同步视角,「不确定」是否需要跨标签同步。
  7. DataViewSwitch 挂载在 src/layout/navBars/breadcrumb/index.vue;是否有其他布局未挂载,未逐布局确认。