finance和marking v3 前端(PC)

Q 4ac0061c12 docs(readme): 三步定稿重写——技术栈(UMD自托管OSS+本地编译)/目录结构/编码规范 4 zile în urmă
.gitignore fe3642da19 docs: 初始化 PC 端前端仓库(工程约定 README + .gitignore 占位) 4 zile în urmă
README.md 4ac0061c12 docs(readme): 三步定稿重写——技术栈(UMD自托管OSS+本地编译)/目录结构/编码规范 4 zile în urmă

README.md

PC 端前端(管理/运营)

Vue3 管理端,承载教务域重写 + 教学 admin 复用两块页面,统一走 nginx 入口 + 统一 Go 后端 SSO。 本文档覆盖四块:技术栈 / 目录结构与用途 / 编码规范 / 构建。接口契约与角色权限见工作区文档库 unified/、academic/05-architecture/、teaching/05-architecture/(本仓库不复制契约原文)。 框架层选型(2026-11-06 定稿):UMD 全局脚本 + 自托管 OSS + 业务层本地编译 + 默认简体中文。

定位

单一 Vue3 项目,面向登录角色中的 超管/教务/财务/老师(管理侧) 等 PC 端角色(市场角色登录入口暂禁用——不建市场端入口/路由,后续启用时再单独拍板)。学生/家长日常操作走移动端仓库 DEV-MERGE-LEGACY-MOBILE-FRONTEND-2026。

1. 技术栈

1.1 资源双层结构(核心选型)

层 内容 引入方式 版本管理
框架层 Vue 3 / Vue Router 4 / Pinia / Element Plus(JS + CSS + zh-cn locale)/ axios,共 7 项 UMD 资源 index.html 内 <link>/<script> 按固定顺序引入,源文件自托管到自有 OSS(自定义域名 + SSL,不用第三方 CDN) 版本事实 = index.html 头部"vendor 版本表"(钉死次版本)+ 仓库 vendor/ 源文件(git 入库)
业务层 本项目全部业务代码(TS 编写) 本地 pnpm 工具链编译(Vite/vue-tsc/ESLint/Prettier 为 dev 依赖),产物 dist/ 经 nginx 部署 pnpm-lock.yaml 入库(只锁工具链,不含框架)
  • 开发测试:pnpm dev 起 Vite 本地服务(HMR 只热更业务代码,框架层从自托管 OSS 加载);/api 代理到后端 dev 地址。
  • 构建:pnpm build 只打包业务代码 → dist/;产物禁止包含框架运行时(质量门禁 grep 抽查,防 external 配错)。

1.2 框架层 UMD 资源清单(加载顺序固定,版本以 index.html 版本表为准)

# 资源 OSS 路径模板 全局变量 用途
1 Vue 3(prod UMD) vue/{v}/vue.global.prod.js Vue 框架本体
2 Vue Router 4 vue-router/{v}/vue-router.global.js VueRouter 路由(按端组织 + 守卫链)
3 Pinia pinia/{v}/pinia.iife.prod.js Pinia 状态(login / scope 两 store)
4 Element Plus CSS element-plus/{v}/index.css — UI 样式(走自托管,不打包)
5 Element Plus(full UMD) element-plus/{v}/index.full.min.js ElementPlus UI 组件(表格/表单/日期等全量)
6 Element Plus 中文 locale element-plus/{v}/locale/zh-cn.js ElementPlusLocaleZhCn 默认简体中文
7 axios axios/{v}/axios.min.js axios HTTP(统一封装于 src/api/)

OSS 根路径固定:https://<自有OSS域名>/static/vendor/<库名>/<版本号>/<文件名>/,只增不删(旧版本路径保留,回滚 = 改回 index.html 版本号)。

1.3 业务层工具链(本地编译)

关注点 选型 理由 / 依据
构建 Vite 6.x 业务代码 HMR + 产物构建;框架 external 后主 chunk 只剩业务代码
语言 TypeScript strict 契约类型化:R<T> / PageData<T> / 各域 DTO;UMD 全局经 src/types/globals.d.ts 声明类型
UI 组件库 Element Plus 2.x(UMD 自托管) 教学 admin 现有栈,复用成本最低;zhCn locale 双注入
状态 Pinia login(token/角色/权限码)+ scope(institution/campus 作用域)
路由 Vue Router 4 按端组织 + 守卫链(登录态 → 静默刷新 → 权限码 → 作用域就绪)
HTTP axios 1.x(UMD 自托管,封装于 src/api/) 统一信封处理 + 401 refresh 单例重放;业务错误不 reject
鉴权 SSO token(access 30min + refresh 30 天,ADR-014) 统一 identity 签发;菜单/按钮按权限码显隐,禁 role == N 硬编码
包管理 pnpm 10.12.1(lockfile 入库) 本机已装;与移动端仓同栈
代码风格 ESLint 9 + Prettier 3 提交前 lint;no-explicit-any 强制
类型检查 vue-tsc --noEmit 质量门禁 0 错误

