feat: 完成系统管理模块功能迭代
新增用户、菜单、日志、问题帮助等业务模块,优化角色权限分配功能,新增依赖包与全局组件
This commit is contained in:
1730
docs/功能测试流程文档.md
Normal file
1730
docs/功能测试流程文档.md
Normal file
File diff suppressed because it is too large
Load Diff
333
docs/表格组件使用说明.md
333
docs/表格组件使用说明.md
@@ -18,7 +18,9 @@
|
||||
8. [API 文件写法](#8-api-文件写法)
|
||||
9. [旧代码迁移对照](#9-旧代码迁移对照)
|
||||
10. [接口请求错误处理规范](#10-接口请求错误处理规范)
|
||||
11. [常见问题排查](#11-常见问题排查)
|
||||
11. [依赖安装规范](#11-依赖安装规范)
|
||||
12. [常见问题排查](#12-常见问题排查)
|
||||
13. [特殊弹出框组件规范](#13-特殊弹出框组件规范)
|
||||
|
||||
---
|
||||
|
||||
@@ -860,6 +862,125 @@ useTableButtons({
|
||||
|
||||
推荐使用公共 key `page.common.help`(模板中用 `$t(ckey('help'))`)。不传 `help-url` 则不显示。
|
||||
|
||||
### 场景 8:折叠式搜索区(搜索条件过多时)
|
||||
|
||||
当搜索条件超过一行时,用 `v-show` + `searchExpanded` 控制额外条件的显隐,避免 header 区域过长。
|
||||
|
||||
**数据定义(`data()` 中)**:
|
||||
|
||||
```js
|
||||
data () {
|
||||
return {
|
||||
searchExpanded: false, // 控制展开/收起
|
||||
// ...其他数据
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**模板结构**:
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" ref="searchFormRef" size="mini" @submit.native.prevent>
|
||||
<!-- 常用条件:始终可见 -->
|
||||
<el-form-item :label="$t(key('ip'))">
|
||||
<el-input v-model="search.ip" :placeholder="$t(key('placeholder_ip'))"
|
||||
clearable style="width:160px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('status'))">
|
||||
<el-select v-model="search.status" :placeholder="$t(key('placeholder_status'))"
|
||||
clearable style="width:120px">
|
||||
<el-option :value="200" :label="$t(key('success'))" />
|
||||
<el-option :value="4001" :label="$t(key('failure'))" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<!-- 搜索 + 重置 + 展开/收起按钮 -->
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="onSearch">
|
||||
{{ $t(key('search')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="onReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="!searchExpanded"
|
||||
type="text"
|
||||
icon="el-icon-arrow-down"
|
||||
@click="searchExpanded = true"
|
||||
>
|
||||
{{ $t(key('expand')) }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else
|
||||
type="text"
|
||||
icon="el-icon-arrow-up"
|
||||
@click="searchExpanded = false"
|
||||
>
|
||||
{{ $t(key('collapse')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 扩展条件:v-show 控制显隐 -->
|
||||
<div v-show="searchExpanded" class="search-bar__extra">
|
||||
<el-form-item :label="$t(key('tray_number'))">
|
||||
<el-input v-model="search.tray" :placeholder="$t(key('placeholder_tray_no'))"
|
||||
clearable style="width:160px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('process_code'))">
|
||||
<el-input v-model="search.process_code"
|
||||
:placeholder="$t(key('placeholder_process_code'))"
|
||||
clearable style="width:160px" @keyup.enter.native="onSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('create_time'))">
|
||||
<el-date-picker v-model="search.time" type="datetimerange"
|
||||
:placeholder="$t(key('placeholder_create_time'))"
|
||||
range-separator="-" start-placeholder="" end-placeholder=""
|
||||
value-format="yyyy-MM-dd HH:mm:ss" style="width:340px" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
<!-- ... -->
|
||||
</d2-container>
|
||||
</template>
|
||||
```
|
||||
|
||||
**样式**:
|
||||
|
||||
```css
|
||||
.search-bar {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.search-bar .el-form-item--mini.el-form-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.search-bar__extra {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
}
|
||||
```
|
||||
|
||||
**i18n 附加 key**:
|
||||
|
||||
| key | 中文 | English |
|
||||
|-----|------|---------|
|
||||
| `expand` | 展开更多 | Expand |
|
||||
| `collapse` | 收起 | Collapse |
|
||||
|
||||
**设计要点**:
|
||||
|
||||
1. 常用高频筛选条件放在第一行始终可见(如 IP、状态)
|
||||
2. 低频条件用 `v-show="searchExpanded"` 包裹,默认隐藏
|
||||
3. 展开/收起按钮放在操作按钮组同一行,使用 `type="text"` + 箭头图标
|
||||
4. 折叠时重置不展开,重置只清空 `search` 数据,不改变 `searchExpanded` 状态
|
||||
5. 适用于只读类页面(如日志查看),无需选中框和批量操作
|
||||
|
||||
**完整参考**:[接口日志页](file:///d:/code/mes/mes-ui/src/views/system-administration/system-utilities/api-logs/index.vue)
|
||||
|
||||
---
|
||||
|
||||
## 7. 路由配置
|
||||
@@ -1172,7 +1293,37 @@ this.fetchData()
|
||||
|
||||
---
|
||||
|
||||
## 11. 常见问题排查
|
||||
## 11. 依赖安装规范
|
||||
|
||||
### 包管理器
|
||||
|
||||
本项目使用 **pnpm** 作为包管理器,**禁止使用 npm 或 yarn**。
|
||||
|
||||
### 安装新依赖
|
||||
|
||||
当页面需要额外的第三方库时(如 `mavon-editor`、`vue-json-tree-view` 等),使用:
|
||||
|
||||
```bash
|
||||
pnpm add <package-name>
|
||||
```
|
||||
|
||||
### 常见页面依赖速查
|
||||
|
||||
| 依赖包 | 用途 | 安装命令 |
|
||||
|--------|------|---------|
|
||||
| `mavon-editor` | Markdown 编辑器(问题帮助、文档编辑) | `pnpm add mavon-editor` |
|
||||
| `vue-json-tree-view` | JSON 树形展示(日志查看响应) | 已预装 |
|
||||
| `marked` + `highlight.js` | Markdown 渲染(d2-markdown 组件依赖) | 已预装 |
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. 安装前检查 `package.json` 是否已有该依赖(`vue-json-tree-view`、`marked` 等项目已预装)
|
||||
2. 安装后确认版本兼容性,特别是 Vue 2.x 项目不要安装仅支持 Vue 3 的包
|
||||
3. 若因网络问题安装失败,检查 registry 配置(本项目使用 `npmmirror.com` 镜像)
|
||||
|
||||
---
|
||||
|
||||
## 12. 常见问题排查
|
||||
|
||||
### Q1:弹框打开后不显示内容?
|
||||
|
||||
@@ -1197,3 +1348,181 @@ this.fetchData()
|
||||
### Q6:表单验证错误提示显示为原始 i18n key?
|
||||
|
||||
`page-dialog-form` 会通过 `translatedRules` 计算属性自动翻译验证规则的 `message` 字段。确认传入的 `rules.message` 使用了 `this.key()` 传入完整 key。
|
||||
|
||||
---
|
||||
|
||||
## 13. 特殊弹出框组件规范
|
||||
|
||||
### 适用范围
|
||||
|
||||
当页面需要**超出 `page-dialog-form` 能力的弹出框**时(如权限分配树、多步骤向导、ifream 嵌入、复杂联动表单等),**禁止在 `index.vue` 中直接堆砌内联代码**,必须抽离为独立组件。
|
||||
|
||||
### 目录结构标准
|
||||
|
||||
```
|
||||
src/views/{模块}/{功能}/
|
||||
├── index.vue ← 主页面,只负责引入和组装
|
||||
└── components/ ← 所有额外弹框统一放这里
|
||||
├── PermDrawer/ ← 示例:权限分配抽屉
|
||||
│ └── index.vue
|
||||
├── ImportDialog/ ← 示例:批量导入
|
||||
│ └── index.vue
|
||||
└── DetailDrawer/ ← 示例:详情抽屉
|
||||
└── index.vue
|
||||
```
|
||||
|
||||
### 命名规范
|
||||
|
||||
| 规则 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 组件文件夹 | **英文 PascalCase**,描述功能 | `PermDrawer`(权限抽屉)、`ImportDialog`(导入弹框)、`DetailDrawer`(详情抽屉) |
|
||||
| 组件入口文件 | 统一 `index.vue` | `PermDrawer/index.vue` |
|
||||
| 组件注册名 | 英文 PascalCase 或 kebab-case 前缀 | `RolePermDrawer`、`role-perm-drawer` |
|
||||
| 禁止 | 中文名、拼音、模糊命名 | ❌ `权限分配/`、❌ `quanxian/`、❌ `Popup/` |
|
||||
|
||||
### 主页面用法(`index.vue`)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<d2-container>
|
||||
<!-- 搜索区 -->
|
||||
<template #header>...</template>
|
||||
|
||||
<!-- 标准 CRUD 表格 -->
|
||||
<page-table ... />
|
||||
|
||||
<!-- 标准新增/编辑弹框 -->
|
||||
<page-dialog-form ... />
|
||||
|
||||
<!-- 特殊弹框:仅引用 + 传 props,简洁清晰 -->
|
||||
<role-perm-drawer
|
||||
:visible.sync="permVisible"
|
||||
:role="permRole"
|
||||
:title="key('assign_permissions')"
|
||||
:confirm-text="key('confirm')"
|
||||
:cancel-text="key('cancel')"
|
||||
@saved="fetchData"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RolePermDrawer from './components/PermDrawer/index.vue'
|
||||
|
||||
export default {
|
||||
components: { ..., RolePermDrawer },
|
||||
data () {
|
||||
return {
|
||||
permVisible: false,
|
||||
permRole: {} // 只存当前操作行,其余逻辑全在子组件
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openPermDialog (row) {
|
||||
this.permRole = row
|
||||
this.permVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 子组件模板(`components/PermDrawer/index.vue`)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-drawer
|
||||
:visible.sync="visibleProxy"
|
||||
:title="$t(title)"
|
||||
:size="width"
|
||||
:close-on-click-modal="false"
|
||||
direction="rtl"
|
||||
@close="onClose"
|
||||
>
|
||||
<div v-loading="loading">
|
||||
<!-- 业务内容,如 el-tree、el-transfer 等 -->
|
||||
</div>
|
||||
|
||||
<div class="my-drawer__footer">
|
||||
<el-button size="mini" @click="onCancel">{{ $t(cancelText) }}</el-button>
|
||||
<el-button type="primary" size="mini" :loading="submitting" @click="onSubmit">
|
||||
{{ $t(confirmText) }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
|
||||
export default {
|
||||
name: 'RolePermDrawer',
|
||||
mixins: [i18nMixin('page.xxx.xxx.xxx')],
|
||||
props: {
|
||||
visible: Boolean, // 用 .sync 双向绑定
|
||||
title: String, // 弹框标题(i18n key)
|
||||
confirmText: String, // 确定按钮文本(i18n key)
|
||||
cancelText: String, // 取消按钮文本(i18n key)
|
||||
width: { type: String, default: '360px' }
|
||||
// 业务 props 按需添加,如 role、data 等
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (val) {
|
||||
if (val) { this.init() } // 打开时加载数据
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init () { /* 加载数据 */ },
|
||||
onSubmit () {
|
||||
this.submitting = true
|
||||
try {
|
||||
// 提交后
|
||||
this.$emit('saved') // 通知父组件刷新
|
||||
this.visibleProxy = false
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
},
|
||||
onCancel () { this.visibleProxy = false },
|
||||
onClose () { /* 清理状态 */ }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 组件通信约定
|
||||
|
||||
| 方向 | 方式 | 说明 |
|
||||
|------|------|------|
|
||||
| 父 → 子 | `props` | 传递 `visible`(sync)、业务数据对象、i18n key |
|
||||
| 子 → 父 | `$emit` | `@saved` 通知父组件刷新表格,`@closed` 通知关闭完成 |
|
||||
| 避免 | `$parent` / `$refs` | 禁止子组件通过 `$refs.parent.xxx` 访问父组件方法 |
|
||||
|
||||
### 判断标准:何时需要独立组件?
|
||||
|
||||
| 场景 | 使用方案 |
|
||||
|------|---------|
|
||||
| 新增/编辑表单(input + select + textarea) | `page-dialog-form` 即可 |
|
||||
| 权限分配树 | **独立组件** → `components/PermDrawer/` |
|
||||
| 批量导入/导出向导 | **独立组件** → `components/ImportDialog/` |
|
||||
| 关联数据选择器(多选表格) | **独立组件** → `components/SelectorDialog/` |
|
||||
| 详情查看(非编辑) | **独立组件** → `components/DetailDrawer/` |
|
||||
| 复杂多步骤流程 | **独立组件** → `components/FlowWizard/` |
|
||||
|
||||
### 实际案例
|
||||
|
||||
参考完整实现:
|
||||
- 权限分配抽屉:[`src/views/system-administration/user-management/role/components/PermDrawer/index.vue`](file:///d:/code/mes/mes-ui/src/views/system-administration/user-management/role/components/PermDrawer/index.vue)
|
||||
- 主页面引用方式:[`src/views/system-administration/user-management/role/index.vue`](file:///d:/code/mes/mes-ui/src/views/system-administration/user-management/role/index.vue#L79-L87)
|
||||
|
||||
@@ -15,6 +15,56 @@
|
||||
- 参考文档:[表格组件使用说明.md](file:///d:/code/mes/mes-ui/docs/表格组件使用说明.md)
|
||||
- 参考示例:`src/views/production-master-data/factory-model/factory-area/index.vue`
|
||||
|
||||
#### 1.1 特殊弹出框组件迁移(重要)
|
||||
|
||||
当旧页面中存在**超出 `page-dialog-form` 能力的弹出框**时(权限分配树、批量导入、关联选择器、详情查看、多步骤向导等),必须遵循以下流程:
|
||||
|
||||
##### 第一步:分析旧代码
|
||||
- **仔细阅读旧项目的弹出框代码**,理解其数据结构、交互逻辑、API 调用
|
||||
- 关注旧代码的数据来源路径(如 `row.menu_admin` 直接从列表获取、还是额外调 API)
|
||||
- 记录旧的参数名、method 名,确保迁移时不遗漏
|
||||
|
||||
##### 第二步:提出优化方案
|
||||
迁移时**不能简单照搬旧代码**,必须结合新版能力提出优化:
|
||||
|
||||
| 旧写法 | 优化方向 |
|
||||
|--------|---------|
|
||||
| `el-dialog` 内联在页面中 | 抽离为独立组件 → `components/` |
|
||||
| `sct-base-dialog` + 内联表单 | 独立组件 + `el-drawer`(抽屉式体验更佳) |
|
||||
| 额外调 API 获取数据(如行数据已包含) | 直接从 `row.xxx` 取值,减少请求 |
|
||||
| `setTimeout` 硬编码延迟 | 提出来讨论,结合 `v-if` + `$nextTick` 优化 |
|
||||
| `$parent` / `$refs` 跨组件通信 | 改为 `props` + `$emit` |
|
||||
|
||||
示例:角色权限分配迁移优化方案
|
||||
|
||||
```
|
||||
旧方案 优化方案
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
el-dialog 内联 42 行代码 → 独立 PermDrawer 组件(components/PermDrawer/index.vue)
|
||||
getRoleMenu API 额外请求 → 直接从 row.menu_admin JSON.parse 获取(列表自带)
|
||||
setTimeout 1000ms 关闭 → 保留(树渲染需要等待),但抽取到子组件内部
|
||||
索引页 data 6 个 perm 字段 → 仅 2 个(permVisible + permRole)
|
||||
```
|
||||
|
||||
##### 第三步:按标准实现
|
||||
参考 [表格组件使用说明.md 第 13 节](file:///d:/code/mes/mes-ui/docs/表格组件使用说明.md#13-特殊弹出框组件规范),严格遵循:
|
||||
|
||||
| 规则 | 说明 |
|
||||
|------|------|
|
||||
| 目录位置 | `src/views/{模块}/{功能}/components/{英文PascalCase}/index.vue` |
|
||||
| 文件夹命名 | 英文 PascalCase,描述功能(如 `PermDrawer`、`ImportDialog`) |
|
||||
| 组件通信 | 父→子 `props`(含 `.sync`),子→父 `$emit('saved')`,禁止 `$parent` / `$refs` |
|
||||
| 主页面数据 | 只存 `visible` Boolean + 业务对象,其余状态在子组件内部 |
|
||||
|
||||
##### 第四步:完整案例
|
||||
|
||||
参考角色权限分配抽屉的完整迁移:
|
||||
- 旧代码:`D:\code\company\SCTMES_MES_V5\vue-app\src\views\system_settings\user_management\role\components\PageMain\index.vue`(`dialogVisibleGive` + `el-tree`)
|
||||
- 新代码:[`src/views/system-administration/user-management/role/components/PermDrawer/index.vue`](file:///d:/code/mes/mes-ui/src/views/system-administration/user-management/role/components/PermDrawer/index.vue)
|
||||
- 主页面引用:[`src/views/system-administration/user-management/role/index.vue`](file:///d:/code/mes/mes-ui/src/views/system-administration/user-management/role/index.vue#L79-L87)
|
||||
|
||||
---
|
||||
|
||||
#### 2. i18n 国际化(重要)
|
||||
- 使用 `mixins: [i18nMixin('完整i18n前缀')]`,不要在每个页面手动定义 `T` 常量和 `tkey()` 方法
|
||||
- `data()` 中用 `this.key('xxx')` 传完整 i18n key,**不要用 `k()` 提前翻译**(翻译由 page-table / page-dialog-form 内部处理,切换语言自动响应)
|
||||
@@ -64,6 +114,7 @@
|
||||
| `page.system_settings.menu_configuration.menu` | `page.system_administration.menu_management.menu_configuration` |
|
||||
| `page.system_settings.system_assistant.operate_log` | `page.system_administration.system_utilities.operation_logs` |
|
||||
| `page.system_settings.system_assistant.api_log` | `page.system_administration.system_utilities.api_logs` |
|
||||
| `page.system_settings.system_assistant.problem_help` | `page.system_administration.system_utilities.problem_help` |
|
||||
| `page.system_settings.system_monitoring.system.login` | `page.system_administration.system_monitoring.login` |
|
||||
| `page.production_configuration.matetial_model.*` | `page.production_master_data.material_model.*` |
|
||||
| `page.planning_production.production_batch_management.batch` | `page.planning_production.batch_management.batch_list` |
|
||||
@@ -90,6 +141,16 @@
|
||||
- **Columns**:`useTableColumns([...])`
|
||||
- **Methods**:`fetchData / onSearch / onReset / onPageChange / openAdd / openEdit / onDialogSubmit / handleDelete`
|
||||
- **i18n key 模板**(每个页面至少要有这些):`search / reset / add / edit / delete / operation / add_title / edit_title / code / name / remark / enter_code / enter_name / remark_length / operation_success / confirm / cancel / tip / confirm_delete`
|
||||
- **搜索条件过多时**参考 [表格组件使用说明.md - 场景 8](file:///d:/code/mes/mes-ui/docs/表格组件使用说明.md#场景-8折叠式搜索区搜索条件过多时),使用 `searchExpanded` + `v-show` 实现折叠式搜索区,需额外添加 i18n key:`expand`(展开更多/Expand)、`collapse`(收起/Collapse)
|
||||
|
||||
#### 6.1 依赖安装
|
||||
|
||||
当迁移涉及新的第三方库时:
|
||||
|
||||
1. **包管理器**:本项目使用 **pnpm**,安装命令为 `pnpm add <package-name>`
|
||||
2. **安装前检查**:先在 `package.json` 中确认依赖是否已存在,避免重复安装
|
||||
3. **版本兼容**:确保安装的包支持 Vue 2.x(本项目为 Vue 2.7)
|
||||
4. **参考标准**:[表格组件使用说明.md - 第 11 节](file:///d:/code/mes/mes-ui/docs/表格组件使用说明.md#11-依赖安装规范)
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user