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

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)