1.4 默认简体中文

  • <html lang="zh-CN">;页面文案、placeholder、提示一律中文,默认且唯一语言。
  • Element Plus locale 双注入(教务实战铁律):app.use(ElementPlus, { locale: ElementPlusLocaleZhCn })(命令式 ElMessageBox/Notification)+ <el-config-provider :locale="ElementPlusLocaleZhCn">(声明式),单用其一必有遗漏。
  • 不引入 vue-i18n(7 角色全中文、无多语言需求);将来真需多语言再上 i18n 框架,属另立决策。

1.5 框架升级规则(自托管五步,缺一不可)

  1. 下载新版本 UMD 文件 → 2. 覆盖仓库 vendor/ 对应目录并 commit → 3. 上传 OSS 对应路径 → 4. 改 index.html 版本表 → 5. 全量回归(vue-tsc + build + 端点联调)。 > 版本升级不再受 lockfile 保护,靠仓库 diff + 回归兜底;发布前 vendor/ 与 OSS 内容 md5 对账。

2. 目录结构与用途

.
├── index.html                 ← 【框架层挂载点】lang="zh-CN";7 项 vendor 资源按固定顺序引入;
│                                  头部注释含"vendor 版本表"(库 → OSS 路径 → 版本,一处维护);
│                                  <div id="app"> + 业务入口 <script type="module" src="/src/main.ts">
├── vendor/                    ← 【框架层自托管源文件】git 入库的版本事实(升级五步流程落点)
│   ├── vue@x.y/                 vue.global.prod.js
│   ├── vue-router@x.y/          vue-router.global.js
│   ├── pinia@x.y/               pinia.iife.prod.js
│   ├── element-plus@x.y/        index.full.min.js + index.css + locale/zh-cn.js
│   └── axios@x.y/               axios.min.js
│                                  (上传 OSS 经 /static/vendor/<库名>/<版本号>/ 暴露;只增不删)
├── package.json               ← 仅【业务层工具链】:vite / @vitejs/plugin-vue / vue-tsc / eslint / prettier / typescript
├── pnpm-lock.yaml             ← 工具链版本锁定(入库)
├── vite.config.ts             ← ① rollup external:vue/vue-router/pinia/axios → 全局变量映射(运行时只认 UMD 全局)
│                                  ② dev server:/api 代理 → 后端 dev 地址  ③ 产物 chunk 拆分(业务代码)
├── tsconfig.json              ← strict 基线 + paths 别名(@ → src/)
├── .env.example               ← 环境变量模板(VITE_API_BASE 等;实际 .env.* 走 .gitignore)
└── src/                       ← 【业务层】全部业务代码(本地编译进 dist/)
    ├── main.ts                ← 业务装配入口:取 UMD 全局 Vue.createApp + Element Plus locale 双注入
    │                              + Pinia + Router 挂载;不 import 框架运行时
    ├── types/
    │   ├── globals.d.ts       ←   UMD 全局变量类型声明(declare global:Vue/VueRouter/Pinia/ElementPlus/axios)
    │   └── api.d.ts           ←   契约类型:R<T> 信封 / PageData<T>(list,total,page,pageSize,pages)/ 角色与权限码
    ├── views/                 ← 页面层(只放视图与页面级状态;禁散写 axios)
    │   ├── academic/          ←   教务域:排班/费用/打卡/充值/报课 ——【新写】(旧 jQuery 视图全部重写)
    │   ├── teaching/          ←   教学 admin:备课/题库/组卷/练习分析 ——【按现有 Vue3 逐步迁入】
    │   ├── org/               ←   机构/校区/班级/成员管理
    │   └── system/            ←   系统:用户/角色/字典/任务监控/日志
    ├── components/            ← 跨页面复用组件:ListPage(表格+分页+筛选+批量收口)、作用域选择器
    │                              (institution·campus 联动)、GradeBadge/SubjectBadge(色板纯数据零 Vue 依赖)
    ├── api/                   ← 接口封装(一域一目录、一资源一文件):axios 实例 + 统一信封处理 +
    │                              401 refresh 单例重放 + 分页统一解构 res.data.{list,total,page,pageSize,pages}
    ├── router/                ← 路由表按端组织 + 守卫链:登录态 → 静默刷新 → 权限码 → 作用域就绪
    ├── store/                 ← Pinia:login(token/角色/权限码)、scope(institution/campus)——唯一 token 存取点
    ├── composables/           ← 可组合逻辑:useListPage<T>(列表页唯一规范入口)等
    ├── utils/                 ← 纯函数工具集(无 Vue 依赖):日期/金额格式化、脱敏回显、下载/导出辅助
    └── styles/                ← 全局样式:variables.scss(色板/间距 token)+ reset + Element Plus 主题覆盖

