update: 菜单管理

This commit is contained in:
easy 2023-01-31 18:23:56 +08:00
parent e3183975c3
commit 730a77d2c4
5 changed files with 416 additions and 37 deletions

View File

@ -170,22 +170,18 @@
placeholder="请选择关联菜单" placeholder="请选择关联菜单"
multiple multiple
show-search show-search
tree-default-expand-all
:tree-data="form.treeData" :tree-data="form.treeData"
:field-names="{
children: 'children',
label: 'name',
value: 'id',
}"
> >
<template #title="{ value: val, title }">
<b
v-if="val === 'parent 1-1'"
style="color: #08c"
>{{ val }}</b
>
<template v-else>{{ title }}</template>
</template>
</a-tree-select> </a-tree-select>
</a-form-item> </a-form-item>
</a-form-item> </a-form-item>
<a-form-item label="权限"> <a-form-item label="权限">
<PermissChoose v-model:value="form.data.permissions" /> <PermissChoose :first-width="3" max-height="350px" v-model:value="form.data.permissions" />
</a-form-item> </a-form-item>
</a-form> </a-form>
@ -245,17 +241,16 @@ const form = reactive({
treeData: [], // treeData: [], //
assetsType: [] as assetType[], // assetsType: [] as assetType[], //
premissonList: [], //
init: () => { init: () => {
// //
routeParams.id && routeParams.id &&
getMenuInfo_api(routeParams.id).then((resp) => { getMenuInfo_api(routeParams.id).then((resp) => {
console.log('菜单详情', resp); form.data = resp.result as formType
}); });
// //
getMenuTree_api({ paging: false }).then((resp) => { getMenuTree_api({ paging: false }).then((resp: any) => {
console.log('关联菜单', resp); form.treeData = resp.result;
}); });
// //
getAssetsType_api().then((resp: any) => { getAssetsType_api().then((resp: any) => {

View File

@ -1,18 +1,134 @@
<template> <template>
<div class="button-mange-container"> <div class="button-mange-container">
<JTable
ref="tableRef"
:columns="table.columns"
model="TABLE"
:dataSource="table.data"
>
<template #headerTitle>
<a-button
type="primary"
style="margin-right: 10px"
@click="() => dialog.openDialog()"
><plus-outlined />新增</a-button
>
</template>
<template #action="slotProps">
<a-space :size="16">
<a-tooltip>
<template #title>编辑</template>
<a-button
style="padding: 0"
type="link"
@click="() => dialog.openDialog(slotProps)"
>
<edit-outlined />
</a-button>
</a-tooltip>
<a-tooltip>
<template #title>查看</template>
<a-button
style="padding: 0"
type="link"
@click="() => dialog.openDialog(slotProps)"
>
<edit-outlined />
</a-button>
</a-tooltip>
<a-popconfirm
title="确认删除"
ok-text="确定"
cancel-text="取消"
:disabled="slotProps.status"
>
<a-tooltip>
<template #title>删除</template>
<a-button style="padding: 0" type="link">
<delete-outlined />
</a-button>
</a-tooltip>
</a-popconfirm>
</a-space>
</template>
</JTable>
<div class="dialog">
<ButtonAddDialog ref="dialogRef" @confirm="dialog.confirm" />
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import {
EditOutlined,
DeleteOutlined,
PlusOutlined,
} from '@ant-design/icons-vue';
import ButtonAddDialog from '../components/ButtonAddDialog.vue';
import { getMenuInfo_api } from '@/api/system/menu';
//
const route = useRoute(); const route = useRoute();
const routeParams = { const routeParams = {
id: route.params.id === ':id' ? '' : route.params.id, id: route.params.id === ':id' ? '' : (route.params.id as string),
...route.query, ...route.query,
}; };
//
const dialogRef = ref<any>(null);
const dialog = {
//
openDialog: (row?: object) => {
dialogRef.value && dialogRef.value.openDialog(row);
},
confirm: () => {},
};
//
const table = reactive({
columns: [
{
title: '编码',
dataIndex: 'id',
key: 'id',
width: 220,
},
{
title: '名称',
dataIndex: 'name',
key: 'name',
width: 300,
},
{
title: '说明',
dataIndex: 'description',
key: 'description',
},
{
title: '操作',
dataIndex: 'action',
key: 'action',
scopedSlots: true,
width: 240,
},
],
data: [] as tableDataItem[],
getList: () => {
routeParams.id &&
getMenuInfo_api(routeParams.id).then((resp: any) => {
table.data = resp.result.buttons as tableDataItem[];
});
},
});
table.getList();
type tableDataItem = {
id: string;
name: string;
description?: string;
permissions: object[];
};
</script> </script>
<style scoped> <style scoped></style>
</style>

View File

@ -0,0 +1,97 @@
<template>
<a-modal
v-model:visible="dialog.visible"
title="新增"
width="660px"
@ok="dialog.handleOk"
>
<a-form :model="form.data" class="basic-form">
<a-form-item
label="编码"
name="id"
:rules="[
{ required: true, message: '请输入编码' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input v-model:value="form.data.id" />
</a-form-item>
<a-form-item
label="名称"
name="name"
:rules="[
{ required: true, message: '请输入名称' },
{ max: 64, message: '最多可输入64个字符' },
]"
>
<a-input v-model:value="form.data.name" />
</a-form-item>
<a-form-item label="权限">
<PermissChoose
:first-width="8"
max-height="350px"
v-model:value="form.data.permissions"
/>
</a-form-item>
<a-form-item label="说明" name="describe">
<a-textarea
v-model:value="form.data.describe"
:rows="4"
placeholder="请输入说明"
/>
</a-form-item>
</a-form>
</a-modal>
</template>
<script setup lang="ts">
import PermissChoose from '../components/PermissChoose.vue';
const emits = defineEmits(['confirm']);
const dialog = reactive({
visible: false,
handleOk: () => {
dialog.changeVisible();
},
changeVisible: (formValue?: formType, show?: boolean) => {
dialog.visible = show === undefined ? !dialog.visible : show;
form.data = formValue || { ...initForm };
console.log(1111111111, form.data);
},
});
const initForm = {
name: '',
id: '',
permissions: [],
describe: '',
} as formType;
const form = reactive({
data: { ...initForm },
});
//
defineExpose({
openDialog: dialog.changeVisible,
});
type formType = {
name: string;
id: string;
permissions: any[];
describe: string;
};
</script>
<style lang="less" scoped>
.basic-form {
.ant-form-item {
display: block;
:deep(.ant-form-item-label) {
overflow: inherit;
label::after {
display: none;
}
}
}
}
</style>

View File

@ -5,43 +5,207 @@
style="width: 300px" style="width: 300px"
allowClear allowClear
placeholder="请输入权限名称" placeholder="请输入权限名称"
@input="search.search"
/> />
<div class="permission-table">
<a-row :gutter="24" class="table-head">
<a-col :span="props.firstWidth">权限名称</a-col
><a-col :span="24 - props.firstWidth">权限操作</a-col>
</a-row>
<div class="table-body" :style="{ 'max-height': props.maxHeight }">
<a-row
:gutter="24"
class="row"
v-for="rowItem in permission.list"
>
<a-col :span="props.firstWidth" class="item-name">
<a-checkbox
v-model:checked="rowItem.checkAll"
:indeterminate="rowItem.indeterminate"
@change="() => permission.selectAllOpions(rowItem)"
>
{{ rowItem.name }}
</a-checkbox>
</a-col>
<a-col :span="24 - props.firstWidth">
<a-checkbox-group
v-model:value="rowItem.checkedList"
:options="rowItem.options"
@change="((checkValue:string[])=>permission.selectOption(rowItem, checkValue))"
/>
</a-col>
</a-row>
</div>
</div>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { exportPermission_api } from '@/api/system/permission'; import { exportPermission_api } from '@/api/system/permission';
const props = defineProps({ const props = defineProps<{
value: Array, value: any[];
}); firstWidth: number;
maxHeight: string;
}>();
const emits = defineEmits(['update:value']);
const searchValue = ref<string>(''); const searchValue = ref<string>('');
const search = reactive({
value: '',
searchTimer: null as null | number,
search: () => {
if (search.searchTimer) {
clearTimeout(search.searchTimer);
}
search.searchTimer = setTimeout(() => {
nextTick(() => permission.getList());
search.searchTimer = null;
}, 1000);
},
});
const permission = reactive({ const permission = reactive({
list: [] as permissionType[], list: [] as permissionType[],
// //
getList: () => { getList: () => {
const params: paramsType = {
exportPermission_api({ paging: false }).then((resp) => { paging: false,
permission.list = resp.result as permissionType[] };
if (search.value) {
params.terms = [
{ column: 'name$like', value: `%${search.value}%` },
];
}
exportPermission_api(params).then((resp) => {
permission.list = permission.makeList(
props.value,
resp.result as any[],
);
}); });
}, },
// /
selectAllOpions: (row: permissionType) => {
const newValue = props.value.filter(
(item) => item.permission !== row.id,
);
row = toRaw(row);
if (row.checkAll) {
row.checkedList = row.options.map((item) => item.value);
newValue.push({
permission: row.id,
actions: row.checkedList,
});
} else {
row.checkedList = [];
}
emits('update:value', newValue);
},
//
selectOption: (row: permissionType, newValue: string[]) => {
const newProp = props.value.filter(
(item) => item.permission !== row.id,
);
if (newValue.length === row.options.length) {
row.checkAll = true;
row.indeterminate = false;
newProp.push({
permission: row.id,
actions: newValue,
});
} else if (newValue.length > 0) {
row.checkAll = false;
row.indeterminate = true;
newProp.push({
permission: row.id,
actions: newValue,
});
}
emits('update:value', newProp);
},
makeList: (checkedValue: any[], sourceList: any[]): permissionType[] => {
console.log(checkedValue);
const result = sourceList.map((item) => {
const checked = checkedValue.find(
(checkedItem) => checkedItem.permission === item.id,
);
const options = item.actions.map((actionItem: any) => ({
label: actionItem.name,
value: actionItem.action,
}));
console.log(item, checked);
return {
id: item.id,
name: item.name,
checkedList: (checked && checked.actions) || [],
checkAll:
(checked &&
item.actions &&
checked?.actions.length === item.actions.length) ||
false,
indeterminate:
(checked &&
item.actions &&
checked.actions.length < item.actions.length) ||
false,
options,
};
}) as permissionType[];
return result;
},
}); });
permission.getList() permission.getList();
type permissionType = { type permissionType = {
id: string; id: string;
name: string; name: string;
actions: object[] checkedList: string[];
} checkAll: boolean;
indeterminate: boolean;
options: any[];
};
type paramsType = {
paging: boolean;
terms?: object[];
};
</script> </script>
<style scoped></style> <style lang="less" scoped>
.permission-choose-container {
.permission-table {
margin-top: 12px;
font-size: 14px;
border: 1px solid #d9d9d9;
color: rgba(0, 0, 0, 0.85);
.table-head {
padding: 12px;
background-color: #d9d9d9;
margin: 0 !important;
}
.table-body {
overflow: auto;
.row {
margin: 0 !important;
border-bottom: 1px solid #d9d9d9;
> div {
padding: 8px 12px;
}
.item-name {
display: flex;
align-items: center;
border-right: 1px solid #d9d9d9;
}
}
}
}
}
</style>

View File

@ -19,7 +19,7 @@
<a-button>菜单实例</a-button> <a-button>菜单实例</a-button>
</template> </template>
<template #createTime="slotProps"> <template #createTime="slotProps">
{{ slotProps.createTime }} {{ moment(slotProps.createTime).format('YYYY-MM-DD HH:mm:ss') }}
</template> </template>
<template #action="slotProps"> <template #action="slotProps">
<a-space :size="16"> <a-space :size="16">
@ -30,7 +30,7 @@
type="link" type="link"
@click="table.toDetails(slotProps)" @click="table.toDetails(slotProps)"
> >
<edit-outlined /> <search-outlined />
</a-button> </a-button>
</a-tooltip> </a-tooltip>
<a-tooltip> <a-tooltip>
@ -40,7 +40,7 @@
type="link" type="link"
@click="table.toDetails(slotProps)" @click="table.toDetails(slotProps)"
> >
<edit-outlined /> <plus-circle-outlined />
</a-button> </a-button>
</a-tooltip> </a-tooltip>
@ -66,6 +66,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { getMenuTree_api } from '@/api/system/menu'; import { getMenuTree_api } from '@/api/system/menu';
import {
SearchOutlined,
DeleteOutlined,
PlusOutlined,
PlusCircleOutlined
} from '@ant-design/icons-vue';
import moment from 'moment';
const router = useRouter(); const router = useRouter();