# mini2.0 项目开发规范

> 适用范围：本仓库所有前端页面、组件、状态、接口和构建配置。
>
> 最后更新：2026-07-10。

## 1. 技术栈与运行环境

| 类别 | 项目选型 |
| --- | --- |
| 核心框架 | Vue 3、TypeScript、`<script setup>` |
| 构建工具 | Vite 8 |
| UI 组件 | Vant 4、Iconify |
| 路由 | Vue Router，Hash 路由 |
| 状态管理 | Pinia、pinia-plugin-persistedstate |
| 网络请求 | Axios、敏行宿主 `MXCommon.ajax` / `NXCommon.ajax` |
| 样式处理 | CSS、Less、PostCSS、px-to-viewport |
| 代码质量 | ESLint、OxcLint、Prettier、vue-tsc |

- Node.js 必须满足 `^20.19.0 || >=22.12.0`。
- 统一使用 npm 和仓库内的 `package-lock.json`，不要混用 pnpm、Yarn 或 Bun。
- 全局源码使用 UTF-8、LF 换行、2 空格缩进。

## 2. 常用命令

```bash
# 安装依赖
npm ci

# 本地开发，使用 DEV 环境连接本地 OMS，并监听 8080 端口
npm run dev

# 自动修复 ESLint 和 OxcLint 问题
npm run lint

# TypeScript / Vue 类型检查
npm run type-check

# 格式化 src 目录
npm run format
```

构建命令仅在发布或明确需要构建验证时执行：

```bash
npm run build:earth2sit
npm run build:test
npm run build:sit
npm run build:uat
npm run build:pvt
npm run build:prod
```

### 2.1 构建副作用

项目的 `plugins/bump-version.ts` 会在构建时执行以下操作：

1. 按构建环境自动修改 `src/config/plugin.properties.earth2sit/test/sit/uat/pvt/prod` 中对应文件的版本号；
2. 输出 `dist/www`、`dist/plugin.properties`；
3. `version_code` 按 `A BB CC` 映射为 `A.B.C` 三段版本号，例如 `10000` 对应 `1.0.0`；
4. 按环境生成 `dist/<环境>/头寸管理2.0_version_<version_name>.zip`，例如 `1.0.0` 的 TEST 包输出为 `dist/TEST/头寸管理2.0_version_1.0.0.zip`。

因此不要把构建产生的版本号变化混入普通功能提交。构建后必须检查 `git diff`，只保留本次任务确实需要的版本文件变更。

## 3. 环境配置

项目按 Vite mode 读取配置：

- `.env.dev`：本地开发启动环境，不用于打包；
- `.env.earth2sit`：EARTH2SIT 打包环境；
- `.env.test`：TEST 打包环境（业务环境标识为 `TEST`）；
- `.env.sit`：系统集成测试环境；
- `.env.uat`：用户验收测试环境；
- `.env.pvt`：生产验证环境；
- `.env.prod`：生产环境。

当前公共变量：

| 变量 | 说明 |
| --- | --- |
| `VITE_APP_ENV` | 当前业务环境标识 |
| `VITE_API_BASE_URL` | 接口基础地址 |
| `VITE_API_TIMEOUT` | 请求超时时间，单位毫秒 |
| `VITE_ENABLE_VUE_DEVTOOLS` | 是否启用 Vue DevTools，设置为 `false` 时关闭 |

规范要求：

- 只有允许暴露到浏览器的变量才使用 `VITE_` 前缀；
- 不得在源码、环境文件、日志或提交记录中写入密码、Token、私钥等敏感信息；
- 新增环境变量时，同时补充 `env.d.ts` 类型和本文档说明；
- 本地开发接口统一经过 Vite `/oms` 代理，不在页面中硬编码服务地址。

## 4. 目录职责

```text
src/
├── api/          # 业务接口、接口入参与响应类型、后端数据归一化
├── components/   # 跨页面复用的通用组件
├── config/       # 环境、宿主应用和打包配置
├── core/         # 基础设施：敏行 JSAPI、双端请求适配等
├── router/       # 路由表和全局路由守卫
├── stores/       # Pinia 全局或业务域共享状态
├── styles/       # 全局样式及第三方组件覆盖
├── types/        # 跨模块公共类型声明
├── utils/        # 无页面依赖的通用工具和业务格式化函数
└── views/        # 页面，按业务域组织
    ├── announcement/
    ├── cps/      # 人民币业务（含头寸预报及查询等模块）
    ├── dashboard/
    ├── fps/      # 外币业务（含头寸预报及查询等模块）
    ├── home/
    ├── login/
    └── mine/
```

### 4.1 页面目录规则

