SFT/AGENTS.md

9.8 KiB

AGENTS.md

凡荣e链管理后台前端。Vue 3 + Vite,纯 JavaScript(无 TypeScript),Ant Design Vue + Pinia + Vue Router + Axios。无 Node 服务端(mock-server 仅本地演示用),构建产物是纯静态 SPA。

常用命令

npm run dev       # 启动开发服务器,端口 5173
npm run build     # 构建
npm run preview   # 预览构建产物
npm run mock      # 启动本地 mock 后端(express),默认端口 8888,可用 MOCK_PORT 覆盖

没有配置 lint / prettier / 单元测试脚本,不要假设它们存在,也不要臆造 npm run lint/npm test

后端接口对接(最重要,容易出错)

  • 权威接口契约在《缺失的后端接口.md》,不是 mock-server。改/加接口调用前必须先读这个文件的 Part 1(已对接契约速查)。核心通用约定:
    • 响应体统一 { code, message, data },业务成功判定为 code === 200;非 200 时用 message 提示,不做具体错误码分支。
    • 除登录/刷新令牌/发短信外,所有请求必须带 Authorization: Bearer {token}
    • 日期时间字段统一 yyyy-MM-dd HH:mm:ss
    • 分页接口 Body 含 page(从1开始)/pageSize,响应 data{ total, page, pageSize, records }——字段是 records,不是 list
    • 绝大多数模块没有批量删除接口,批量操作需用 src/utils/loopDelete.js 循环调单条删除并汇总结果(配合 batchDeleteResult.js 展示成功/失败)。
    • 文件(身份证/营业执照等)以 Base64 字符串放在 JSON body 传输,不用 multipart/form-data
  • .toco/CONFIG 目前只有 ORG_ID/ORG_NAME,没有 PROJECT_ID。若需要用 MCP 工具(getApiList/getApiSwaggerByUris)读取最新 swagger,先确认该文件是否已补上 PROJECT_ID;否则以仓库根目录的 swagger_project_scfs_2026-07-09_14-15-23.json 和《缺失的后端接口.md》为准。
  • mock-server/(npm run mock,端口 8888)是真实后端不可用时的应急本地演示后端,契约已知与真实接口不完全一致(见 mock-server/README.md “已知简化点”),不要把它当作接口设计依据。真实后端已接入,.env.development 默认 VITE_API_BASE_URL=http://localhost:8888,登录页齿轮图标可在运行时切换到真实后端地址(存 localStorage.base_url_override,见 src/api/request.jsgetBaseUrl/setBaseUrlOverride)。
  • 本地联调真实后端遇 CORS 时,用 .env.development.local(已 gitignore)配 DEV_API_PROXY_TARGET,走 vite.config.js 里的同源代理方案;别改生产构建逻辑去“解决” CORS。
  • 每次请求必须附带以下 5 个 header(字段名大小写固定为下列形式,首字母大写、其余小写):
    • appNo:随机值
    • channelNo:渠道 ID,来自页面上用户选择的渠道(非固定值,不能硬编码/写死)
    • serialNo:流水号,随机值
    • transDate:yyyy-MM-dd 格式日期
    • transTradeTime:yyyy-MM-dd HH:mm:ss 格式时间
    • ⚠️ 现状:src/utils/traceHeaders.jsbuildTraceHeaders() 目前只生成 serialNo/transDate/transTradeTime 三个小写驼峰字段,缺少 Appno/Channelno,尚未按最新要求改造;涉及请求 header 的改动前务必先核对/修复这里,不要照抄旧的三字段实现。
    • channelNo 如果页面没有渠道信息,则不需要传值
  • 枚举值必须来自 swagger/《缺失的后端接口.md》,已整理在 src/constants/*Enums.js,不要编造新枚举。

路由架构(新增页面容易漏配置)

路由不是静态声明的,而是登录后按后端返回的菜单权限树动态生成(src/router/dynamic.jsinstallDynamicRoutes)。新增一个菜单页面必须同时做两件事,少一件都会报错或 404:

  1. src/views/** 下新建组件。
  2. src/router/componentRegistry.jsroutePathComponentMap 里把后端菜单 path 手工映射到组件相对路径。
    • 新增/编辑/详情等不在侧边栏菜单里的表单页,注册到同文件的 extraChildRoutes(而不是 routePathComponentMap)。
  3. 权限字典由权限管理页面动态维护,但组件映射永远是前端手工登记,后端加菜单不会自动出现页面。

鉴权流程(src/router/index.js + src/stores/auth.js):accessToken 存 localStorage;刷新页面后靠 fetchCurrentUser/rebuildPermissions 重建菜单树再挂路由;401 由 src/api/request.js 响应拦截器用 httpOnly cookie 里的 refresh_token 静默刷新(请求本身不带 Authorization 头),刷新失败清空登录态跳 /login

项目范围(不要擅自扩展)

  • docs/superpowers/specs/2026-07-08-fryl-frontend-overall-design.md 是范围与阶段划分的权威文档,明确列出了 9 个一级模块的实施范围,以及明确排除的模块(非银专区/分账专区/税筹专区/农都专区/驾驶舱/首页仪表盘等,及各模块下需求文档未提及的原型子页面)。
  • website_detail/ 是从已上线的稠州银行商业平台完整抓取的原型参考(84 个页面,每页含截图 png + html + summary.md),覆盖面远大于本项目实际需要实现的功能,不能当作“待实现清单”,只用于查阅已排除模块之外的页面交互细节。
  • docs/superpowers/specs/docs/superpowers/plans/ 保存了各阶段已确认的设计文档与实施计划(按 YYYY-MM-DD-主题.md 命名),开工前先查有没有相关阶段的既有设计,避免重新决策已定的架构/字段方案。
  • 机构数据隔离(下拉框可选范围裁剪等)由后端按登录用户身份限定,前端不做二次裁剪,详见 src/utils/orgScope.js 注释与 docs/superpowers/specs/2026-07-16-org-data-isolation-design.md

目录速览

  • src/api/*.js:按后端模块拆分的请求封装,src/api/request.js 是唯一的 axios 实例(含鉴权/刷新/trace header/错误提示拦截器)。
  • src/components/ProTable.vue:通用查询表单+分页表格封装,列表页优先复用它而不是重写查询/分页逻辑。
  • src/constants/*Enums.js:各模块枚举字典(状态/类型等),来源必须是 swagger。
  • src/views/<module>/<sub-module>/:页面按模块/子模块分文件夹,列表页与新增/编辑/查看表单通常拆成 XxxList.vue + 共用的 XxxForm.vue(查看态字段只读,不是单独组件)。

图片/证件照片上传交互规范

所有"图片上传卡片"(身份证、营业执照、人脸照片等 list-type="picture-card"a-upload 封装组件)一旦已选中图片,必须支持鼠标 hover 缩略图时显示半透明遮罩 + 预览/重新上传/删除三个操作按钮,不允许只放一张裸 <img>。以 src/components/IdCardUpload.vue 为标准实现参照,新增或修改任何图片上传组件都要照此模式实现(src/components/LicenseUpload.vue 已按此模式改造):

  • 缩略图外层包 .thumb-wrapper(position: relative),内部 <img> + 一个 position:absolute; inset:0.hover-mask(rgba(0,0,0,0.5) 背景,默认 opacity:0,:hoveropacity:1,transition:opacity 0.2s)。
  • 遮罩内横向排列三个图标(EyeOutlined 预览 / EditOutlined 重新上传 / DeleteOutlined 删除),之间用 1px 竖线 .mask-divider 分隔,图标颜色 #fff,hover 变 #1677ff
  • 预览:图标加 @click.stop,点击打开一个 a-modal(:footer="null")放大展示图片,不要用浏览器默认行为。
  • 删除:图标加 @click.stop,清空本地预览态并 emit('update:fileNo', ''),让父组件感知已清空。
  • 重新上传:图标不加 @click.stop,让点击事件自然冒泡到外层 a-upload 容器,复用其原生文件选择流程触发重新选图(不要单独写一套"重新上传"逻辑)。
  • 组件需支持 readonly prop(默认 false):为 true 时只保留预览图标,隐藏"重新上传/删除"——用于查看详情等只读表单场景。调用处如果页面本身有 isDetail 之类的只读态标记,必须把它传给上传组件的 readonly,不要漏传(否则只读页面会出现可用的删除按钮)。
  • 当前项目没有抽出通用的图片上传基础组件,IdCardUpload.vue/LicenseUpload.vue/FacePhotoUpload.vue 是各自独立实现(结构相似但不复用)。FacePhotoUpload.vue 目前还没有按此规范改造,后续涉及到它时要一并补上。

耗时操作统一等待态规范

图片/文件上传、点击"提交"/"创建"/"保存"等触发接口调用的按钮,以及其他明显耗时的异步操作,必须统一展示等待态,期间禁止用户对页面做其他操作,不允许无反馈地"卡住"或允许重复点击。以 src/views/customer/enterprise/EnterpriseForm.vue(及 src/views/customer/personal/PersonalForm.vue)已有实现为标准参照:

  • 页面级遮罩:最外层 a-card/表单内容用 <a-spin :spinning="busy" :tip="busyTip"> 包裹,转菊花+可选提示文案,遮罩期间用户无法操作表单其他元素。
  • busy 用一个 computed 统一收敛所有"进行中"状态(如 submitting.value || xxxUploading.value,多个耗时状态用 || 合并),不要每个异步操作各自维护一套互不关联的 loading 变量却不接入统一遮罩。
  • busyTip 按当前处于哪个耗时状态给出对应提示文案(如"营业执照上传中,请稍候..."/"提交中,请稍候..."),没有耗时状态时留空。
  • 触发耗时操作的按钮本身也要加 :loading="submitting"(或对应状态),双重防止重复点击。
  • 图片上传组件(见上一节规范)通过 update:uploading 事件把自身上传中状态回传给父表单,父表单纳入 busy 统一遮罩,并在提交前校验各 xxxUploading 状态、阻止在文件尚未上传完成时提交表单。
  • 新增页面/表单时按此模式补齐,不需要抽公共 hook/组件,保持和现有页面一致的写法即可。

其他约定

  • BASE_URL 必须保留界面可配置入口(已实现,见上文),不要改成硬编码。
  • 不写测试用例、不写 CHANGELOG,不新建项目子目录当根目录。