front/docs/前端页面设计规范.md
zzh 88909258cc
All checks were successful
Build and Deploy Front / deploy (push) Successful in 17m52s
feat: 客户门户可折叠侧栏导航,去掉页签并区分打印入口提示
运营端顶栏布局不变;客户侧栏按常用/仓储/更多分组,余额与打印沉底,打印提示按门户区分。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-24 18:51:35 +08:00

693 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 中后台前端页面设计规范
> 基于 itfx-front 项目现有技术栈与实现,整理的一套通用、可直接落地的前端设计规范。
> 后续开发须遵循本规范,确保视觉与交互一致性。
---
## 一、技术栈约定
| 类别 | 技术选型 | 版本/说明 |
|------|----------|-----------|
| 框架 | Vue 3 | 使用 Composition API、`<script setup>` |
| UI 库 | Arco Design Vue | 主 UI 框架,优先使用 |
| 辅助 UI | Element Plus | 仅在特殊场景使用 |
| 表格 | vxe-table / Handsontable | 见「表格」章节 |
| 构建 | Vite | 6.x |
| 状态 | Pinia | |
| 国际化 | vue-i18n | |
| 样式 | Less + UnoCSS + 全局 CSS 变量 | |
| 路由 | vue-router | 4.x |
---
## 二、布局规范
### 2.1 整体结构
```
a-layout (垂直)
├── TopNav(仅运营端,fixed)
├── a-layout-content
│ ├── 运营端:paddingTop = 顶栏 + Tab;PageLayout
│ └── 客户门户:CustomerSideRail(全高)+ PageLayout(无顶栏、无多页签)
└── Footer(可选)
```
运营端布局 DOM/样式与改前一致;客户门户不挂载 TopNav / TabBar。
**约定:**
- 主内容背景色:`#f0f3f4`
- 页面容器使用 `[class$='-container']`,统一 `padding: 16px`
- 内容区域使用 `overflow: hidden`、`height: 100%` 保证高度正确
**客户门户 vs 运营端导航(差异约定):**
| | 运营端 | 客户门户 |
|---|---|---|
| 顶栏 | 一级模块 + hover 二级 + 右侧余额/打印/用户 | **无顶栏** |
| 页签 | TabBar 多页签 | **无多页签**(当前页仅由侧栏高亮表达) |
| 侧栏 | 无全局侧栏 | **全高可折叠左栏**(默认展开):顶 Logo;中按「常用 / 仓储 / 更多」浅分组;**资产与账户收进用户下拉**;底沉余额/打印(含下载)/消息/仓库;**语言收进用户菜单** |
| 高亮 | 路由最长路径匹配 | 侧栏最长路径匹配;账户类路由高亮底栏用户区 |
| 数据 | RBAC 菜单树原样 | 仍用同一菜单树,`flatMap` 二级即可,不改表结构 |
### 2.2 页面内容结构
单页内容推荐结构:
```html
<div class="xxx-container" style="background-color: #fff;">
<!-- 1. 页面头部操作区(可选) -->
<div class="page-header">
<a-space>...</a-space>
<a-space>...</a-space>
</div>
<!-- 2. 表格工具栏 -->
<div class="table-toolbar">
<div class="toolbar-left">主操作、批量操作</div>
<div class="toolbar-right">分页控制等</div>
</div>
<!-- 3. 表格/表单内容 -->
...
</div>
```
### 2.3 工具栏布局
- 使用 `table-toolbar` 作为表格上方工具栏
- 左侧 `toolbar-left`:新增、删除、导出等主操作
- 右侧 `toolbar-right`:分页、筛选等
- **分页位置(必选)**:所有列表的分页组件统一放在**紧邻列表上方**工具栏的**右侧**(即相对列表区域的**右上角**),见「七、7.3 分页」;不得放在表格下方或列表底部
- 使用 `a-space :size="8"` 或 `display: flex; gap: 8px` 控制按钮间距
### 2.4 响应式断点(Less)
```less
@screen-xs: 480px; // 手机
@screen-sm: 576px; // 平板
@screen-md: 768px; // 小屏
@screen-lg: 992px; // 桌面
@screen-xl: 1200px; // 大屏
@screen-xxl: 1600px; // 超大屏
```
### 2.5 页面壳、白卡片与列表区尺寸(列表 / Handsontable 类页面)
以下约定与 `default-layout` + `page-layout`、`*-container` / `*-page` 等实现一致,避免「灰底大块留白、列表高度塌缩、内外边距叠成 32px/40px」等问题。
#### 2.5.1 页面层(灰底壳)
| 项目 | 约定 |
|------|------|
| 背景色 | `#f0f3f4`(与布局主内容区一致) |
| 页面根容器 padding | **`16px 16px 24px`**:上/左/右 **16px**,**底 24px** —— 与视口底边留白,列表区随 flex 撑满后,整块内容与屏幕底边保持 **24px** 间距 |
| 参与主内容区高度 | `flex: 1; min-height: 0; display: flex; flex-direction: column;`,与 `PageLayout` 子节点 flex 链衔接 |
| 纵向滚动 | 页面根容器宜 `overflow-y: hidden`(或仅最外层允许滚动),**表格/Handsontable 在内部 `.list-wrap` / `.wtHolder` 滚动**,避免整页与表格双滚动条 |
#### 2.5.2 白卡片(白色内容块)
| 项目 | 约定 |
|------|------|
| 视觉 | 背景 `#fff`,`border-radius: 8px`,`box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08)`(可按页面微调透明度) |
| 撑满剩余高度 | `flex: 1; min-height: 0; display: flex; flex-direction: column; overflow: hidden` |
| 内边距(与列表区配合) | **二选一,避免与 `.list-wrap` 重复叠加**:<br>• **推荐**:白卡片 **`padding: 0`**,由 **页头/搜索行、`.table-toolbar`、`.list-wrap`** 分区控制留白(见下);<br>• **简化页**:白卡片统一 `padding: 16px`,则内部列表区不再重复加左右底 padding |
#### 2.5.3 表格工具栏(`.table-toolbar`)
| 项目 | 约定 |
|------|------|
| 与列表横向对齐 | `padding: 16px 16px 0`(上 16、左右 16,底由与列表的间距承担) |
| 与分页/标题间距 | `margin-bottom: 16px`(分页组件(`PaginationPro`)与列表区 `.list-wrap` 的固定间距) |
| 收缩 | `flex-shrink: 0` |
分页组件放置约束:`PaginationPro` 放在 `.toolbar-right` 内,并与 `toolbar-left` 保持在同一行(同一个 `flex` 行中)展示;工具栏外层使用 `padding: 16px 16px 0` 以保证分页组件距上边/右边均为 16px,且不需要在 `PaginationPro` 外再额外套一层 padding。
有**独立搜索行**(如轨迹配置页)时:搜索框可用 `margin: 16px 16px 16px`,与工具栏、列表区 **左右 16px** 对齐。
#### 2.5.4 列表区(`.list-wrap`)
用于包裹 **HandsontablePro** 或需 **固定测量高度** 的表格容器(配合 `useTableContainerHeight` 的 `tableContainerRef` **挂在本层**)。
| 项目 | 约定 |
|------|------|
| 与内容区四边距离 | **默认**:`padding: 16px`(上、右、下、左均为 **16px**,相对**白卡片内容边缘**或分区后的可用区域,勿与白卡片 padding 重复叠加)。<br>**与 `.list-toolbar` 联用**:当 `.list-toolbar` 使用 `margin-bottom: 16px` 作为“分页组件到列表”的固定间距时,为避免重复留白,建议 `.list-wrap` 使用 `padding: 0 16px 16px`(上 0、左右/下 16)。 |
| 布局 | `flex: 1; min-height: 0; width: 100%; box-sizing: border-box; overflow: hidden; display: flex; flex-direction: column` |
| 触摸/滚动冒泡 | 可选:`overscroll-behavior: contain; touch-action: pan-x pan-y` |
| 子级 Handsontable | 建议 `:deep(.handsontable-pro-container) { flex: 1; min-height: 0; display: flex; flex-direction: column; }`,保证纵向撑满 |
分页组件同一行约束(列表工具栏场景):当页面存在 `.list-toolbar` / `table-toolbar` 时,`PaginationPro` 必须放在右侧同一行的工具栏区域(如 `.toolbar-right`),不要将分页组件单独再做成一行(避免“工具栏一行 + 分页下一行”的不一致布局)。
#### 2.5.5 与布局组件的关系
- `PageLayout` 下直接子节点(路由页根)需能被主内容区赋予高度:项目中通过 `flex: 1; min-height: 0` 等与 `a-layout-content` 链式配合。
- `default-layout` 中对 `[class$='-container']` 的样式仅作用于布局模板内节点;**业务页根类名**(如 `xxx-page`、`track-order-container`)需在**页面自身样式**中按上表补齐 flex 与 padding。
---
## 三、色彩规范
### 3.1 主题色(CSS 变量)
通过 `:root` 统一管理,运行时由 `themeColor` 工具更新:
```css
:root {
--app-primary-color: #1d7281;
--app-primary-color-hover: #186170;
--app-primary-color-light: #e8f3f5;
--app-navbar-bg: #1d7281;
--app-sidebar-bg: #1d7281;
--app-button-gap: 8px; /* 相邻按钮间距 */
}
```
**约定:**
- 主色:`var(--app-primary-color)` 或 `#1d7281`
- Hover 色:`var(--app-primary-color-hover)`
- 浅底(选中背景):`var(--app-primary-color-light)`
- 页面背景:`var(--color-bg-1)`(Arco)
- 内容区背景:`#fff` 或 `#f0f3f4`
### 3.2 组件配色
- 主按钮、Switch、Tabs 指示线、菜单选中:使用主题色
- 日期选择器、Input focus:`border-color: var(--app-primary-color)`
- 链接激活态:`color: var(--app-primary-color)`
---
## 四、字体规范
### 4.1 字体栈
```css
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Microsoft YaHei", Inter, Roboto, "Helvetica Neue", Arial, sans-serif;
```
代码字体:
```css
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono",
"Courier New", monospace;
```
### 4.2 字号与行高
- 全局基准字号:`14px`
- 表单、按钮、输入框:`14px`
- 日期选择器、部分紧凑区域:`12px`(如 `.notice` 内)
- 菜单文字:`12px`,`font-weight: bold`
---
## 五、组件规范
### 5.1 按钮
- 高度:`32px`,`border-radius: 4px`(抽屉/卡片内软操作可用 `12px`,与亚克力面板一致)
- 图标按钮:`32px × 32px` 正方形
- **相邻按钮间距**:统一 `8px`
- 使用 `a-space :size="8"` 或 `gap: 8px` / `gap: var(--app-button-gap)`
- 适用于工具栏、表格操作列、弹窗底部按钮组
- **推荐用法**
- 主操作:`type="primary"`,或工具栏新增用 `app-btn-primary-soft`
- 查询:`app-btn-search`
- 次要/取消:默认 `a-button`(无 type)或抽屉内与面板同风格的软按钮
- 表格行内操作:优先图标按钮 / `type="text"`(见操作列规范)
- **禁止**:Arco `type="outline"` 默认描边按钮(灰硬边框、白底,与主题亚克力/软主色体系完全不搭)。新页面与改版时一律不要使用;次要操作改用上文推荐样式。
### 5.2 输入类(Input / Select / Picker)
- 统一高度:`32px`(Input)、`28px`(Picker)
- `border-radius: 4px`
- Focus / Hover 使用主题色 border 与 shadow
### 5.3 卡片 / 弹窗 / 下拉
- `border-radius: 8px`(Card、Modal、Popover、Dropdown、Drawer、Table、Tag、Alert、Message、Notification)
### 5.4 Pro 系列组件
| 组件 | 用途 |
|------|------|
| FormPro | 配置驱动表单,itemList + comp + compProps |
| TablePro | VxeGrid 封装,tableConfig + api + formConfig |
| CustomList | 自定义列 + TablePro,支持列管理 |
| HandsontablePro | 类 Excel 表格 |
| ModalPro | 弹窗,配合 useModal |
| DrawerPro | 抽屉 |
| SelectPro | 增强选择器 |
| JsonEditorPro / JsonDiffPro | JSON 编辑与对比 |
| CodeMirror | 代码编辑 |
**约定:**
- 增强型封装统一使用 `Pro` 后缀
- 优先使用 Pro 组件,减少重复造轮子
### 5.5 消息与确认
- 使用 `$baseMessage`、`$baseConfirm`(如有)
- 或 Arco 的 `Message`、`Modal.confirm`、`a-popconfirm`
---
## 六、表单规范
### 6.1 布局
- 表单类型:`horizontal`、`vertical`、`inline`
- 标签列:`label-col-props="{ span: 6 }"`(或 5)
- 内容列:`wrapper-col-props="{ span: 16 }"`(或 19)
- FormPro:`labelColProps: { span: 5 }`、`wrapperColProps: { span: 19 }`、`rowGutter: 20`、`defaultSpan: 6`
### 6.2 FormPro 配置示例
```ts
itemList: [
{
comp: 'AInput',
field: 'name',
label: '名称',
required: true,
span: 6,
rules: [{ required: true, message: '请输入名称' }],
compProps: { placeholder: '请输入' },
},
{
comp: 'ASelect',
field: 'status',
label: '状态',
compProps: { options: [...], placeholder: '请选择' },
},
]
```
### 6.3 校验
- 使用 Arco Form 的 `rules`
- 提交前调用 `formRef.validate()` 统一校验
- 必填项使用 `required: true` 并配置 `message`
---
## 七、表格规范
### 7.1 组件选择
| 场景 | 组件 | 说明 |
|------|------|------|
| 常规 CRUD 列表 | TablePro | 带搜索表单、分页、API 集成 |
| 自定义列 + 列管理 | CustomList | 包裹 TablePro |
| 类 Excel 编辑 | HandsontablePro | 单元格编辑、复制粘贴 |
| 简单列表(角色、菜单等) | a-table | Arco 原生 |
### 7.2 TablePro 配置
- `tableConfig`:columns、toolbarConfig 等
- `api`:分页接口,参数含 `pageIndex`、`pageSize`、表单值
- `formConfig`:搜索区域
- `columnConfig.useKey: true`、`resizable: true`
- `showOverflow: 'tooltip'`
### 7.3 分页
- **位置(必选)**:所有分页组件(`PaginationPro`、Arco `a-pagination`、项目内其它分页封装等)须置于**列表的右上角**——即放在**紧邻列表上方**的工具栏**右侧**(`table-toolbar` / `.list-toolbar` 的 `.toolbar-right`,与 `toolbar-left` 同一行),与列表左右对齐、视觉上位于列表区域上方靠右。禁止将分页放在表格**下方**、列表**底部**、或单独居中另起一行(与「二、2.5.3」「2.5.4」工具栏约定一致)。
- 可选 pageSize:`10 / 30 / 50 / 100`(按需裁剪)
- 默认 pageSize:`50`
- 接口参数:`pageIndex`、`pageSize`;返回:`totalCount`、`items` 或 `data.list`
### 7.4 操作列
- 编辑、删除、分配等使用 `a-button type="text" size="small"`
- 删除使用 `a-popconfirm`
- 操作按钮需权限控制(见「权限」)
### 7.5 HandsontablePro 列定义(必读)
**HandsontablePro 的 `columns` 与原生 Handsontable 不同**,必须使用项目约定的 `ColumnDef` 结构:
| 字段 | 类型 | 说明 |
|------|------|------|
| `key` | `string` | 对应行数据字段名(如 `jobId`、`statusLabel`) |
| `label` | `string` | 表头文案(支持 `t('...')`) |
| `width` | `number` | 列宽(像素) |
| `headerMode` | `'inline' \| 'icon' \| 'none'` | 混合表头:内嵌搜索 / 仅图标 / 纯展示;未设时由 `searchable` + `filterType` 推导 |
| `searchable` / `searchField` / `multiValue` | — | 表头搜索;`multiValue` 显示「多」批量入口 |
**正确示例:**
```ts
const hotColumns = computed(() => [
{ key: 'jobId', label: t('order.batchJobs.table.jobId'), width: 90 },
{ key: 'statusLabel', label: t('order.batchJobs.table.status'), width: 100 },
])
```
**错误示例(会导致表头、单元格均为空):**
```ts
// ❌ 勿用 Handsontable 原生 data/title
{ data: 'jobId', title: '任务 ID', width: 90 }
```
**操作列渲染器**签名固定为 `(rowData, rowIndex) => string`,勿写成 `(row, col, rowData)`。
**固定列序(组件内置,勿再传 `action-column-position` / `action-column-width`):**
| 顺序 | 列 | 说明 |
|------|----|------|
| 1 | 操作列(可选) | 有 `show-action-column` 时永远第一列;宽度按按钮内容自适应;开启 `stretch-columns` 时不被拉伸 |
| 2 | 序号列 | 永远紧随操作列(无操作时为第一列);固定宽度 `50px`;开启 stretch 时不被拉伸 |
| 3 | 复选框列(可选) | `show-checkbox-column`;固定 `60px`,stretch 时不被拉伸 |
| 4+ | 数据列 | `columns` 定义的业务列(stretch 时仅这些列被拉宽) |
`customCellRenderer(row, col, value, rowData)` 的 `col` 为**表格绝对列索引**(含操作/序号/复选框)。有操作列时数据列从 `col - 2` 起算(另有复选框再减 1)。
行数据字段名须与 `key` 一致;展示用衍生字段(如 `actionLabel`)在 `mapRow` 中提前组装。
---
## 八、交互规范
### 8.1 弹窗 / 抽屉
- 使用 `ModalPro` + `useModal()` 管理显隐与数据
- 标准流程:`openModal(true, data)` → 编辑 → `setLoading(true)` → 提交 → 成功 → `closeModal()`、刷新列表
- 弹窗宽度:常规 `600px`,复杂表单 `800px` 或更高
### 8.2 加载与空态
- 表格:`loading` 状态
- 弹窗提交:`confirm-loading`
- 空数据:使用 Arco `Empty` 或统一空态组件
### 8.3 按钮顺序
- 主操作(确定、保存)在右侧
- 取消 / 重置在左侧
- destructive 操作(删除)单独放置,并加确认
---
## 九、权限规范
### 9.1 权限格式
`Module.Resource.Action`,如:
- `Rbac.Roles.Create`
- `Rbac.Users.Update`
- `Api.Interfaces.Query`
- `Track.Orders.Query`
### 9.2 使用方式
**模板:**
```vue
<a-button
v-if="hasPermission('Rbac.Roles.Create')"
type="primary"
@click="handleAdd"
>
新增
</a-button>
```
**路由:**
```ts
{
path: '/system/roles',
meta: { permission: 'Rbac.Roles.Query' },
}
```
**工具函数:**
```ts
import { hasPermission, hasAnyPermission, hasAllPermissions, hasRole } from '@/utils/rbac-permission';
```
### 9.3 菜单
- 菜单基于 RBAC `menus` 与 `userMenuIdSet` 过滤
- 无权限的菜单项不展示
---
## 十、命名规范
### 10.1 文件与目录
| 类型 | 规范 | 示例 |
|------|------|------|
| 页面组件 | `index.vue` 置于功能目录下 | `views/api/upstreamInterface/index.vue` |
| 子组件 | PascalCase 或 kebab-case | `ServiceProviderInterfaceModal.vue`、`AccountList.vue` |
| 布局组件 | kebab-case | `top-nav`、`tab-bar`、`page-layout` |
| 共享组件 | PascalCase | `FormPro`、`TablePro`、`CustomList` |
| API 模块 | camelCase 或 PascalCase | `interface-serviceProvider`、`TrackOrder` |
| Store 模块 | kebab-case | `rbac-user`、`tab-bar`、`app` |
| 国际化 | `zh-CN.ts`、`en-US.ts` | `locale/zh-CN.ts` |
### 10.2 代码命名
| 类型 | 规范 |
|------|------|
| 变量 / 方法 | camelCase |
| 常量 | UPPER_SNAKE_CASE 或 camelCase |
| 类型 / 接口 | PascalCase |
| 组件名 | PascalCase |
### 10.3 路由
- path:camelCase,如 `/api/upstreamInterface`、`/track/kpiEarlyWarningConfig`
- name:PascalCase,如 `InterfaceDetailTest`
### 10.4 国际化 Key
- 格式:`模块.资源.键` 或 `common.xxx`
- 示例:`system.role.actions.add`、`common.search`、`common.reset`
---
## 十一、国际化规范
### 11.1 结构
- 根:`src/locale/zh-CN.ts`、`en-US.ts`
- 按页面拆分:`views/xxx/locale/zh-CN.ts`,在根 locale 中合并
### 11.2 使用
```vue
<template>{{ t('common.search') }}</template>
<script setup>
const { t } = useI18n();
</script>
```
### 11.3 必备 Key
- `common.search`、`common.reset`、`common.add`、`common.delete`、`common.editSuccess`、`common.addSuccess`、`common.saveSuccess`
- `common.selectPlaceholder`、`common.actions`、`common.viewDetail`
---
## 十二、API 与请求规范
### 12.1 分页接口
**请求:**
```ts
{
pageIndex: number;
pageSize: number;
// 其它搜索参数
}
```
**响应:**
```ts
{
totalCount: number;
items: T[]; // 或 data.list
}
```
### 12.2 错误处理
- 使用统一的 axios 拦截器(如 `rbac-error-handler`)
- 401 自动跳转登录;其它错误统一提示
---
## 十三、样式约定
### 13.1 作用域
- 组件内使用 `scoped`
- 覆盖第三方样式时使用 `:deep()` 或全局类
### 13.2 容器类
- 页面根容器:`xxx-container` / `xxx-page`(如 `role-page`、`track-order-container`),**列表类页面**推荐 `padding: 16px 16px 24px`,详见 **§2.5 页面壳、白卡片与列表区尺寸**
- 白卡片:`*-page-card`、`*-section` 等,与 `.list-wrap` 的 padding **勿重复叠加**
- 列表区:`.list-wrap`,**四边 16px**(相对分区后的内容边缘)
- 表格工具栏:`table-toolbar`、`toolbar-left`、`toolbar-right`,工具栏 **`padding: 16px 16px 0`** 与列表对齐
- Handsontable/列表分页工具栏:`.list-toolbar`(当页面没有标准 `table-toolbar` 时),与 `table-toolbar` 保持一致,使用 **`padding: 16px 16px 0`** 且 `margin-bottom: 16px`;`PaginationPro` 必须与 `toolbar-left` 同一行展示(放在 `.toolbar-right` 内,禁止分页另起行),同时 `.list-wrap` 在联用场景建议 `padding-top: 0`(例如 `padding: 0 16px 16px`),实现分页距上/右 16px、距列表 16px
### 13.3 按钮间距
- 相邻 `arco-btn` 之间:`margin-left: 8px`(全局已统一)
- 使用 `a-space :size="8"` 包裹一组按钮
---
## 十四、检查清单(新页面开发)
- [ ] 使用 `xxx-container` / `xxx-page` 或等效布局结构;列表页见 **§2.5**:灰底 **`padding: 16px 16px 24px`**、`flex: 1; min-height: 0`
- [ ] 白卡片与 **`.list-wrap`** 内边距勿重复叠加(推荐白卡片 `padding: 0` + 分区 padding)
- [ ] **`.list-wrap`**:`padding: 16px`、`flex: 1; min-height: 0`;需测高时 `ref` 挂在本层
- [ ] 工具栏使用 `table-toolbar`、`toolbar-left`、`toolbar-right`,工具栏 **`padding: 16px 16px 0`**
- [ ] 按钮、输入框符合全局样式(高度、圆角);未使用 `type="outline"` 描边按钮
- [ ] 使用主题色变量,不硬编码 `#1d7281`
- [ ] 弹窗 / 抽屉使用 ModalPro / DrawerPro + useModal
- [ ] 表单使用 FormPro 或统一 label/span
- [ ] 表格优先 TablePro / CustomList / HandsontablePro
- [ ] **HandsontablePro** 列定义使用 **`key` + `label`**,勿用 `data` + `title`(**§7.5**)
- [ ] 分页组件置于**列表上方工具栏右侧**(列表右上角),**§7.3**;分页 10/30/50/100,默认 50
- [ ] 操作按钮按权限使用 `hasPermission`
- [ ] 路由 meta 配置 `permission`
- [ ] **部署**:前端与 Admin API **分域名**(如 `ideship.com` / `api.ideship.com`);TLS 走 Traefik + cert-manager(**§十五**)
- [ ] 文案通过 i18n,不写死中文
- [ ] 命名符合本规范(文件、路由、变量、i18n key)
---
## 十五、部署与网关(Traefik / SPA)
生产已弃用站内 nginx。静态资源由前端容器(`serve -s`)提供,**HTTPS** 由 k3s Traefik `websecure` + cert-manager(Let's Encrypt)签发;流量路径:
`浏览器 → :443 Traefik(websecure) → Ingress(tls) → Service → 前端 Pod(:80)`
**约定:**
1. **前后端分域名**(方案 A):站点如 `ideship.com` / `test.ideship.com`,API 如 `api.ideship.com` / `api.test.ideship.com`。浏览器直连 API,前端容器不做 Admin 反代,也无 `/__cos__` 反代。
2. SPA 回退由 `serve -s`(或等价静态服务器)处理;刷新 `/order/batchJobs` 等路径应回 `index.html`,不依赖 nginx `try_files`。
3. 官网 **`/`** 为 Vue 路由(`src/views/site`);静态资源在 **`/site/`**;**`/9610.html`** 为 `public/9610.html`。客户登录 **`/login`**,管理端 **`/admin`**。
4. 面单/附件预览:开发可用 Vite `/__cos__`;生产优先 `file-storage/access-url` 新鲜签名直开,失败再 `stream` → blob(勿依赖过期库内签名 URL)。
5. 本地 `pnpm dev` 不受 Ingress 限制;`/` 即为官网。
---
## 附录:常用代码片段
### 页面头部 + 权限按钮
```vue
<div class="page-header">
<a-space>
<a-button
v-if="hasPermission('Module.Resource.Create')"
type="primary"
@click="handleAdd"
>
<template #icon><icon-plus /></template>
{{ t('common.add') }}
</a-button>
</a-space>
<a-space>
<a-button @click="loadData">
<template #icon><icon-refresh /></template>
</a-button>
</a-space>
</div>
```
### 表格操作列(编辑 / 删除)
```vue
<template #action="{ record }">
<div class="action-buttons">
<a-button
v-if="hasPermission('Module.Resource.Update')"
type="text"
size="small"
@click="handleEdit(record)"
>
<IconEdit />
</a-button>
<a-popconfirm
v-if="hasPermission('Module.Resource.Delete')"
:content="t('common.deleteConfirm')"
@ok="handleDelete(record.id)"
>
<a-button type="text" size="small">
<IconDelete />
</a-button>
</a-popconfirm>
</div>
</template>
```
### HandsontablePro 列表页(列 + 高度)
```vue
<script setup lang="ts">
import HandsontablePro from '@/components/HandsontablePro/index.vue'
import { useTableContainerHeight } from '@/composables/useTableContainerHeight'
const { tableContainerRef, tableHeight: handsontableHeight } = useTableContainerHeight()
const hotColumns = computed(() => [
{ key: 'jobId', label: t('module.table.jobId'), width: 90 },
{ key: 'statusLabel', label: t('module.table.status'), width: 100 },
])
</script>
<template>
<div class="module-page">
<div class="module-page-card">
<div class="table-toolbar">...</div>
<a-spin class="list-spin" :loading="listLoading">
<div ref="tableContainerRef" class="list-wrap">
<HandsontablePro
:columns="hotColumns"
:data="tableRows"
:height="handsontableHeight"
:stretch-columns="true"
/>
</div>
</a-spin>
</div>
</div>
</template>
```
布局样式见 **§2.5**(`flex: 1; min-height: 0`,勿给 `.list-wrap` 写死 `min-height: 480px` 导致列表区塌缩或撑破)。
### FormPro 查询表单
```vue
<FormPro
ref="formProRef"
:item-list="formItemList"
:button-config="{ showBtn: true }"
@submit="handleSearch"
@reset="handleReset"
/>
```
---
*文档版本:1.2 | 更新日期:2026-03-23*