- 页面必须按业务域归档，人民币业务放在 `views/cps`，外币业务放在 `views/fps`。
- 人民币业务页面的 URL 统一使用 `/cps` 路径前缀，并保持页面内跳转与首页入口一致。
- 列表、详情、新增等同一业务的页面放在同一模块下，例如：

  ```text
  views/cps/position-forecast/
  ├── index.vue
  ├── create/index.vue
  └── detail/index.vue
  ```

- 移动页面文件时，默认只调整目录和路由 import，不改变已对外使用的 URL。
- `src/views/home/index.vue` 是首页业务入口清单；新增、删除或调整首页模块时需要同步检查它。
- 只在多个页面实际复用时抽取到 `components`；页面私有组件应放在对应页面模块内，避免过早全局化。

## 5. 命名规范

| 对象 | 规范 | 示例 |
| --- | --- | --- |
| 业务目录 | kebab-case | `position-estimate` |
| 页面入口 | `index.vue` | `views/cps/position-forecast/index.vue` |
| 通用组件 | PascalCase | `AppTitleBar.vue` |
| 变量、函数 | camelCase | `systemWorkDate`、`handleQuery` |
| 常量 | UPPER_SNAKE_CASE | `DETAIL_TRAN_CODE` |
| 类型、接口 | PascalCase | `PbcPositionBalance` |
| Store Hook | `useXxxStore` | `usePositionFilterStore` |
| 事件处理函数 | `handleXxx` 或 `onXxx` | `handleSubmit`、`onMounted` |
| CSS 类 | kebab-case，组件内建议 BEM | `app-title-bar__back` |

- 布尔值使用 `is`、`has`、`show`、`can`、`should` 等可读前缀。
- API 字段必须保留后端原始命名时，在 API 层声明类型并集中映射，不把含义不明的缩写继续扩散到视图层。
- import 优先使用 `@/` 别名，避免多层 `../../../` 相对路径。

## 6. TypeScript 规范

- 禁止无理由使用 `any`；外部未知数据先使用 `unknown`，再通过类型守卫收窄。
- 请求入参、响应体、组件 Props、Emits 和 Store 状态必须声明类型。
- 类型仅用于编译时，应使用 `import type`。
- 项目启用了 `noUncheckedIndexedAccess`，数组或对象索引结果必须处理 `undefined`。
- 可选字段在进入计算和展示前应提供明确兜底，不使用非空断言掩盖真实问题。
- 优先使用联合类型表达有限状态，不用散落的魔法字符串。
- 共用类型放在 `src/types`；仅被一个 API 使用的类型与对应 API 文件放在一起。
- 金额等高精度后端字段优先保留为字符串，在格式化或计算边界再显式转换。

## 7. Vue 组件规范

### 7.1 脚本

- 新组件统一使用 `<script setup lang="ts">` 和 Composition API。
- 单文件组件按“依赖导入 → Props/Emits → 响应式状态 → 计算属性 → 生命周期 → 事件方法”的顺序组织。
- Props 和 Emits 使用类型声明；有默认值时使用 `withDefaults`。
- 派生数据使用 `computed`，不要用 `watch` 手动维护可计算状态。
- 注册了原生事件或定时器时，必须在 `onBeforeUnmount` / `onUnmounted` 中清理。
- Vue、Vue Router、Pinia、VueUse 常用 API 已配置自动导入；业务模块、工具函数和类型仍需显式 import。
- Vant 组件已按需自动注册，直接使用 `van-*` 组件，不在页面重复做全量注册。

### 7.2 模板

- 模板只负责声明展示，不放复杂数据转换或多层业务判断；复杂逻辑放到 `computed` 或函数中。
- 列表渲染必须提供稳定且唯一的 `:key`，不得默认使用数组下标。
- 原生 `<button>` 必须设置 `type="button"` 或正确的提交类型。
- 图标按钮、返回按钮和自定义交互控件必须提供 `aria-label`，并保留键盘可操作性。
- “隐藏”和“禁用”是不同语义：需求要求隐藏时使用 `v-if`，不要仅设置 `disabled`。
- 请求中的按钮应绑定 loading 状态，避免重复提交。

### 7.3 样式

- 页面和组件样式默认使用 `<style scoped>`；真正全局的样式才放入 `src/styles`。
- 覆盖 Vant 内部样式时使用 `:deep()`，并把影响范围限制在当前页面或组件。
- 全局 Vant 覆盖统一放在 `src/styles/vant-overrides.css`。
- 页面最小高度优先使用 `var(--app-page-min-height)`，不要各自重复计算标题栏和 Tabbar 高度。
- 必须考虑 `env(safe-area-inset-top)` 和 `env(safe-area-inset-bottom)`，避免内容被刘海、标题栏或底部导航遮挡。
- 主内容在大屏环境下应遵守项目现有的 `750px` 最大宽度约束。
- PostCSS 以 `375px` 设计稿宽度把大于等于 `2px` 的 px 转为 vw；`1px` 边框会保留。编写尺寸时必须意识到这一转换规则。
- 优先复用 Vant CSS 变量和项目现有色彩，不随意增加新的全局颜色或层级值。

