feat: 完成系统管理模块功能迭代

新增用户、菜单、日志、问题帮助等业务模块,优化角色权限分配功能,新增依赖包与全局组件
This commit is contained in:
sheng
2026-05-29 18:12:54 +08:00
parent a61036e5dc
commit 20a821ba32
28 changed files with 5512 additions and 39984 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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-treeel-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)

View File

@@ -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-依赖安装规范)
---