13 KiB
AGENTS.md
本仓库包含两个独立的 npm 工程,各自 package.json/node_modules/dist,不是 monorepo/workspaces:
- 根目录:凡荣e链管理后台前端(PC 端,银行内部人员用)。Vue 3 + Vite,纯 JavaScript(无 TypeScript),Ant Design Vue + Pinia + Vue Router + Axios。无 Node 服务端(mock-server 仅本地演示用),构建产物是纯静态 SPA。
fryl_h5/:凡荣e链移动端 H5(手机端,未登录终端客户扫码后访问,用于开户/贷款申请信息核对)。Vue 3 + Vite + Vant(不是 Ant Design Vue)+ Vue Router + Axios,无 Pinia/任何状态管理库,纯静态路由(无动态菜单)。它没有自己的 README/AGENTS.md,本文件下方"H5 子工程"一节即是它的说明来源;不是 git submodule,是根仓库直接纳管的目录,有自己独立的 commit 历史(git log -- fryl_h5)。- 两者共享同一个真实后端,接口契约统一记录在根目录《缺失的后端接口.md》和
docs/superpowers/specs/(fryl_h5自己没有这些文档)。改动前先确认改的是哪一个工程,不要把两者的依赖/组件/约定搞混。
常用命令
根目录(管理后台):
npm run dev # 启动开发服务器,端口 5173
npm run build # 构建
npm run preview # 预览构建产物
npm run mock # 启动本地 mock 后端(express),默认端口 8888,可用 MOCK_PORT 覆盖
fryl_h5/(H5,命令要在该目录下跑):
npm run dev # 端口 5174(特意与根项目 5173 区分,可同时开两个工程调试)
npm run build
npm run preview
fryl_h5 没有自己的 mock 命令,本地联调依赖根目录的 npm run mock(默认端口 8888,fryl_h5/.env.development 的 VITE_API_BASE_URL 也指向它)。
两个工程都没有配置 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_*.json(目前有2026-07-09/2026-07-31/2026-08-05三份快照,取文件名日期最大的)和《缺失的后端接口.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.js的getBaseUrl/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格式时间channelNo如果页面没有渠道信息,则不需要传值src/utils/traceHeaders.js的buildTraceHeaders(channelNo)已正确实现上述 5 个字段(含固定大小写、channelNo按需可选),不需要再改造;fryl_h5/src/utils/traceHeaders.js是同一实现的独立拷贝(H5 端匿名接口,永远不传Channelno)。
- 枚举值必须来自 swagger/《缺失的后端接口.md》,已整理在
src/constants/*Enums.js,不要编造新枚举。
路由架构(新增页面容易漏配置)
路由不是静态声明的,而是登录后按后端返回的菜单权限树动态生成(src/router/dynamic.js 的 installDynamicRoutes)。新增一个菜单页面必须同时做两件事,少一件都会报错或 404:
- 在
src/views/**下新建组件。 - 在
src/router/componentRegistry.js的routePathComponentMap里把后端菜单path手工映射到组件相对路径。- 新增/编辑/详情等不在侧边栏菜单里的表单页,注册到同文件的
extraChildRoutes(而不是routePathComponentMap)。
- 新增/编辑/详情等不在侧边栏菜单里的表单页,注册到同文件的
- 权限字典由权限管理页面动态维护,但组件映射永远是前端手工登记,后端加菜单不会自动出现页面。
鉴权流程(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:通用查询表单+分页表格封装,列表页优先复用它而不是重写查询/分页逻辑。传给它的fetch-data函数必须返回{ list, total }(注意:后端分页字段是records,列表页自己的loadList/fetchData函数里要做records → list的映射,不要指望ProTable或 api 封装层自动转换)。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 为标准实现参照,新增或修改任何图片上传组件都要照此模式实现(LicenseUpload.vue/FacePhotoUpload.vue 均已按此模式改造):
- 缩略图外层包
.thumb-wrapper(position: relative),内部<img>+ 一个position:absolute; inset:0的.hover-mask(rgba(0,0,0,0.5)背景,默认opacity:0,:hover时opacity: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容器,复用其原生文件选择流程触发重新选图(不要单独写一套"重新上传"逻辑)。 - 组件需支持
readonlyprop(默认false):为true时只保留预览图标,隐藏"重新上传/删除"——用于查看详情等只读表单场景。调用处如果页面本身有isDetail之类的只读态标记,必须把它传给上传组件的readonly,不要漏传(否则只读页面会出现可用的删除按钮)。 - 当前项目没有抽出通用的图片上传基础组件,
IdCardUpload.vue/LicenseUpload.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/组件,保持和现有页面一致的写法即可。
H5 子工程(fryl_h5/)特殊约定
- 路由是纯静态声明(
fryl_h5/src/router/index.js),没有根项目那套动态菜单机制;向导式流程(信息核对→协议/人脸→短验→完成)的步骤切换靠页面内部currentStepref 维护,不为每个步骤单独设路由,避免用户分享/刷新中间步骤 URL 导致状态错乱。新增步骤直接加views/steps/**组件即可,不用碰路由文件。 fryl_h5/src/api/request.js是独立的精简版 axios 实例,不注入Authorization、不做 401 刷新(面向未登录终端客户的匿名接口),不要照抄根项目src/api/request.js的鉴权逻辑。- 无 Pinia,所有状态用组件内
ref管理,不要为这个子工程引入状态管理库。 - Trace header 同样用
buildTraceHeaders(),但永远不传channelNo(二维码链接不带渠道上下文)。 - 与根项目复用同一批后端接口:例如协议模板接口
GET /api/customer-info/bank-agreement/base64根项目在src/api/customer.js封装,fryl_h5在src/api/personalOpen.js里对同一接口重新封装了一份(两处各自维护,改接口字段要两边一起改)。 - 生成二维码指向
fryl_h5哪个页面的环境变量(VITE_H5_QRCODE_BASE_URL/VITE_H5_QRCODE_PATH*)配置在根项目的.env.development/.env.production里,由根项目src/utils/qrCode.js拼出完整链接,不在fryl_h5自己的 env 文件中。 - 相关设计文档在根目录
docs/superpowers/specs/,文件名含mobile-h5/loan-application-mobile关键字(如2026-07-27-personal-open-mobile-h5-design.md、2026-08-11-loan-application-mobile-confirm-design.md),改动前先查。 - 企业开户 H5 的提交接口本地
mock-server未实现,只能连真实后端联调验证;涉及企业开户 H5 时先看《缺失的后端接口.md》里标注"前端假设"的段落,不要假定 mock 能跑通。
其他约定
- BASE_URL 必须保留界面可配置入口(已实现,见上文),不要改成硬编码。
- 不写测试用例、不写 CHANGELOG,不新建项目子目录当根目录。