## 8. 路由规范

所有页面路由集中在 `src/router/index.ts`：

- 页面组件使用动态 import，保持路由级懒加载；
- `name` 使用唯一的 PascalCase 名称；
- 需要登录的页面必须配置 `meta.requiresAuth: true`；
- 标题统一配置在 `meta.title`；
- 只有需要底部导航的一级页面才设置 `meta.showTabbar: true`；
- 新增一级页面时，同步检查 `src/App.vue` 中的 Tabbar、无返回按钮路由和标题栏行为；
- 路由参数使用语义化名称，例如 `:predRefNo`、`:id`；
- 不在单个页面重复实现登录跳转，统一交给全局路由守卫。

示例：

```ts
{
  path: '/cps/example',
  name: 'Example',
  component: () => import('@/views/cps/example/index.vue'),
  meta: { requiresAuth: true, title: '示例页面' },
}
```

## 9. Pinia 状态规范

按状态作用域决定存放位置：

1. 仅当前组件使用、离开页面即可丢失：使用组件内 `ref` / `reactive`；
2. 同一业务的多个页面或组件共享：新建业务 Store；
3. 需要刷新后保留：在对应业务 Store 中配置持久化；
4. 全应用通用运行状态和应用设置：放在 `src/stores/app.ts`；
5. 登录态和用户信息：放在 `src/stores/user.ts`。

具体要求：

- 统一使用 Setup Store 写法；
- 页面专属状态不得为了省事全部塞入 `app.ts`，应创建独立的 feature store；
- 共享业务状态通过 Store action 修改，不让页面到处直接拼装同一业务规则；
- 持久化必须设置唯一、稳定的 key，并用 `pick` 只保存必要字段；
- 新增持久化字段时考虑旧数据兼容、默认值和退出登录后的清理策略；
- 除明确设计的登录态外，不持久化敏感数据、临时响应和可重新计算的数据；
- `piniaPluginPersistedstate` 已在 `src/main.ts` 注册，不要重复注册。

## 10. 接口与请求规范

### 10.1 分层

```text
页面 / Store
    ↓
src/api/* 业务接口与数据映射
    ↓
src/core/request 双端请求适配
    ├── 浏览器：Axios
    └── 敏行宿主：MXCommon.ajax / NXCommon.ajax
```

- 页面不得直接调用 Axios 或 `MXCommon.ajax`，统一调用 `src/api` 暴露的业务函数。
- 业务 API 必须从 `@/core/request` 引入 `request`，确保浏览器和敏行宿主行为一致。
- GET 查询参数放 `params`，POST/PUT 请求体放 `data`。
- 登录、刷新 Token 等无需鉴权的接口才允许设置 `skipAuth: true`。
- 敏行原生请求仅支持 GET、POST、PUT、DELETE；新增 PATCH 接口前必须先确认宿主兼容方案。
- TAM 交易统一复用 `buildTamRequestPayload` 和 `tamRequest`，交易码定义为带业务说明的常量。

示例：

```ts
import { request } from '@/core/request'

export type ExampleQuery = {
  date: string
}

export type ExampleResult = {
  amount: string
}

export function queryExample(params: ExampleQuery) {
  return request<ExampleResult>({
    url: '/example',
    method: 'GET',
    params,
  })
}
```

### 10.2 响应与错误处理

- Axios 请求层统一处理进度条、鉴权头、Token 刷新、HTTP 错误和通用业务 code。
- API 层负责兼容后端包装结构、字段别名和数据归一化，页面只消费稳定模型。
- 页面异步操作使用 `try/catch/finally`，并在 `finally` 中恢复 loading 状态。
- 用户提示统一使用 `showAppToast` / `showAppLoadingToast`，不直接散落 Vant Toast 配置。
- 不吞掉异常；无法在当前层恢复时继续抛出，让调用方决定页面提示和交互。
- 日志必须包含可定位的业务上下文，但不得输出密码、完整 Token、敏感账户信息或整段隐私报文。

## 11. 业务数据规范

### 11.1 金额

