90 lines
9.8 KiB
Markdown
90 lines
9.8 KiB
Markdown
# AGENTS.md
|
|
|
|
凡荣e链管理后台前端。Vue 3 + Vite,**纯 JavaScript(无 TypeScript)**,Ant Design Vue + Pinia + Vue Router + Axios。无 Node 服务端(mock-server 仅本地演示用),构建产物是纯静态 SPA。
|
|
|
|
## 常用命令
|
|
|
|
```bash
|
|
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:
|
|
|
|
1. 在 `src/views/**` 下新建组件。
|
|
2. 在 `src/router/componentRegistry.js` 的 `routePathComponentMap` 里把后端菜单 `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`,`: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` 容器,复用其原生文件选择流程触发重新选图(不要单独写一套"重新上传"逻辑)。
|
|
- 组件需支持 `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,不新建项目子目录当根目录。
|