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.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格式时间- ⚠️ 现状:
src/utils/traceHeaders.js的buildTraceHeaders()目前只生成serialNo/transDate/transTradeTime三个小写驼峰字段,缺少Appno/Channelno,尚未按最新要求改造;涉及请求 header 的改动前务必先核对/修复这里,不要照抄旧的三字段实现。 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:通用查询表单+分页表格封装,列表页优先复用它而不是重写查询/分页逻辑。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,: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是各自独立实现(结构相似但不复用)。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,不新建项目子目录当根目录。