目录用途约定:

目录 用途 约束
index.html 框架层挂载 + 版本表 7 项 vendor 引用顺序固定;版本号只改这里,禁止散落 src
vendor/ 框架层源文件版本事实 与 OSS 内容一致(提交前 diff 核对);只增不删,禁止覆盖已发布旧版本路径
src/views/{域}/ 页面层(四域分包:academic/teaching/org/system) 只经 src/api/ 发请求;views 之间互引禁止(复用 → 上提 components/)
src/components/ 跨页面复用组件 零业务耦合(作用域感知组件除外,从 store 读)
src/api/{域}/ 接口封装(一域一目录,一资源一文件) 信封处理与分页解构只在此;返回类型化 DTO
src/router/ 路由与守卫 权限码驱动;不写 role == N 硬编码
src/store/ 全局状态(登录态/作用域) 唯一 token 存取点(禁 localStorage 散写);scope 未就绪时守卫拦截
src/composables/ 可组合逻辑 列表页一律复用 useListPage<T>
src/utils/ 纯函数工具集 文件内零组件、零 store 依赖;可被 composables/api/views 任意引用,反向禁止
src/styles/ 全局样式与 token 业务组件 <style> 禁硬编码色值/尺寸,必须引 variables token;页面级局部样式留组件内 <style scoped>
src/types/ 契约类型 R<T>/PageData<T> 只在此定义一次;UMD 全局声明全覆盖(新增框架资源先补声明后写业务)

3. 编码规范

3.1 命名规则

对象 规则 示例
文件 组件 PascalCase.vue;其余 kebab-case.ts ListPage.vue、use-list-page.ts、grade-badge.ts
组件 注册名 PascalCase,模板里 kebab-case <list-page> / ListPage
组合式函数 useXxx useListPage<T>()、useScope()
变量/函数 camelCase institutionId、handleSubmitRefund
常量 模块内 UPPER_SNAKE(仅真常量:错误码段、权限码枚举) PERM_CODE
接口/类型 名词 PascalCase,DTO 带域前缀 PageData<T>、AcademicFeeDTO、TeachingQuestionDTO
API 字段 一律 camelCase,与后端 DTO 严格对齐(institutionId 非 institution_id);禁止前端自造映射层 —
路由 path kebab-case,与后端端点一致(/{端}/{模块}/{资源}) /admin/academic/fees
路由 name PascalCase 页名 AcademicFeeList
权限码 域.资源.动作 三段式(点分) academic.fee.refund
事件/方法 内部回调 onXxx;用户触发的提交类 handleXxx onScopeChange、handleDelete
禁词 禁用 userid/schoolid/campusid 类旧系统无下划线混用命名 —

3.2 文件组织

组件 SFC 骨架(顺序固定):

<script setup lang="ts">   ← ① 类型 import(仅类型与本地 DTO,禁 import 框架运行时)
                            ← ② defineProps/defineEmits 显式类型(禁 any,unknown 收窄)
                            ← ③ store 注入(useLogin/useScope)
                            ← ④ 响应式状态(ref/computed,单一数据源)
                            ← ⑤ 生命周期(onMounted 拉数据)
                            ← ⑥ 方法(handleXxx 在前,onXxx 在后)
</script>
<template>                  ← ⑦ 结构;列表页一律 <list-page> 收口
</template>
<style scoped>              ← ⑧ 局部样式;色值/尺寸引 @/styles/variables token

域目录内约定:

  • views/{域}/{模块}/:list.vue(列表)+ detail.vue(详情)+ 页内私有子组件 {模块}-xxx.vue(私有组件不放 components/);
  • api/{域}/{资源}.ts:一资源一文件,只导出该资源的请求函数 + 对应 DTO 类型;
  • 跨域引用只允许经 api/ 与 components/。

