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-依赖安装规范)
|
||||
|
||||
---
|
||||
|
||||
|
||||
39947
package-lock.json
generated
39947
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"lowdb": "^1.0.0",
|
||||
"marked": "^2.1.3",
|
||||
"mavon-editor": "^2.10.4",
|
||||
"nprogress": "^0.2.0",
|
||||
"qs": "^6.11.0",
|
||||
"quill": "^1.3.7",
|
||||
|
||||
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
@@ -68,6 +68,9 @@ importers:
|
||||
marked:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
mavon-editor:
|
||||
specifier: ^2.10.4
|
||||
version: 2.10.4
|
||||
nprogress:
|
||||
specifier: ^0.2.0
|
||||
version: 0.2.0
|
||||
@@ -2640,6 +2643,9 @@ packages:
|
||||
engines: {node: '>=4'}
|
||||
hasBin: true
|
||||
|
||||
cssfilter@0.0.10:
|
||||
resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}
|
||||
|
||||
cssnano-preset-default@4.0.8:
|
||||
resolution: {integrity: sha512-LdAyHuq+VRyeVREFmuxUZR1TXjQm8QQU/ktoo/x7bz+SdOge1YKc5eMN6pRW7YWBmyq59CqYba1dJ5cUukEjLQ==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
@@ -4492,9 +4498,6 @@ packages:
|
||||
json-parse-better-errors@1.0.2:
|
||||
resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==}
|
||||
|
||||
json-parse-even-better-errors@2.3.1:
|
||||
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||
|
||||
json-schema-traverse@0.4.1:
|
||||
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||
|
||||
@@ -4590,9 +4593,6 @@ packages:
|
||||
resolution: {integrity: sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
load-json-file@4.0.0:
|
||||
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4726,6 +4726,9 @@ packages:
|
||||
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
mavon-editor@2.10.4:
|
||||
resolution: {integrity: sha512-CFsBLkgt/KZBDg+SJYe2fyYv4zClY149PiwpH0rDAiiP4ae1XNs0GC8nBsoTeipsHcebDLN1QMkt3bUsnMDjQw==}
|
||||
|
||||
md5.js@1.3.5:
|
||||
resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
|
||||
|
||||
@@ -7115,6 +7118,11 @@ packages:
|
||||
xmlchars@2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
|
||||
xss@1.0.15:
|
||||
resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==}
|
||||
engines: {node: '>= 0.10.0'}
|
||||
hasBin: true
|
||||
|
||||
xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
@@ -10346,6 +10354,8 @@ snapshots:
|
||||
|
||||
cssesc@3.0.0: {}
|
||||
|
||||
cssfilter@0.0.10: {}
|
||||
|
||||
cssnano-preset-default@4.0.8:
|
||||
dependencies:
|
||||
css-declaration-sorter: 4.0.1
|
||||
@@ -12725,8 +12735,6 @@ snapshots:
|
||||
|
||||
json-parse-better-errors@1.0.2: {}
|
||||
|
||||
json-parse-even-better-errors@2.3.1: {}
|
||||
|
||||
json-schema-traverse@0.4.1: {}
|
||||
|
||||
json-schema-traverse@1.0.0:
|
||||
@@ -12820,8 +12828,6 @@ snapshots:
|
||||
prelude-ls: 1.1.2
|
||||
type-check: 0.3.2
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-json-file@4.0.0:
|
||||
dependencies:
|
||||
graceful-fs: 4.2.11
|
||||
@@ -12953,6 +12959,10 @@ snapshots:
|
||||
|
||||
math-intrinsics@1.1.0: {}
|
||||
|
||||
mavon-editor@2.10.4:
|
||||
dependencies:
|
||||
xss: 1.0.15
|
||||
|
||||
md5.js@1.3.5:
|
||||
dependencies:
|
||||
hash-base: 3.0.5
|
||||
@@ -13521,12 +13531,7 @@ snapshots:
|
||||
error-ex: 1.3.4
|
||||
json-parse-better-errors: 1.0.2
|
||||
|
||||
parse-json@5.2.0:
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.29.0
|
||||
error-ex: 1.3.4
|
||||
json-parse-even-better-errors: 2.3.1
|
||||
lines-and-columns: 1.2.4
|
||||
parse-json@5.2.0: {}
|
||||
|
||||
parse-passwd@1.0.0: {}
|
||||
|
||||
@@ -15835,6 +15840,11 @@ snapshots:
|
||||
|
||||
xmlchars@2.2.0: {}
|
||||
|
||||
xss@1.0.15:
|
||||
dependencies:
|
||||
commander: 2.20.3
|
||||
cssfilter: 0.0.10
|
||||
|
||||
xtend@4.0.2: {}
|
||||
|
||||
y18n@4.0.3: {}
|
||||
|
||||
15
src/api/system-administration/api-logs.js
Normal file
15
src/api/system-administration/api-logs.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'system_settings/system_assistant/interface_log/'
|
||||
|
||||
export function getInterfaceLogList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_interface_log_list',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
67
src/api/system-administration/menu-configuration.js
Normal file
67
src/api/system-administration/menu-configuration.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'system_settings/menu_configuration/menu/'
|
||||
|
||||
function apiParams (method, data = {}) {
|
||||
return {
|
||||
method: `system_settings_menu_configuration_menu_${method}`,
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
}
|
||||
|
||||
export function getMenuAll (data) {
|
||||
return request({
|
||||
url: BASE + 'all',
|
||||
method: 'get',
|
||||
params: apiParams('all', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function getMenuList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: apiParams('list', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function createMenu (data) {
|
||||
return request({
|
||||
url: BASE + 'create',
|
||||
method: 'post',
|
||||
data: apiParams('create', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function editMenu (data) {
|
||||
return request({
|
||||
url: BASE + 'edit',
|
||||
method: 'put',
|
||||
data: apiParams('edit', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteMenu (data) {
|
||||
return request({
|
||||
url: BASE + 'delete',
|
||||
method: 'delete',
|
||||
data: apiParams('delete', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function updateMenuStatus (data) {
|
||||
return request({
|
||||
url: BASE + 'update_status',
|
||||
method: 'put',
|
||||
data: apiParams('update_status', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function sortMenu (data) {
|
||||
return request({
|
||||
url: BASE + 'sort',
|
||||
method: 'put',
|
||||
data: apiParams('sort', data)
|
||||
})
|
||||
}
|
||||
15
src/api/system-administration/operation-logs.js
Normal file
15
src/api/system-administration/operation-logs.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'system_settings/system_assistant/operate_log/'
|
||||
|
||||
export function getOperateLogList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_operate_log_list',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
149
src/api/system-administration/problem-help.js
Normal file
149
src/api/system-administration/problem-help.js
Normal file
@@ -0,0 +1,149 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const CATEGORY_BASE = 'system_settings/system_assistant/problem_help/category/'
|
||||
const MARKDOWN_BASE = 'system_settings/system_assistant/problem_help/markdown/'
|
||||
|
||||
export function getCategoryTree (data) {
|
||||
return request({
|
||||
url: CATEGORY_BASE + 'tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_problem_category_tree',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function setCategoryAdd (data) {
|
||||
return request({
|
||||
url: CATEGORY_BASE + 'add',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_category_add',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function setCategoryUpdate (data) {
|
||||
return request({
|
||||
url: CATEGORY_BASE + 'edit',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_category_edit',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function delCategory (data) {
|
||||
return request({
|
||||
url: CATEGORY_BASE + 'delete',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_category_delete',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getProblemTree (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'tree',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_tree',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getMarkdownDetails (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'details',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_details',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function setMarkdownAdd (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'add',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_add',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function setMarkdownEdit (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'edit',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_edit',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function delMarkdownDetails (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'del',
|
||||
method: 'delete',
|
||||
data: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_del',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteProblem (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'delete',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'del.problem.item',
|
||||
platform: 'admin',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function searchMarkdown (data) {
|
||||
return request({
|
||||
url: MARKDOWN_BASE + 'search',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_system_assistant_problem_markdown_search',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function getRoleAll (data) {
|
||||
return request({
|
||||
url: 'system_settings/user_management/role/list',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_user_management_role_list',
|
||||
platform: 'background',
|
||||
page_size: 9999,
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -10,6 +10,14 @@ function apiParams (method, data = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getRoleAll (data) {
|
||||
return request({
|
||||
url: BASE + 'all',
|
||||
method: 'get',
|
||||
params: apiParams('all', data)
|
||||
})
|
||||
}
|
||||
|
||||
export function getRoleList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
|
||||
99
src/api/system-administration/user.js
Normal file
99
src/api/system-administration/user.js
Normal file
@@ -0,0 +1,99 @@
|
||||
import { request } from '@/api/_service'
|
||||
|
||||
const BASE = 'system_settings/user_management/user/'
|
||||
|
||||
export function getUserList (data) {
|
||||
return request({
|
||||
url: BASE + 'list',
|
||||
method: 'get',
|
||||
params: {
|
||||
method: 'system_settings_user_management_user_list',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createUser (data) {
|
||||
return request({
|
||||
url: BASE + 'create',
|
||||
method: 'post',
|
||||
data: {
|
||||
method: 'system_settings_user_management_user_create',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function editUser (data) {
|
||||
return request({
|
||||
url: BASE + 'edit',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_user_management_user_edit',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteUser (data) {
|
||||
return request({
|
||||
url: BASE + 'delete',
|
||||
method: 'delete',
|
||||
data: {
|
||||
method: 'system_settings_user_management_user_delete',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function batchDeleteUser (data) {
|
||||
return request({
|
||||
url: BASE + 'batch_delete',
|
||||
method: 'delete',
|
||||
data: {
|
||||
method: 'system_settings_user_management_user_batch_delete',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function enableUser (data) {
|
||||
return request({
|
||||
url: BASE + 'enable',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_user_management_enable_user',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function disableUser (data) {
|
||||
return request({
|
||||
url: BASE + 'disable',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_user_management_disable_user',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function resetUserPwd (data) {
|
||||
return request({
|
||||
url: BASE + 'reset_pwd',
|
||||
method: 'put',
|
||||
data: {
|
||||
method: 'system_settings_user_management_user_reset_pwd',
|
||||
platform: 'background',
|
||||
...data
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -77,6 +77,232 @@
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"tip": "Tip"
|
||||
},
|
||||
"user": {
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"index": "No.",
|
||||
"username": "Username",
|
||||
"full_name": "Full Name",
|
||||
"pass_number": "Pass Number",
|
||||
"status": "Status",
|
||||
"enable": "Enabled",
|
||||
"disable": "Disabled",
|
||||
"user_group": "User Group",
|
||||
"login_ip": "Last Login IP",
|
||||
"last_login_time": "Last Login Time",
|
||||
"actions": "Actions",
|
||||
"add": "Add",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"add_title": "Add User",
|
||||
"edit_title": "Edit User",
|
||||
"enter_username": "Please enter username",
|
||||
"enter_name": "Please enter full name",
|
||||
"enter_password": "Please enter password",
|
||||
"enter_confirm_password": "Please confirm password",
|
||||
"enter_pass_number": "Please enter pass number",
|
||||
"password": "Password",
|
||||
"confirm_password": "Confirm Password",
|
||||
"select_user_group": "Please select user group",
|
||||
"select_rows_first": "Please select rows first",
|
||||
"operation_success": "Operation succeeded",
|
||||
"confirm_delete": "Are you sure to delete this user?",
|
||||
"confirm_batch_delete": "Are you sure to batch delete selected users?",
|
||||
"confirm_reset_pwd": "Are you sure to reset this user's password?",
|
||||
"confirm_execute": "Are you sure to execute this operation?",
|
||||
"password_not_match": "Passwords do not match",
|
||||
"password_length": "Length should be 6 to 64 characters",
|
||||
"username_length": "Length should be 3 to 20 characters",
|
||||
"cannot_delete_self": "Cannot delete own account",
|
||||
"cannot_operate_self": "Cannot operate own account",
|
||||
"batch_delete": "Batch Delete",
|
||||
"reset_password": "Reset Password",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"prompt": "Prompt"
|
||||
}
|
||||
},
|
||||
"menu_management": {
|
||||
"menu_configuration": {
|
||||
"add_top_menu": "Add Top-Level Menu",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"add": "Add",
|
||||
"enable": "Enable",
|
||||
"disable": "Disable",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
"cancel": "Cancel",
|
||||
"modify": "Edit",
|
||||
"filter": "Filter",
|
||||
"filter_placeholder": "Enter keywords to filter",
|
||||
"parent_menu": "Parent Menu",
|
||||
"parent_menu_placeholder": "Leave blank for top-level menu. Try searching: Home",
|
||||
"menu_name": "Menu Name",
|
||||
"menu_name_placeholder": "Please enter menu name",
|
||||
"menu_alias": "Alias",
|
||||
"menu_alias_placeholder": "Enter menu alias (optional)",
|
||||
"icon": "Icon",
|
||||
"select_menu_icon": "Select Menu Icon",
|
||||
"sort": "No.",
|
||||
"navigation": "Navigation",
|
||||
"link_type": "Link Type",
|
||||
"link_type_module": "Internal Module",
|
||||
"link_type_external": "External URL",
|
||||
"open_type": "Open Method",
|
||||
"open_self": "Same Tab",
|
||||
"open_blank": "New Tab",
|
||||
"url": "URL",
|
||||
"url_placeholder": "Enter link address",
|
||||
"params": "Parameters",
|
||||
"params_placeholder": "Enter URL parameters (optional)",
|
||||
"remark": "Remark",
|
||||
"remark_placeholder": "Enter menu notes (optional)",
|
||||
"add_menu": "Add Menu",
|
||||
"edit_menu": "Edit Menu",
|
||||
"name_required": "Menu name cannot be empty",
|
||||
"name_max_length": "Must be 32 characters or less",
|
||||
"alias_max_length": "Must be 16 characters or less",
|
||||
"must_be_number": "Please enter a valid number",
|
||||
"link_type_required": "Link type cannot be empty",
|
||||
"max_255_length": "Must be 255 characters or less",
|
||||
"operation_success": "Operation successful",
|
||||
"delete_confirm": "Are you sure you want to delete this menu?",
|
||||
"status_change_confirm": "Changing status will affect child/parent menus. Proceed?",
|
||||
"prompt": "Notice",
|
||||
"status": "Status",
|
||||
"admin": "Backend",
|
||||
"pda": "PDA",
|
||||
"navigation_property": "Navigation Property",
|
||||
"navigation_visible": "Visible",
|
||||
"navigation_hidden": "Hidden",
|
||||
"menu_depth": "Menu Level",
|
||||
"query": "Search",
|
||||
"reset": "Reset",
|
||||
"please_select": "Please select"
|
||||
}
|
||||
},
|
||||
"system_utilities": {
|
||||
"api_logs": {
|
||||
"id": "ID",
|
||||
"ip": "IP",
|
||||
"interface_name": "Interface Name",
|
||||
"request_method": "Request Method",
|
||||
"response_status": "Response Status",
|
||||
"response_time_ms": "Response Time (ms)",
|
||||
"process_code": "Process Code",
|
||||
"tray_number": "Tray No.",
|
||||
"battery_id": "Battery Barcode",
|
||||
"batch_number": "Batch No.",
|
||||
"process_id": "Process ID",
|
||||
"create_date": "Create Date",
|
||||
"operation": "Operation",
|
||||
"status": "Status",
|
||||
"batch": "Batch",
|
||||
"create_time": "Create Time",
|
||||
"success": "Success",
|
||||
"failure": "Failure",
|
||||
"view_response": "View Response",
|
||||
"response": "Response",
|
||||
"copy_request_content": "Copy Request",
|
||||
"copy_response_content": "Copy Response",
|
||||
"request_body": "Request Body",
|
||||
"response_content": "Response Body",
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"operation_success": "Operation succeeded",
|
||||
"placeholder_ip": "Please enter IP",
|
||||
"placeholder_interface_name": "Please enter interface name",
|
||||
"placeholder_status": "Please select status",
|
||||
"placeholder_batch": "Please enter batch",
|
||||
"placeholder_tray_no": "Please enter tray no.",
|
||||
"placeholder_process_code": "Please enter process code",
|
||||
"placeholder_battery_barcode": "Please enter battery barcode",
|
||||
"placeholder_create_time": "Please select time",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse"
|
||||
},
|
||||
"operation_logs": {
|
||||
"search": "Search",
|
||||
"reset": "Reset",
|
||||
"expand": "Expand",
|
||||
"collapse": "Collapse",
|
||||
"ip": "IP",
|
||||
"placeholder_ip": "Please enter IP",
|
||||
"operator": "Operator",
|
||||
"placeholder_operator": "Select Operator",
|
||||
"batch": "Batch",
|
||||
"placeholder_batch": "Please enter batch",
|
||||
"tray_no": "Tray No.",
|
||||
"placeholder_tray_no": "Please enter tray no.",
|
||||
"create_time": "Creation Time",
|
||||
"placeholder_time": "Please select time",
|
||||
"success": "Success",
|
||||
"failure": "Failure",
|
||||
"status": "Status",
|
||||
"view_response": "View Response",
|
||||
"operation": "Actions",
|
||||
"response": "Response",
|
||||
"copy_request_content": "Copy Request Content",
|
||||
"copy_response_content": "Copy Response Content",
|
||||
"request_body": "Request Body",
|
||||
"response_content": "Response Content",
|
||||
"action_name": "Action Name",
|
||||
"action_code": "Action Code",
|
||||
"request_path": "Request Path",
|
||||
"create_date": "Creation Date",
|
||||
"id": "ID",
|
||||
"operation_success": "Operation Successful"
|
||||
},
|
||||
"problem_help": {
|
||||
"search_placeholder": "Please enter content",
|
||||
"add_directory": "Add Directory",
|
||||
"add_document": "Add Document",
|
||||
"edit_document": "Edit Document",
|
||||
"edit": "Edit",
|
||||
"delete": "Delete",
|
||||
"download": "Download",
|
||||
"dialog_edit_title": "Edit Category",
|
||||
"dialog_create_title": "Add Category",
|
||||
"category_name": "Category Name",
|
||||
"placeholder_category_name": "Please enter a category name",
|
||||
"parent_category": "Parent Menu",
|
||||
"top_category": "Root Category",
|
||||
"view_permission": "View Permissions",
|
||||
"please_select": "Please select",
|
||||
"sort": "Sort Order",
|
||||
"cancel": "Cancel",
|
||||
"confirm": "Confirm",
|
||||
"update": "Update",
|
||||
"category_name_required": "Category name cannot be empty",
|
||||
"add_success": "Added successfully",
|
||||
"update_success": "Updated successfully",
|
||||
"title": "Title",
|
||||
"describe": "Description",
|
||||
"type": "Type",
|
||||
"document": "Document",
|
||||
"add": "Add",
|
||||
"enter_title": "Please enter title",
|
||||
"enter_describe": "Please enter description",
|
||||
"edit_success": "Modified successfully",
|
||||
"content_required": "Please enter document content",
|
||||
"file_required": "Please upload a file",
|
||||
"select_parent_menu": "Please select parent menu",
|
||||
"select_role_group": "Please select permission role group",
|
||||
"create_success": "Created successfully",
|
||||
"drag_file_here": "Drag file here, or",
|
||||
"click_upload": "Click to upload",
|
||||
"submitter": "Submitter:",
|
||||
"delete_success": "Deleted successfully!",
|
||||
"operation_success": "Operation successful",
|
||||
"confirm_message": "Are you sure you want to proceed?",
|
||||
"prompt": "Prompt",
|
||||
"confirm_close": "Confirm close?",
|
||||
"select_delete_directory": "Please select a directory to delete first",
|
||||
"select_edit_directory": "Please select a directory to edit first",
|
||||
"upload_failed_retry": "Upload failed, please try again later",
|
||||
"select_document_tip": "Please select a document from the left menu to view"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -77,6 +77,232 @@
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"tip": "提示"
|
||||
},
|
||||
"user": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"index": "序号",
|
||||
"username": "账号",
|
||||
"full_name": "姓名",
|
||||
"pass_number": "出入证编号",
|
||||
"status": "状态",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"user_group": "用户组",
|
||||
"login_ip": "上次登录IP",
|
||||
"last_login_time": "上次登录时间",
|
||||
"actions": "操作",
|
||||
"add": "新 增",
|
||||
"edit": "编 辑",
|
||||
"delete": "删 除",
|
||||
"add_title": "新增用户",
|
||||
"edit_title": "编辑用户",
|
||||
"enter_username": "请输入账号",
|
||||
"enter_name": "请输入姓名",
|
||||
"enter_password": "请输入密码",
|
||||
"enter_confirm_password": "请再次输入密码",
|
||||
"enter_pass_number": "请输入出入证编号",
|
||||
"password": "密码",
|
||||
"confirm_password": "确认密码",
|
||||
"select_user_group": "请选择用户组",
|
||||
"select_rows_first": "请先勾选要操作的数据",
|
||||
"operation_success": "操作成功",
|
||||
"confirm_delete": "确定要删除该用户吗?",
|
||||
"confirm_batch_delete": "确定要批量删除勾选的用户吗?",
|
||||
"confirm_reset_pwd": "确定要重置该用户密码吗?",
|
||||
"confirm_execute": "确定要执行该操作吗?",
|
||||
"password_not_match": "两次输入的密码不一致",
|
||||
"password_length": "长度在 6 到 64 个字符",
|
||||
"username_length": "长度在 3 到 20 个字符",
|
||||
"cannot_delete_self": "不能删除自己的账号",
|
||||
"cannot_operate_self": "不能操作自己的账号",
|
||||
"batch_delete": "批量删除",
|
||||
"reset_password": "重置密码",
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"prompt": "提示"
|
||||
}
|
||||
},
|
||||
"menu_management": {
|
||||
"menu_configuration": {
|
||||
"add_top_menu": "新增顶层菜单",
|
||||
"expand": "展开",
|
||||
"collapse": "收起",
|
||||
"add": "新增",
|
||||
"enable": "启用",
|
||||
"disable": "禁用",
|
||||
"delete": "删除",
|
||||
"confirm": "确定",
|
||||
"cancel": "取消",
|
||||
"modify": "修改",
|
||||
"filter": "过滤",
|
||||
"filter_placeholder": "输入关键词进行过滤",
|
||||
"parent_menu": "上级菜单",
|
||||
"parent_menu_placeholder": "不选择表示顶层菜单 试试搜索:首页",
|
||||
"menu_name": "名称",
|
||||
"menu_name_placeholder": "请输入菜单名称",
|
||||
"menu_alias": "别名",
|
||||
"menu_alias_placeholder": "可输入菜单别名",
|
||||
"icon": "图标",
|
||||
"select_menu_icon": "可选择菜单图标",
|
||||
"sort": "排序",
|
||||
"navigation": "导航",
|
||||
"link_type": "链接类型",
|
||||
"link_type_module": "模块",
|
||||
"link_type_external": "外链",
|
||||
"open_type": "打开方式",
|
||||
"open_self": "当前窗口",
|
||||
"open_blank": "新窗口",
|
||||
"url": "URL",
|
||||
"url_placeholder": "可输入链接地址",
|
||||
"params": "参数",
|
||||
"params_placeholder": "可输入链接参数",
|
||||
"remark": "备注",
|
||||
"remark_placeholder": "可输入菜单备注",
|
||||
"add_menu": "新增菜单",
|
||||
"edit_menu": "编辑菜单",
|
||||
"name_required": "名称不能为空",
|
||||
"name_max_length": "长度不能大于 32 个字符",
|
||||
"alias_max_length": "长度不能大于 16 个字符",
|
||||
"must_be_number": "必须为数字值",
|
||||
"link_type_required": "链接类型不能为空",
|
||||
"max_255_length": "长度不能大于 255 个字符",
|
||||
"operation_success": "操作成功",
|
||||
"delete_confirm": "确定要执行该操作吗?",
|
||||
"status_change_confirm": "状态的切换会影响上下级菜单,是否确认操作?",
|
||||
"prompt": "提示",
|
||||
"status": "状态",
|
||||
"admin": "后台",
|
||||
"pda": "PDA",
|
||||
"navigation_property": "导航属性",
|
||||
"navigation_visible": "可见",
|
||||
"navigation_hidden": "隐藏",
|
||||
"menu_depth": "菜单深度",
|
||||
"query": "查询",
|
||||
"reset": "重置",
|
||||
"please_select": "请选择"
|
||||
}
|
||||
},
|
||||
"system_utilities": {
|
||||
"api_logs": {
|
||||
"id": "ID",
|
||||
"ip": "IP",
|
||||
"interface_name": "接口名称",
|
||||
"request_method": "请求方法",
|
||||
"response_status": "响应状态",
|
||||
"response_time_ms": "响应时长(毫秒)",
|
||||
"process_code": "工序编码",
|
||||
"tray_number": "托盘号",
|
||||
"battery_id": "电池条码",
|
||||
"batch_number": "批次号",
|
||||
"process_id": "进程ID",
|
||||
"create_date": "创建日期",
|
||||
"operation": "操作",
|
||||
"status": "状态",
|
||||
"batch": "批次",
|
||||
"create_time": "创建时间",
|
||||
"success": "成功",
|
||||
"failure": "失败",
|
||||
"view_response": "查看响应",
|
||||
"response": "响应",
|
||||
"copy_request_content": "复制请求内容",
|
||||
"copy_response_content": "复制响应内容",
|
||||
"request_body": "请求体",
|
||||
"response_content": "响应内容",
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"operation_success": "操作成功",
|
||||
"placeholder_ip": "请输入IP",
|
||||
"placeholder_interface_name": "请输入接口名称",
|
||||
"placeholder_status": "请选择状态",
|
||||
"placeholder_batch": "请输入批次",
|
||||
"placeholder_tray_no": "请输入托盘号",
|
||||
"placeholder_process_code": "请输入工序编码",
|
||||
"placeholder_battery_barcode": "请输入电池条码",
|
||||
"placeholder_create_time": "请选择时间",
|
||||
"expand": "展开更多",
|
||||
"collapse": "收起"
|
||||
},
|
||||
"operation_logs": {
|
||||
"search": "查询",
|
||||
"reset": "重置",
|
||||
"expand": "展开更多",
|
||||
"collapse": "收起",
|
||||
"ip": "IP",
|
||||
"placeholder_ip": "请输入IP",
|
||||
"operator": "操作人",
|
||||
"placeholder_operator": "选择操作人",
|
||||
"batch": "批次",
|
||||
"placeholder_batch": "请输入批次",
|
||||
"tray_no": "托盘号",
|
||||
"placeholder_tray_no": "请输入托盘号",
|
||||
"create_time": "创建时间",
|
||||
"placeholder_time": "请选择时间",
|
||||
"success": "成功",
|
||||
"failure": "失败",
|
||||
"status": "状态",
|
||||
"view_response": "查看响应",
|
||||
"operation": "操作",
|
||||
"response": "响应",
|
||||
"copy_request_content": "复制请求内容",
|
||||
"copy_response_content": "复制响应内容",
|
||||
"request_body": "请求体",
|
||||
"response_content": "响应内容",
|
||||
"action_name": "操作动作名称",
|
||||
"action_code": "操作动作编码",
|
||||
"request_path": "请求路径",
|
||||
"create_date": "创建日期",
|
||||
"id": "ID",
|
||||
"operation_success": "操作成功"
|
||||
},
|
||||
"problem_help": {
|
||||
"search_placeholder": "请输入内容",
|
||||
"add_directory": "新增目录",
|
||||
"add_document": "新增文档",
|
||||
"edit_document": "编辑文档",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"download": "下载",
|
||||
"dialog_edit_title": "编辑类型",
|
||||
"dialog_create_title": "新增类型",
|
||||
"category_name": "类型名称",
|
||||
"placeholder_category_name": "请输入类型名称",
|
||||
"parent_category": "上级菜单",
|
||||
"top_category": "顶层目录",
|
||||
"view_permission": "查看权限",
|
||||
"please_select": "请选择",
|
||||
"sort": "序号",
|
||||
"cancel": "取消",
|
||||
"confirm": "确定",
|
||||
"update": "修改",
|
||||
"category_name_required": "分类名称不能为空",
|
||||
"add_success": "添加成功",
|
||||
"update_success": "修改成功",
|
||||
"title": "标题",
|
||||
"describe": "描述",
|
||||
"type": "类型",
|
||||
"document": "文档",
|
||||
"add": "新增",
|
||||
"enter_title": "请输入标题",
|
||||
"enter_describe": "请输入描述",
|
||||
"edit_success": "修改成功",
|
||||
"content_required": "请输入文档编辑内容",
|
||||
"file_required": "请上传文件",
|
||||
"select_parent_menu": "请选择对应上级菜单",
|
||||
"select_role_group": "请选择权限用户组",
|
||||
"create_success": "新增成功",
|
||||
"drag_file_here": "将文件拖到此处,或",
|
||||
"click_upload": "点击上传",
|
||||
"submitter": "提交人:",
|
||||
"delete_success": "删除成功!",
|
||||
"operation_success": "操作成功",
|
||||
"confirm_message": "确定要执行该操作吗?",
|
||||
"prompt": "提示",
|
||||
"confirm_close": "确认关闭?",
|
||||
"select_delete_directory": "请先选择需要删除的目录",
|
||||
"select_edit_directory": "请先选择需要编辑的目录",
|
||||
"upload_failed_retry": "上传失败,请稍后重试",
|
||||
"select_document_tip": "请从左侧菜单选择文档查看"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'flex.css'
|
||||
import '@/components'
|
||||
// svg 图标
|
||||
import '@/assets/svg-icons'
|
||||
// JSON 树形视图
|
||||
import vueJsonTreeView from 'vue-json-tree-view'
|
||||
// 国际化
|
||||
import i18n from '@/i18n.js'
|
||||
|
||||
@@ -57,5 +59,7 @@ export default {
|
||||
Vue.use(pluginError)
|
||||
Vue.use(pluginLog)
|
||||
Vue.use(pluginOpen)
|
||||
// JSON 树形视图组件(tree-view)
|
||||
Vue.use(vueJsonTreeView)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,36 @@ export default {
|
||||
name: `${pre}user_management-role`,
|
||||
meta: { ...meta, cache: true, title: '角色' },
|
||||
component: _import('system-administration/user-management/role')
|
||||
},
|
||||
{
|
||||
path: 'user_management/user',
|
||||
name: `${pre}user_management-user`,
|
||||
meta: { ...meta, cache: true, title: '用户' },
|
||||
component: _import('system-administration/user-management/user')
|
||||
},
|
||||
{
|
||||
path: 'menu_configuration/menu',
|
||||
name: `${pre}menu_configuration-menu`,
|
||||
meta: { ...meta, cache: true, title: '菜单配置' },
|
||||
component: _import('system-administration/menu-management/menu-configuration')
|
||||
},
|
||||
{
|
||||
path: 'system_assistant/interface_log',
|
||||
name: `${pre}system_assistant-interface_log`,
|
||||
meta: { ...meta, cache: true, title: '接口日志' },
|
||||
component: _import('system-administration/system-utilities/api-logs')
|
||||
},
|
||||
{
|
||||
path: 'system_assistant/operate_log',
|
||||
name: `${pre}system_assistant-operate_log`,
|
||||
meta: { ...meta, cache: true, title: '操作日志' },
|
||||
component: _import('system-administration/system-utilities/operation-logs')
|
||||
},
|
||||
{
|
||||
path: 'system_assistant/problem_help',
|
||||
name: `${pre}system_assistant-problem_help`,
|
||||
meta: { ...meta, cache: true, title: '问题帮助' },
|
||||
component: _import('system-administration/system-utilities/problem-help')
|
||||
}
|
||||
])('system_settings-')
|
||||
}
|
||||
|
||||
@@ -0,0 +1,682 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<el-form :inline="true" :model="searchForm" ref="searchFormRef" size="mini">
|
||||
<el-form-item :label="$t(key('link_type_module'))" prop="module">
|
||||
<el-radio-group v-model="searchForm.module" @change="handleSearch" size="small">
|
||||
<el-radio-button :label="$t(key('admin'))"></el-radio-button>
|
||||
<el-radio-button :label="$t(key('pda'))"></el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('status'))" prop="status">
|
||||
<el-select
|
||||
v-model="searchForm.status"
|
||||
:placeholder="$t(key('please_select'))"
|
||||
style="width: 120px;"
|
||||
clearable
|
||||
>
|
||||
<el-option :label="$t(key('enable'))" :value="1" />
|
||||
<el-option :label="$t(key('disable'))" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('navigation_property'))" prop="is_navi">
|
||||
<el-select
|
||||
v-model="searchForm.is_navi"
|
||||
:placeholder="$t(key('please_select'))"
|
||||
style="width: 120px;"
|
||||
clearable
|
||||
>
|
||||
<el-option :label="$t(key('navigation_visible'))" :value="1" />
|
||||
<el-option :label="$t(key('navigation_hidden'))" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('menu_depth'))" prop="level">
|
||||
<el-input-number
|
||||
v-model="searchForm.level"
|
||||
controls-position="right"
|
||||
:min="0"
|
||||
style="width: 100px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-search"
|
||||
:disabled="loading"
|
||||
@click="handleSearch"
|
||||
>
|
||||
{{ $t(key('query')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button icon="el-icon-refresh" @click="handleReset">
|
||||
{{ $t(key('reset')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<div class="menu-config">
|
||||
<el-form :inline="true" size="small" @submit.native.prevent>
|
||||
<el-form-item v-if="auth.create">
|
||||
<el-button
|
||||
icon="el-icon-plus"
|
||||
:disabled="loading"
|
||||
@click="handleCreateTop"
|
||||
>
|
||||
{{ $t(key('add_top_menu')) }}
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button-group>
|
||||
<el-button icon="el-icon-circle-plus-outline" :disabled="loading" @click="toggleExpand(true)">
|
||||
{{ $t(key('expand')) }}
|
||||
</el-button>
|
||||
<el-button icon="el-icon-remove-outline" :disabled="loading" @click="toggleExpand(false)">
|
||||
{{ $t(key('collapse')) }}
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('filter'))">
|
||||
<el-input
|
||||
v-model="filterText"
|
||||
:disabled="loading"
|
||||
:placeholder="$t(key('filter_placeholder'))"
|
||||
prefix-icon="el-icon-search"
|
||||
style="width: 240px;"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="10">
|
||||
<el-tree
|
||||
v-if="hackReset"
|
||||
ref="tree"
|
||||
class="tree-scroll"
|
||||
node-key="menu_id"
|
||||
:data="treeData"
|
||||
:props="treeProps"
|
||||
:filter-node-method="filterNode"
|
||||
highlight-current
|
||||
:default-expand-all="isExpandAll"
|
||||
:default-expanded-keys="expanded"
|
||||
draggable
|
||||
:allow-drag="allowDrag"
|
||||
@node-click="handleNodeClick"
|
||||
@node-drop="handleDrop"
|
||||
@node-expand="handleNodeExpand"
|
||||
@node-collapse="handleNodeCollapse"
|
||||
>
|
||||
<span class="custom-tree-node" slot-scope="{ node, data }">
|
||||
<span class="tree-label" :class="{ 'status-disabled': !data.status }">
|
||||
<i v-if="auth.move" class="el-icon-sort move-tree cs-mr-5" />
|
||||
<i v-if="data.icon" :class="'fa fa-' + data.icon" />
|
||||
<i v-else-if="data.children" :class="'el-icon-' + (node.expanded ? 'folder-opened' : 'folder')" />
|
||||
<i v-else class="el-icon-document" />
|
||||
{{ node.label }}
|
||||
</span>
|
||||
|
||||
<span class="node-actions">
|
||||
<el-button
|
||||
v-if="auth.create"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleAppend(data)"
|
||||
>
|
||||
{{ $t(key('add')) }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="auth.disabled_enable"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleToggleStatus(data)"
|
||||
>
|
||||
{{ data.status ? $t(key('disable')) : $t(key('enable')) }}
|
||||
</el-button>
|
||||
|
||||
<el-button
|
||||
v-if="auth.delete"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleRemove(data.menu_id)"
|
||||
>
|
||||
{{ $t(key('delete')) }}
|
||||
</el-button>
|
||||
</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="14">
|
||||
<el-card v-show="auth.create || auth.edit" class="box-card" shadow="never">
|
||||
<div slot="header">
|
||||
<span>{{ $t(key(formStatus === 'create' ? 'add_menu' : 'edit_menu')) }}</span>
|
||||
<el-button
|
||||
v-if="formStatus === 'create' && auth.create"
|
||||
type="text"
|
||||
:loading="formLoading"
|
||||
style="float: right; padding: 3px 0;"
|
||||
@click="handleCreateSubmit"
|
||||
>
|
||||
{{ $t(key('confirm')) }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="formStatus === 'update' && auth.edit"
|
||||
type="text"
|
||||
:loading="formLoading"
|
||||
style="float: right; padding: 3px 0;"
|
||||
@click="handleUpdateSubmit"
|
||||
>
|
||||
{{ $t(key('modify')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-form :model="form" :rules="rules" ref="formRef" label-width="80px">
|
||||
<el-form-item :label="$t(key('parent_menu'))" prop="parent_id">
|
||||
<el-cascader
|
||||
v-model="form.parent_id"
|
||||
:placeholder="$t(key('parent_menu_placeholder'))"
|
||||
:key="form.menu_id"
|
||||
:options="treeData"
|
||||
:props="cascaderProps"
|
||||
style="width: 100%;"
|
||||
filterable
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('menu_name'))" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:placeholder="$t(key('menu_name_placeholder'))"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('menu_alias'))" prop="alias">
|
||||
<el-input
|
||||
v-model="form.alias"
|
||||
:placeholder="$t(key('menu_alias_placeholder'))"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('icon'))" prop="icon">
|
||||
<d2-icon-select
|
||||
v-model="form.icon"
|
||||
:user-input="true"
|
||||
:placeholder="$t(key('select_menu_icon'))"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('sort'))" prop="sort">
|
||||
<el-input-number
|
||||
v-model="form.sort"
|
||||
:min="0"
|
||||
:max="255"
|
||||
style="width: 120px;"
|
||||
controls-position="right"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('navigation'))" prop="is_navi">
|
||||
<el-switch v-model="form.is_navi" active-value="1" inactive-value="0" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('link_type'))" prop="type">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio :label="0">{{ $t(key('link_type_module')) }}</el-radio>
|
||||
<el-radio :label="1">{{ $t(key('link_type_external')) }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item :label="$t(key('open_type'))" prop="target">
|
||||
<el-radio-group v-model="form.target">
|
||||
<el-radio label="_self">{{ $t(key('open_self')) }}</el-radio>
|
||||
<el-radio label="_blank">{{ $t(key('open_blank')) }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="URL" prop="url">
|
||||
<el-input
|
||||
v-model="form.url"
|
||||
:placeholder="$t(key('url_placeholder'))"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('params'))" prop="params">
|
||||
<el-input
|
||||
v-model="form.params"
|
||||
:placeholder="$t(key('params_placeholder'))"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t(key('remark'))" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
:placeholder="$t(key('remark_placeholder'))"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import util from '@/libs/util'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { confirmMixin } from '@/composables/useConfirmHandle'
|
||||
import {
|
||||
getMenuList,
|
||||
createMenu,
|
||||
editMenu,
|
||||
deleteMenu,
|
||||
updateMenuStatus,
|
||||
sortMenu
|
||||
} from '@/api/system-administration/menu-configuration'
|
||||
|
||||
export default {
|
||||
name: 'system-administration-menu-configuration',
|
||||
mixins: [i18nMixin('page.system_administration.menu_management.menu_configuration'), confirmMixin],
|
||||
data () {
|
||||
const $t = this.$t.bind(this)
|
||||
return {
|
||||
loading: false,
|
||||
formLoading: false,
|
||||
treeData: [],
|
||||
module: 'admin',
|
||||
hackReset: true,
|
||||
isExpandAll: false,
|
||||
expanded: [],
|
||||
oldExpanded: [],
|
||||
filterText: '',
|
||||
formStatus: 'create',
|
||||
appendData: {},
|
||||
appendType: '',
|
||||
updateNode: {},
|
||||
auth: {
|
||||
create: true,
|
||||
delete: true,
|
||||
edit: true,
|
||||
disabled_enable: true,
|
||||
move: true
|
||||
},
|
||||
treeProps: {
|
||||
label: 'name',
|
||||
children: 'children'
|
||||
},
|
||||
cascaderProps: {
|
||||
value: 'menu_id',
|
||||
label: 'name',
|
||||
children: 'children',
|
||||
checkStrictly: true,
|
||||
emitPath: false
|
||||
},
|
||||
searchForm: {
|
||||
module: 'admin',
|
||||
status: undefined,
|
||||
is_navi: undefined,
|
||||
level: 0
|
||||
},
|
||||
form: this.getDefaultForm(),
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: $t(this.key('name_required')), trigger: 'blur' },
|
||||
{ max: 32, message: $t(this.key('name_max_length')), trigger: 'blur' }
|
||||
],
|
||||
alias: [
|
||||
{ max: 16, message: $t(this.key('alias_max_length')), trigger: 'blur' }
|
||||
],
|
||||
sort: [
|
||||
{ type: 'number', message: $t(this.key('must_be_number')), trigger: 'blur' }
|
||||
],
|
||||
url: [
|
||||
{ max: 255, message: $t(this.key('max_255_length')), trigger: 'blur' }
|
||||
],
|
||||
params: [
|
||||
{ max: 255, message: $t(this.key('max_255_length')), trigger: 'blur' }
|
||||
],
|
||||
remark: [
|
||||
{ max: 255, message: $t(this.key('max_255_length')), trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
filterText (val) {
|
||||
this.$refs.tree && this.$refs.tree.filter(val)
|
||||
this.expanded = this.oldExpanded
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this._validationAuth()
|
||||
this.handleSearch()
|
||||
},
|
||||
methods: {
|
||||
getDefaultForm () {
|
||||
return {
|
||||
parent_id: 0,
|
||||
name: '',
|
||||
alias: '',
|
||||
icon: '',
|
||||
remark: '',
|
||||
type: 0,
|
||||
url: '',
|
||||
params: '',
|
||||
target: '_self',
|
||||
is_navi: '0',
|
||||
sort: 50
|
||||
}
|
||||
},
|
||||
_validationAuth () {
|
||||
this.auth.create = this.$permission('/system_settings/menu_configuration/menu/create')
|
||||
this.auth.edit = this.$permission('/system_settings/menu_configuration/menu/edit')
|
||||
this.auth.delete = this.$permission('/system_settings/menu_configuration/menu/delete')
|
||||
this.auth.disabled_enable = this.$permission('/system_settings/menu_configuration/menu/disabled_enable')
|
||||
this.auth.move = this.$permission('/system_settings/menu_configuration/menu/sort')
|
||||
},
|
||||
filterNode (value, data) {
|
||||
if (!value) return true
|
||||
return data.name.indexOf(value) !== -1
|
||||
},
|
||||
toggleExpand (isExpand) {
|
||||
this.filterText = ''
|
||||
this.expanded = []
|
||||
this.hackReset = false
|
||||
this.$nextTick(() => {
|
||||
this.isExpandAll = isExpand
|
||||
this.hackReset = true
|
||||
})
|
||||
},
|
||||
resetForm () {
|
||||
this.form = this.getDefaultForm()
|
||||
},
|
||||
resetElements (status = 'create') {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.formRef && this.$refs.formRef.clearValidate()
|
||||
})
|
||||
this.formStatus = status
|
||||
this.formLoading = false
|
||||
},
|
||||
handleSearch () {
|
||||
const form = { ...this.searchForm }
|
||||
form.level = form.level <= 0 ? undefined : form.level - 1
|
||||
this.loading = true
|
||||
getMenuList(form)
|
||||
.then(res => {
|
||||
this.module = form.module
|
||||
const menuKey = 'menu_' + form.module
|
||||
const flatList = Array.isArray(res)
|
||||
? res
|
||||
: (res[menuKey] || (res.data && res.data[menuKey]) || [])
|
||||
this.treeData = util.formatDataToTree(flatList)
|
||||
this.filterText = ''
|
||||
this.resetForm()
|
||||
this.resetElements('create')
|
||||
if (this.$refs.tree && this.$refs.tree.getCurrentKey()) {
|
||||
this.$refs.tree.setCurrentKey(null)
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
handleReset () {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.searchForm.module = 'admin'
|
||||
this.searchForm.level = 0
|
||||
this.handleSearch()
|
||||
},
|
||||
handleNodeClick (data) {
|
||||
if (!this.auth.create && !this.auth.edit) return
|
||||
this.updateNode = data
|
||||
this.resetForm()
|
||||
this.resetElements('update')
|
||||
this.form = { ...data, is_navi: String(data.is_navi) }
|
||||
},
|
||||
handleCreateTop () {
|
||||
this.appendType = 'newTopMenu'
|
||||
this.resetForm()
|
||||
this.resetElements('create')
|
||||
if (this.$refs.tree && this.$refs.tree.getCurrentKey()) {
|
||||
this.$refs.tree.setCurrentKey(null)
|
||||
}
|
||||
},
|
||||
handleAppend (data) {
|
||||
const key = data.menu_id
|
||||
this.handleCreateTop()
|
||||
this.$refs.tree.setCurrentKey(key)
|
||||
this.form.parent_id = key
|
||||
this.appendType = 'newChildMenu'
|
||||
this.appendData = data
|
||||
},
|
||||
appendChildToTree (newNode) {
|
||||
if (!this.appendData.children) {
|
||||
this.$set(this.appendData, 'children', [])
|
||||
}
|
||||
this.appendData.children.push(newNode)
|
||||
},
|
||||
appendTopToTree (newNode) {
|
||||
if (!newNode.children) {
|
||||
this.$set(newNode, 'children', [])
|
||||
}
|
||||
this.treeData.push(newNode)
|
||||
},
|
||||
updateNodeInTree (data) {
|
||||
if (!this.updateNode) return
|
||||
Object.keys(data).forEach(key => {
|
||||
if (Object.prototype.hasOwnProperty.call(this.updateNode, key)) {
|
||||
this.updateNode[key] = data[key]
|
||||
}
|
||||
})
|
||||
this.updateNode = {}
|
||||
},
|
||||
handleCreateSubmit () {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.oldExpanded = this.expanded
|
||||
this.formLoading = true
|
||||
createMenu({ ...this.form, module: this.module })
|
||||
.then(res => {
|
||||
let data = res
|
||||
if (data && data.data) data = data.data
|
||||
if (Array.isArray(data) && data.length > 0) data = data[0]
|
||||
|
||||
if (!this.isExpandAll) {
|
||||
this.expanded = [data.parent_id || data.menu_id]
|
||||
}
|
||||
|
||||
if (this.appendType === 'newChildMenu') {
|
||||
this.appendChildToTree(data)
|
||||
} else if (this.appendType === 'newTopMenu') {
|
||||
this.appendTopToTree(data)
|
||||
}
|
||||
this.appendType = ''
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
handleUpdateSubmit () {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (!valid) return
|
||||
this.oldExpanded = this.expanded
|
||||
this.formLoading = true
|
||||
editMenu(this.form)
|
||||
.then(() => {
|
||||
this.updateNodeInTree(this.form)
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
})
|
||||
.finally(() => {
|
||||
this.formLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
async handleRemove (key) {
|
||||
const cancelled = await this.$confirmAction(
|
||||
{
|
||||
message: this.key('delete_confirm'),
|
||||
title: this.key('prompt'),
|
||||
confirmButtonText: this.key('confirm'),
|
||||
cancelButtonText: this.key('cancel')
|
||||
},
|
||||
() => deleteMenu({ menu_id: key })
|
||||
)
|
||||
if (cancelled) return
|
||||
this.$refs.tree.remove(this.$refs.tree.getNode(key))
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
},
|
||||
async handleToggleStatus (data) {
|
||||
const cancelled = await this.$confirmAction(
|
||||
{
|
||||
message: this.key('status_change_confirm'),
|
||||
title: this.key('prompt'),
|
||||
confirmButtonText: this.key('confirm'),
|
||||
cancelButtonText: this.key('cancel')
|
||||
},
|
||||
() => updateMenuStatus({ menu_id: data.menu_id, status: data.status ? 0 : 1 })
|
||||
)
|
||||
if (cancelled) return
|
||||
if (!this.isExpandAll) {
|
||||
const node = this.$refs.tree.getNode(data.menu_id)
|
||||
this.expanded = [node && node.data ? (node.data.parent_id || data.menu_id) : data.menu_id]
|
||||
}
|
||||
this.handleSearch()
|
||||
},
|
||||
handleDrop (draggingNode, dropNode, dropType) {
|
||||
const setMenu = {
|
||||
menu_id: draggingNode.data.menu_id,
|
||||
parent_id: draggingNode.data.parent_id
|
||||
}
|
||||
const indexMenu = []
|
||||
|
||||
if (dropType === 'inner') {
|
||||
setMenu.parent_id = dropNode.key
|
||||
} else {
|
||||
setMenu.parent_id = dropNode.data.parent_id
|
||||
dropNode.parent.childNodes.forEach((value, index) => {
|
||||
indexMenu.push(value.key)
|
||||
value.data.sort = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
if (indexMenu.length > 0) {
|
||||
sortMenu({ menu_sort: indexMenu, ...setMenu })
|
||||
.finally(() => { this.handleSearch() })
|
||||
.catch(() => { this.handleSearch() })
|
||||
}
|
||||
},
|
||||
allowDrag () {
|
||||
return true
|
||||
},
|
||||
handleNodeExpand (data) {
|
||||
const exists = this.expanded.some(item => item === data.menu_id)
|
||||
if (!exists) {
|
||||
this.expanded.push(data.menu_id)
|
||||
}
|
||||
},
|
||||
handleNodeCollapse (data) {
|
||||
this.expanded = this.expanded.filter(item => item !== data.menu_id)
|
||||
this._removeChildrenIds(data)
|
||||
},
|
||||
_removeChildrenIds (data) {
|
||||
if (!data.children) return
|
||||
data.children.forEach(item => {
|
||||
const index = this.expanded.indexOf(item.menu_id)
|
||||
if (index >= 0) {
|
||||
this.expanded.splice(index, 1)
|
||||
}
|
||||
this._removeChildrenIds(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tree-scroll {
|
||||
max-height: 615px;
|
||||
overflow: auto;
|
||||
padding-bottom: 1px;
|
||||
}
|
||||
|
||||
.custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 14px;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.tree-label {
|
||||
i {
|
||||
width: 16px;
|
||||
}
|
||||
.fa {
|
||||
font-size: 16px;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
}
|
||||
|
||||
.node-actions {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.custom-tree-node:hover .node-actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.move-tree {
|
||||
color: #c0c4cc;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.status-disabled {
|
||||
color: #c0c4cc;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.box-card {
|
||||
border-radius: 0;
|
||||
border: 1px solid #dcdfe6;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="$t(`${prefix}.response`)"
|
||||
:visible.sync="visibleProxy"
|
||||
width="700px"
|
||||
:close-on-click-modal="false"
|
||||
@close="onClose"
|
||||
>
|
||||
<div>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-button type="primary" plain size="mini" @click="copyParam">
|
||||
{{ $t(`${prefix}.copy_request_content`) }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-button type="primary" plain size="mini" @click="copyResult">
|
||||
{{ $t(`${prefix}.copy_response_content`) }}
|
||||
</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider content-position="left">
|
||||
{{ $t(`${prefix}.request_body`) }}
|
||||
</el-divider>
|
||||
<tree-view
|
||||
v-if="paramData"
|
||||
:data="paramData"
|
||||
:options="{ maxDepth: 2 }"
|
||||
style="line-height: 20px; text-align: left;"
|
||||
/>
|
||||
<el-divider content-position="left">
|
||||
{{ $t(`${prefix}.response_content`) }}
|
||||
</el-divider>
|
||||
<tree-view
|
||||
v-if="resultData"
|
||||
:data="resultData"
|
||||
:options="{ maxDepth: 2 }"
|
||||
style="line-height: 20px; text-align: left;"
|
||||
/>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ResponseDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
paramData: { type: Object, default: null },
|
||||
resultData: { type: Object, default: null },
|
||||
prefix: { type: String, required: true }
|
||||
},
|
||||
computed: {
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClose () {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
copyToClipboard (data) {
|
||||
const input = document.createElement('input')
|
||||
input.value = JSON.stringify(data)
|
||||
document.body.appendChild(input)
|
||||
input.select()
|
||||
document.execCommand('Copy')
|
||||
input.style.display = 'none'
|
||||
this.$message.success(this.$t(`${this.prefix}.operation_success`))
|
||||
},
|
||||
copyParam () {
|
||||
this.copyToClipboard(this.paramData)
|
||||
},
|
||||
copyResult () {
|
||||
this.copyToClipboard(this.resultData)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,277 @@
|
||||
<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('interface_name'))">
|
||||
<el-input
|
||||
v-model="search.unit"
|
||||
:placeholder="$t(key('placeholder_interface_name'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
@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 :label="$t(key('batch'))">
|
||||
<el-input
|
||||
v-model="search.batch"
|
||||
:placeholder="$t(key('placeholder_batch'))"
|
||||
clearable
|
||||
style="width:160px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</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>
|
||||
<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('battery_id'))">
|
||||
<el-input
|
||||
v-model="search.battery_id"
|
||||
:placeholder="$t(key('placeholder_battery_barcode'))"
|
||||
clearable
|
||||
style="width:220px"
|
||||
@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>
|
||||
|
||||
<page-table
|
||||
ref="pageTable"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
>
|
||||
<template #col-status="{ row }">
|
||||
<span v-if="row.status === 0" style="color: #67c23a;">
|
||||
<i class="el-icon-circle-check" />
|
||||
{{ $t(key('success')) }}
|
||||
</span>
|
||||
<span v-else style="color: #F56C6C;">
|
||||
<i class="el-icon-circle-close" />
|
||||
{{ $t(key('failure')) }}
|
||||
</span>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<response-dialog
|
||||
:visible.sync="respVisible"
|
||||
:param-data="respParam"
|
||||
:result-data="respResult"
|
||||
:prefix="prefix"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { getInterfaceLogList } from '@/api/system-administration/api-logs'
|
||||
import PageTable from '@/components/page-table'
|
||||
import ResponseDialog from './components/ResponseDialog/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'system-administration-api-logs',
|
||||
components: { PageTable, ResponseDialog },
|
||||
mixins: [i18nMixin('page.system_administration.system_utilities.api_logs')],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
tableData: [],
|
||||
columns: [],
|
||||
rowButtons: [],
|
||||
pagination: { current: 1, size: 10, total: 0 },
|
||||
search: {
|
||||
ip: '',
|
||||
unit: '',
|
||||
status: undefined,
|
||||
batch: '',
|
||||
tray: '',
|
||||
process_code: '',
|
||||
battery_id: '',
|
||||
time: undefined
|
||||
},
|
||||
respVisible: false,
|
||||
respParam: null,
|
||||
respResult: null,
|
||||
searchExpanded: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
prefix () {
|
||||
return 'page.system_administration.system_utilities.api_logs'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'id', label: this.key('id'), width: 65 },
|
||||
{ prop: 'client_ip', label: this.key('ip'), width: 140 },
|
||||
{ prop: 'unit', label: this.key('request_method'), minWidth: 150 },
|
||||
{ prop: 'status', label: this.key('response_status'), slot: 'status', width: 100 },
|
||||
{ prop: 'insterface_time', label: this.key('response_time_ms'), width: 130 },
|
||||
{ prop: 'data1', label: this.key('process_code'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'data2', label: this.key('tray_number'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'data3', label: this.key('battery_id'), showOverflowTooltip: true, minWidth: 130 },
|
||||
{ prop: 'data4', label: this.key('batch_number'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'data5', label: this.key('process_id'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'create_time', label: this.key('create_date'), width: 170 },
|
||||
{ prop: '_actions', label: this.key('operation'), width: 100, fixed: 'right' }
|
||||
], { selectionWidth: 0 })
|
||||
const btns = useTableButtons({
|
||||
row: [
|
||||
{
|
||||
key: 'view',
|
||||
label: this.key('view_response'),
|
||||
icon: 'el-icon-view',
|
||||
auth: '/system_settings/system_assistant/interface_log/view',
|
||||
onClick: this.handleViewResponse
|
||||
}
|
||||
]
|
||||
}, this.$permission)
|
||||
this.rowButtons = btns.rowButtons
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.search,
|
||||
page_no: this.pagination.current,
|
||||
page_size: this.pagination.size
|
||||
}
|
||||
const res = await getInterfaceLogList(params)
|
||||
const data = Array.isArray(res) ? res : (res.data || [])
|
||||
this.tableData = data
|
||||
this.pagination.total = res.count || data.length
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
onSearch () {
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onReset () {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.search.ip = ''
|
||||
this.search.unit = ''
|
||||
this.search.status = undefined
|
||||
this.search.batch = ''
|
||||
this.search.tray = ''
|
||||
this.search.process_code = ''
|
||||
this.search.battery_id = ''
|
||||
this.search.time = undefined
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
handleViewResponse (row) {
|
||||
try {
|
||||
this.respParam = JSON.parse(row.params || '{}')
|
||||
this.respResult = JSON.parse(row.result || '{}')
|
||||
} catch {
|
||||
this.respParam = row.params || {}
|
||||
this.respResult = row.result || {}
|
||||
}
|
||||
this.respVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,259 @@
|
||||
<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('operator'))">
|
||||
<el-select
|
||||
v-model="search.user_id"
|
||||
:placeholder="$t(key('placeholder_operator'))"
|
||||
clearable
|
||||
filterable
|
||||
style="width:200px"
|
||||
>
|
||||
<el-option
|
||||
v-for="u in userList"
|
||||
:key="u.user_id"
|
||||
:value="u.user_id"
|
||||
:label="u.username"
|
||||
/>
|
||||
</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>
|
||||
<div v-show="searchExpanded" class="search-bar__extra">
|
||||
<el-form-item :label="$t(key('batch'))">
|
||||
<el-input
|
||||
v-model="search.batch"
|
||||
:placeholder="$t(key('placeholder_batch'))"
|
||||
clearable
|
||||
style="width:160px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('tray_no'))">
|
||||
<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('create_time'))">
|
||||
<el-date-picker
|
||||
v-model="search.time"
|
||||
type="datetimerange"
|
||||
:placeholder="$t(key('placeholder_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>
|
||||
|
||||
<page-table
|
||||
ref="pageTable"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
>
|
||||
<template #col-status="{ row }">
|
||||
<span v-if="row.status === 0" style="color: #67c23a;">
|
||||
<i class="el-icon-circle-check" />
|
||||
{{ $t(key('success')) }}
|
||||
</span>
|
||||
<span v-else style="color: #F56C6C;">
|
||||
<i class="el-icon-circle-close" />
|
||||
{{ $t(key('failure')) }}
|
||||
</span>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<response-dialog
|
||||
:visible.sync="respVisible"
|
||||
:param-data="respParam"
|
||||
:result-data="respResult"
|
||||
:prefix="prefix"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { getOperateLogList } from '@/api/system-administration/operation-logs'
|
||||
import { getUserList } from '@/api/system-administration/user'
|
||||
import PageTable from '@/components/page-table'
|
||||
import ResponseDialog from '../api-logs/components/ResponseDialog/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'system-administration-operation-logs',
|
||||
components: { PageTable, ResponseDialog },
|
||||
mixins: [i18nMixin('page.system_administration.system_utilities.operation_logs')],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
tableData: [],
|
||||
columns: [],
|
||||
rowButtons: [],
|
||||
userList: [],
|
||||
pagination: { current: 1, size: 10, total: 0 },
|
||||
search: {
|
||||
ip: '',
|
||||
user_id: undefined,
|
||||
batch: '',
|
||||
tray: '',
|
||||
time: undefined
|
||||
},
|
||||
respVisible: false,
|
||||
respParam: null,
|
||||
respResult: null,
|
||||
searchExpanded: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
prefix () {
|
||||
return 'page.system_administration.system_utilities.operation_logs'
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'id', label: this.key('id'), width: 65 },
|
||||
{ prop: 'username', label: this.key('operator'), width: 120 },
|
||||
{ prop: 'ip', label: this.key('ip'), width: 140 },
|
||||
{ prop: 'status', label: this.key('status'), slot: 'status', width: 90 },
|
||||
{ prop: 'action_name', label: this.key('action_name'), showOverflowTooltip: true, minWidth: 130 },
|
||||
{ prop: 'action', label: this.key('action_code'), showOverflowTooltip: true, minWidth: 130 },
|
||||
{ prop: 'path', label: this.key('request_path'), showOverflowTooltip: true, minWidth: 180 },
|
||||
{ prop: 'batch', label: this.key('batch'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'tray', label: this.key('tray_no'), showOverflowTooltip: true, minWidth: 120 },
|
||||
{ prop: 'create_time', label: this.key('create_date'), width: 170 },
|
||||
{ prop: '_actions', label: this.key('operation'), width: 100, fixed: 'right' }
|
||||
], { selectionWidth: 0 })
|
||||
const btns = useTableButtons({
|
||||
row: [
|
||||
{
|
||||
key: 'view',
|
||||
label: this.key('view_response'),
|
||||
icon: 'el-icon-view',
|
||||
auth: '/system_settings/system_assistant/operate_log/view',
|
||||
onClick: this.handleViewResponse
|
||||
}
|
||||
]
|
||||
}, this.$permission)
|
||||
this.rowButtons = btns.rowButtons
|
||||
this.fetchUsers()
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async fetchUsers () {
|
||||
try {
|
||||
const res = await getUserList({ page_no: 1, page_size: 9999 })
|
||||
this.userList = Array.isArray(res) ? res : (res.data || [])
|
||||
} catch { /* 用户列表加载失败不影响主流程 */ }
|
||||
},
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.search,
|
||||
page_no: this.pagination.current,
|
||||
page_size: this.pagination.size
|
||||
}
|
||||
const res = await getOperateLogList(params)
|
||||
const data = Array.isArray(res) ? res : (res.data || [])
|
||||
this.tableData = data
|
||||
this.pagination.total = res.count || data.length
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
onSearch () {
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onReset () {
|
||||
this.$refs.searchFormRef.resetFields()
|
||||
this.search = {
|
||||
ip: '',
|
||||
user_id: undefined,
|
||||
batch: '',
|
||||
tray: '',
|
||||
time: undefined
|
||||
}
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
handleViewResponse (row) {
|
||||
try {
|
||||
this.respParam = JSON.parse(row.params || '{}')
|
||||
this.respResult = JSON.parse(row.result || '{}')
|
||||
} catch {
|
||||
this.respParam = row.params || {}
|
||||
this.respResult = row.result || {}
|
||||
}
|
||||
this.respVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.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%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="textMap[dialogStatus]"
|
||||
:visible.sync="visibleProxy"
|
||||
:append-to-body="true"
|
||||
:close-on-click-modal="false"
|
||||
width="600px"
|
||||
@closed="onClosed"
|
||||
>
|
||||
<el-form :model="form" :rules="rules" ref="form" label-width="110px">
|
||||
<el-form-item :label="$t(`${pre}.category_name`)" prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
:placeholder="$t(`${pre}.placeholder_category_name`)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.parent_category`)" prop="parent_id">
|
||||
<el-cascader
|
||||
v-model="form.parent_id"
|
||||
:options="cascaderOptions"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
expandTrigger: 'hover',
|
||||
checkStrictly: true
|
||||
}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.view_permission`)" prop="role_ids">
|
||||
<el-select v-model="form.role_ids" multiple :placeholder="$t(`${pre}.please_select`)">
|
||||
<el-option
|
||||
v-for="item in roleList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="String(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.sort`)" prop="sort">
|
||||
<el-input-number v-model="form.sort" :min="0" :max="10" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button size="small" @click="close">{{ $t(`${pre}.cancel`) }}</el-button>
|
||||
<el-button type="primary" size="small" :loading="loading" @click="handleSubmit">
|
||||
{{ dialogStatus === 'create' ? $t(`${pre}.confirm`) : $t(`${pre}.update`) }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const TOP_CATEGORY = { id: 0, name: '' }
|
||||
|
||||
export default {
|
||||
name: 'CategoryDialog',
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
categoryTree: { type: Array, default: () => [] },
|
||||
roleList: { type: Array, default: () => [] },
|
||||
pre: { type: String, required: true }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dialogStatus: 'create',
|
||||
loading: false,
|
||||
editId: null,
|
||||
form: this.getDefaultForm(),
|
||||
rules: {
|
||||
name: [{ required: true, message: this.$t(`${this.pre}.category_name_required`), trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
},
|
||||
textMap () {
|
||||
return {
|
||||
create: this.$t(`${this.pre}.dialog_create_title`),
|
||||
update: this.$t(`${this.pre}.dialog_edit_title`)
|
||||
}
|
||||
},
|
||||
cascaderOptions () {
|
||||
const top = { ...TOP_CATEGORY, name: this.$t(`${this.pre}.top_category`) }
|
||||
return [top].concat(this.categoryTree)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaultForm () {
|
||||
return { name: undefined, parent_id: undefined, sort: 0, role_ids: [] }
|
||||
},
|
||||
openCreate () {
|
||||
this.dialogStatus = 'create'
|
||||
this.form = this.getDefaultForm()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) this.$refs.form.clearValidate()
|
||||
})
|
||||
},
|
||||
openEdit (row) {
|
||||
this.dialogStatus = 'update'
|
||||
this.editId = row.id
|
||||
this.form = {
|
||||
name: row.name,
|
||||
parent_id: row.parent_id != null ? [row.parent_id] : [],
|
||||
role_ids: row.role_ids || [],
|
||||
sort: row.sort || 0
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) this.$refs.form.clearValidate()
|
||||
})
|
||||
},
|
||||
handleSubmit () {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
this.loading = true
|
||||
this.$emit('submit', {
|
||||
status: this.dialogStatus,
|
||||
id: this.editId,
|
||||
form: { ...this.form }
|
||||
})
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
onClosed () {
|
||||
this.form = this.getDefaultForm()
|
||||
},
|
||||
close () {
|
||||
this.$emit('update:visible', false)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<el-drawer
|
||||
:title="drawerTitle"
|
||||
:visible.sync="visibleProxy"
|
||||
direction="rtl"
|
||||
size="80%"
|
||||
:before-close="handleBeforeClose"
|
||||
>
|
||||
<div v-loading="loading" class="editor-body">
|
||||
<el-form
|
||||
ref="form"
|
||||
label-position="right"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
label-width="80px"
|
||||
>
|
||||
<el-form-item :label="$t(`${pre}.title`)" prop="title">
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.describe`)" prop="describe">
|
||||
<el-input type="textarea" :maxlength="30" show-word-limit v-model="form.describe" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.parent_category`)" prop="category_id">
|
||||
<el-cascader
|
||||
v-model="form.category_id"
|
||||
:options="categoryTreeData"
|
||||
:props="{
|
||||
value: 'id',
|
||||
label: 'name',
|
||||
expandTrigger: 'hover',
|
||||
checkStrictly: true
|
||||
}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.view_permission`)" prop="role_ids">
|
||||
<el-select v-model="form.role_ids" multiple :placeholder="$t(`${pre}.please_select`)">
|
||||
<el-option
|
||||
v-for="item in roleList"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="String(item.id)"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.sort`)" prop="sort">
|
||||
<el-input-number v-model="form.sort" :min="0" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`${pre}.type`)">
|
||||
<el-radio-group v-model="form.type">
|
||||
<el-radio label="md">{{ $t(`${pre}.document`) }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item v-show="form.type === 'md'" style="width:100%">
|
||||
<mavon-editor
|
||||
ref="mavon"
|
||||
style="height:500px"
|
||||
v-model="form.markdown"
|
||||
@imgAdd="handleUploadImages"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" :disabled="submitting" @click="handleAdd">
|
||||
{{ $t(`${pre}.add`) }}
|
||||
</el-button>
|
||||
<el-button @click="closeDrawer">{{ $t(`${pre}.cancel`) }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mavonEditor } from 'mavon-editor'
|
||||
import 'mavon-editor/dist/css/index.css'
|
||||
import axios from 'axios'
|
||||
|
||||
export default {
|
||||
name: 'MarkdownEditor',
|
||||
components: { mavonEditor },
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
editData: { type: Object, default: null },
|
||||
categoryTreeData: { type: Array, default: () => [] },
|
||||
roleList: { type: Array, default: () => [] },
|
||||
pre: { type: String, required: true }
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
isEdit: false,
|
||||
editId: null,
|
||||
form: this.getDefaultForm(),
|
||||
rules: {
|
||||
title: [{ required: true, message: this.$t(`${this.pre}.enter_title`), trigger: 'blur' }],
|
||||
describe: [{ required: true, message: this.$t(`${this.pre}.enter_describe`), trigger: 'blur' }],
|
||||
category_id: [{ required: true, message: this.$t(`${this.pre}.select_parent_menu`), trigger: 'blur' }],
|
||||
role_ids: [{ required: true, message: this.$t(`${this.pre}.select_role_group`), trigger: 'blur' }]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
visibleProxy: {
|
||||
get () { return this.visible },
|
||||
set (val) { this.$emit('update:visible', val) }
|
||||
},
|
||||
drawerTitle () {
|
||||
return this.isEdit ? this.$t(`${this.pre}.edit_document`) : this.$t(`${this.pre}.add_document`)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible (val) {
|
||||
if (val && this.editData) {
|
||||
this.isEdit = true
|
||||
this.editId = this.editData.id
|
||||
this.form = {
|
||||
title: this.editData.title,
|
||||
category_id: this.editData.category_id,
|
||||
describe: this.editData.describe,
|
||||
markdown: this.editData.markdown || '',
|
||||
type: this.editData.type || 'md',
|
||||
role_ids: this.editData.role_ids ? this.editData.role_ids.map(String) : [],
|
||||
url: this.editData.url || '',
|
||||
sort: this.editData.sort || 0
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) this.$refs.form.clearValidate()
|
||||
})
|
||||
} else if (val && !this.editData) {
|
||||
this.isEdit = false
|
||||
this.editId = null
|
||||
this.resetForm()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getDefaultForm () {
|
||||
return {
|
||||
title: undefined,
|
||||
category_id: undefined,
|
||||
describe: undefined,
|
||||
markdown: '',
|
||||
type: 'md',
|
||||
role_ids: [],
|
||||
url: '',
|
||||
sort: 0
|
||||
}
|
||||
},
|
||||
resetForm () {
|
||||
this.form = this.getDefaultForm()
|
||||
this.submitting = false
|
||||
this.isEdit = false
|
||||
this.editId = null
|
||||
},
|
||||
handleAdd () {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return
|
||||
if (this.form.type === 'md' && !this.form.markdown) {
|
||||
this.$message.warning(this.$t(`${this.pre}.content_required`))
|
||||
return
|
||||
}
|
||||
this.submitting = true
|
||||
this.$emit('submit', this.isEdit
|
||||
? { id: this.editId, ...this.form }
|
||||
: { ...this.form }
|
||||
)
|
||||
})
|
||||
},
|
||||
handleBeforeClose (done) {
|
||||
this.$confirm(this.$t(`${this.pre}.confirm_close`))
|
||||
.then(() => {
|
||||
this.resetForm()
|
||||
done()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
closeDrawer () {
|
||||
this.resetForm()
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
handleUploadImages (pos, file) {
|
||||
const formdata = new FormData()
|
||||
formdata.append('file', file)
|
||||
formdata.append('type', 'image')
|
||||
axios({
|
||||
url: process.env.VUE_APP_UPLOAD_PATH,
|
||||
method: 'post',
|
||||
data: formdata,
|
||||
headers: { 'Content-Type': 'multipart/form-data' }
|
||||
}).then(url => {
|
||||
this.$refs.mavon.$img2Url(pos, process.env.VUE_APP_PRO_PUBLIC_URL + url.data.url)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.editor-body {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="custom-menu-tree">
|
||||
<template v-for="item in dataList">
|
||||
<el-menu-item v-if="!item.children" :key="item.id" :index="String(item.id)">
|
||||
<span class="tree-icon">
|
||||
<i v-if="item.type === 'md'" class="el-icon-folder-opened" style="color: rgba(144, 198, 252, 1)" />
|
||||
<i v-else class="el-icon-files" style="color: rgba(144, 198, 252, 1)" />
|
||||
</span>
|
||||
<span class="tree-name" :title="item.name">{{ item.name }}</span>
|
||||
<slot name="menu" :menu-id="item.id" :type="item.type" :file-list="{ url: item.url, name: item.url, file_type: item.file_type }" />
|
||||
</el-menu-item>
|
||||
<el-submenu v-else :key="item.id" :index="String(item.id)">
|
||||
<template slot="title">
|
||||
<i :class="[item.icon]" />
|
||||
<span>{{ item.name }}</span>
|
||||
<slot name="submenu" :menu-data="item" :menu-id="item.id" />
|
||||
</template>
|
||||
<MenuTree :data-list="item.children">
|
||||
<template slot="menu" scope="ctx">
|
||||
<slot name="menu" :menu-id="ctx.menuId" :type="ctx.type" :file-list="ctx.fileList" />
|
||||
</template>
|
||||
<template slot="submenu" scope="ctx">
|
||||
<slot name="submenu" :menu-data="ctx.menuData" :menu-id="ctx.menuId" />
|
||||
</template>
|
||||
</MenuTree>
|
||||
</el-submenu>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MenuTree',
|
||||
props: {
|
||||
dataList: { type: Array, default: () => [] }
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
::v-deep .is-opened > .el-submenu__title {
|
||||
border-left: 3px solid #409EFF;
|
||||
}
|
||||
.tree-name {
|
||||
display: inline-block;
|
||||
width: 58%;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin-left: 5px;
|
||||
}
|
||||
.tree-icon {
|
||||
background: rgba(232, 239, 248, 1);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 0 0 2px 2px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="problem-header">
|
||||
<el-input
|
||||
:placeholder="$t(key('search_placeholder'))"
|
||||
v-model="searchTitle"
|
||||
size="mini"
|
||||
clearable
|
||||
style="width:220px"
|
||||
@clear="onClearSearch"
|
||||
@keyup.enter.native="onSearch"
|
||||
>
|
||||
<el-button slot="append" icon="el-icon-search" @click="onSearch" />
|
||||
</el-input>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
style="margin-left:10px"
|
||||
@click="openCategoryCreate"
|
||||
>
|
||||
{{ $t(key('add_directory')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-container class="problem-content">
|
||||
<el-aside width="300px" class="problem-aside">
|
||||
<div v-if="searchShow" class="search-result">
|
||||
<el-card
|
||||
v-for="item in searchData"
|
||||
:key="item.id"
|
||||
:body-style="{ padding: '10px' }"
|
||||
shadow="never"
|
||||
>
|
||||
<div class="search-item-title" @click="handleSelect(item.id)">
|
||||
<span class="tree-icon">
|
||||
<i
|
||||
v-if="item.type === 'md'"
|
||||
class="el-icon-folder-opened"
|
||||
style="color: rgba(144, 198, 252, 1)"
|
||||
/>
|
||||
<i v-else class="el-icon-files" style="color: rgba(144, 198, 252, 1)" />
|
||||
</span>
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="search-item-desc">{{ item.describe }}</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="aside-toolbar">
|
||||
<el-button type="primary" size="mini" icon="el-icon-plus" @click="openEditor">
|
||||
{{ $t(key('add_document')) }}
|
||||
</el-button>
|
||||
<el-button type="success" size="mini" icon="el-icon-edit-outline" @click="openCategoryEdit" v-permission="'/setting/aide/problem/set'">
|
||||
{{ $t(key('edit')) }}
|
||||
</el-button>
|
||||
<el-button type="danger" size="mini" icon="el-icon-delete" @click="handleDeleteCategory" v-permission="'/setting/aide/problem/del'">
|
||||
{{ $t(key('delete')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-menu
|
||||
class="problem-menu"
|
||||
:unique-opened="true"
|
||||
@select="handleSelect"
|
||||
@open="handleOpen"
|
||||
>
|
||||
<MenuTree :data-list="problemTree">
|
||||
<template slot="menu" scope="ctx">
|
||||
<div class="menu-actions">
|
||||
<el-button
|
||||
v-if="ctx.type === 'file'"
|
||||
type="text"
|
||||
size="mini"
|
||||
@click.stop="handleDownload(ctx.fileList.url, ctx.fileList.file_type)"
|
||||
>
|
||||
{{ $t(key('download')) }}
|
||||
</el-button>
|
||||
<el-button v-if="ctx.type === 'md'" type="text" size="mini" @click.stop="handleEditMarkdown(ctx.menuId)">
|
||||
{{ $t(key('edit')) }}
|
||||
</el-button>
|
||||
<el-button type="text" size="mini" style="margin-left:0" @click.stop="handleDeleteMarkdown(ctx.menuId)">
|
||||
{{ $t(key('delete')) }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</MenuTree>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
|
||||
<el-main class="problem-main">
|
||||
<el-card v-if="docVisible" class="doc-card" shadow="never">
|
||||
<div slot="header" class="doc-header">
|
||||
<h1 style="margin:0">{{ docDetail.title }}</h1>
|
||||
<h6 style="margin:0">
|
||||
{{ docDetail.create_time }}
|
||||
{{ $t(key('submitter')) }}{{ docDetail.admin && docDetail.admin.name }}
|
||||
</h6>
|
||||
</div>
|
||||
<d2-markdown :key="markdownKey" :source="docDetail.markdown" />
|
||||
</el-card>
|
||||
<div v-else class="doc-empty">
|
||||
<i class="el-icon-document" style="font-size:64px;color:#dcdfe6" />
|
||||
<p style="color:#909399">{{ $t(key('select_document_tip')) }}</p>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
|
||||
<category-dialog
|
||||
ref="categoryDialog"
|
||||
:visible.sync="categoryVisible"
|
||||
:category-tree="categoryTree"
|
||||
:role-list="roleList"
|
||||
:pre="i18nPrefix"
|
||||
@submit="onCategorySubmit"
|
||||
/>
|
||||
|
||||
<markdown-editor
|
||||
ref="editorDrawer"
|
||||
:visible.sync="editorVisible"
|
||||
:edit-data="editDocData"
|
||||
:category-tree-data="categoryTree"
|
||||
:role-list="roleList"
|
||||
:pre="i18nPrefix"
|
||||
@submit="onEditorSubmit"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import {
|
||||
getCategoryTree,
|
||||
setCategoryAdd,
|
||||
setCategoryUpdate,
|
||||
delCategory,
|
||||
getProblemTree,
|
||||
getMarkdownDetails,
|
||||
setMarkdownAdd,
|
||||
setMarkdownEdit,
|
||||
delMarkdownDetails,
|
||||
searchMarkdown,
|
||||
getRoleAll
|
||||
} from '@/api/system-administration/problem-help'
|
||||
import MenuTree from './components/MenuTree/index.vue'
|
||||
import CategoryDialog from './components/CategoryDialog/index.vue'
|
||||
import MarkdownEditor from './components/MarkdownEditor/index.vue'
|
||||
|
||||
export default {
|
||||
name: 'system-administration-problem-help',
|
||||
components: { MenuTree, CategoryDialog, MarkdownEditor },
|
||||
mixins: [i18nMixin('page.system_administration.system_utilities.problem_help')],
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
searchTitle: '',
|
||||
searchData: [],
|
||||
searchShow: false,
|
||||
categoryTree: [],
|
||||
problemTree: [],
|
||||
roleList: [],
|
||||
selectedMenuId: null,
|
||||
docVisible: false,
|
||||
docDetail: { title: '', create_time: '', admin: {}, markdown: '' },
|
||||
markdownKey: false,
|
||||
categoryVisible: false,
|
||||
editorVisible: false,
|
||||
editDocData: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
i18nPrefix () {
|
||||
return 'page.system_administration.system_utilities.problem_help'
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchTree()
|
||||
},
|
||||
methods: {
|
||||
async fetchTree () {
|
||||
try {
|
||||
const [catRes, probRes, roleRes] = await Promise.all([
|
||||
getCategoryTree(),
|
||||
getProblemTree(),
|
||||
getRoleAll()
|
||||
])
|
||||
this.categoryTree = catRes.data || []
|
||||
this.problemTree = probRes.data || []
|
||||
this.roleList = Array.isArray(roleRes) ? roleRes : (roleRes.data || [])
|
||||
} catch { /* ignore */ }
|
||||
},
|
||||
onSearch () {
|
||||
if (!this.searchTitle) { this.onClearSearch(); return }
|
||||
searchMarkdown({ title: this.searchTitle }).then(res => {
|
||||
this.searchData = res.data || []
|
||||
this.searchShow = this.searchData.length > 0
|
||||
})
|
||||
},
|
||||
onClearSearch () {
|
||||
this.searchShow = false
|
||||
this.searchData = []
|
||||
},
|
||||
handleOpen (key) {
|
||||
this.selectedMenuId = key
|
||||
},
|
||||
handleSelect (id) {
|
||||
getMarkdownDetails({ id }).then(res => {
|
||||
if (res.data && res.data.type !== 'file') {
|
||||
this.docDetail = res.data
|
||||
this.markdownKey = !this.markdownKey
|
||||
this.docVisible = true
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDeleteCategory () {
|
||||
if (!this.selectedMenuId) {
|
||||
this.$message.error(this.$t(this.key('select_delete_directory')))
|
||||
return
|
||||
}
|
||||
this.$confirm(this.$t(this.key('confirm_message')), this.$t(this.key('prompt')), { type: 'warning' })
|
||||
.then(() => delCategory({ id: this.selectedMenuId }))
|
||||
.then(() => {
|
||||
this.$message.success(this.$t(this.key('delete_success')))
|
||||
this.fetchTree()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleEditMarkdown (menuId) {
|
||||
getMarkdownDetails({ id: menuId }).then(res => {
|
||||
if (res.data && res.data.type !== 'file') {
|
||||
this.editDocData = res.data
|
||||
this.editorVisible = true
|
||||
}
|
||||
})
|
||||
},
|
||||
handleDeleteMarkdown (menuId) {
|
||||
this.$confirm(this.$t(this.key('confirm_message')), this.$t(this.key('prompt')), { type: 'warning' })
|
||||
.then(() => delMarkdownDetails({ id: menuId }))
|
||||
.then(() => {
|
||||
this.$message.success(this.$t(this.key('delete_success')))
|
||||
this.fetchTree()
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
handleDownload (url, fileType) {
|
||||
window.open(url, '_blank')
|
||||
},
|
||||
openCategoryCreate () {
|
||||
this.$refs.categoryDialog && this.$refs.categoryDialog.openCreate()
|
||||
this.categoryVisible = true
|
||||
},
|
||||
openCategoryEdit () {
|
||||
if (!this.selectedMenuId) {
|
||||
this.$message.warning(this.$t(this.key('select_edit_directory')))
|
||||
return
|
||||
}
|
||||
const findNode = (nodes) => {
|
||||
for (const n of nodes) {
|
||||
if (n.id === this.selectedMenuId) return n
|
||||
if (n.children) {
|
||||
const found = findNode(n.children)
|
||||
if (found) return found
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
const node = findNode(this.categoryTree)
|
||||
if (node) {
|
||||
this.$refs.categoryDialog && this.$refs.categoryDialog.openEdit(node)
|
||||
this.categoryVisible = true
|
||||
}
|
||||
},
|
||||
onCategorySubmit ({ status, id, form }) {
|
||||
const api = status === 'create' ? setCategoryAdd : setCategoryUpdate
|
||||
const data = status === 'create' ? form : { id, ...form }
|
||||
api(data).then(() => {
|
||||
this.$message.success(status === 'create'
|
||||
? this.$t(this.key('add_success'))
|
||||
: this.$t(this.key('update_success')))
|
||||
this.categoryVisible = false
|
||||
this.fetchTree()
|
||||
})
|
||||
},
|
||||
openEditor () {
|
||||
this.editDocData = null
|
||||
this.editorVisible = true
|
||||
},
|
||||
onEditorSubmit (form) {
|
||||
const promise = form.id
|
||||
? setMarkdownEdit({ id: form.id, ...form })
|
||||
: setMarkdownAdd(form)
|
||||
promise.then(() => {
|
||||
this.$message.success(this.$t(this.key(form.id ? 'edit_success' : 'create_success')))
|
||||
this.editorVisible = false
|
||||
this.editDocData = null
|
||||
this.fetchTree()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.problem-header {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.problem-content {
|
||||
height: calc(100vh - 160px);
|
||||
}
|
||||
.problem-aside {
|
||||
border: solid 1px rgba(240, 240, 240, 1);
|
||||
border-radius: 4px 0 0 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.problem-menu {
|
||||
border-top: solid 1px rgba(240, 240, 240, 1);
|
||||
}
|
||||
.aside-toolbar {
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.aside-toolbar .el-button {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.search-result {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.search-item-title {
|
||||
font-size: 14px;
|
||||
padding: 3px 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.search-item-desc {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.menu-actions {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 0;
|
||||
}
|
||||
.tree-icon {
|
||||
background: rgba(232, 239, 248, 1);
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
padding: 0 0 2px 2px;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
.problem-main {
|
||||
border: solid 1px rgba(240, 240, 240, 1);
|
||||
border-left: none;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
.doc-card {
|
||||
min-height: 100%;
|
||||
}
|
||||
.doc-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -14,10 +14,11 @@
|
||||
node-key="menu_id"
|
||||
:data="treeData"
|
||||
:props="{ label: 'name', children: 'children' }"
|
||||
:default-expand-all="false"
|
||||
:expand-on-click-node="false"
|
||||
:check-strictly="true"
|
||||
:default-expand-all="true"
|
||||
:expand-on-click-node="true"
|
||||
:default-checked-keys="checkedMenuIds"
|
||||
show-checkbox
|
||||
:draggable="true"
|
||||
@check="onTreeCheck"
|
||||
>
|
||||
<span slot-scope="{ node, data }" class="perm-drawer__tree-node">
|
||||
@@ -46,14 +47,14 @@
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import util from '@/libs/util'
|
||||
import { getMenuAll } from '@/api/menu'
|
||||
import { giveRoleMenu, getRoleMenu } from '@/api/system-administration/role'
|
||||
import { giveRoleMenu } from '@/api/system-administration/role'
|
||||
|
||||
export default {
|
||||
name: 'RolePermDrawer',
|
||||
mixins: [i18nMixin('page.system_administration.user_management.role')],
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
roleId: { type: Number, default: 0 },
|
||||
role: { type: Object, default: () => ({}) },
|
||||
title: { type: String, default: '' },
|
||||
confirmText: { type: String, default: '' },
|
||||
cancelText: { type: String, default: '' },
|
||||
@@ -84,18 +85,15 @@ export default {
|
||||
this.treeReady = false
|
||||
this.treeLoading = true
|
||||
try {
|
||||
const [menuRes, roleMenuRes] = await Promise.all([
|
||||
getMenuAll()
|
||||
])
|
||||
const menuRes = await getMenuAll()
|
||||
const menuData = Array.isArray(menuRes) ? menuRes : (menuRes.data || [])
|
||||
const roleData = Array.isArray(roleMenuRes) ? roleMenuRes : (roleMenuRes.data || [])
|
||||
this.checkedMenuIds = roleData.map(item => item.menu_id)
|
||||
this.checkedMenuIds = JSON.parse(this.role.menu_admin || '[]')
|
||||
this.treeData = util.formatDataToTree(menuData)
|
||||
this.treeReady = true
|
||||
await this.$nextTick()
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
this.$refs.permTree.setCheckedKeys(this.checkedMenuIds)
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
this.treeLoading = false
|
||||
}, 1000)
|
||||
} catch {
|
||||
this.treeLoading = false
|
||||
}
|
||||
},
|
||||
@@ -112,7 +110,7 @@ export default {
|
||||
this.submitting = true
|
||||
try {
|
||||
const menuIds = this.$refs.permTree.getCheckedKeys()
|
||||
await giveRoleMenu({ role_id: this.roleId, role_menu: menuIds })
|
||||
await giveRoleMenu({ role_id: this.role.id, role_menu: menuIds })
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.visibleProxy = false
|
||||
this.$emit('saved')
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
<role-perm-drawer
|
||||
:visible.sync="permVisible"
|
||||
:role-id="permRole.id"
|
||||
:role="permRole"
|
||||
:title="key('assign_permissions')"
|
||||
:confirm-text="key('confirm')"
|
||||
:cancel-text="key('cancel')"
|
||||
@@ -322,7 +322,7 @@ export default {
|
||||
}
|
||||
try {
|
||||
await updateRoleStatus({
|
||||
ids: rows.map(row => row.id),
|
||||
id: rows.map(row => row.id),
|
||||
status
|
||||
})
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
|
||||
443
src/views/system-administration/user-management/user/index.vue
Normal file
443
src/views/system-administration/user-management/user/index.vue
Normal file
@@ -0,0 +1,443 @@
|
||||
<template>
|
||||
<d2-container>
|
||||
<template #header>
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" size="mini">
|
||||
<el-form-item :label="$t(key('username'))">
|
||||
<el-input
|
||||
v-model="search.username"
|
||||
:placeholder="$t(key('enter_username'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(key('full_name'))">
|
||||
<el-input
|
||||
v-model="search.nickname"
|
||||
:placeholder="$t(key('enter_name'))"
|
||||
clearable
|
||||
style="width:200px"
|
||||
@keyup.enter.native="onSearch"
|
||||
/>
|
||||
</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-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<page-table
|
||||
ref="pageTable"
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:toolbar-buttons="toolbarButtons"
|
||||
:row-buttons="rowButtons"
|
||||
:pagination="pagination"
|
||||
auto-height
|
||||
@page-change="onPageChange"
|
||||
@selection-change="onSelect"
|
||||
>
|
||||
<template #col-status="{ row }">
|
||||
<span v-if="row.status === 1" style="color: #67c23a;">
|
||||
<i class="el-icon-circle-check" />
|
||||
{{ $t(key('enable')) }}
|
||||
</span>
|
||||
<span v-else style="color: #909399;">
|
||||
<i class="el-icon-circle-close" />
|
||||
{{ $t(key('disable')) }}
|
||||
</span>
|
||||
</template>
|
||||
</page-table>
|
||||
|
||||
<page-dialog-form
|
||||
ref="dialogForm"
|
||||
:visible.sync="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
width="35%"
|
||||
:form-cols="dialogFormCols"
|
||||
:form-data="formData"
|
||||
:rules="dialogRules"
|
||||
label-width="100px"
|
||||
:submitting="submitting"
|
||||
:confirm-text="key('confirm')"
|
||||
:cancel-text="key('cancel')"
|
||||
@submit="onDialogSubmit"
|
||||
@close="onDialogClose"
|
||||
/>
|
||||
</d2-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useTableColumns } from '@/composables/useTableColumns'
|
||||
import { useTableButtons } from '@/composables/useTableButtons'
|
||||
import { i18nMixin } from '@/composables/useI18n'
|
||||
import { confirmMixin } from '@/composables/useConfirmHandle'
|
||||
import { getRoleAll } from '@/api/system-administration/role'
|
||||
import {
|
||||
getUserList,
|
||||
createUser,
|
||||
editUser,
|
||||
deleteUser,
|
||||
batchDeleteUser,
|
||||
enableUser,
|
||||
disableUser,
|
||||
resetUserPwd
|
||||
} from '@/api/system-administration/user'
|
||||
import PageTable from '@/components/page-table'
|
||||
import PageDialogForm from '@/components/page-dialog-form'
|
||||
|
||||
const ownUserId = () => localStorage.getItem('user_id')
|
||||
|
||||
export default {
|
||||
name: 'system-administration-user',
|
||||
components: { PageTable, PageDialogForm },
|
||||
mixins: [i18nMixin('page.system_administration.user_management.user'), confirmMixin],
|
||||
data () {
|
||||
const key = this.key.bind(this)
|
||||
const $t = this.$t.bind(this)
|
||||
return {
|
||||
loading: false,
|
||||
submitting: false,
|
||||
tableData: [],
|
||||
selectedRows: [],
|
||||
dialogVisible: false,
|
||||
dialogTitle: '',
|
||||
editId: '',
|
||||
handleType: 'create',
|
||||
search: { username: '', nickname: '' },
|
||||
pagination: { current: 1, size: 10, total: 0 },
|
||||
roleOptions: [],
|
||||
columns: [],
|
||||
toolbarButtons: [],
|
||||
rowButtons: [],
|
||||
baseFormCols: {
|
||||
create: [
|
||||
[{ type: 'input', prop: 'username', label: key('username'), placeholder: key('enter_username'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'input', prop: 'password', inputType: 'password', label: key('password'), placeholder: key('enter_password'), clearable: true, showPassword: true, style: { width: '90%' } }],
|
||||
[{ type: 'input', prop: 'password_confirm', inputType: 'password', label: key('confirm_password'), placeholder: key('enter_confirm_password'), clearable: true, showPassword: true, style: { width: '90%' } }],
|
||||
[{ type: 'select', prop: 'role_id', label: key('user_group'), placeholder: key('select_user_group'), clearable: true, style: { width: '90%' }, options: [] }],
|
||||
[{ type: 'input', prop: 'nickname', label: key('full_name'), placeholder: key('enter_name'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'input', prop: 'pass_number', label: key('pass_number'), placeholder: key('enter_pass_number'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'select', prop: 'status', label: key('status'), clearable: false, style: { width: '90%' }, options: [{ value: '1', label: $t(key('enable')) }, { value: '0', label: $t(key('disable')) }] }]
|
||||
],
|
||||
edit: [
|
||||
[{ type: 'input', prop: 'username', label: key('username'), placeholder: key('enter_username'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'select', prop: 'role_id', label: key('user_group'), placeholder: key('select_user_group'), clearable: true, style: { width: '90%' }, options: [] }],
|
||||
[{ type: 'input', prop: 'nickname', label: key('full_name'), placeholder: key('enter_name'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'input', prop: 'pass_number', label: key('pass_number'), placeholder: key('enter_pass_number'), clearable: true, style: { width: '90%' } }],
|
||||
[{ type: 'select', prop: 'status', label: key('status'), clearable: false, style: { width: '90%' }, options: [{ value: '1', label: $t(key('enable')) }, { value: '0', label: $t(key('disable')) }] }]
|
||||
]
|
||||
},
|
||||
baseRules: {
|
||||
username: [
|
||||
{ required: true, message: key('enter_username'), trigger: 'blur' },
|
||||
{ min: 3, max: 20, message: key('username_length'), trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: key('enter_password'), trigger: 'blur' },
|
||||
{ min: 6, max: 64, message: key('password_length'), trigger: 'blur' }
|
||||
],
|
||||
password_confirm: [
|
||||
{ required: true, message: key('enter_confirm_password'), trigger: 'blur' },
|
||||
{ min: 6, max: 64, message: key('password_length'), trigger: 'blur' }
|
||||
],
|
||||
role_id: [
|
||||
{ required: true, message: key('select_user_group'), trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogFormCols () {
|
||||
const cols = this.baseFormCols[this.handleType] || this.baseFormCols.create
|
||||
const roleIdx = this.handleType === 'create' ? 3 : 1
|
||||
if (cols[roleIdx] && cols[roleIdx][0]) {
|
||||
cols[roleIdx][0].options = this.roleOptions
|
||||
}
|
||||
return cols
|
||||
},
|
||||
dialogRules () {
|
||||
if (this.handleType === 'edit') {
|
||||
return {
|
||||
username: this.baseRules.username,
|
||||
role_id: this.baseRules.role_id
|
||||
}
|
||||
}
|
||||
return this.baseRules
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.initRoleOptions()
|
||||
this.columns = useTableColumns([
|
||||
{ prop: 'sort', label: this.key('index'), width: 80 },
|
||||
{ prop: 'username', label: this.key('username'), minWidth: 120 },
|
||||
{ prop: 'nickname', label: this.key('full_name'), minWidth: 100 },
|
||||
{ prop: 'pass_number', label: this.key('pass_number'), minWidth: 120 },
|
||||
{ prop: 'status', label: this.key('status'), slot: 'status', width: 100 },
|
||||
{ prop: 'role_name', label: this.key('user_group'), minWidth: 100 },
|
||||
{ prop: 'last_ip', label: this.key('login_ip'), width: 130 },
|
||||
{ prop: 'create_time', label: this.key('last_login_time'), width: 160 },
|
||||
{ prop: '_actions', label: this.key('actions'), width: 240, fixed: 'right' }
|
||||
])
|
||||
const btns = useTableButtons({
|
||||
toolbar: [
|
||||
{
|
||||
key: 'add',
|
||||
label: this.key('add'),
|
||||
icon: 'el-icon-plus',
|
||||
type: 'primary',
|
||||
auth: '/system_settings/user_management/member/create',
|
||||
onClick: this.openAdd
|
||||
},
|
||||
{
|
||||
key: 'enable',
|
||||
label: this.key('enable'),
|
||||
icon: 'el-icon-check',
|
||||
type: 'success',
|
||||
auth: '/system_settings/user_management/member/enable',
|
||||
onClick: () => this.batchUpdateStatus(1)
|
||||
},
|
||||
{
|
||||
key: 'disable',
|
||||
label: this.key('disable'),
|
||||
icon: 'el-icon-close',
|
||||
type: 'warning',
|
||||
auth: '/system_settings/user_management/member/disable',
|
||||
onClick: () => this.batchUpdateStatus(0)
|
||||
},
|
||||
{
|
||||
key: 'batch_delete',
|
||||
label: this.key('batch_delete'),
|
||||
icon: 'el-icon-delete',
|
||||
type: 'danger',
|
||||
auth: '/system_settings/user_management/member/batch-delete',
|
||||
onClick: this.handleBatchDelete
|
||||
}
|
||||
],
|
||||
row: [
|
||||
{
|
||||
key: 'edit',
|
||||
label: this.key('edit'),
|
||||
icon: 'el-icon-edit',
|
||||
auth: '/system_settings/user_management/member/edit',
|
||||
onClick: this.openEdit
|
||||
},
|
||||
{
|
||||
key: 'reset_pwd',
|
||||
label: this.key('reset_password'),
|
||||
icon: 'el-icon-refresh',
|
||||
auth: '/system_settings/user_management/member/reset-pwd',
|
||||
onClick: this.handleResetPwd
|
||||
},
|
||||
{
|
||||
key: 'delete',
|
||||
label: this.key('delete'),
|
||||
icon: 'el-icon-delete',
|
||||
color: 'danger',
|
||||
auth: '/system_settings/user_management/member/delete',
|
||||
onClick: this.handleDelete
|
||||
}
|
||||
]
|
||||
}, this.$permission)
|
||||
this.toolbarButtons = btns.toolbarButtons
|
||||
this.rowButtons = btns.rowButtons
|
||||
this.fetchData()
|
||||
},
|
||||
methods: {
|
||||
async initRoleOptions () {
|
||||
try {
|
||||
const res = await getRoleAll()
|
||||
const data = Array.isArray(res) ? res : (res.data || [])
|
||||
this.roleOptions = data.map(item => ({ value: item.id, label: item.name }))
|
||||
} catch { /* 忽略 */ }
|
||||
},
|
||||
async fetchData () {
|
||||
this.loading = true
|
||||
try {
|
||||
const res = await getUserList({
|
||||
...this.search,
|
||||
page_no: this.pagination.current,
|
||||
page_size: this.pagination.size
|
||||
})
|
||||
const list = Array.isArray(res) ? res : (res.data || [])
|
||||
const total = Array.isArray(res) ? res.length : (res.count || 0)
|
||||
this.tableData = list
|
||||
this.pagination.total = total
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
onSearch () {
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onReset () {
|
||||
this.search = { username: '', nickname: '' }
|
||||
this.pagination.current = 1
|
||||
this.fetchData()
|
||||
},
|
||||
onPageChange (page) {
|
||||
this.pagination.current = page.current
|
||||
this.pagination.size = page.size
|
||||
this.fetchData()
|
||||
},
|
||||
onSelect (rows) {
|
||||
this.selectedRows = rows
|
||||
},
|
||||
resetForm () {
|
||||
this.formData = {
|
||||
username: '',
|
||||
password: '',
|
||||
password_confirm: '',
|
||||
role_id: '',
|
||||
nickname: '',
|
||||
pass_number: '',
|
||||
status: '1'
|
||||
}
|
||||
this.editId = ''
|
||||
},
|
||||
openAdd () {
|
||||
this.handleType = 'create'
|
||||
this.dialogTitle = this.key('add_title')
|
||||
this.$nextTick(() => {
|
||||
this.$refs.dialogForm && this.$refs.dialogForm.reset()
|
||||
this.resetForm()
|
||||
this.dialogVisible = true
|
||||
})
|
||||
},
|
||||
openEdit (row) {
|
||||
this.handleType = 'edit'
|
||||
this.dialogTitle = this.key('edit_title')
|
||||
this.editId = row.user_id
|
||||
this.formData = {
|
||||
username: row.username,
|
||||
role_id: row.role_id,
|
||||
nickname: row.nickname || '',
|
||||
pass_number: row.pass_number || '',
|
||||
status: String(row.status)
|
||||
}
|
||||
this.dialogVisible = true
|
||||
},
|
||||
async onDialogSubmit () {
|
||||
this.submitting = true
|
||||
try {
|
||||
if (this.handleType === 'create') {
|
||||
if (this.formData.password !== this.formData.password_confirm) {
|
||||
this.$message.error(this.$t(this.key('password_not_match')))
|
||||
return
|
||||
}
|
||||
await createUser(this.formData)
|
||||
} else {
|
||||
await editUser({ ...this.formData, id: this.editId })
|
||||
}
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.dialogVisible = false
|
||||
this.fetchData()
|
||||
} finally {
|
||||
this.submitting = false
|
||||
}
|
||||
},
|
||||
onDialogClose () {
|
||||
this.resetForm()
|
||||
},
|
||||
batchUpdateStatus (status) {
|
||||
const uid = ownUserId()
|
||||
const rows = this.selectedRows.filter(row => String(row.user_id) !== uid)
|
||||
if (rows.length === 0 && this.selectedRows.length > 0) {
|
||||
this.$message.warning(this.$t(this.key('cannot_operate_self')))
|
||||
return
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
this.$message.warning(this.$t(this.key('select_rows_first')))
|
||||
return
|
||||
}
|
||||
const ids = rows.map(row => row.user_id)
|
||||
this.$confirm(
|
||||
this.$t(this.key('confirm_execute')),
|
||||
this.$t(this.key('prompt')),
|
||||
{
|
||||
confirmButtonText: this.$t(this.key('confirm')),
|
||||
cancelButtonText: this.$t(this.key('cancel')),
|
||||
type: 'warning',
|
||||
closeOnClickModal: false
|
||||
}
|
||||
).then(async () => {
|
||||
try {
|
||||
await (status === 1 ? enableUser({ id: ids, status }) : disableUser({ id: ids, status: 0 }))
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.fetchData()
|
||||
} catch { /* 拦截器已处理 */ }
|
||||
}).catch(() => {})
|
||||
},
|
||||
async handleDelete (row) {
|
||||
if (String(row.user_id) === ownUserId()) {
|
||||
this.$message.warning(this.$t(this.key('cannot_delete_self')))
|
||||
return
|
||||
}
|
||||
const cancelled = await this.$confirmAction(
|
||||
{ message: this.key('confirm_delete'), title: this.key('prompt') },
|
||||
() => deleteUser({ id: [row.user_id] })
|
||||
)
|
||||
if (cancelled) return
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.pagination.current = Math.min(
|
||||
this.pagination.current,
|
||||
Math.ceil((this.pagination.total - 1) / this.pagination.size) || 1
|
||||
)
|
||||
this.fetchData()
|
||||
},
|
||||
async handleBatchDelete () {
|
||||
const uid = ownUserId()
|
||||
const rows = this.selectedRows.filter(row => String(row.user_id) !== uid)
|
||||
if (rows.length === 0 && this.selectedRows.length > 0) {
|
||||
this.$message.warning(this.$t(this.key('cannot_delete_self')))
|
||||
return
|
||||
}
|
||||
if (rows.length === 0) {
|
||||
this.$message.warning(this.$t(this.key('select_rows_first')))
|
||||
return
|
||||
}
|
||||
const cancelled = await this.$confirmAction(
|
||||
{ message: this.key('confirm_batch_delete'), title: this.key('prompt') },
|
||||
() => batchDeleteUser({ id: rows.map(row => row.user_id) })
|
||||
)
|
||||
if (cancelled) return
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
this.fetchData()
|
||||
},
|
||||
async handleResetPwd (row) {
|
||||
try {
|
||||
await this.$confirm(
|
||||
this.$t(this.key('confirm_reset_pwd')),
|
||||
this.$t(this.key('prompt')),
|
||||
{
|
||||
confirmButtonText: this.$t(this.key('confirm')),
|
||||
cancelButtonText: this.$t(this.key('cancel')),
|
||||
type: 'warning',
|
||||
closeOnClickModal: false
|
||||
}
|
||||
)
|
||||
await resetUserPwd({ id: row.user_id })
|
||||
this.$message.success(this.$t(this.key('operation_success')))
|
||||
} catch { /* 取消或失败 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.search-bar {
|
||||
padding: 10px 0;
|
||||
}
|
||||
/deep/ .el-form-item--mini.el-form-item {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -73,8 +73,8 @@ export default {
|
||||
return this.aside[key].children.filter(item => {
|
||||
const title = item.title || ''
|
||||
if (title.indexOf('首页') !== -1) return false
|
||||
if (item.icon === 'home') return false
|
||||
if (this.$route.path === item.path) return false
|
||||
// if (item.icon === 'home') return false
|
||||
// if (this.$route.path === item.path) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user