feat: 完成系统管理模块功能迭代
新增用户、菜单、日志、问题帮助等业务模块,优化角色权限分配功能,新增依赖包与全局组件
This commit is contained in:
@@ -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