This commit is contained in:
weicw
2021-07-29 16:17:26 +08:00
commit a6eb6f83d1
127 changed files with 60792 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
<template>
<div class="ele-body ele-body-card">
<a-row :gutter="16">
<a-col :lg="6" :md="10" :sm="24" :xs="24">
<a-card
:bordered="false"
:body-style="{padding: '24px 16px'}">
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="dictId"
:datasource="url"
:columns="columns"
v-model:current="current"
:need-page="false"
:row-selection="{columnWidth: 38}"
:toolkit="[]"
@done="done">
<template #toolbar>
<a-space size="middle">
<a-button
type="primary"
@click="openEdit()">新建
</a-button>
<a-button
type="primary"
@click="openEdit(current)"
:disabled="!current">修改
</a-button>
<a-button
danger
type="primary"
@click="remove"
:disabled="!current">删除
</a-button>
</a-space>
</template>
</ele-pro-table>
</a-card>
</a-col>
<a-col :lg="18" :md="14" :sm="24" :xs="24">
<a-card :bordered="false">
<sys-dict-data
v-if="current"
:dict-id="current.dictId"/>
</a-card>
</a-col>
</a-row>
</div>
<!-- 编辑弹窗 -->
<sys-dict-edit
v-model:visible="showEdit"
:data="editData"
@done="reload"/>
</template>
<script>
import {createVNode} from 'vue';
import {ExclamationCircleOutlined} from '@ant-design/icons-vue';
import SysDictData from './sys-dict-data';
import SysDictEdit from './sys-dict-edit';
export default {
name: 'SystemDictionary',
components: {SysDictData, SysDictEdit},
data() {
return {
// 表格数据接口
url: '/sys/dict',
// 表格列配置
columns: [
{
key: 'index',
width: 38,
customRender: ({index}) => index + 1
},
{
title: '字典名称',
dataIndex: 'dictName'
}
],
// 表格选中数据
current: null,
// 是否显示编辑弹窗
showEdit: false,
// 编辑回显数据
editData: null
};
},
methods: {
/* 表格渲染完成回调 */
done(res) {
if (res.data.length > 0) {
this.current = res.data[0];
}
},
/* 刷新表格 */
reload() {
this.$refs.table.reload();
},
/* 打开编辑弹窗 */
openEdit(row) {
this.editData = row;
this.showEdit = true;
},
/* 删除 */
remove() {
this.$confirm({
title: '提示',
content: '确定要删除选中的字典吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/dict/' + this.current.dictId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
}
}
}
</script>
<style scoped>
@media screen and (min-width: 768px) {
.ant-card {
min-height: calc(100vh - 122px);
}
}
</style>

View File

@@ -0,0 +1,135 @@
<!-- 字典项编辑弹窗 -->
<template>
<a-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:body-style="{paddingBottom: '8px'}"
:title="isUpdate?'修改字典项':'添加字典项'"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-form-item label="字典项名称:" name="dictDataName">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入字典项名称"
v-model:value="form.dictDataName"/>
</a-form-item>
<a-form-item label="字典项值:" name="dictDataCode">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入字典项值"
v-model:value="form.dictDataCode"/>
</a-form-item>
<a-form-item label="排序号:" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"/>
</a-form-item>
<a-form-item label="备注:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'SysDictDataEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 字典id
dictId: Number
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data),
// 表单验证规则
rules: {
dictDataName: [
{required: true, message: '请输入字典项名称', type: 'string', trigger: 'blur'}
],
dictDataCode: [
{required: true, message: '请输入字典项值', type: 'string', trigger: 'blur'}
],
sortNumber: [
{required: true, message: '请输入排序号', type: 'number', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data);
this.isUpdate = true;
} else {
this.form = {};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/dictdata',
Object.assign({}, this.form, {
dictId: this.dictId
})
).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,178 @@
<template>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="dictDataId"
:datasource="url"
:columns="columns"
:where="where"
tool-class="ele-toolbar-form"
v-model:selection="selection"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-row :gutter="16">
<a-col :lg="6" :md="8" :sm="24" :xs="24">
<a-input
v-model:value.trim="where.keywords"
placeholder="输入关键字搜索"
allow-clear/>
</a-col>
<a-col :lg="6" :md="8" :sm="24" :xs="24">
<a-space size="middle">
<a-button type="primary" @click="reload">查询</a-button>
<a-button type="primary" @click="openEdit()">新建</a-button>
<a-button type="primary" danger @click="removeBatch">删除</a-button>
</a-space>
</a-col>
</a-row>
</template>
<template #action="{ record }">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a-popconfirm
title="确定要删除此字典项吗?"
@confirm="remove(record)">
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</ele-pro-table>
<!-- 编辑弹窗 -->
<sys-dict-data-edit
v-model:visible="showEdit"
:data="current"
:dict-id="dictId"
@done="reload"/>
</template>
<script>
import {createVNode} from 'vue';
import {ExclamationCircleOutlined} from '@ant-design/icons-vue';
import SysDictDataEdit from './sys-dict-data-edit';
export default {
name: 'SysDictData',
components: {SysDictDataEdit},
props: {
// 字典id
dictId: Number
},
data() {
return {
// 表格数据接口
url: '/sys/dictdata/page',
// 表格列配置
columns: [
{
title: '字典项名称',
dataIndex: 'dictDataName',
sorter: true
},
{
title: '字典项值',
dataIndex: 'dictDataCode',
sorter: true
},
{
title: '排序号',
dataIndex: 'sortNumber',
sorter: true,
width: 120,
align: 'center'
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '操作',
key: 'action',
width: 130,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {
dictId: this.dictId
},
// 表格选中数据
selection: [],
// 当前编辑数据
current: null,
// 是否显示编辑弹窗
showEdit: false
};
},
methods: {
/* 刷新表格 */
reload() {
this.$refs.table.reload({page: 1, where: this.where});
},
/* 打开编辑弹窗 */
openEdit(row) {
this.current = row;
this.showEdit = true;
},
/* 删除单个 */
remove(row) {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/dictdata/' + row.dictDataId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
},
/* 批量删除 */
removeBatch() {
if (!this.selection.length) {
this.$message.error('请至少选择一条数据');
return;
}
this.$confirm({
title: '提示',
content: '确定要删除选中的字典项吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/dictdata/batch', {
data: this.selection.map(d => d.dictDataId)
}).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
}
},
watch: {
// 监听字典id变化
dictId() {
this.where.dictId = this.dictId;
this.reload();
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,129 @@
<!-- 字典编辑弹窗 -->
<template>
<a-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改字典':'添加字典'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 5}, sm: {span: 24}}"
:wrapper-col="{md: {span: 19}, sm: {span: 24}}">
<a-form-item label="字典名称:" name="dictName">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入字典名称"
v-model:value="form.dictName"/>
</a-form-item>
<a-form-item label="字典值:" name="dictCode">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入字典值"
v-model:value="form.dictCode"/>
</a-form-item>
<a-form-item label="排序号:" name="sortNumber">
<a-input-number
:min="0"
:max="9999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"/>
</a-form-item>
<a-form-item label="备注:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'SysDictEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data),
// 表单验证规则
rules: {
dictName: [
{required: true, message: '请输入字典名称', type: 'string', trigger: 'blur'}
],
dictCode: [
{required: true, message: '请输入字典值', type: 'string', trigger: 'blur'}
],
sortNumber: [
{required: true, message: '请输入排序号', type: 'number', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data);
this.isUpdate = true;
} else {
this.form = {};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/dict', this.form).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,214 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 搜索表单 -->
<a-form
:model="where"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="用户账号:">
<a-input
v-model:value.trim="where.username"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="用户名:">
<a-input
v-model:value.trim="where.nickname"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="登录时间:">
<a-range-picker
v-model:value="daterange"
value-format="YYYY-MM-DD"
class="ele-fluid">
<template #suffixIcon>
<calendar-outlined/>
</template>
</a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{span: 24}">
<a-space>
<a-button type="primary" @click="reload">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="id"
:datasource="url"
:columns="columns"
:where="where"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-space>
<a-button type="primary" @click="exportData">
<template #icon>
<download-outlined/>
</template>
<span>导出</span>
</a-button>
</a-space>
</template>
<template #operType="{ record }">
<a-tag :color="['green', 'red', '', 'orange'][record.operType]">
{{ ['登录成功', '登录失败', '退出登录', '刷新TOKEN'][record.operType] }}
</a-tag>
</template>
</ele-pro-table>
</a-card>
</div>
</template>
<script>
import XLSX from 'xlsx';
import {
DownloadOutlined,
CalendarOutlined
} from '@ant-design/icons-vue';
export default {
name: 'SystemLoginRecord',
components: {
DownloadOutlined,
CalendarOutlined
},
data() {
return {
// 表格数据接口
url: '/sys/loginRecord/page',
// 表格列配置
columns: [
{
key: 'index',
width: 48,
align: 'center',
customRender: ({index}) => this.$refs.table.tableIndex + index
},
{
title: '账号',
dataIndex: 'username',
sorter: true
},
{
title: '用户名',
dataIndex: 'nickname',
sorter: true
},
{
title: 'IP地址',
dataIndex: 'ip',
sorter: true
},
{
title: '设备型号',
dataIndex: 'device',
sorter: true
},
{
title: '操作系统',
dataIndex: 'os',
sorter: true
},
{
title: '浏览器',
dataIndex: 'browser',
sorter: true
},
{
title: '操作类型',
dataIndex: 'operType',
sorter: true,
width: 120,
slots: {customRender: 'operType'}
},
{
title: '备注',
dataIndex: 'comments',
sorter: true
},
{
title: '登录时间',
dataIndex: 'createTime',
sorter: true,
width: 150,
customRender: ({text}) => this.$util.toDateString(text)
}
],
// 表格搜索条件
where: {},
// 日期范围选择
daterange: []
};
},
methods: {
/* 刷新表格 */
reload() {
this.$refs.table.reload({page: 1, where: this.where});
},
/* 重置搜索 */
reset() {
this.where = {};
this.daterange = [];
this.reload();
},
/* 导出数据 */
exportData() {
let array = [['账号', '用户名', 'IP地址', '设备型号', '操作系统', '浏览器', '操作类型', '备注', '登录时间']];
// 请求查询全部(不分页)的接口
const hide = this.$message.loading('请求中..', 0);
this.$http.get('/sys/loginRecord/page?page=1&limit=2000').then(res => {
hide();
if (res.data.code === 0) {
res.data.data.forEach(d => {
array.push([
d.username,
d.nickname,
d.ip,
d.device,
d.os,
d.browser,
['登录成功', '登录失败', '退出登录', '刷新TOKEN'][d.operType],
d.comments,
this.$util.toDateString(d.createTime)
]);
});
this.$util.exportSheet(XLSX, array, '登录日志');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
},
watch: {
daterange() {
if (this.daterange && this.daterange.length === 2) {
this.where.createTimeStart = this.daterange[0] + ' 00:00:00';
this.where.createTimeEnd = this.daterange[1] + ' 23:59:59';
} else {
this.where.createTimeStart = null;
this.where.createTimeEnd = null;
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,275 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 搜索表单 -->
<a-form
:model="where"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="菜单名称:">
<a-input
v-model:value.trim="where.title"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="菜单地址:">
<a-input
v-model:value.trim="where.path"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="权限标识:">
<a-input
v-model:value.trim="where.authority"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{span: 24}">
<a-space>
<a-button type="primary" @click="reload">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="menuId"
:datasource="url"
:columns="columns"
:where="where"
:need-page="false"
:parse-data="parseData"
:expand-icon-column-index="1"
:expanded-row-keys="expandedRowKeys"
:scroll="{x: 'max-content'}"
@expandedRowsChange="onExpandedRowsChange">
<template #toolbar>
<a-space>
<a-button type="primary" @click="openEdit()">
<template #icon>
<plus-outlined/>
</template>
<span>新建</span>
</a-button>
<a-button @click="expandAll">展开全部</a-button>
<a-button @click="foldAll">折叠全部</a-button>
</a-space>
</template>
<template #menuType="{ record }">
<a-tag
v-if="isUrl(record.path)"
color="orange">外链
</a-tag>
<a-tag
v-else-if="isUrl(record.component)"
color="green">内链
</a-tag>
<a-tag
v-else
:color="['blue', ''][record.menuType]">
{{ ['菜单', '按钮'][record.menuType] }}
</a-tag>
</template>
<template #action="{ record }">
<a-space>
<a @click="openEdit(null, record.menuId)">添加</a>
<a-divider type="vertical"/>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a-popconfirm @confirm="remove(record)" title="确定要删除此菜单吗?">
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</ele-pro-table>
</a-card>
</div>
<!-- 编辑弹窗 -->
<menu-edit
v-model:visible="showEdit"
:data="current"
:menu-list="menuList"
@done="reload"/>
</template>
<script>
import {PlusOutlined} from '@ant-design/icons-vue';
import MenuEdit from './menu-edit';
export default {
name: 'SystemMenu',
components: {PlusOutlined, MenuEdit},
data() {
return {
// 表格数据接口
url: '/sys/menu',
// 表格列配置
columns: [
{
key: 'index',
dataIndex: 'index',
width: 48,
align: 'center',
customRender: ({index}) => index + 1
},
{
title: '菜单名称',
dataIndex: 'title',
sorter: true
},
{
title: '路由地址',
dataIndex: 'path',
sorter: true
},
{
title: '组件路径',
dataIndex: 'component',
sorter: true
},
{
title: '权限标识',
dataIndex: 'authority',
sorter: true
},
{
title: '排序',
dataIndex: 'sortNumber',
sorter: true
},
{
title: '可见',
dataIndex: 'hide',
sorter: true,
customRender: ({text}) => ['是', '否'][text]
},
{
title: '类型',
dataIndex: 'menuType',
sorter: true,
slots: {customRender: 'menuType'}
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '操作',
key: 'action',
width: 150,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {},
// 表格选中数据
selection: [],
// 当前编辑数据
current: null,
// 是否显示编辑弹窗
showEdit: false,
// 表格展开的行
expandedRowKeys: [],
// 全部菜单数据
menuList: []
};
},
methods: {
/* 解析接口返回数据 */
parseData(res) {
res.data = this.$util.toTreeData(res.data.map(d => {
d.key = d.menuId;
d.value = d.menuId;
return d;
}), 'menuId', 'parentId');
if (!Object.keys(this.where).length) {
this.menuList = res.data;
}
if (!this.expandedRowKeys.length) {
this.expandAll();
}
return res;
},
/* 刷新表格 */
reload() {
this.$refs.table.reload({where: this.where});
},
/* 重置搜索 */
reset() {
this.where = {};
this.reload();
},
/* 打开编辑弹窗 */
openEdit(row, parentId) {
this.current = Object.assign({
menuType: 0,
hide: 0,
parentId: parentId
}, row);
this.showEdit = true;
},
/* 删除单个 */
remove(row) {
if (row.children && row.children.length > 0) {
this.$message.error('请先删除子节点');
return;
}
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/menu/' + row.menuId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
},
/* 展开全部 */
expandAll() {
let keys = [];
this.$util.eachTreeData(this.menuList, (d) => {
if (d.children && d.children.length) {
keys.push(d.menuId);
}
});
this.expandedRowKeys = keys;
},
/* 折叠全部 */
foldAll() {
this.expandedRowKeys = [];
},
/* 展开的行变化 */
onExpandedRowsChange(expandedRows) {
this.expandedRowKeys = expandedRows;
},
/* 判断是否是网址 */
isUrl(url) {
return url && (
url.startsWith('http://') ||
url.startsWith('https://') ||
url.startsWith('://'));
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,263 @@
<!-- 编辑弹窗 -->
<template>
<a-modal
:width="680"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改菜单':'新建菜单'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 7}, sm: {span: 24}}"
:wrapper-col="{md: {span: 17}, sm: {span: 24}}">
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="上级菜单" name="parentId">
<a-tree-select
allow-clear
:tree-data="menuList"
tree-default-expand-all
placeholder="请选择上级菜单"
v-model:value="form.parentId"
:dropdown-style="{maxHeight: '360px', overflow: 'auto'}"/>
</a-form-item>
<a-form-item label="菜单名称" name="title">
<a-input
allow-clear
placeholder="请输入菜单名称"
v-model:value="form.title"/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="菜单类型" name="menuType">
<a-radio-group
v-model:value="form.menuType"
@change="onMenuTypeChange">
<a-radio :value="0">菜单</a-radio>
<a-radio :value="1">按钮</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="打开方式">
<a-radio-group
v-model:value="form.openType"
:disabled="form.menuType === 1"
@change="onOpenTypeChange">
<a-radio :value="0">组件</a-radio>
<a-radio :value="1">内链</a-radio>
<a-radio :value="2">外链</a-radio>
</a-radio-group>
</a-form-item>
</a-col>
</a-row>
<div style="margin-bottom: 22px;">
<a-divider/>
</div>
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="菜单图标" name="icon">
<ele-icon-picker
v-model:value="form.icon"
:disabled="form.menuType===1"
placeholder="请选择菜单图标"/>
</a-form-item>
<a-form-item name="path">
<template #label>
<a-tooltip
v-if="form.openType === 2"
title="需要以`http://`、`https://`、`//`开头">
<question-circle-outlined
style="vertical-align: -2px;margin-right: 4px;"/>
</a-tooltip>
<span>{{ form.openType === 2 ? '外链地址' : '路由地址' }}</span>
</template>
<a-input
allow-clear
v-model:value="form.path"
:disabled="form.menuType===1"
:placeholder="form.openType === 2 ? '请输入外链地址' : '请输入路由地址'"/>
</a-form-item>
<a-form-item name="component">
<template #label>
<a-tooltip
v-if="form.openType === 1"
title="需要以`http://`、`https://`、`//`开头">
<question-circle-outlined
style="vertical-align: -2px;margin-right: 4px;"/>
</a-tooltip>
<span>{{ form.openType === 1 ? '内链地址' : '组件路径' }}</span>
</template>
<a-input
allow-clear
v-model:value="form.component"
:disabled="form.menuType === 1 || form.openType === 2"
:placeholder="form.openType === 1 ? '请输入内链地址' : '请输入组件路径'"/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="权限标识" name="authority">
<a-input
allow-clear
placeholder="请输入权限标识"
v-model:value="form.authority"
:disabled="form.menuType === 0"/>
</a-form-item>
<a-form-item label="排序号:" name="sortNumber">
<a-input-number
:min="0"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"/>
</a-form-item>
<a-form-item label="是否可见">
<a-switch
checked-children=""
un-checked-children=""
v-model:checked="form.isShow"
:disabled="form.menuType === 1"/>
<a-tooltip title="选择不可见只注册路由不显示在侧边栏,比如添加页面应该选择不可见">
<question-circle-outlined
style="vertical-align: -3px;margin-left: 16px;"/>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script>
import EleIconPicker from 'ele-admin-pro/packages/ele-icon-picker';
import {QuestionCircleOutlined} from '@ant-design/icons-vue';
export default {
name: 'MenuEdit',
components: {EleIconPicker, QuestionCircleOutlined},
emits: [
'done',
'update:visible'
],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 全部菜单数据
menuList: Array
},
data() {
return {
// 表单数据
form: this.initFormData(this.data),
// 表单验证规则
rules: {
title: [
{required: true, type: 'string', message: '请输入菜单名称', trigger: 'blur'}
],
sortNumber: [
{required: true, type: 'number', message: '请输入排序号', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
watch: {
data() {
this.isUpdate = !!(this.data && this.data.menuId);
this.form = this.initFormData(this.data);
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/menu',
Object.assign({}, this.form, {
parentId: this.form.parentId || 0,
hide: this.form.isShow ? 0 : 1
})
).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
},
/* menuType选择改变 */
onMenuTypeChange() {
if (this.form.menuType === 0) {
this.form.authority = '';
} else {
this.form.openType = 0;
this.form.icon = '';
this.form.path = '';
this.form.component = '';
this.form.hide = 0;
this.form.isShow = true;
}
},
/* openType选择改变 */
onOpenTypeChange() {
if (this.form.openType === 2) {
this.form.component = '';
}
},
/* 初始化form数据 */
initFormData(data) {
let form = {
menuType: 0,
openType: 0,
hide: 0,
isShow: true
};
if (data) {
let openType = 0;
if (this.isUrl(data.path)) {
openType = 2;
} else if (this.isUrl(data.component)) {
openType = 1;
}
Object.assign(form, data, {
parentId: data.parentId === 0 ? null : data.parentId,
isShow: data.hide === 0,
openType: openType
});
}
return form;
},
/* 判断是否是网址 */
isUrl(url) {
return url && (
url.startsWith('http://') ||
url.startsWith('https://') ||
url.startsWith('://'));
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,243 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 搜索表单 -->
<a-form
:model="where"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="用户账号:">
<a-input
v-model:value.trim="where.username"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="操作模块:">
<a-input
v-model:value.trim="where.model"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="操作时间:">
<a-range-picker
v-model:value="daterange"
:show-time="true"
value-format="YYYY-MM-DD HH:mm:ss"
class="ele-fluid">
<template #suffixIcon>
<calendar-outlined/>
</template>
</a-range-picker>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{span: 24}">
<a-space>
<a-button type="primary" @click="reload">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="id"
:datasource="url"
:columns="columns"
:where="where"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-space>
<a-button type="primary" @click="exportData">
<template #icon>
<download-outlined/>
</template>
<span>导出</span>
</a-button>
</a-space>
</template>
<template #state="{ record }">
<a-tag :color="['green', 'red'][record.state]">
{{ ['正常', '异常'][record.state] }}
</a-tag>
</template>
<template #action="{ record }">
<a @click="openDetail(record)">详情</a>
</template>
</ele-pro-table>
</a-card>
</div>
<!-- 详情弹窗 -->
<oper-record-detail
v-model:visible="showInfo"
:data="current||{}"/>
</template>
<script>
import XLSX from 'xlsx';
import {
DownloadOutlined,
CalendarOutlined
} from '@ant-design/icons-vue';
import OperRecordDetail from './oper-record-detail';
export default {
name: 'SystemOperRecord',
components: {
DownloadOutlined,
CalendarOutlined,
OperRecordDetail
},
data() {
return {
// 表格数据接口
url: '/sys/operRecord/page',
// 表格列配置
columns: [
{
key: 'index',
width: 48,
align: 'center',
customRender: ({index}) => this.$refs.table.tableIndex + index
},
{
title: '账号',
dataIndex: 'username',
sorter: true
},
{
title: '用户名',
dataIndex: 'nickname',
sorter: true
},
{
title: '操作模块',
dataIndex: 'model',
sorter: true
},
{
title: '操作功能',
dataIndex: 'description',
sorter: true
},
{
title: '请求地址',
dataIndex: 'url',
sorter: true
},
{
title: '方式',
dataIndex: 'requestMethod',
sorter: true,
width: 90
},
{
title: '状态',
dataIndex: 'state',
sorter: true,
width: 90,
slots: {customRender: 'state'}
},
{
title: '耗时',
dataIndex: 'spendTime',
sorter: true,
width: 100,
customRender: ({text}) => text / 1000 + 's'
},
{
title: '操作时间',
dataIndex: 'createTime',
sorter: true,
width: 150,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '操作',
key: 'action',
width: 90,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {},
// 当前选中数据
current: null,
// 是否显示查看弹窗
showInfo: false,
// 日期范围选择
daterange: []
};
},
methods: {
/* 刷新表格 */
reload() {
this.$refs.table.reload({page: 1, where: this.where});
},
/* 重置搜索 */
reset() {
this.where = {};
this.daterange = [];
this.reload();
},
/* 详情 */
openDetail(row) {
this.current = row;
this.showInfo = true;
},
/* 导出数据 */
exportData() {
let array = [['账号', '用户名', '操作模块', '操作功能', '请求地址', '请求方式', '状态', '耗时', '操作时间']];
// 请求查询全部(不分页)的接口
const hide = this.$message.loading('请求中..', 0);
this.$http.get('/sys/loginRecord/page?page=1&limit=2000').then(res => {
hide();
if (res.data.code === 0) {
res.data.data.forEach(d => {
array.push([
d.username,
d.nickname,
d.model,
d.description,
d.url,
d.requestMethod,
['正常', '异常'][d.state],
d.spendTime,
this.$util.toDateString(d.createTime)
]);
});
this.$util.exportSheet(XLSX, array, '操作日志');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
},
watch: {
daterange() {
if (this.daterange && this.daterange.length === 2) {
this.where.createTimeStart = this.daterange[0];
this.where.createTimeEnd = this.daterange[1];
} else {
this.where.createTimeStart = null;
this.where.createTimeEnd = null;
}
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,126 @@
<!-- 详情弹窗 -->
<template>
<a-modal
title="详情"
:width="640"
:footer="null"
:visible="visible"
@update:visible="updateVisible">
<a-form
class="ele-form-detail"
:label-col="{sm: {span: 8}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 16}, xs: {span: 18}}">
<a-row :gutter="16">
<a-col :sm="12" :xs="24">
<a-form-item label="操作人:">
<div class="ele-text-secondary">
{{ data.nickname }}({{ data.username }})
</div>
</a-form-item>
<a-form-item label="操作模块:">
<div class="ele-text-secondary">
{{ data.model }}
</div>
</a-form-item>
<a-form-item label="操作时间:">
<div class="ele-text-secondary">
{{ $util.toDateString(data.createTime) }}
</div>
</a-form-item>
<a-form-item label="请求方式:">
<div class="ele-text-secondary">
{{ data.requestMethod }}
</div>
</a-form-item>
</a-col>
<a-col :sm="12" :xs="24">
<a-form-item label="IP地址:">
<div class="ele-text-secondary">
{{ data.ip }}
</div>
</a-form-item>
<a-form-item label="操作功能:">
<div class="ele-text-secondary">
{{ data.description }}
</div>
</a-form-item>
<a-form-item label="请求耗时:">
<div class="ele-text-secondary">
{{ data.spendTime / 1000 }}s
</div>
</a-form-item>
<a-form-item label="请求状态:">
<a-tag :color="['green', 'red'][data.state]">
{{ ['正常', '异常'][data.state] }}
</a-tag>
</a-form-item>
</a-col>
</a-row>
<div style="margin: 12px 0;">
<a-divider/>
</div>
<a-form-item
label="请求地址:"
:label-col="{sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 20}, xs: {span: 18}}">
<div class="ele-text-secondary">
{{ data.url }}
</div>
</a-form-item>
<a-form-item
label="调用方法:"
:label-col="{sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 20}, xs: {span: 18}}">
<div class="ele-text-secondary">
{{ data.operMethod }}
</div>
</a-form-item>
<a-form-item
label="请求参数:"
:label-col="{sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 20}, xs: {span: 18}}">
<div class="ele-text-secondary">
{{ data.param }}
</div>
</a-form-item>
<a-form-item
label="返回结果:"
:label-col="{sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 20}, xs: {span: 18}}">
<div class="ele-text-secondary">
{{ data.result }}
</div>
</a-form-item>
<a-form-item
label="备注:"
:label-col="{sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{sm: {span: 20}, xs: {span: 18}}">
<div class="ele-text-secondary">
{{ data.comments }}
</div>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'OperRecordDetail',
emits: ['update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 数据
data: Object
},
methods: {
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,166 @@
<template>
<div class="ele-body ele-body-card">
<a-row :gutter="16">
<a-col :lg="6" :md="24" :sm="24" :xs="24">
<a-card
:bordered="false"
:body-style="{padding: '24px 16px'}">
<div class="ele-table-tool">
<a-space size="middle">
<a-button
type="primary"
@click="openEdit()">新建
</a-button>
<a-button
type="primary"
@click="openEdit(current)"
:disabled="!current">修改
</a-button>
<a-button
danger
type="primary"
@click="remove"
:disabled="!current">删除
</a-button>
</a-space>
</div>
<a-tree
:tree-data="data"
v-model:expanded-keys="expandedRowKeys"
v-model:selected-keys="selectedRowKeys"
@select="onTreeSelect"/>
</a-card>
</a-col>
<a-col :lg="18" :md="24" :sm="24" :xs="24">
<a-card :bordered="false">
<org-user-list
v-if="current"
:organization-id="current.organizationId"
:organization-list="data"/>
</a-card>
</a-col>
</a-row>
</div>
<!-- 编辑弹窗 -->
<org-edit
v-model:visible="showEdit"
:data="editData"
:organization-list="data"
@done="query"/>
</template>
<script>
import {createVNode} from 'vue';
import {ExclamationCircleOutlined} from '@ant-design/icons-vue';
import OrgUserList from './org-user-list';
import OrgEdit from './org-edit';
export default {
name: 'SystemOrganization',
components: {
OrgUserList,
OrgEdit
},
data() {
return {
// 加载状态
loading: true,
// 树形数据
data: [],
// 树展开的key
expandedRowKeys: [],
// 树选中的key
selectedRowKeys: [],
// 选中数据
current: null,
// 是否显示表单弹窗
showEdit: false,
// 编辑回显数据
editData: null
};
},
mounted() {
this.query();
},
methods: {
/* 查询 */
query() {
this.loading = true;
this.$http.get('/sys/organization').then(res => {
this.loading = false;
if (res.data.code === 0) {
let eks = [];
res.data.data.forEach(d => {
d.key = d.organizationId;
d.value = d.organizationId;
d.title = d.organizationName;
eks.push(d.key);
});
this.expandedRowKeys = eks;
this.data = this.$util.toTreeData(res.data.data, 'organizationId', 'parentId');
if (this.data.length) {
this.selectedRowKeys = [this.data[0].key];
this.onTreeSelect();
} else {
this.selectedRowKeys = [];
this.current = null;
}
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
},
/* 选择数据 */
onTreeSelect() {
this.$util.eachTreeData(this.data, (d) => {
if (this.selectedRowKeys.indexOf(d.key) !== -1) {
this.current = d;
return false;
}
});
},
/* 打开编辑弹窗 */
openEdit(item) {
this.editData = Object.assign({}, {
parentId: this.current.parentId
}, item);
this.showEdit = true;
},
/* 删除 */
remove() {
this.$confirm({
title: '提示',
content: '确定要删除选中的机构吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/organization/' + this.current.organizationId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.query();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
}
}
}
</script>
<style scoped>
@media screen and (min-width: 768px) {
.ant-card {
min-height: calc(100vh - 122px);
}
}
</style>

View File

@@ -0,0 +1,201 @@
<!-- 机构编辑弹窗 -->
<template>
<a-modal
:width="680"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改机构':'添加机构'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 7}, sm: {span: 24}}"
:wrapper-col="{md: {span: 17}, sm: {span: 24}}">
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="上级机构:" name="parentId">
<a-tree-select
allow-clear
tree-default-expand-all
placeholder="请选择上级机构"
v-model:value="form.parentId"
:tree-data="organizationList"
:dropdown-style="{maxHeight: '360px', overflow: 'auto'}"/>
</a-form-item>
<a-form-item label="机构名称:" name="organizationName">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入机构名称"
v-model:value="form.organizationName"/>
</a-form-item>
<a-form-item label="机构全称:" name="organizationFullName">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入机构全称"
v-model:value="form.organizationFullName"/>
</a-form-item>
<a-form-item label="机构代码:" name="organizationCode">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入机构代码"
v-model:value="form.organizationCode"/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="机构类型:" name="organizationType">
<a-select
allow-clear
placeholder="请选择机构类型"
v-model:value="form.organizationType">
<a-select-option
v-for="item in organizationTypeList"
:key="item.dictDataId"
:value="item.dictDataId">
{{ item.dictDataName }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="排序号:" name="sortNumber">
<a-input-number
:min="0"
:max="99999"
class="ele-fluid"
placeholder="请输入排序号"
v-model:value="form.sortNumber"/>
</a-form-item>
<a-form-item label="备注:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'OrgEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 全部机构
organizationList: Array
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data, {
parentId: this.data ? (this.data.parentId === 0 ? null : this.data.parentId) : null
}),
// 表单验证规则
rules: {
organizationName: [
{required: true, message: '请输入机构名称', type: 'string', trigger: 'blur'}
],
organizationFullName: [
{required: true, message: '请输入机构全称', type: 'string', trigger: 'blur'}
],
organizationCode: [
{required: true, message: '请输入机构代码', type: 'string', trigger: 'blur'}
],
organizationType: [
{required: true, message: '请选择机构类型', type: 'number', trigger: 'blur'}
],
sortNumber: [
{required: true, message: '请输入排序号', type: 'number', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false,
// 机构类型列表
organizationTypeList: []
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data, {
parentId: this.data.parentId === 0 ? null : this.data.parentId
});
this.isUpdate = !!this.data.organizationId;
} else {
this.form = {};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
mounted() {
this.queryOrganizationType(); // 获取机构类型
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/organization',
Object.assign({}, this.form, {
parentId: this.form.parentId || 0
})
).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
},
/* 查询机构类型 */
queryOrganizationType() {
this.$http.get('/sys/dictdata', {
params: {
dictCode: 'organization_type'
}
}).then(res => {
if (res.data.code === 0) {
this.organizationTypeList = res.data.data;
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.$message.error(e.message);
});
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,248 @@
<!-- 用户编辑弹窗 -->
<template>
<a-modal
:width="680"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改用户':'新建用户'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 7}, sm: {span: 24}}"
:wrapper-col="{md: {span: 17}, sm: {span: 24}}">
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="所属机构:">
<a-tree-select
allow-clear
tree-default-expand-all
placeholder="请选择所属机构"
v-model:value="form.organizationId"
:tree-data="organizationList"
:dropdown-style="{maxHeight: '360px', overflow: 'auto'}"/>
</a-form-item>
<a-form-item label="用户账号:" name="username">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入用户账号"
v-model:value="form.username"/>
</a-form-item>
<a-form-item label="用户名:" name="nickname">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入用户名"
v-model:value="form.nickname"/>
</a-form-item>
<a-form-item label="性别:" name="sex">
<a-select
allow-clear
placeholder="请选择性别"
v-model:value="form.sex">
<a-select-option :value="1"></a-select-option>
<a-select-option :value="2"></a-select-option>
</a-select>
</a-form-item>
<a-form-item label="角色:" name="roleIds">
<a-select
allow-clear
mode="multiple"
placeholder="请选择角色"
v-model:value="form.roleIds">
<a-select-option
v-for="item in roleList"
:key="item.roleId"
:value="item.roleId">
{{ item.roleName }}
</a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="手机号:" name="phone">
<a-input
allow-clear
:maxlength="11"
placeholder="请输入手机号"
v-model:value="form.phone"/>
</a-form-item>
<a-form-item label="邮箱:" name="email">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入邮箱"
v-model:value="form.email"/>
</a-form-item>
<a-form-item
v-if="!isUpdate"
label="登录密码:"
name="password">
<a-input-password
:maxlength="20"
v-model:value="form.password"
placeholder="请输入登录密码"/>
</a-form-item>
<a-form-item label="个人简介:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入个人简介"
v-model:value="form.introduction"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script>
import validate from 'ele-admin-pro/packages/validate';
export default {
name: 'OrgUserEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object,
// 全部机构
organizationList: Array,
// 机构id
organizationId: Number
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data, {
organizationId: this.organizationId
}),
// 表单验证规则
rules: {
username: [
{
required: true,
type: 'string',
trigger: 'blur',
asyncValidator: (rule, value) => {
return new Promise((resolve, reject) => {
if (!value) {
return reject('请输入用户账号');
}
this.$http.get('/sys/user?username=' + value).then(res => {
if (res.data.code !== 0 || !res.data.data.length) {
return resolve();
}
if (this.isUpdate && res.data.data[0].username === this.data.username) {
return resolve();
}
reject('账号已经存在');
}).catch(() => {
resolve();
});
});
}
}
],
nickname: [
{required: true, message: '请输入用户名', type: 'string', trigger: 'blur'}
],
sex: [
{required: true, message: '请选择性别', type: 'number', trigger: 'blur'}
],
roleIds: [
{required: true, message: '请选择角色', type: 'array', trigger: 'blur'}
],
email: [
{pattern: validate.email, message: '邮箱格式不正确', type: 'string', trigger: 'blur'}
],
password: [
{required: true, pattern: /^[\S]{5,18}$/, message: '密码必须为5-18位非空白字符', type: 'string', trigger: 'blur'}
],
phone: [
{pattern: validate.phone, message: '手机号格式不正确', type: 'string', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false,
// 角色列表
roleList: []
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data, {
roleIds: this.data.roles.map(d => d.roleId)
});
this.isUpdate = true;
} else {
this.form = {organizationId: this.organizationId};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
},
organizationId() {
if (!this.isUpdate) {
this.form = {organizationId: this.organizationId};
}
}
},
mounted() {
this.queryRoles(); // 查询角色列表
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/user', this.form).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
},
/* 查询角色列表 */
queryRoles() {
this.$http.get('/sys/role').then(res => {
if (res.data.code === 0) {
this.roleList = res.data.data;
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.$message.error(e.message);
});
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,207 @@
<template>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="userId"
:datasource="url"
:columns="columns"
:where="where"
tool-class="ele-toolbar-form"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-row :gutter="16">
<a-col :lg="6" :md="8" :sm="24" :xs="24">
<a-input
v-model:value.trim="where.username"
placeholder="请输入用户账号"
allow-clear/>
</a-col>
<a-col :lg="6" :md="8" :sm="24" :xs="24">
<a-input
v-model:value.trim="where.nickname"
placeholder="请输入用户名"
allow-clear/>
</a-col>
<a-col :lg="6" :md="8" :sm="24" :xs="24">
<a-space size="middle">
<a-button
type="primary"
@click="reload">查询
</a-button>
<a-button
type="primary"
@click="openEdit()">新建
</a-button>
</a-space>
</a-col>
</a-row>
</template>
<template #roles="{ record }">
<a-tag
v-for="(item, index) in record.roles"
:key="index"
color="blue">{{ item.roleName }}
</a-tag>
</template>
<template #state="{ text,record }">
<a-switch
:checked="text===0"
@change="(checked) => changeState(checked, record)"/>
</template>
<template #action="{ record }">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a-popconfirm
title="确定要删除此用户吗?"
@confirm="remove(record)">
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</ele-pro-table>
<!-- 编辑弹窗 -->
<org-user-edit
v-model:visible="showEdit"
:data="current"
:organization-list="organizationList"
:organization-id="organizationId"
@done="reload"/>
</template>
<script>
import OrgUserEdit from './org-user-edit';
export default {
name: 'SysOrgUserList',
components: {OrgUserEdit},
props: {
// 机构id
organizationId: Number,
// 全部机构
organizationList: Array
},
data() {
return {
// 表格数据接口
url: '/sys/user/page',
// 表格列配置
columns: [
{
key: 'index',
width: 48,
align: 'center',
customRender: ({index}) => this.$refs.table.tableIndex + index
},
{
title: '用户账号',
dataIndex: 'username',
sorter: true
},
{
title: '用户名',
dataIndex: 'nickname',
sorter: true
},
{
title: '性别',
dataIndex: 'sexName',
sorter: true
},
{
title: '手机号',
dataIndex: 'phone',
sorter: true,
},
{
title: '角色',
key: 'roles',
slots: {customRender: 'roles'}
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
width: 150,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '状态',
dataIndex: 'state',
sorter: true,
width: 90,
align: 'center',
slots: {customRender: 'state'}
},
{
title: '操作',
key: 'action',
width: 120,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {
organizationId: this.organizationId
},
// 当前编辑数据
current: null,
// 是否显示编辑弹窗
showEdit: false
};
},
methods: {
/* 刷新表格 */
reload() {
this.$refs.table.reload({page: 1, where: this.where});
},
/* 打开编辑弹窗 */
openEdit(row) {
this.current = row;
this.showEdit = true;
},
/* 删除单个 */
remove(row) {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/user/' + row.userId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
},
/* 修改用户状态 */
changeState(checked, row) {
let params = new FormData();
params.append('state', checked ? 0 : 1);
this.$http.put('/sys/user/state/' + row.userId, params).then(res => {
if (res.data.code === 0) {
row.state = checked ? 0 : 1;
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.$message.error(e.message);
});
}
},
watch: {
/* 监听机构id变化 */
organizationId() {
this.where.organizationId = this.organizationId;
this.reload();
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,238 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 搜索表单 -->
<a-form
:model="where"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="角色名称:">
<a-input
v-model:value.trim="where.roleName"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="角色标识:">
<a-input
v-model:value.trim="where.roleCode"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="备注:">
<a-input
v-model:value.trim="where.comments"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{span: 24}">
<a-space>
<a-button type="primary" @click="reload">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="roleId"
:datasource="url"
:columns="columns"
:where="where"
v-model:selection="selection"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-space>
<a-button type="primary" @click="openEdit()">
<template #icon>
<plus-outlined/>
</template>
<span>新建</span>
</a-button>
<a-button type="primary" danger @click="removeBatch">
<template #icon>
<delete-outlined/>
</template>
<span>删除</span>
</a-button>
</a-space>
</template>
<template #action="{ record }">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a @click="openAuth(record)">分配权限</a>
<a-divider type="vertical"/>
<a-popconfirm
title="确定要删除此角色吗?"
@confirm="remove(record)">
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</ele-pro-table>
</a-card>
</div>
<!-- 编辑弹窗 -->
<role-edit
v-model:visible="showEdit"
:data="current"
@done="reload"/>
<!-- 权限分配弹窗 -->
<role-auth
v-model:visible="showAuth"
:data="current"/>
</template>
<script>
import {createVNode} from 'vue'
import {
PlusOutlined,
DeleteOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import RoleEdit from './role-edit';
import RoleAuth from './role-auth';
export default {
name: 'SystemRole',
components: {
PlusOutlined,
DeleteOutlined,
RoleEdit,
RoleAuth
},
data() {
return {
// 表格数据接口
url: '/sys/role/page',
// 表格列配置
columns: [
{
key: 'index',
width: 48,
customRender: ({index}) => this.$refs.table.tableIndex + index
},
{
title: '角色名称',
dataIndex: 'roleName',
sorter: true
},
{
title: '角色标识',
dataIndex: 'roleCode',
sorter: true
},
{
title: '备注',
dataIndex: 'comments',
sorter: true
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {},
// 表格选中数据
selection: [],
// 当前编辑数据
current: null,
// 是否显示编辑弹窗
showEdit: false,
// 是否显示权限分配弹窗
showAuth: false
};
},
methods: {
/* 搜索 */
reload() {
this.selection = [];
this.$refs.table.reload({page: 1, where: this.where});
},
/* 重置搜索 */
reset() {
this.where = {};
this.reload();
},
/* 删除单个 */
remove(row) {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/role/' + row.roleId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
},
/* 批量删除 */
removeBatch() {
if (!this.selection.length) {
this.$message.error('请至少选择一条数据');
return;
}
this.$confirm({
title: '提示',
content: '确定要删除选中的角色吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/role/batch', {
data: this.selection.map(d => d.roleId)
}).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
},
/* 打开编辑弹窗 */
openEdit(row) {
this.current = row;
this.showEdit = true;
},
/* 打开权限分配弹窗 */
openAuth(row) {
this.current = row;
this.showAuth = true;
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,134 @@
<!-- 角色权限分配弹窗 -->
<template>
<a-modal
:width="460"
title="分配权限"
:visible="visible"
:confirm-loading="loading"
@update:visible="updateVisible"
@ok="save">
<a-spin :spinning="authLoading">
<div style="height: 60vh;" class="ele-scrollbar-hover">
<a-tree
checkable
:tree-data="authData"
v-model:expandedKeys="expandKeys"
v-model:checkedKeys="checkedKeys"/>
</div>
</a-spin>
</a-modal>
</template>
<script>
export default {
name: 'RoleAuth',
emits: ['update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 当前角色数据
data: Object
},
data() {
return {
// 权限数据
authData: [],
// 权限数据请求状态
authLoading: false,
// 提交状态
loading: false,
// 角色权限展开的keys
expandKeys: [],
// 角色权限选中的keys
checkedKeys: [],
};
},
watch: {
visible() {
if (this.visible) {
this.query();
}
}
},
methods: {
/* 查询权限数据 */
query() {
this.authData = [];
this.expandKeys = [];
this.checkedKeys = [];
if (!this.data) {
return;
}
this.authLoading = true;
this.$http.get('/sys/role/menu', {
params: {
roleId: this.data.roleId
}
}).then(res => {
this.authLoading = false;
if (res.data.code === 0) {
let eks = [], cks = [];
// 转成树形结构的数据
this.authData = this.$util.toTreeData({
data: res.data.data,
idKey: 'menuId',
pidKey: 'parentId',
addPIds: true,
parentIds: []
});
// 全部默认展开以及回显选中的数据
this.$util.eachTreeData(this.authData, (d) => {
d.key = d.menuId;
eks.push(d.key);
if (d.checked && (!d.children || !d.children.length)) {
cks.push(d.key);
}
});
this.expandKeys = eks;
this.checkedKeys = cks;
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.authLoading = false;
this.$message.error(e.message);
});
},
/* 保存权限分配 */
save() {
this.loading = true;
// 获取选中的id包含所有半选的父级的id
const ids = new Set();
this.$util.eachTreeData(this.authData, (d) => {
if (this.checkedKeys.some(c => c === d.key)) {
ids.add(d.key);
if (d.parentIds) {
d.parentIds.forEach((id) => {
ids.add(id);
});
}
}
});
this.$http.put('/sys/role/menu/' + this.data.roleId, Array.from(ids)).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.updateVisible(false);
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,118 @@
<!-- 角色编辑弹窗 -->
<template>
<a-modal
:width="460"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改角色':'添加角色'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 5}, sm: {span: 24}}"
:wrapper-col="{md: {span: 19}, sm: {span: 24}}">
<a-form-item label="角色名称:" name="roleName">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入角色名称"
v-model:value="form.roleName"/>
</a-form-item>
<a-form-item label="角色标识:" name="roleCode">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入角色标识"
v-model:value="form.roleCode"/>
</a-form-item>
<a-form-item label="备注:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入备注"
v-model:value="form.comments"/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script>
export default {
name: 'RoleEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data),
// 表单验证规则
rules: {
roleName: [
{required: true, message: '请输入角色名称', type: 'string', trigger: 'blur'}
],
roleCode: [
{required: true, message: '请输入角色标识', type: 'string', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data);
this.isUpdate = true;
} else {
this.form = {};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/role', this.form).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,321 @@
<template>
<div class="ele-body">
<a-card :bordered="false">
<!-- 搜索表单 -->
<a-form
:model="where"
:label-col="{md: {span: 6}, sm: {span: 24}}"
:wrapper-col="{md: {span: 18}, sm: {span: 24}}">
<a-row>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="用户账号:">
<a-input
v-model:value.trim="where.username"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="用户名:">
<a-input
v-model:value.trim="where.nickname"
placeholder="请输入"
allow-clear/>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item label="性别:">
<a-select
v-model:value="where.sex"
placeholder="请选择"
allow-clear>
<a-select-option :value="1"></a-select-option>
<a-select-option :value="2"></a-select-option>
</a-select>
</a-form-item>
</a-col>
<a-col :lg="6" :md="12" :sm="24" :xs="24">
<a-form-item class="ele-text-right" :wrapper-col="{span: 24}">
<a-space>
<a-button type="primary" @click="reload">查询</a-button>
<a-button @click="reset">重置</a-button>
</a-space>
</a-form-item>
</a-col>
</a-row>
</a-form>
<!-- 表格 -->
<ele-pro-table
ref="table"
row-key="userId"
:datasource="url"
:columns="columns"
:where="where"
v-model:selection="selection"
:scroll="{x: 'max-content'}">
<template #toolbar>
<a-space>
<a-button type="primary" @click="openEdit()">
<template #icon>
<plus-outlined/>
</template>
<span>新建</span>
</a-button>
<a-button type="primary" danger @click="removeBatch">
<template #icon>
<delete-outlined/>
</template>
<span>删除</span>
</a-button>
<a-button @click="showImport=true">
<template #icon>
<upload-outlined/>
</template>
<span>导入</span>
</a-button>
</a-space>
</template>
<template #nickname="{ record }">
<router-link :to="'/system/user/info?id=' + record.userId">
{{ record.nickname }}
</router-link>
</template>
<template #roles="{ record }">
<a-tag
v-for="(item, index) in record.roles"
:key="index"
color="blue">
{{ item.roleName }}
</a-tag>
</template>
<template #state="{ text, record }">
<a-switch
:checked="text===0"
@change="(checked) => editState(checked, record)"/>
</template>
<template #action="{ record }">
<a-space>
<a @click="openEdit(record)">修改</a>
<a-divider type="vertical"/>
<a @click="resetPsw(record)">重置密码</a>
<a-divider type="vertical"/>
<a-popconfirm
title="确定要删除此用户吗?"
@confirm="remove(record)">
<a class="ele-text-danger">删除</a>
</a-popconfirm>
</a-space>
</template>
</ele-pro-table>
</a-card>
</div>
<!-- 编辑弹窗 -->
<user-edit
v-model:visible="showEdit"
:data="current"
@done="reload"/>
<!-- 导入弹窗 -->
<user-import
v-model:visible="showImport"
@done="reload"/>
</template>
<script>
import {createVNode} from 'vue';
import {
PlusOutlined,
DeleteOutlined,
UploadOutlined,
ExclamationCircleOutlined
} from '@ant-design/icons-vue';
import UserEdit from './user-edit';
import UserImport from './user-import';
export default {
name: 'SystemUser',
components: {
PlusOutlined,
DeleteOutlined,
UploadOutlined,
UserImport,
UserEdit
},
data() {
return {
// 表格数据接口
url: '/sys/user/page',
// 表格列配置
columns: [
{
key: 'index',
width: 48,
customRender: ({index}) => this.$refs.table.tableIndex + index
},
{
title: '用户账号',
dataIndex: 'username',
sorter: true
},
{
title: '用户名',
dataIndex: 'nickname',
sorter: true,
slots: {customRender: 'nickname'}
},
{
title: '性别',
dataIndex: 'sexName',
sorter: true
},
{
title: '手机号',
dataIndex: 'phone',
sorter: true,
},
{
title: '角色',
key: 'roles',
slots: {customRender: 'roles'}
},
{
title: '创建时间',
dataIndex: 'createTime',
sorter: true,
customRender: ({text}) => this.$util.toDateString(text)
},
{
title: '状态',
dataIndex: 'state',
sorter: true,
width: 90,
align: 'center',
slots: {customRender: 'state'}
},
{
title: '操作',
key: 'action',
width: 200,
align: 'center',
slots: {customRender: 'action'}
}
],
// 表格搜索条件
where: {},
// 表格选中数据
selection: [],
// 当前编辑数据
current: null,
// 是否显示编辑弹窗
showEdit: false,
// 是否显示用户导入弹窗
showImport: false
};
},
methods: {
/* 搜索 */
reload() {
this.selection = [];
this.$refs.table.reload({page: 1, where: this.where});
},
/* 重置搜索 */
reset() {
this.where = {};
this.reload();
},
/* 删除单个 */
remove(row) {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/user/' + row.userId).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
},
/* 批量删除 */
removeBatch() {
if (!this.selection.length) {
this.$message.error('请至少选择一条数据');
return;
}
this.$confirm({
title: '提示',
content: '确定要删除选中的用户吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
this.$http.delete('/sys/user/batch', {
data: this.selection.map(d => d.userId)
}).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.reload();
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
},
/* 打开编辑弹窗 */
openEdit(row) {
this.current = row;
this.showEdit = true;
},
/* 重置用户密码 */
resetPsw(row) {
this.$confirm({
title: '提示',
content: '确定要重置此用户的密码为"123456"吗?',
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: () => {
const hide = this.$message.loading('请求中..', 0);
const formData = new FormData();
formData.append('password', '123456');
this.$http.put('/sys/user/psw/' + row.userId, formData).then(res => {
hide();
if (res.data.code === 0) {
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
hide();
this.$message.error(e.message);
});
}
});
},
/* 修改用户状态 */
editState(checked, row) {
let params = new FormData();
params.append('state', checked ? 0 : 1);
this.$http.put('/sys/user/state/' + row.userId, params).then(res => {
if (res.data.code === 0) {
row.state = checked ? 0 : 1;
this.$message.success(res.data.msg);
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.$message.error(e.message);
});
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,86 @@
<template>
<div class="ele-body">
<a-card title="基本信息" :bordered="false">
<a-form
class="ele-form-detail"
:label-col="{md: {span: 2}, sm: {span: 4}, xs: {span: 6}}"
:wrapper-col="{md: {span: 22}, sm: {span: 20}, xs: {span: 18}}">
<a-form-item label="账号">
<div class="ele-text-secondary">{{ user.username }}</div>
</a-form-item>
<a-form-item label="用户名">
<div class="ele-text-secondary">{{ user.nickname }}</div>
</a-form-item>
<a-form-item label="性别">
<div class="ele-text-secondary">{{ user.sexName }}</div>
</a-form-item>
<a-form-item label="手机号">
<div class="ele-text-secondary">{{ user.phone }}</div>
</a-form-item>
<a-form-item label="角色">
<a-tag v-for="item in user.roles" :key="item.roleId" color="blue">
{{ item.roleName }}
</a-tag>
</a-form-item>
<a-form-item label="创建时间">
<div class="ele-text-secondary">{{ $util.toDateString(user.createTime) }}</div>
</a-form-item>
<a-form-item label="状态">
<a-badge
:status="['processing', 'error'][user.state]"
:text="['正常', '冻结'][user.state]"/>
</a-form-item>
</a-form>
</a-card>
</div>
</template>
<script>
import {setPageTab} from '@/utils/page-tab-util';
export default {
name: 'SystemUserInfo',
data() {
return {
user: {},
loading: true
};
},
mounted() {
this.query(this.$route.query.id);
},
methods: {
/* 查询用户信息 */
query(id) {
if (!id) {
return;
}
this.loading = true;
this.$http.get('/sys/user/' + id).then((res) => {
this.loading = false;
if (res.data.code === 0) {
this.user = res.data.data;
// 修改页签标题
setPageTab({
fullPath: this.$route.fullPath,
title: this.user.nickname + '的信息'
});
} else {
this.$message.error(res.data.msg);
}
}).catch((e) => {
this.loading = false;
this.$message.error(e.message);
});
}
},
watch: {
$route() {
this.query(this.$route.query.id);
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,235 @@
<!-- 用户编辑弹窗 -->
<template>
<a-modal
:width="680"
:visible="visible"
:confirm-loading="loading"
:title="isUpdate?'修改用户':'新建用户'"
:body-style="{paddingBottom: '8px'}"
@update:visible="updateVisible"
@ok="save">
<a-form
ref="form"
:model="form"
:rules="rules"
:label-col="{md: {span: 7}, sm: {span: 24}}"
:wrapper-col="{md: {span: 17}, sm: {span: 24}}">
<a-row :gutter="16">
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="用户账号:" name="username">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入用户账号"
v-model:value="form.username"/>
</a-form-item>
<a-form-item label="用户名:" name="nickname">
<a-input
allow-clear
:maxlength="20"
placeholder="请输入用户名"
v-model:value="form.nickname"/>
</a-form-item>
<a-form-item label="性别:" name="sex">
<a-select
allow-clear
placeholder="请选择性别"
v-model:value="form.sex">
<a-select-option :value="1"></a-select-option>
<a-select-option :value="2"></a-select-option>
</a-select>
</a-form-item>
<a-form-item label="角色:" name="roleIds">
<a-select
allow-clear
mode="multiple"
placeholder="请选择角色"
v-model:value="form.roleIds">
<a-select-option
v-for="item in roleList"
:key="item.roleId"
:value="item.roleId">
{{ item.roleName }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="邮箱:" name="email">
<a-input
allow-clear
:maxlength="100"
placeholder="请输入邮箱"
v-model:value="form.email"/>
</a-form-item>
</a-col>
<a-col :md="12" :sm="24" :xs="24">
<a-form-item label="手机号:" name="phone">
<a-input
allow-clear
:maxlength="11"
placeholder="请输入手机号"
v-model:value="form.phone"/>
</a-form-item>
<a-form-item label="出生日期:" name="birthday">
<a-date-picker
class="ele-fluid"
value-format="YYYY-MM-DD"
placeholder="请选择出生日期"
v-model:value="form.birthday"/>
</a-form-item>
<a-form-item
v-if="!isUpdate"
label="登录密码:"
name="password">
<a-input-password
:maxlength="20"
v-model:value="form.password"
placeholder="请输入登录密码"/>
</a-form-item>
<a-form-item label="个人简介:">
<a-textarea
:rows="4"
:maxlength="200"
placeholder="请输入个人简介"
v-model:value="form.introduction"/>
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-modal>
</template>
<script>
import validate from 'ele-admin-pro/packages/validate';
export default {
name: 'UserEdit',
emits: ['done', 'update:visible'],
props: {
// 弹窗是否打开
visible: Boolean,
// 修改回显的数据
data: Object
},
data() {
return {
// 表单数据
form: Object.assign({}, this.data),
// 表单验证规则
rules: {
username: [
{
required: true,
type: 'string',
trigger: 'blur',
asyncValidator: (rule, value) => {
return new Promise((resolve, reject) => {
if (!value) {
return reject('请输入用户账号');
}
this.$http.get('/sys/user?username=' + value).then(res => {
if (res.data.code !== 0 || !res.data.data.length) {
return resolve();
}
if (this.isUpdate && res.data.data[0].username === this.data.username) {
return resolve();
}
reject('账号已经存在');
}).catch(() => {
resolve();
});
});
}
}
],
nickname: [
{required: true, message: '请输入用户名', type: 'string', trigger: 'blur'}
],
sex: [
{required: true, message: '请选择性别', type: 'number', trigger: 'blur'}
],
roleIds: [
{required: true, message: '请选择角色', type: 'array', trigger: 'blur'}
],
email: [
{pattern: validate.email, message: '邮箱格式不正确', type: 'string', trigger: 'blur'}
],
password: [
{required: true, pattern: /^[\S]{5,18}$/, message: '密码必须为5-18位非空白字符', type: 'string', trigger: 'blur'}
],
phone: [
{pattern: validate.phone, message: '手机号格式不正确', type: 'string', trigger: 'blur'}
]
},
// 提交状态
loading: false,
// 是否是修改
isUpdate: false,
// 角色列表
roleList: []
};
},
watch: {
data() {
if (this.data) {
this.form = Object.assign({}, this.data, {
roleIds: this.data.roles.map(d => d.roleId)
});
this.isUpdate = true;
} else {
this.form = {};
this.isUpdate = false;
}
if (this.$refs.form) {
this.$refs.form.clearValidate();
}
}
},
mounted() {
this.queryRoles(); // 查询角色列表
},
methods: {
/* 保存编辑 */
save() {
this.$refs.form.validate().then(() => {
this.loading = true;
this.$http[this.isUpdate ? 'put' : 'post']('/sys/user', this.form).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
if (!this.isUpdate) {
this.form = {};
}
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
}).catch(() => {
});
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
},
/* 查询角色列表 */
queryRoles() {
this.$http.get('/sys/role').then(res => {
if (res.data.code === 0) {
this.roleList = res.data.data;
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.$message.error(e.message);
});
}
}
}
</script>
<style scoped>
</style>

View File

@@ -0,0 +1,77 @@
<!-- 用户导入弹窗 -->
<template>
<a-modal
:width="520"
:footer="null"
title="导入用户"
:visible="visible"
@update:visible="updateVisible">
<a-spin :spinning="loading">
<a-upload-dragger
accept=".xls,.xlsx"
:show-upload-list="false"
:customRequest="doUpload"
style="padding: 24px 0;margin-bottom: 16px;">
<p class="ant-upload-drag-icon">
<cloud-upload-outlined/>
</p>
<p class="ant-upload-hint">将文件拖到此处或点击上传</p>
</a-upload-dragger>
</a-spin>
<div class="ele-text-center">
<span>只能上传xlsxlsx文件</span>
<a href="https://cdn.eleadmin.com/20200610/用户导入模板.xlsx"
download="用户导入模板.xlsx">下载模板
</a>
</div>
</a-modal>
</template>
<script>
import {CloudUploadOutlined} from '@ant-design/icons-vue';
export default {
name: 'UserImport',
components: {CloudUploadOutlined},
emits: ['done', 'update:visible'],
props: {
// 是否打开弹窗
visible: Boolean
},
data() {
return {
// 导入请求状态
loading: false
};
},
methods: {
/* 上传 */
doUpload({file}) {
this.loading = true;
let formData = new FormData();
formData.append('file', file);
this.$http.post('/sys/user/import', formData).then(res => {
this.loading = false;
if (res.data.code === 0) {
this.$message.success(res.data.msg);
this.updateVisible(false);
this.$emit('done');
} else {
this.$message.error(res.data.msg);
}
}).catch(e => {
this.loading = false;
this.$message.error(e.message);
});
return false;
},
/* 更新visible */
updateVisible(value) {
this.$emit('update:visible', value);
}
}
}
</script>
<style scoped>
</style>