- 后端返回的人民币金额默认按“元”理解，视图主展示统一通过共享工具转换为“亿元”。
- 转换前先去除千分位逗号，使用 `src/utils/rmb-forecast.ts` 中的共享函数，避免页面各写一套算法。
- `0`、`0.00` 是有效业务值，不得用普通真值判断当作空数据。
- 只有产品需求明确要求时，才在“亿元”下补充完整“元”金额。
- 保留正负号、精度和异常值兜底，不直接在模板中执行 `Number(...).toFixed(...)`。

### 11.2 日期

- 页面日期控件使用 `YYYY-MM-DD`；TAM 等接口按约定转换为 `YYYYMMDD`。
- 系统工作日优先复用 `useAppStore()` 的 `systemWorkDate` / `systemWorkDateCompact`。
- 日期格式化集中到 API 或 utils 层，不在多个页面重复切片拼接。

### 11.3 富文本

- 使用 `v-html` 前必须经过白名单清洗，统一复用公告模块的 `sanitizeArticleHtml` 思路。
- 图片协议、Base64、链接协议和危险属性都必须显式校验。
- 富文本点击、图片预览等行为优先使用容器事件委托，避免给动态 HTML 注入脚本处理器。

## 12. 注释与日志规范

- 注释使用中文，重点解释“为什么”和业务约束，不复述代码字面含义。
- 交易码、字段匹配、跨接口关联、单位转换和兼容逻辑必须补充注释。
- 公共函数和复杂类型优先使用 JSDoc，说明参数、返回值、单位和异常情况。
- 临时调试日志在提交前删除；确需保留的联调日志使用稳定前缀，例如 `[人行头寸余额查询]`。
- 禁止在生产日志中打印认证凭证或完整敏感报文。

## 13. 格式化与质量检查

代码风格以仓库配置为准：

- 不使用分号；
- 字符串使用单引号；
- 单行宽度尽量不超过 100；
- 2 空格缩进；
- 文件末尾保留换行；
- 删除行尾空格。

注意：`npm run lint` 和 `npm run format` 都会直接修改文件。执行后必须重新检查 diff，避免把无关格式化混入当前任务。

项目当前没有统一的自动化测试脚本。功能改动至少应覆盖以下检查：

1. 检查修改文件的 ESLint / TypeScript 问题；
2. 在目标环境手动验证成功、空数据、错误和重复点击场景；
3. 涉及移动端布局时检查安全区、长文本、小屏和 750px 宽屏表现；
4. 涉及双端请求时分别考虑浏览器 Axios 与敏行宿主 AJAX 的差异；
5. 只有明确需要时再执行构建，并处理版本文件副作用。

## 14. Git 与发布规范

- 一次提交只处理一个明确目标，不夹带无关重构、格式化或生成文件。
- 提交信息遵循仓库现有 Conventional Commits 风格：

  ```text
  feat(position-filter): 持久化筛选区展开状态
  fix(announcement): 修复详情图片预览
  refactor(router): 调整业务页面目录
  chore(ci): 固定 npm 发布版本
  ```

- 常用类型：`feat`、`fix`、`refactor`、`docs`、`style`、`chore`。
- 不提交 `dist`、`.playwright-cli`、日志、缓存和 IDE 私有配置。
- 移动文件后必须检查 `src/router/index.ts` 和全仓旧 import，确保没有残留路径。
- 推送前检查 `git status` 和 `git diff`，保留用户已有且与任务无关的修改。
- `main` 分支每次 push 都会触发 `.github/workflows/publish.yml`：自动提升 npm patch 版本、发布 npm 包并回推版本提交与 tag。普通开发不得把“推送 main”当作无副作用操作。
- 发布流程使用 npm Trusted Publishing；不要在仓库中新增长期 npm Token。

## 15. 提交前检查清单

- [ ] 修改范围与需求一致，没有顺手改动无关功能。
- [ ] 页面、API、Store、工具函数放在正确目录。
- [ ] 新路由已配置唯一名称、标题、鉴权和正确的懒加载路径。
- [ ] 页面共享或持久化状态已放入独立、职责清晰的 Pinia Store。
- [ ] 请求通过 `src/api` 和 `@/core/request` 发出，没有绕过双端适配层。
- [ ] 入参、响应、Props、Emits 和 Store 状态具备 TypeScript 类型。
- [ ] 金额、日期、空值和 `0` 值按业务约定处理。
- [ ] 用户提示、loading、异常和重复提交状态完整。
- [ ] 移动端安全区、标题栏、Tabbar 和响应式宽度未被破坏。
- [ ] 没有敏感信息、临时日志、生成文件或无关格式化进入 diff。
- [ ] 如果执行过构建，已确认版本配置文件是否发生非预期变化。
- [ ] 已查看最终 `git status` 和 `git diff`。
