feat: 菜单权限

fix/0524_ch
向文可 3 years ago
parent 2dcc29c2f3
commit fb14c93ae6

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

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

@ -0,0 +1,79 @@
import * as api from '@/api/permission/feature.js';
import { ElMessage, ElMessageBox } from '@/plugins/element-plus';
const state = () => ({
code: 'FeatureManagement',
condition: {},
list: [],
total: 0,
opts: {
init: false,
},
});
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,
});
},
detail: async (context, id) => {
let res = await api.detail(id);
if (!res) {
ElMessage.error('加载详情失败');
}
return res;
},
save: async ({ dispatch }, data) => {
if (!data.parentId) {
data.parentId = 0;
}
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,
};

@ -9,6 +9,38 @@ const state = () => ({
opts: { opts: {
init: false, init: false,
system: [], system: [],
type: [
{
label: '目录',
value: 0,
},
{
label: '菜单',
value: 1,
},
{
label: '按钮',
value: 2,
},
],
method: [
{
label: 'GET',
value: 'GET',
},
{
label: 'POST',
value: 'POST',
},
{
label: 'PUT',
value: 'PUT',
},
{
label: 'DELETE',
value: 'DELETE',
},
],
}, },
}); });
const getters = {}; const getters = {};
@ -42,6 +74,9 @@ const actions = {
return res; return res;
}, },
save: async ({ dispatch }, data) => { save: async ({ dispatch }, data) => {
if (!data.parentId) {
data.parentId = 0;
}
let save = data.id ? api.update : api.create; let save = data.id ? api.update : api.create;
let res = await save(data); let res = await save(data);
if (res) { if (res) {

@ -5,21 +5,23 @@
<el-form-item label="系统" prop="systemId"> <el-form-item label="系统" prop="systemId">
<el-select <el-select
v-model="state.condition.systemId" v-model="state.condition.systemId"
:config="{ label: 'systemName', value: 'systemCode' }" :config="{ label: 'systemName', value: 'id' }"
:opts="opts.system" :opts="opts.system"
/> />
</el-form-item> </el-form-item>
</el-form> </el-form>
</div> </div>
<div class="body"> <div class="body">
<div class="aside"> <el-card class="aside">
<div class="aside-header"> <template #header>
<div class="aside-title">菜单</div> <div class="card-header">
<div class="aside-action"> <div class="title">菜单</div>
<el-button type="text" @click="handleCreate"></el-button> <div class="action">
<el-button type="text" @click="handleCreateMenu()"></el-button>
</div> </div>
</div> </div>
<div class="aside-body"> </template>
<div v-loading="loading" class="card-body">
<el-tree <el-tree
:data="list" :data="list"
default-expand-all default-expand-all
@ -27,77 +29,185 @@
highlight-current highlight-current
node-key="id" node-key="id"
:props="{ :props="{
label: 'name', label: 'title',
value: 'id', value: 'id',
children: 'childDepartment', children: 'menuChild',
disabled: (data) => !data.isEnable,
}" }"
@current-change="(data) => (state.condition2.departmentId = data.id)" @current-change="(data) => (state.condition2.menuId = data.id)"
> >
<template #default="{ data, node }"> <template #default="{ data }">
<div class="flex"> <div class="flex">
<div class="name"> <div class="name">
{{ data.name }} {{ data.title }}
</div> </div>
<div v-if="node.level > 1" class="btns"> <div class="btns">
<el-button type="text" @click.stop="handleCreate(null, data)"> <el-button type="text" @click.stop="handleCreateMenu(null, data)">
<el-icon name="Plus" /> <el-icon name="Plus" />
</el-button> </el-button>
<el-button type="text" @click.stop="handleCreate(data)"> <el-button type="text" @click.stop="handleCreateMenu(data)">
<el-icon name="Edit" /> <el-icon name="Edit" />
</el-button> </el-button>
<el-switch v-model="data.isEnable" @click.stop="handleEnabled(data)" /> <el-button type="text" @click.stop="handleDeleteMenu([data])">
<el-icon name="Delete" />
</el-button>
</div> </div>
</div> </div>
</template> </template>
</el-tree> </el-tree>
<el-dialog </div>
v-model="formState.formVisible" </el-card>
:title="(formState.form.id ? '编辑' : '新增') + '部门'" <el-card class="feature">
width="300px" <template #header>
<div class="card-header">
<div class="title">功能</div>
<div class="action">
<el-button type="text" @click="handleCreateFeature()"></el-button>
</div>
</div>
</template>
<div v-loading="loading2" class="card-body">
<el-tree
:data="featureList"
default-expand-all
:expand-on-click-node="false"
highlight-current
node-key="id"
:props="{
label: 'name',
value: 'id',
}"
>
<template #default="{ data }">
<div class="flex">
<div class="name">
{{ data.name }}
</div>
<div class="btns">
<el-button type="text" @click.stop="handleCreateFeature(data)">
<el-icon name="Edit" />
</el-button>
<el-button type="text" @click.stop="handleDeleteFeature([data])">
<el-icon name="Delete" />
</el-button>
</div>
</div>
</template>
</el-tree>
</div>
</el-card>
<el-card v-show="menuState.formVisible" class="form">
<template #header>
<div class="card-header">
<div class="title">菜单表单</div>
<div class="action"></div>
</div>
</template>
<el-form
ref="refsMenuForm"
v-loading="menuState.submitting"
label-width="100px"
:model="menuState.form"
:rules="menuState.rules"
> >
<el-form-item label="所属系统" prop="systemId">
<el-select
v-model="menuState.form.systemId"
:config="{ label: 'systemName', value: 'id' }"
:opts="opts.system"
/>
</el-form-item>
<el-form-item label="上级菜单" prop="parentId">
<el-cascader
v-model="menuState.form.parentId"
:options="list"
:props="{ label: 'title', value: 'id' }"
/>
</el-form-item>
<el-form-item label="菜单类型" prop="type">
<el-radio-group v-model="menuState.form.type" :opts="opts.type" />
</el-form-item>
<el-form-item label="菜单名称" prop="title">
<el-input v-model="menuState.form.title" />
</el-form-item>
<el-form-item label="权限标识" prop="permission">
<el-input v-model="menuState.form.permission" />
</el-form-item>
<el-form-item label="路由路径" prop="path">
<el-input v-model="menuState.form.path" />
</el-form-item>
<el-form-item label="前端页面" prop="page">
<el-input v-model="menuState.form.page" />
</el-form-item>
<el-form-item label="菜单图标" prop="icon">
<el-input v-model="menuState.form.icon" />
</el-form-item>
<el-form-item label="菜单排序" prop="sort">
<el-input-number v-model="menuState.form.sort" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSaveMenu"></el-button>
</el-form-item>
</el-form>
</el-card>
<el-card v-show="featureState.formVisible" class="form">
<template #header>
<div class="card-header">
<div class="title">功能表单</div>
<div class="action"></div>
</div>
</template>
<el-form <el-form
ref="refsForm" ref="refsFeatureForm"
v-loading="formState.submitting" v-loading="featureState.submitting"
label-width="100px" label-width="100px"
:model="formState.form" :model="featureState.form"
:rules="formState.rules" :rules="featureState.rules"
> >
<el-form-item label="上级组织" prop="parentId"> <el-form-item label="所属系统" prop="systemId">
<el-select
v-model="featureState.form.systemId"
:config="{ label: 'systemName', value: 'id' }"
:opts="opts.system"
@change="state.condition.systemId = $event"
/>
</el-form-item>
<el-form-item label="所属菜单" prop="menuId">
<el-cascader <el-cascader
v-model="formState.form.parentId" v-model="featureState.form.menuId"
:options="list" :options="list"
:props="{ :props="{
label: 'name', label: 'title',
value: 'id', value: 'id',
children: 'childDepartment', children: 'menuChild',
disabled: (data) => !data.isEnable,
checkStrictly: true, checkStrictly: true,
expandTrigger: 'hover', expandTrigger: 'hover',
emitPath: false, emitPath: false,
}" }"
/> />
</el-form-item> </el-form-item>
<el-form-item label="组织名称" prop="name"> <el-form-item label="功能名称" prop="name">
<el-input v-model="formState.form.name" /> <el-input v-model="featureState.form.name" />
</el-form-item>
<el-form-item label="请求方式" prop="method">
<el-select v-model="featureState.form.method" :opts="opts.method" />
</el-form-item>
<el-form-item label="接口路径" prop="uri">
<el-input v-model="featureState.form.uri" />
</el-form-item>
<el-form-item label="是否启用" prop="isEnable">
<el-switch v-model="featureState.form.isEnable" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSaveFeature"></el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<template #footer> </el-card>
<el-button @click="handleCancel"></el-button>
<el-button :loading="formState.submitting" type="primary" @click="handleSave">
保存
</el-button>
</template>
</el-dialog>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<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 '@/components/extra/ElSwitch.vue';
const store = useStore(); const store = useStore();
const { proxy } = getCurrentInstance(); const { proxy } = getCurrentInstance();
const opts = computed(() => store.state.menu.opts); const opts = computed(() => store.state.menu.opts);
@ -105,13 +215,14 @@
store.dispatch('menu/load'); store.dispatch('menu/load');
} }
/* 查询树结构 */ /* 查询菜单 */
const loading = ref(false); const loading = ref(false);
const list = computed(() => _.cloneDeep(store.state.menu.list)); const list = computed(() => _.cloneDeep(store.state.menu.list));
const state = reactive({ const state = reactive({
condition: { systemId: null }, condition: { systemId: null },
condition2: { menuId: null },
}); });
const handleSearch = async () => { const handleSearchMenu = async () => {
loading.value = true; loading.value = true;
await store.dispatch('menu/search'); await store.dispatch('menu/search');
loading.value = false; loading.value = false;
@ -119,59 +230,154 @@
watch( watch(
() => state.condition, () => state.condition,
(value) => { (value) => {
store.commit('dept/setCondition', _.cloneDeep(value)); store.commit('menu/setCondition', _.cloneDeep(value));
handleSearch(); handleSearchMenu();
},
{ immediate: true, deep: true }
);
/* 查询功能 */
const loading2 = ref(false);
const featureList = computed(() => _.cloneDeep(store.state.feature.list));
const handleSearchFeature = async () => {
loading2.value = true;
await store.dispatch('feature/search');
loading2.value = false;
};
watch(
() => state.condition2,
(value) => {
store.commit('feature/setCondition', _.cloneDeep(value));
handleSearchFeature();
}, },
{ immediate: true, deep: true } { immediate: true, deep: true }
); );
/* 表单 */ /* 菜单表单 */
const refsForm = ref(null); const refsMenuForm = ref(null);
const formState = reactive({ const menuState = reactive({
formVisible: false, formVisible: false,
submitting: false, submitting: false,
form: { form: {
id: null, id: null,
systemId: null,
parentId: null, parentId: null,
name: null, type: 0,
title: '',
permission: '',
path: '',
page: '',
icon: '',
sort: 0,
}, },
rules: { rules: {
name: [{ required: true, message: '请输入组织名称' }], systemId: [{ required: true, message: '所属系统不能为空' }],
parentId: [{ required: true, message: '请选择组织类型' }], type: [{ required: true, message: '菜单类型不能为空' }],
title: [{ required: true, message: '菜单名称不能为空' }],
permission: [{ required: true, message: '权限标识不能为空' }],
path: [{ required: true, message: '路由路径不能为空' }],
page: [{ required: true, message: '前端页面不能为空' }],
}, },
}); });
const handleCreate = (row, parent) => { const handleCreateMenu = (row, parent) => {
formState.formVisible = true; menuState.formVisible = true;
Object.assign( Object.assign(
formState.form, menuState.form,
row || { row || {
id: null, id: null,
parentId: null, systemId: state.condition.systemId,
name: null, parentId: state.condition2.menuId,
isEnable: true, type: 0,
title: '',
permission: '',
path: '',
page: '',
icon: '',
sort: 0,
} }
); );
if (parent) { if (parent) {
formState.form.parentId = parent.id; menuState.form.parentId = parent.id;
} }
}; };
const handleCancel = () => { const handleSaveMenu = async () => {
formState.formVisible = false; menuState.submitting = true;
try {
await proxy.$validate(refsMenuForm);
let data = _.cloneDeep(menuState.form);
let res = await store.dispatch('menu/save', data);
if (res) {
menuState.formVisible = false;
}
} catch (e) {
console.info('取消保存', e);
}
menuState.submitting = false;
}; };
const handleEnabled = (row) => { const handleDeleteMenu = (data) => {
store.dispatch('dept/save', row); store.dispatch(
'menu/remove',
data.map((item) => item.id)
);
}; };
const handleSave = async () => {
formState.submitting = true; /* 功能表单 */
const refsFeatureForm = ref(null);
const featureState = reactive({
formVisible: false,
submitting: false,
form: {
id: null,
systemId: null,
menuId: null,
name: null,
method: 'GET',
uri: null,
isEnable: true,
},
rules: {
systemId: [{ required: true, message: '所属系统不能为空' }],
menuId: [{ required: true, message: '所属菜单不能为空' }],
name: [{ required: true, message: '功能名称不能为空' }],
method: [{ required: true, message: '请求方式不能为空' }],
uri: [{ required: true, message: '接口路径不能为空' }],
isEnable: [{ required: true, message: '是否启用不能为空' }],
},
});
const handleCreateFeature = (row) => {
featureState.formVisible = true;
Object.assign(
featureState.form,
row || {
id: null,
systemId: state.condition.systemId,
menuId: state.condition2.menuId,
name: null,
method: 'GET',
uri: null,
isEnable: true,
}
);
};
const handleSaveFeature = async () => {
featureState.submitting = true;
try { try {
await proxy.$validate(refsForm); await proxy.$validate(refsFeatureForm);
let data = _.cloneDeep(formState.form); let data = _.cloneDeep(featureState.form);
await store.dispatch('dept/save', data); let res = await store.dispatch('feature/save', data);
formState.formVisible = false; if (res) {
featureState.formVisible = false;
}
} catch (e) { } catch (e) {
console.info('取消保存', e); console.info('取消保存', e);
} }
formState.submitting = false; featureState.submitting = false;
};
const handleDeleteFeature = (data) => {
store.dispatch(
'feature/remove',
data.map((item) => item.id)
);
}; };
</script> </script>
@ -183,21 +389,12 @@
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
.aside { > * {
margin-right: @layout-space; flex: 1;
.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; margin-left: @layout-space;
} }
} }
.aside-body {
:deep(.el-tree) { :deep(.el-tree) {
.flex { .flex {
width: 100%; width: 100%;
@ -222,6 +419,16 @@
} }
} }
} }
.card-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;
} }
} }
} }

@ -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