feat: 用户管理

fix/0524_ch
向文可 3 years ago
parent c4cbcc91d7
commit 0837259f96

@ -0,0 +1,29 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/uc/menu/tree',
method: 'get',
params,
});
};
export const create = (data) => {
return request({
url: '/uc/menu',
method: 'post',
data,
});
};
export const update = (data) => {
return request({
url: '/uc/menu/' + data.id,
method: 'put',
data,
});
};
export const remove = (idList) => {
return request({
url: '/uc/menu',
method: 'delete',
params: { idList },
});
};

@ -0,0 +1,8 @@
import request from '@/utils/request.js';
export const search = (params) => {
return request({
url: '/uc/system',
method: 'get',
params,
});
};

@ -18,6 +18,15 @@ export default [
icon: 'organization-chart', icon: 'organization-chart',
}, },
}, },
{
path: 'menu',
name: 'MenuManagement',
component: () => import('@/views/permission/menu/index.vue'),
meta: {
title: '菜单管理',
icon: 'menu-2',
},
},
{ {
path: 'employee', path: 'employee',
name: 'EmployeeManagement', name: 'EmployeeManagement',

@ -42,8 +42,8 @@ const actions = {
}, },
load: async ({ state, commit }) => { load: async ({ state, commit }) => {
commit('setOpts', { commit('setOpts', {
init: true,
...state.opts, ...state.opts,
init: true,
}); });
}, },
detail: async (context, id) => { detail: async (context, id) => {

@ -1,47 +1,32 @@
import * as api from '@/api/permission/dept.js'; import * as api from '@/api/permission/dept.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus'; import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({ const state = () => ({
employeeCode: 'DeptEmployeeManagement', condition: {},
roleCode: 'DeptRoleManagement',
condition1: {},
condition2: {},
list: [], list: [],
total: 0, total: 0,
employeeList: [],
employeeTotal: 0,
opts: { opts: {
init: false, init: false,
}, },
}); });
const getters = {}; const getters = {};
const mutations = { const mutations = {
setCondition1: (state, data) => (state.condition1 = data), setCondition: (state, data) => (state.condition = data),
setCondition2: (state, data) => (state.condition2 = data),
setList: (state, data) => (state.list = data), setList: (state, data) => (state.list = data),
setTotal: (state, data) => (state.total = data), setTotal: (state, data) => (state.total = data),
setEmployeeList: (state, data) => (state.employeeList = data),
setEmployeeTotal: (state, data) => (state.employeeTotal = data),
setOpts: (state, data) => (state.opts = data), setOpts: (state, data) => (state.opts = data),
}; };
const actions = { const actions = {
search: async ({ state, commit }) => { search: async ({ state, commit }) => {
let res = await api.search(state.condition1); let res = await api.search(state.condition);
commit('setList', res || []); commit('setList', res || []);
if (!res) { if (!res) {
ElMessage.error('查询失败'); ElMessage.error('查询失败');
} }
return res; return res;
}, },
searchEmployee: async ({ state, commit }) => { load: async ({ state, commit }) => {
let res = await api.searchEmployee(state.condition2);
commit('setEmployeeList', res || []);
if (!res) {
ElMessage.error('查询部门员工失败');
}
return res;
},
load: async ({ commit }) => {
commit('setOpts', { commit('setOpts', {
...state.opts,
init: true, init: true,
}); });
}, },

@ -27,8 +27,8 @@ const actions = {
}, },
load: async ({ state, commit }) => { load: async ({ state, commit }) => {
commit('setOpts', { commit('setOpts', {
init: true,
...state.opts, ...state.opts,
init: true,
}); });
}, },
detail: async (context, id) => { detail: async (context, id) => {

@ -1,3 +1,4 @@
import * as deptAPI from '@/api/permission/dept.js';
import * as api from '@/api/system/employee.js'; import * as api from '@/api/system/employee.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus'; import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({ const state = () => ({
@ -7,7 +8,7 @@ const state = () => ({
total: 0, total: 0,
opts: { opts: {
init: false, init: false,
type: [], dept: [],
}, },
}); });
const getters = {}; const getters = {};
@ -30,10 +31,11 @@ const actions = {
} }
return res; return res;
}, },
load: async ({ commit }) => { load: async ({ state, commit }) => {
commit('setOpts', { commit('setOpts', {
...state.opts,
init: true, init: true,
type: [{ label: '普通员工', value: 1 }], dept: await deptAPI.search({ hiddenDisable: true }),
}); });
}, },
detail: async (context, id) => { detail: async (context, id) => {

@ -0,0 +1,79 @@
import * as api from '@/api/permission/menu.js';
import * as systemAPI from '@/api/permission/system.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
code: 'MenuManagement',
condition: {},
list: [],
total: 0,
opts: {
init: false,
system: [],
},
});
const getters = {};
const mutations = {
setCondition: (state, data) => (state.condition = data),
setList: (state, data) => (state.list = data),
setTotal: (state, data) => (state.total = data),
setOpts: (state, data) => (state.opts = data),
};
const actions = {
search: async ({ state, commit }) => {
let res = await api.search(state.condition);
commit('setList', res || []);
if (!res) {
ElMessage.error('查询失败');
}
return res;
},
load: async ({ state, commit }) => {
commit('setOpts', {
...state.opts,
init: true,
system: await systemAPI.search(),
});
},
detail: async (context, id) => {
let res = await api.detail(id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
save: async ({ dispatch }, data) => {
let save = data.id ? api.update : api.create;
let res = await save(data);
if (res) {
ElMessage.success('保存成功');
dispatch('search');
} else {
ElMessage.error('保存失败');
}
return res;
},
remove: async ({ dispatch }, idList) => {
if (!idList.length) {
ElMessage.warning('请选择要删除的数据');
} else {
try {
await ElMessageBox.confirm('数据删除后无法恢复,确定要删除吗?', '危险操作');
let res = await api.remove(idList.join(','));
if (res) {
ElMessage.success('删除成功');
dispatch('search');
} else {
ElMessage.error('删除失败');
}
} catch (e) {
console.info('取消删除', e);
}
}
},
};
export default {
state,
getters,
mutations,
actions,
};

@ -164,7 +164,7 @@
watch( watch(
() => state.condition1, () => state.condition1,
(value) => { (value) => {
store.commit('dept/setCondition1', _.cloneDeep(value)); store.commit('dept/setCondition', _.cloneDeep(value));
handleSearch(); handleSearch();
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }

@ -8,6 +8,20 @@
:model="form" :model="form"
:rules="rules" :rules="rules"
> >
<el-form-item v-if="!form.id" label="所在组织" prop="departmentId">
<el-cascader
v-model="form.departmentId"
:options="opts.dept"
:props="{
label: 'name',
value: 'id',
trigger: 'hover',
checkStrictly: true,
emitPath: false,
children: 'childDepartment',
}"
/>
</el-form-item>
<el-form-item label="用户名" prop="userName"> <el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" /> <el-input v-model="form.userName" />
</el-form-item> </el-form-item>
@ -17,9 +31,6 @@
<el-form-item label="员工姓名" prop="employeeName"> <el-form-item label="员工姓名" prop="employeeName">
<el-input v-model="form.employeeName" /> <el-input v-model="form.employeeName" />
</el-form-item> </el-form-item>
<el-form-item label="员工类型" prop="employeeType">
<el-select v-model="form.employeeType" :opts="opts.type" />
</el-form-item>
<el-form-item label="手机号" prop="phone"> <el-form-item label="手机号" prop="phone">
<el-input v-model="form.phone" /> <el-input v-model="form.phone" />
</el-form-item> </el-form-item>
@ -52,6 +63,7 @@
const refsForm = ref(null); const refsForm = ref(null);
const form = reactive({ const form = reactive({
id: null, id: null,
departmentId: null,
email: '', email: '',
employeeName: '', employeeName: '',
employeeType: 1, employeeType: 1,
@ -61,11 +73,11 @@
userName: '', userName: '',
}); });
const rules = reactive({ const rules = reactive({
departmentId: [{ required: unref(computed(() => !form.id)), message: '所在组织不能为空' }],
userName: [{ required: true, message: '用户名不能为空' }], userName: [{ required: true, message: '用户名不能为空' }],
phone: [{ required: true, message: '手机号码不能为空' }], phone: [{ required: true, message: '手机号码不能为空' }],
email: [{ required: true, message: '邮箱地址不能为空' }], email: [{ required: true, message: '邮箱地址不能为空' }],
employeeName: [{ required: true, message: '员工姓名不能为空' }], employeeName: [{ required: true, message: '员工姓名不能为空' }],
employeeType: [{ required: true, message: '员工类型不能为空' }],
}); });
const opts = computed(() => store.state.employee.opts); const opts = computed(() => store.state.employee.opts);
if (!unref(opts).init) { if (!unref(opts).init) {

@ -14,9 +14,29 @@
> >
<template #search> <template #search>
<el-form inline> <el-form inline>
<el-form-item label="用户名" prop="userName"> <el-form-item label="姓名" prop="employeeName">
<el-input v-model="state.condition.employeeName" />
</el-form-item>
<el-form-item label="花名" prop="userName">
<el-input v-model="state.condition.userName" /> <el-input v-model="state.condition.userName" />
</el-form-item> </el-form-item>
<el-form-item label="手机号" prop="phone">
<el-input v-model="state.condition.phone" />
</el-form-item>
<el-form-item label="所在组织" prop="departmentId">
<el-cascader
v-model="state.condition.departmentId"
:options="opts.dept"
:props="{
label: 'name',
value: 'id',
trigger: 'hover',
checkStrictly: true,
emitPath: false,
children: 'childDepartment',
}"
/>
</el-form-item>
</el-form> </el-form>
</template> </template>
</table-list> </table-list>
@ -25,7 +45,6 @@
<script setup lang="jsx"> <script setup lang="jsx">
import ElButton from '@/components/extra/ElButton.vue'; import ElButton from '@/components/extra/ElButton.vue';
import { ElSwitch } from 'element-plus/es'; import { ElSwitch } from 'element-plus/es';
const { proxy } = getCurrentInstance();
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
const loading = ref(false); const loading = ref(false);
@ -38,7 +57,10 @@
} }
const state = reactive({ const state = reactive({
condition: { condition: {
departmentId: null,
employeeName: null,
userName: null, userName: null,
phone: null,
}, },
}); });
watch( watch(
@ -50,7 +72,9 @@
); );
const handleReset = () => { const handleReset = () => {
state.condition = { state.condition = {
departmentId: null,
employeeName: null, employeeName: null,
userName: null,
phone: null, phone: null,
}; };
}; };
@ -92,7 +116,7 @@
fixed: 'left', fixed: 'left',
}, },
{ {
label: '用户名', label: '名',
prop: 'userName', prop: 'userName',
minWidth: 160, minWidth: 160,
}, },
@ -103,26 +127,22 @@
}, },
{ {
label: '所在组织', label: '所在组织',
prop: 'depts', prop: 'departmentNameChain',
minWidth: 160, minWidth: 160,
}, },
{ {
label: '拥有角色', label: '拥有角色',
prop: 'roles', prop: 'roles',
minWidth: 160, minWidth: 160,
slots: {
default: ({ row }) => row.roleList.map((item) => item.roleName).join(','),
},
}, },
{ {
label: '邮箱', label: '邮箱',
prop: 'email', prop: 'email',
width: 160, width: 160,
}, },
{
label: '员工类型',
width: 120,
slots: {
default: ({ row }) => proxy.$dict(unref(opts).type, row.employeeType),
},
},
{ {
label: '是否启用', label: '是否启用',
prop: 'isEnable', prop: 'isEnable',

@ -0,0 +1,229 @@
<template>
<div class="list-container">
<div class="header">
<el-form inline :model="state.condition">
<el-form-item label="系统" prop="systemId">
<el-select
v-model="state.condition.systemId"
:config="{ label: 'systemName', value: 'systemCode' }"
:opts="opts.system"
/>
</el-form-item>
</el-form>
</div>
<div class="body">
<div class="aside">
<div class="aside-header">
<div class="aside-title">菜单</div>
<div class="aside-action">
<el-button type="text" @click="handleCreate"></el-button>
</div>
</div>
<div class="aside-body">
<el-tree
:data="list"
default-expand-all
:expand-on-click-node="false"
highlight-current
node-key="id"
:props="{
label: 'name',
value: 'id',
children: 'childDepartment',
disabled: (data) => !data.isEnable,
}"
@current-change="(data) => (state.condition2.departmentId = data.id)"
>
<template #default="{ data, node }">
<div class="flex">
<div class="name">
{{ data.name }}
</div>
<div v-if="node.level > 1" class="btns">
<el-button type="text" @click.stop="handleCreate(null, data)">
<el-icon name="Plus" />
</el-button>
<el-button type="text" @click.stop="handleCreate(data)">
<el-icon name="Edit" />
</el-button>
<el-switch v-model="data.isEnable" @click.stop="handleEnabled(data)" />
</div>
</div>
</template>
</el-tree>
<el-dialog
v-model="formState.formVisible"
:title="(formState.form.id ? '编辑' : '新增') + '部门'"
width="300px"
>
<el-form
ref="refsForm"
v-loading="formState.submitting"
label-width="100px"
:model="formState.form"
:rules="formState.rules"
>
<el-form-item label="上级组织" prop="parentId">
<el-cascader
v-model="formState.form.parentId"
:options="list"
:props="{
label: 'name',
value: 'id',
children: 'childDepartment',
disabled: (data) => !data.isEnable,
checkStrictly: true,
expandTrigger: 'hover',
emitPath: false,
}"
/>
</el-form-item>
<el-form-item label="组织名称" prop="name">
<el-input v-model="formState.form.name" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="handleCancel"></el-button>
<el-button :loading="formState.submitting" type="primary" @click="handleSave">
保存
</el-button>
</template>
</el-dialog>
</div>
</div>
</div>
</div>
</template>
<script setup lang="jsx">
import ElButton from '@/components/extra/ElButton.vue';
import ElSwitch from '@/components/extra/ElSwitch.vue';
const store = useStore();
const { proxy } = getCurrentInstance();
const opts = computed(() => store.state.menu.opts);
if (!unref(opts).init) {
store.dispatch('menu/load');
}
/* 查询树结构 */
const loading = ref(false);
const list = computed(() => _.cloneDeep(store.state.menu.list));
const state = reactive({
condition: { systemId: null },
});
const handleSearch = async () => {
loading.value = true;
await store.dispatch('menu/search');
loading.value = false;
};
watch(
() => state.condition,
(value) => {
store.commit('dept/setCondition', _.cloneDeep(value));
handleSearch();
},
{ immediate: true, deep: true }
);
/* 表单 */
const refsForm = ref(null);
const formState = reactive({
formVisible: false,
submitting: false,
form: {
id: null,
parentId: null,
name: null,
},
rules: {
name: [{ required: true, message: '请输入组织名称' }],
parentId: [{ required: true, message: '请选择组织类型' }],
},
});
const handleCreate = (row, parent) => {
formState.formVisible = true;
Object.assign(
formState.form,
row || {
id: null,
parentId: null,
name: null,
isEnable: true,
}
);
if (parent) {
formState.form.parentId = parent.id;
}
};
const handleCancel = () => {
formState.formVisible = false;
};
const handleEnabled = (row) => {
store.dispatch('dept/save', row);
};
const handleSave = async () => {
formState.submitting = true;
try {
await proxy.$validate(refsForm);
let data = _.cloneDeep(formState.form);
await store.dispatch('dept/save', data);
formState.formVisible = false;
} catch (e) {
console.info('取消保存', e);
}
formState.submitting = false;
};
</script>
<style lang="less" scoped>
.list-container {
display: flex;
flex-direction: column;
.body {
flex: 1;
display: flex;
flex-direction: row;
.aside {
margin-right: @layout-space;
.aside-header {
min-width: max-content;
display: flex;
justify-content: space-between;
align-items: center;
.aside-title {
font-size: @layout-h3;
}
.aside-action {
margin-left: @layout-space;
}
}
.aside-body {
:deep(.el-tree) {
.flex {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.btns {
display: flex;
align-items: center;
margin-left: @layout-space-super;
.el-button {
position: relative;
top: 2px;
+ .el-button {
margin-left: @layout-space-small;
}
}
.el-switch {
height: 100%;
transform: scale(0.6);
}
}
}
}
}
}
}
</style>

@ -21,10 +21,10 @@ export default (configEnv) => {
proxy: { proxy: {
'/api': { '/api': {
// target: 'http://192.168.10.109:8090/', // 显雨 // target: 'http://192.168.10.109:8090/', // 显雨
// target: 'http://192.168.10.5:4500', // 高玉 target: 'http://192.168.10.5:4500', // 高玉
// target: 'http://192.168.10.67:8090', // 罗战 // target: 'http://192.168.10.67:8090', // 罗战
// target: 'http://192.168.10.93:8090', // 周渺 // target: 'http://192.168.10.93:8090', // 周渺
target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址 // target: 'https://k8s-horse-gateway.mashibing.cn/', // 测试地址
// target: 'https://you-gateway.mashibing.com', // 生产环境 // target: 'https://you-gateway.mashibing.com', // 生产环境
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''), rewrite: (path) => path.replace(/^\/api/, ''),

Loading…
Cancel
Save