3.3 组件编写

  1. 框架运行时全走 UMD 全局:src/ 内禁止 import Vue/VueRouter/Pinia/ElementPlus/axios;组合 API 标识符经 vite define 注入映射到 UMD 全局(或统一 const { ref } = Vue 解构,两种写法脚手架定稿时二选一),规范条文恒为"禁 import 框架"。
  2. Props:显式 interface 类型;可选项给默认值;禁内联 any。
  3. 状态最小化:能用 store 的(token/scope/权限码)不本地存;列表状态收口 useListPage<T>(page/pageSize/total/loading 全在里面,组件只传 fetcher)。
  4. 作用域纪律:业务查询参数里的 institutionId/campusId 只从 useScope() 取,禁止页面自拼(对齐后端"作用域强制注入",消灭旧系统隐式 schoolid 坑)。
  5. 表单:el-form + 显式 rules(提交前 validate());服务端 2001 错误 message 直接 toast,前端规则只拦格式。
  6. 表格:列定义数组化(prop/label/width/formatter 数据驱动),新增列不改模板结构。
  7. 权限:按钮级 v-perm="'域.资源.动作'" 自定义指令(读 login store 权限码集);禁止 role === N 硬编码。
  8. 三态:所有异步区域必须有 loading + 空态 + 错误态;错误态展示后端 message(已脱敏),不二次包装文案。
  9. 敏感操作(充值/退款/改密/删除):ElMessageBox.confirm 二次确认 + 操作后刷新 + 后端留痕。
  10. 脱敏:手机号/身份证直接展示后端已脱敏值,前端不存明文、不二次处理。

3.4 接口与响应契约(前后端统一,2026-11-05 拍板)

  • 统一「HTTP 恒 200 + body code」信封 {code, data, message, success};判成功读 code === 200,错误文案读 message,不靠拦截器 reject、不靠 HTTP status(源自教务 Go 重构在产验证)。
  • 错误码分段(与后端一致):1xxx 认证/权限 · 2xxx 参数 · 3xxx 数据 · 4xxx 依赖 · 5xxx 系统;1001/1002 统一走 refresh 单例(并发只刷一次,Promise 复用),成功后重放原请求;refresh 失败才清 login store 跳登录。错误 toast 去重:同一 message 1s 内不重复弹。
  • 统一分页:请求 page/pageSize(默认 1/10,上限 200);响应 data 恒为 PageData 对象 {list, total, page, pageSize, pages},useListPage<T> 统一解构 res.data.{list, total, page, pageSize, pages},组件层只见 rows/total 两个绑定(不再区分分页/非分页两套读法)。

3.5 类型纪律(TS strict)

  • any 全面禁用(vue-tsc + ESLint no-explicit-any 双卡);未知结构用 unknown + 类型收窄。
  • DTO 与后端字段逐字段对齐:新增接口先补 api/{域}/{资源}.ts 类型定义再写调用;禁止"先 any 后补类型"。
  • R<T>/PageData<T> 泛型只在 types/api.d.ts 定义一次,全仓引用。

3.6 质量门禁

门禁 命令 / 做法 基线
类型检查 vue-tsc --noEmit 0 错误
构建 pnpm build(vite) 0 错误;主 chunk < 1024KB;dist/ 产物 grep 不得含 vue/element-plus 运行时(防 external 配错回退成本地打包)
Lint pnpm lint(eslint + prettier) 0 错误
vendor 一致性 发布前 vendor/ 与 OSS 路径内容 md5 对账 一致方可发布
端点联调 真实登录 token → 页面操作 → 断言 body code + 结构 结构不符先查后端路由重复注册

3.7 Git 与提交规范

  • 工程纪律:本地 commit 可执行;脚手架搭建与 git push 前必须经用户确认(逐次确认)。
  • 提交信息:conventional commits(feat: / fix: / chore: / docs: / refactor:),正文写清影响域。
  • 提交前自检:vue-tsc + pnpm build 通过;敏感值未入库(.env 走 .gitignore,仅 .env.example 入库);vendor 变更必须与 index.html 版本表同步提交。

4. 构建与本地运行

pnpm install          # 工具链依赖(pnpm 10.12.1;lockfile 入库)
pnpm dev              # 本地开发(vite dev,业务代码 HMR;框架层自 OSS 加载;/api 代理到后端 dev 地址)
pnpm build            # 产物 → dist/(仅业务代码;部署经 nginx 按 location 分发,ADR-010)

环境变量(.env.example 入库,实际 .env.* 不入库):VITE_API_BASE(后端 base,dev 走 vite proxy 时可省)。

框架层升级:执行 §1.5 五步流程(下载 → 覆盖 vendor/ 并 commit → 上传 OSS → 改 index.html 版本表 → 全量回归)。

5. 与文档目录的关系

  • 本仓库只放前端代码;接口契约、端点清单、错误码见工作区文档库 academic/05-architecture/ 与 teaching/05-architecture/;前端策略(重写 vs 复用)见 unified/00-planning/cross-system-reconciliation.md §5。
  • 与移动端仓库共享编码惯例(统一信封/统一分页 PageData/脱敏/权限码三段式/commit 规范),差异:PC 端为 UMD 自托管 + 单项目,移动端为 pnpm monorepo + Vant 4。