添加菜单页面.
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<a-modal v-model:visible="visible"
|
||||
title-align="start"
|
||||
:title="title"
|
||||
:top="80"
|
||||
:align-center="false"
|
||||
:draggable="true"
|
||||
:mask-closable="false"
|
||||
:unmount-on-close="true"
|
||||
:ok-button-props="{ disabled: loading }"
|
||||
:cancel-button-props="{ disabled: loading }"
|
||||
:on-before-ok="handlerOk"
|
||||
@close="handleClose">
|
||||
<a-spin :loading="loading">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="right"
|
||||
:style="{ width: '440px' }"
|
||||
:label-col-props="{ span: 5 }"
|
||||
:wrapper-col-props="{ span: 19 }"
|
||||
:rules="formRules">
|
||||
<!-- 上级菜单 -->
|
||||
<a-form-item field="parentId" label="上级菜单">
|
||||
<menu-tree-selector v-model="formModel.parentId"
|
||||
:disabled="!!(isAddHandle && formModel.id)" />
|
||||
</a-form-item>
|
||||
<!-- 名称 -->
|
||||
<a-form-item field="name" label="菜单名称">
|
||||
<a-input v-model="formModel.name" placeholder="请输入菜单名称" />
|
||||
</a-form-item>
|
||||
<!-- 菜单类型 -->
|
||||
<a-form-item field="type" label="菜单类型">
|
||||
<a-radio-group type="button"
|
||||
v-model="formModel.type"
|
||||
:options="toOptions(MenuTypeEnum)" />
|
||||
</a-form-item>
|
||||
<!-- 菜单图标 -->
|
||||
<a-form-item field="icon" label="菜单图标">
|
||||
<icon-picker v-model:icon="formModel.icon">
|
||||
<template #iconSelect>
|
||||
<a-input v-model="formModel.icon" placeholder="请选择菜单图标" />
|
||||
</template>
|
||||
</icon-picker>
|
||||
</a-form-item>
|
||||
<!-- 菜单权限 -->
|
||||
<a-form-item field="permission" label="菜单权限">
|
||||
<a-input v-model="formModel.permission"
|
||||
placeholder="菜单权限 infra:system-menu:query"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 外链地址 -->
|
||||
<a-form-item field="path" label="外链地址">
|
||||
<a-input v-model="formModel.path"
|
||||
placeholder="外链地址与组件名称二选一"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 组件名称 -->
|
||||
<a-form-item field="component" label="组件名称">
|
||||
<a-input v-model="formModel.component"
|
||||
placeholder="路由组件名称"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 菜单排序 -->
|
||||
<a-form-item field="sort" label="菜单排序">
|
||||
<a-input-number v-model="formModel.sort"
|
||||
placeholder="排序"
|
||||
:style="{ width: '120px' }"
|
||||
allow-clear />
|
||||
</a-form-item>
|
||||
<!-- 可见状态 -->
|
||||
<a-form-item field="type" label="可见状态">
|
||||
<a-radio-group type="button"
|
||||
v-model="formModel.visible"
|
||||
:options="toOptions(MenuVisibleEnum)" />
|
||||
</a-form-item>
|
||||
<!-- 缓存状态 -->
|
||||
<a-form-item field="type" label="缓存状态">
|
||||
<a-radio-group type="button"
|
||||
v-model="formModel.cache"
|
||||
:options="toOptions(MenuCacheEnum)" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</a-spin>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'menu-form-modal'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useVisible from '@/hooks/visible';
|
||||
import formRules from '../type/form.rules';
|
||||
import { MenuTypeEnum, MenuVisibleEnum, MenuCacheEnum } from '../type/enum.types';
|
||||
import { toOptions } from '@/utils/enum';
|
||||
import IconPicker from '@sanqi377/arco-vue-icon-picker';
|
||||
import MenuTreeSelector from '@/views/system/menu/components/menu-tree-selector.vue';
|
||||
import { createMenu, updateMenu } from '@/api/system/menu';
|
||||
|
||||
const { visible, setVisible } = useVisible();
|
||||
const { loading, setLoading } = useLoading();
|
||||
|
||||
const title = ref<string>();
|
||||
const isAddHandle = ref<boolean>(true);
|
||||
|
||||
const formRef = ref<any>();
|
||||
const formModel = reactive<Record<string, any>>({
|
||||
id: undefined,
|
||||
parentId: undefined,
|
||||
name: undefined,
|
||||
type: undefined,
|
||||
permission: undefined,
|
||||
sort: undefined,
|
||||
visible: undefined,
|
||||
cache: undefined,
|
||||
icon: undefined,
|
||||
path: undefined,
|
||||
component: undefined,
|
||||
});
|
||||
|
||||
const emits = defineEmits(['added', 'updated']);
|
||||
|
||||
// 打开新增
|
||||
const openAdd = (record: any) => {
|
||||
title.value = '添加菜单';
|
||||
isAddHandle.value = true;
|
||||
renderForm(record);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
// 打开修改
|
||||
const openUpdate = (record: any) => {
|
||||
title.value = '修改菜单';
|
||||
isAddHandle.value = false;
|
||||
renderForm(record);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
defineExpose({ openAdd, openUpdate });
|
||||
|
||||
// 渲染表单
|
||||
const renderForm = (record: any) => {
|
||||
Object.keys(formModel).forEach(k => {
|
||||
formModel[k] = record[k];
|
||||
});
|
||||
};
|
||||
|
||||
// 确定
|
||||
const handlerOk = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// 验证参数
|
||||
const error = await formRef.value.validate();
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
if (isAddHandle.value) {
|
||||
// 新增
|
||||
await createMenu(formModel as any);
|
||||
emits('added', { ...formModel });
|
||||
} else {
|
||||
// 修改
|
||||
await updateMenu(formModel as any);
|
||||
emits('updated', { ...formModel });
|
||||
}
|
||||
// 清空
|
||||
// handlerClear();
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 关闭
|
||||
const handleClose = () => {
|
||||
handlerClear();
|
||||
};
|
||||
|
||||
// 清空
|
||||
const handlerClear = () => {
|
||||
setLoading(false);
|
||||
setVisible(false);
|
||||
renderForm({});
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-form) {
|
||||
&-item {
|
||||
&:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
249
orion-ops-ui/src/views/system/menu/components/menu-table.vue
Normal file
249
orion-ops-ui/src/views/system/menu/components/menu-table.vue
Normal file
@@ -0,0 +1,249 @@
|
||||
<template>
|
||||
<!-- 搜索 -->
|
||||
<a-card class="general-card table-search-card">
|
||||
<a-row style="margin-top: 16px">
|
||||
<!-- 搜索框 -->
|
||||
<a-col :span="12">
|
||||
<a-form :model="formModel"
|
||||
ref="formRef"
|
||||
label-align="left"
|
||||
@keydown.enter="loadMenuData">
|
||||
<a-row :gutter="32">
|
||||
<!-- 菜单名称 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item field="name" label="菜单名称" label-col-flex="60px">
|
||||
<a-input v-model="formModel.name" placeholder="请输入菜单名称" allow-clear />
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<!-- 菜单状态 -->
|
||||
<a-col :span="12">
|
||||
<a-form-item field="status" label="菜单状态" label-col-flex="60px">
|
||||
<a-select
|
||||
v-model="formModel.status"
|
||||
:options="toOptions(MenuStatusEnum)"
|
||||
placeholder="请选择菜单状态"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</a-col>
|
||||
<!-- 操作 -->
|
||||
<a-col :span="12" class="form-option">
|
||||
<a-space>
|
||||
<!-- 新增 -->
|
||||
<a-button type="primary"
|
||||
@click="emits('openAdd',{ parentId: 0 })"
|
||||
v-permission="['infra:system-menu:create']">
|
||||
新增
|
||||
<template #icon>
|
||||
<icon-plus />
|
||||
</template>
|
||||
</a-button>
|
||||
<!-- 查询 -->
|
||||
<a-button type="primary" :loading="fetchLoading" @click="loadMenuData">
|
||||
查询
|
||||
<template #icon>
|
||||
<icon-search />
|
||||
</template>
|
||||
</a-button>
|
||||
<!-- 重置 -->
|
||||
<a-button @click="resetForm" :loading="fetchLoading">
|
||||
重置
|
||||
<template #icon>
|
||||
<icon-refresh />
|
||||
</template>
|
||||
</a-button>
|
||||
<!-- 展开 -->
|
||||
<a-button @click="toggleExpand">
|
||||
折叠/展开
|
||||
<template #icon>
|
||||
<icon-expand />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<!-- 表格 -->
|
||||
<a-card class="general-card table-card">
|
||||
<a-table row-key="id"
|
||||
class="table-wrapper"
|
||||
ref="tableRef"
|
||||
label-align="left"
|
||||
:loading="fetchLoading"
|
||||
:pagination="false"
|
||||
:columns="columns"
|
||||
:data="tableRenderData"
|
||||
:bordered="false">
|
||||
<!-- 菜单名称 -->
|
||||
<template #menuName="{ record }">
|
||||
<span class="ml8">{{ record.name }}</span>
|
||||
</template>
|
||||
<!-- 图标 -->
|
||||
<template #icon="{ record }">
|
||||
<component v-if="record.icon" :is="record.icon" />
|
||||
</template>
|
||||
<!-- 类型 -->
|
||||
<template #type="{ record }">
|
||||
<a-tag>{{ getEnumValue(record.type, MenuTypeEnum) }}</a-tag>
|
||||
</template>
|
||||
<!-- 状态 -->
|
||||
<template #status="{ record }">
|
||||
<a-space>
|
||||
<!-- 菜单状态 -->
|
||||
<a-tag :color="getEnumValue(record.status, MenuStatusEnum,'color')">
|
||||
{{ getEnumValue(record.status, MenuStatusEnum) }}
|
||||
</a-tag>
|
||||
<!-- 显示状态 -->
|
||||
<a-tag :color="getEnumValue(record.visible, MenuVisibleEnum,'color')">
|
||||
{{ getEnumValue(record.visible, MenuVisibleEnum) }}
|
||||
</a-tag>
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 操作 -->
|
||||
<template #option="{ record }">
|
||||
<div class="table-option-wrapper">
|
||||
<!-- 新增 -->
|
||||
<a-button type="text"
|
||||
size="mini"
|
||||
v-if="record.type !== MenuTypeEnum.FUNCTION.value"
|
||||
v-permission="['infra:system-menu:create']"
|
||||
@click="emits('openAdd', { id: record.id, parentId: record.id })">
|
||||
新增
|
||||
</a-button>
|
||||
<!-- 修改 -->
|
||||
<a-button type="text"
|
||||
size="mini"
|
||||
v-permission="['infra:system-menu:update']"
|
||||
@click="emits('openUpdate', record)">
|
||||
修改
|
||||
</a-button>
|
||||
<!-- 删除 -->
|
||||
<a-popconfirm content="确认删除这条记录吗?"
|
||||
position="left"
|
||||
type="warning"
|
||||
@ok="doDeleteMenu(record)">
|
||||
<a-button type="text" size="mini"
|
||||
status="danger"
|
||||
v-permission="['infra:system-menu:delete']">
|
||||
删除
|
||||
</a-button>
|
||||
</a-popconfirm>
|
||||
</div>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'menu-table'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, ref, onUnmounted } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { getMenuList, deleteMenu, MenuQueryResponse } from '@/api/system/menu';
|
||||
import { toOptions, getEnumValue } from '@/utils/enum';
|
||||
import { MenuStatusEnum, MenuVisibleEnum, MenuTypeEnum } from '../type/enum.types';
|
||||
import columns from '../type/table.columns';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { useMenuStore } from '@/store';
|
||||
|
||||
const menuStore = useMenuStore();
|
||||
|
||||
const formRef = ref<any>();
|
||||
const formModel = reactive({
|
||||
name: undefined,
|
||||
status: undefined
|
||||
});
|
||||
|
||||
const tableRef = ref<any>();
|
||||
const expandStatus = ref<boolean>(false);
|
||||
|
||||
const tableRenderData = ref<MenuQueryResponse[]>([]);
|
||||
const { loading: fetchLoading, setLoading: setFetchLoading } = useLoading();
|
||||
|
||||
const emits = defineEmits(['openAdd', 'openUpdate']);
|
||||
|
||||
// 删除菜单
|
||||
const doDeleteMenu = async ({ id }: any) => {
|
||||
try {
|
||||
setFetchLoading(true);
|
||||
// 调用删除接口
|
||||
await deleteMenu(id);
|
||||
// 获取父级容器
|
||||
const parent = menuStore.findParentMenu(tableRenderData.value, id) as unknown as MenuQueryResponse[];
|
||||
if (parent) {
|
||||
// 删除
|
||||
for (let i = 0; i < parent.length; i++) {
|
||||
if (parent[i].id === id) {
|
||||
parent.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
Message.success({ content: '删除成功' });
|
||||
} finally {
|
||||
setFetchLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 添加后回调
|
||||
const addedCallback = (record: any) => {
|
||||
console.log('addCallback', record);
|
||||
};
|
||||
|
||||
// 更新后回调
|
||||
const updatedCallback = (record: any) => {
|
||||
console.log('updateCallback', record);
|
||||
};
|
||||
|
||||
defineExpose({
|
||||
addedCallback, updatedCallback
|
||||
});
|
||||
|
||||
// 加载菜单
|
||||
const loadMenuData = async () => {
|
||||
try {
|
||||
setFetchLoading(true);
|
||||
const { data } = await getMenuList(formModel);
|
||||
tableRenderData.value = data as MenuQueryResponse[];
|
||||
menuStore.updateMenu(tableRenderData.value);
|
||||
} finally {
|
||||
setFetchLoading(false);
|
||||
}
|
||||
};
|
||||
loadMenuData();
|
||||
|
||||
// 重置菜单
|
||||
const resetForm = () => {
|
||||
formRef.value.resetFields();
|
||||
loadMenuData();
|
||||
};
|
||||
|
||||
// 切换展开/折叠
|
||||
const toggleExpand = () => {
|
||||
tableRef.value.expandAll(expandStatus.value = !expandStatus.value);
|
||||
};
|
||||
|
||||
// 卸载时清除 store
|
||||
onUnmounted(() => {
|
||||
menuStore.reset();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.form-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end
|
||||
}
|
||||
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<a-tree-select :default-value="modelValue"
|
||||
:data="treeData"
|
||||
:disabled="disabled"
|
||||
:allow-search="true"
|
||||
:filter-tree-node="filterTreeNode"
|
||||
placeholder="请选择上级菜单"
|
||||
@change="onChange" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'menu-tree-selector'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useMenuStore } from '@/store';
|
||||
|
||||
defineProps(['modelValue', 'disabled']);
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const onChange = (e: any) => {
|
||||
emits('update:modelValue', e);
|
||||
};
|
||||
|
||||
// 树数据
|
||||
const menuStore = useMenuStore();
|
||||
const treeData = menuStore.treeData;
|
||||
|
||||
// 搜索
|
||||
const filterTreeNode = (searchValue: string, nodeData: { title: string; }) => {
|
||||
return nodeData.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>;
|
||||
28
orion-ops-ui/src/views/system/menu/index.vue
Normal file
28
orion-ops-ui/src/views/system/menu/index.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="layout-container">
|
||||
<!-- 表格 -->
|
||||
<menu-table ref="table"
|
||||
@openAdd="(e) => modal.openAdd(e)"
|
||||
@openUpdate="(e) => modal.openUpdate(e)" />
|
||||
<!-- 添加修改框 -->
|
||||
<menu-form-modal ref="modal"
|
||||
@added="(e) => table.addedCallback(e)"
|
||||
@updated="(e) => table.updatedCallback(e)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'systemMenu'
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MenuTable from '@/views/system/menu/components/menu-table.vue';
|
||||
import { ref } from 'vue';
|
||||
import MenuFormModal from '@/views/system/menu/components/menu-form-modal.vue';
|
||||
|
||||
const table = ref<any>();
|
||||
const modal = ref<any>();
|
||||
|
||||
</script>
|
||||
63
orion-ops-ui/src/views/system/menu/type/enum.types.ts
Normal file
63
orion-ops-ui/src/views/system/menu/type/enum.types.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* 菜单类型
|
||||
*/
|
||||
export const MenuTypeEnum = {
|
||||
PARENT_MENU: {
|
||||
value: 1,
|
||||
label: '父菜单'
|
||||
},
|
||||
SUB_MENU: {
|
||||
value: 2,
|
||||
label: '子菜单'
|
||||
},
|
||||
FUNCTION: {
|
||||
value: 3,
|
||||
label: '功能'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 菜单状态
|
||||
*/
|
||||
export const MenuStatusEnum = {
|
||||
DISABLED: {
|
||||
value: 0,
|
||||
label: '停用',
|
||||
color: 'orange'
|
||||
},
|
||||
ENABLED: {
|
||||
value: 1,
|
||||
label: '启用',
|
||||
color: 'blue'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 菜单是否可见
|
||||
*/
|
||||
export const MenuVisibleEnum = {
|
||||
HIDE: {
|
||||
value: 0,
|
||||
label: '隐藏',
|
||||
color: 'orange'
|
||||
},
|
||||
SHOW: {
|
||||
value: 1,
|
||||
label: '显示',
|
||||
color: 'blue'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 菜单缓存状态
|
||||
*/
|
||||
export const MenuCacheEnum = {
|
||||
HIDE: {
|
||||
value: 0,
|
||||
label: '不缓存',
|
||||
},
|
||||
SHOW: {
|
||||
value: 1,
|
||||
label: '缓存',
|
||||
}
|
||||
};
|
||||
11
orion-ops-ui/src/views/system/menu/type/form.rules.ts
Normal file
11
orion-ops-ui/src/views/system/menu/type/form.rules.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { FieldRule } from '@arco-design/web-vue';
|
||||
|
||||
export const name = [{
|
||||
required: true,
|
||||
|
||||
}] as FieldRule[];
|
||||
|
||||
|
||||
export default {
|
||||
name
|
||||
} as Record<string, FieldRule | FieldRule[]>;
|
||||
58
orion-ops-ui/src/views/system/menu/type/table.columns.ts
Normal file
58
orion-ops-ui/src/views/system/menu/type/table.columns.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { TableColumnData } from '@arco-design/web-vue/es/table/interface';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '菜单名称',
|
||||
dataIndex: 'menuName',
|
||||
slotName: 'menuName',
|
||||
fixed: 'left',
|
||||
width: 250,
|
||||
}, {
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
slotName: 'icon',
|
||||
align: 'center',
|
||||
width: 60,
|
||||
}, {
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
width: 80,
|
||||
}, {
|
||||
title: '排序',
|
||||
dataIndex: 'sort',
|
||||
slotName: 'sort',
|
||||
width: 70,
|
||||
}, {
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
slotName: 'status',
|
||||
width: 120,
|
||||
}, {
|
||||
title: '权限标识',
|
||||
dataIndex: 'permission',
|
||||
slotName: 'permission',
|
||||
ellipsis: true,
|
||||
tooltip: true
|
||||
}, {
|
||||
title: '组件名称',
|
||||
dataIndex: 'component',
|
||||
slotName: 'component',
|
||||
width: 150,
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '链接路径',
|
||||
dataIndex: 'path',
|
||||
slotName: 'path',
|
||||
ellipsis: true,
|
||||
tooltip: true,
|
||||
}, {
|
||||
title: '操作',
|
||||
slotName: 'option',
|
||||
width: 158,
|
||||
fixed: 'right',
|
||||
}
|
||||
] as TableColumnData[];
|
||||
|
||||
export default columns;
|
||||
Reference in New Issue
Block a user