| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658 |
- <template>
- <div>
- <PlusPage
- ref="plusPageInstance"
- :columns="tableConfig"
- :request="getList"
- :before-search-submit="handleBeforeSearch"
- :is-card="true"
- :search="{
- labelWidth: 100,
- showNumber: 3
- }"
- :table="{
- actionBar: { buttons, type: 'link', width: 140 },
- adaptive: { offsetBottom: 50 },
- treeProps: { children: 'children' },
- rowKey: 'menuId'
- }"
- :pagination="false"
- >
- <template #table-title>
- <el-row class="button-row">
- <el-button size="default" type="success" @click="handleCreate">新增</el-button>
- </el-row>
- </template>
- </PlusPage>
- <!-- 弹窗编辑 -->
- <PlusDialogForm
- ref="dialogForm"
- v-model="form"
- v-model:visible="dialogVisible"
- @confirm="handleSubmit"
- @close="handleClose"
- :form="{ columns, labelPosition: 'right',labelWidth: 100, rules, rowProps: {gutter: 20}, colProps: {span: 12} }"
- :dialog="{ title: dialogTitle + '菜单', width: 800, confirmLoading }"
- />
- </div>
- </template>
- <script lang="ts" setup>
- import {computed, defineOptions, h, nextTick, onMounted, reactive, ref, toRefs} from "vue";
- import type {FormRules} from 'element-plus'
- import {ElMessage} from "element-plus";
- import {
- type FieldValues,
- type PlusColumn,
- PlusDialogForm,
- PlusPage,
- PlusPageInstance,
- useTable
- } from "plus-pro-components";
- import {cloneDeep} from "lodash-es";
- import {
- addMenu,
- deleteMenu,
- getSystemList,
- getSystemMenuDetailById,
- getSystemMenuList,
- updateMenu
- } from "@/api/system/menu";
- defineOptions({
- name: "PageTable"
- });
- interface TableRow {
- id: number;
- name: string;
- status: string;
- tag: string;
- time: Date;
- }
- onMounted(async () => {
- await getSystem()
- })
- // 菜单列表
- const menuList = ref([]);
- const plusPageInstance = ref<PlusPageInstance | null>(null)
- const getList = async (query: Record<string, any>) => {
- let res = await getSystemMenuList(query);
- let list = listToTree(res.data);
- menuList.value = list;
- return {
- data: list,
- total: list.length,
- }
- };
- const listToTree = (data: object[]) => {
- // * 先生成parent建立父子关系
- const obj = {};
- data.forEach((item: any) => {
- obj[item.menuId] = item;
- });
- // * obj -> {1001: {id: 1001, parentId: 0, name: 'AA'}, 1002: {...}}
- // console.log(obj, "obj")
- const parentList = [];
- data.forEach((item: any) => {
- const parent = obj[item.parentId];
- if (parent) {
- // * 当前项有父节点
- parent.children = parent.children || [];
- parent.children.push(item);
- } else {
- // * 当前项没有父节点 -> 顶层
- parentList.push(item);
- }
- });
- return parentList;
- }
- // 重新请求列表接口
- const refresh = () => {
- plusPageInstance.value?.getList()
- }
- // 搜索之前函数
- const handleBeforeSearch = (values: any) => {
- // 返回新的参数
- return cloneDeep(values);
- };
- const dialogTitle = computed(() => (state.isCreate ? '新增' : '编辑'))
- const {buttons} = useTable<TableRow[]>();
- const systemList = ref([]);
- const getSystem = async () => {
- // 获取系统列表
- let res = await getSystemList();
- systemList.value = res.data.map((item: any) => ({
- label: item.sysName,
- value: item.sysCode
- }))
- }
- // 表格数据
- const tableConfig: PlusColumn[] = [
- {
- label: "菜单名称",
- prop: "menuName",
- width: 180,
- tableColumnProps: {
- showOverflowTooltip: true
- },
- },
- {
- label: "图标",
- prop: "icon",
- hideInSearch: true,
- width: 80,
- // 返回一个组件
- render: (value) =>
- h('i',
- {
- class: `${value} iconfont`,
- style: {fontSize: '16px'}
- },
- )
- },
- {
- label: "排序",
- prop: "orderNum",
- width: 80,
- hideInSearch: true
- },
- {
- label: "权限标识",
- prop: "perms",
- hideInSearch: true
- },
- {
- label: "网关URL",
- prop: "url",
- hideInSearch: true
- },
- {
- label: "组件路径",
- prop: "component",
- hideInSearch: true
- },
- {
- label: "状态",
- prop: "status",
- valueType: "tag",
- hideInSearch: true,
- fieldProps: value => ({
- type: value === '0' ? 'primary' : 'danger'
- }),
- fieldSlots: {
- default: ({value}) => (value === '0' ? '正常' : '停用')
- }
- },
- {
- label: "系统",
- prop: "systemCode",
- valueType: "select",
- options: computed(() => systemList.value),
- hideInTable: true,
- fieldProps: {
- clearable: true,
- placeholder: '请选择系统',
- style: {width: '100%'}
- }
- },
- {
- label: "菜单状态",
- prop: "status",
- valueType: "select",
- options: [{label: '正常', value: '0'}, {label: '停用', value: '1'}],
- hideInTable: true,
- fieldProps: {
- clearable: true,
- placeholder: '请选择菜单状态',
- style: {width: '100%'}
- }
- },
- {
- label: "创建时间",
- prop: "createTime",
- valueType: "date-picker",
- hideInSearch: true,
- minWidth: 180
- }
- ];
- /*--------------------表单--------------------*/
- // 表单实例
- const dialogForm = ref(null);
- interface State {
- dialogVisible: boolean;
- detailsVisible: boolean;
- confirmLoading: boolean;
- selectedIds: number[];
- isCreate: boolean;
- form: {
- component: string, // 组件路径
- icon: string, // 菜单图标
- isCache: string, // 是否缓存 (0缓存 1不缓存)
- isFrame: string, // 是否外链 (0是 1否)
- menuName: string,// 菜单名称
- menuType: string, // 菜单类型(M目录 C菜单 F按钮)
- orderNum: string,// 显示排序
- parentId: number,// 上级菜单
- path: string, // 路由地址
- perms: string, // 权限标识
- query: string, // 路由参数
- routeName: string, // 路由名称
- status: string, // 菜单状态(0正常 1停用)
- systemCode: string, // 系统编码
- url: string, // 网关URL
- urlmatch: string, // 网关权限
- visible: string, // 显示状态(0显示 1隐藏)
- };
- rules: FormRules;
- }
- const state = reactive<State>({
- dialogVisible: false,
- detailsVisible: false,
- confirmLoading: false,
- selectedIds: [],
- isCreate: false,
- form: {
- component: null, // 组件路径
- icon: null, // 菜单图标
- isCache: '0', // 是否缓存 (0缓存 1不缓存)
- isFrame: '1', // 是否外链 (0是 1否)
- menuName: null,// 菜单名称
- menuType: 'M', // 菜单类型(M目录 C菜单 F按钮)
- orderNum: null,// 显示排序
- parentId: 0,// 上级菜单
- path: null, // 路由地址
- perms: null, // 权限标识
- query: null, // 路由参数
- routeName: null, // 路由名称
- status: '0', // 菜单状态(0正常 1停用)
- systemCode: null, // 系统编码
- url: null, // 网关URL
- urlmatch: null, // 网关权限
- visible: '0', // 显示状态(0显示 1隐藏)
- },
- rules: {
- parentId: [{required: true, message: '请选择上级菜单', trigger: 'blur'}],
- orderNum: [{required: true, message: '请输入排序', trigger: 'blur'}],
- menuName: [{required: true, message: '请输入菜单名称', trigger: 'blur'}],
- path: [{required: true, message: '请输入路由地址', trigger: 'blur'}],
- }
- })
- const columns: PlusColumn[] = [
- {
- label: '上级菜单',
- prop: 'parentId',
- colProps: {span: 24},
- valueType: 'tree-select',
- fieldProps: {
- placeholder: '请选择上级菜单',
- clearable: true,
- showSearch: true,
- checkStrictly: true,
- defaultExpandAll: false,
- style: {width: '100%'},
- data: computed(() => {
- return [{menuId: 0, menuName: '主类目', children: [...menuList.value]},]
- }),
- // el-tree-select 组件属性
- props: {
- label: 'menuName',
- value: 'menuId',
- children: 'children'
- }
- }
- },
- {
- label: "系统编码",
- prop: "systemCode",
- valueType: 'input',
- colProps: {span: 24},
- fieldProps: {
- placeholder: '请输入系统编码',
- clearable: true
- }
- },
- {
- label: "菜单类型",
- prop: "menuType",
- valueType: 'radio',
- colProps: {span: 24},
- fieldProps: {
- placeholder: '请选择菜单类型',
- clearable: true,
- options: [
- {label: '目录', value: 'M'},
- {label: '菜单', value: 'C'},
- {label: '按钮', value: 'F'}
- ],
- },
- },
- {
- label: "菜单图标",
- prop: "icon",
- valueType: 'input',
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入菜单图标',
- clearable: true,
- },
- hideInForm: computed(() => form.value.menuType === 'F')
- },
- {
- label: "显示排序",
- prop: "orderNum",
- valueType: 'input-number',
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入显示排序',
- style: {width: '100%'},
- min: 0
- }
- },
- {
- label: "菜单名称",
- prop: "menuName",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入菜单名称',
- clearable: true
- }
- },
- {
- label: "路由名称",
- prop: "routeName",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入路由名称',
- clearable: true
- },
- hideInForm: computed(() => form.value.menuType !== 'C')
- },
- {
- label: "是否外链",
- prop: "isFrame",
- valueType: 'radio',
- colProps: {span: 12},
- fieldProps: {
- options: [
- {label: '是', value: '0'},
- {label: '否', value: '1'}
- ]
- },
- tooltip: '选择是外链则路由地址需要以`http(s)://`开头',
- hideInForm: computed(() => form.value.menuType === 'F')
- },
- {
- label: "路由地址",
- prop: "path",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入路由地址',
- clearable: true
- },
- tooltip: "访问的路由地址,如:`role`,如外网地址需内链访问则以`http(s)://`开头",
- hideInForm: computed(() => form.value.menuType === 'F')
- },
- {
- label: "组件路径",
- prop: "component",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入组件路径',
- clearable: true
- },
- tooltip: "访问的组件路径,如:`system/role/index`,默认在`views`目录下",
- hideInForm: computed(() => form.value.menuType !== 'C')
- },
- {
- label: "权限字符",
- prop: "perms",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入权限标识',
- clearable: true
- },
- tooltip: "控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasPermi('system:role:list')`)",
- hideInForm: computed(() => form.value.menuType === 'M')
- },
- {
- label: "网关URL",
- prop: "url",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入URL',
- clearable: true
- },
- },
- {
- label: "网关权限",
- prop: "urlmatch",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入网关权限标识',
- clearable: true
- },
- tooltip: "控制器中定义的权限字符",
- },
- {
- label: "路由参数",
- prop: "query",
- colProps: {span: 12},
- fieldProps: {
- placeholder: '请输入路由参数',
- clearable: true
- },
- tooltip: "访问路由的默认传递参数,如:`{\"id\": 1, \"name\": \"ry\"}`",
- hideInForm: computed(() => form.value.menuType !== 'C')
- },
- {
- label: "是否缓存",
- prop: "isCache",
- valueType: 'radio',
- colProps: {span: 12},
- fieldProps: {
- options: [
- {label: '是', value: '0'},
- {label: '否', value: '1'}
- ]
- },
- tooltip: "选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致",
- hideInForm: computed(() => form.value.menuType !== 'C')
- },
- {
- label: "显示状态",
- prop: "visible",
- valueType: 'radio',
- colProps: {span: 12},
- fieldProps: {
- options: [
- {label: '显示', value: '0'},
- {label: '隐藏', value: '1'}
- ]
- },
- tooltip: "选择隐藏则路由将不会出现在侧边栏,但仍然可以访问",
- hideInForm: computed(() => form.value.menuType === 'F')
- },
- {
- label: "菜单状态",
- prop: "status",
- valueType: 'radio',
- colProps: {span: 12},
- fieldProps: {
- options: [
- {label: '正常', value: '0'},
- {label: '停用', value: '1'}
- ]
- },
- tooltip: "选择停用则路由将不会出现在侧边栏,也不能被访问"
- },
- ]
- // 创建
- const handleCreate = (): void => {
- form.value = {
- component: null, // 组件路径
- icon: null, // 菜单图标
- isCache: '0', // 是否缓存 (0缓存 1不缓存)
- isFrame: '1', // 是否外链 (0是 1否)
- menuName: null,// 菜单名称
- menuType: 'M', // 菜单类型(M目录 C菜单 F按钮)
- orderNum: null,// 显示排序
- parentId: 0,// 上级菜单
- path: null, // 路由地址
- perms: null, // 权限标识
- query: null, // 路由参数
- routeName: null, // 路由名称
- status: '0', // 菜单状态(0正常 1停用)
- systemCode: null, // 系统编码
- url: null, // 网关URL
- urlmatch: null, // 网关权限
- visible: '0', // 显示状态(0显示 1隐藏)
- }
- state.isCreate = true
- state.dialogVisible = true
- }
- const handleSubmit = async (values: FieldValues) => {
- console.log(values, 'Submit')
- confirmLoading.value = true
- if (state.isCreate) {
- try {
- let params = form.value
- let res = await addMenu(params)
- if (res.code === 200) {
- ElMessage.success('新增成功')
- confirmLoading.value = false
- dialogVisible.value = false
- refresh()
- } else {
- ElMessage.error(res.msg)
- }
- } finally {
- confirmLoading.value = false
- }
- } else {
- // 编辑
- try {
- let params = form.value
- let res = await updateMenu(params)
- if (res.code === 200) {
- ElMessage.success('修改成功')
- confirmLoading.value = false
- dialogVisible.value = false
- refresh()
- } else {
- ElMessage.error(res.msg)
- }
- } finally {
- confirmLoading.value = false
- }
- }
- }
- const handleClose = () => {
- // 重置表单
- nextTick(() => {
- if (dialogForm.value) {
- dialogForm.value.formInstance.resetFields()
- }
- })
- console.log(dialogForm.value.formInstance)
- }
- buttons.value = [
- {
- // 修改
- text: "修改",
- code: "edit",
- // props v0.1.16 版本新增函数类型
- props: {
- type: "primary"
- },
- onClick(params) {
- getSystemMenuDetailById(params.row.menuId).then(res => {
- form.value = res.data
- state.isCreate = false
- state.dialogVisible = true
- })
- },
- },
- {
- // 新增
- text: "新增",
- code: "view",
- props: {
- type: "success"
- },
- onClick(params) {
- console.log("新增", params)
- form.value = {
- component: null, // 组件路径
- icon: null, // 菜单图标
- isCache: '0', // 是否缓存 (0缓存 1不缓存)
- isFrame: '1', // 是否外链 (0是 1否)
- menuName: null,// 菜单名称
- menuType: 'M', // 菜单类型(M目录 C菜单 F按钮)
- orderNum: null,// 显示排序
- parentId: params.row.menuId,// 上级菜单
- path: null, // 路由地址
- perms: null, // 权限标识
- query: null, // 路由参数
- routeName: null, // 路由名称
- status: '0', // 菜单状态(0正常 1停用)
- systemCode: null, // 系统编码
- url: null, // 网关URL
- urlmatch: null, // 网关权限
- visible: '0', // 显示状态(0显示 1隐藏)
- }
- state.isCreate = true
- state.dialogVisible = true
- },
- },
- {
- // 删除
- text: "删除",
- code: "delete",
- // props v0.1.16 版本新增计算属性支持
- props: computed(() => ({type: "danger"})),
- confirm: {
- options: {
- draggable: true,
- message: "确定删除此数据吗?"
- }
- },
- onConfirm: async (params) => {
- try {
- let res = await deleteMenu(params.row.menuId)
- if (res.code === 200) {
- ElMessage.success('删除成功')
- refresh()
- } else {
- ElMessage.error(res.msg)
- }
- } catch (e) {
- ElMessage.error('删除失败')
- }
- }
- }
- ];
- const {form, confirmLoading, rules, dialogVisible} = toRefs(state)
- </script>
|