completepage: 🍻 菜单管理

This commit is contained in:
bunny 2024-09-29 10:47:37 +08:00
parent ed277c1b81
commit 024188ee3d
15 changed files with 249 additions and 115 deletions

View File

@ -17,3 +17,59 @@
## 许可证 ## 许可证
[MIT © 2020-present, pure-admin](./LICENSE) [MIT © 2020-present, pure-admin](./LICENSE)
## 配置文件
```json
{
"Version": "5.8.0",
// 平台版本号
"Title": "PureAdmin",
// 平台标题
"FixedHeader": true,
// 是否固定页头和标签页true 内容区超出出现纵向滚动条 false 页头、标签页、内容区可纵向滚动)
"HiddenSideBar": false,
// 隐藏菜单和页头,只显示标签页和内容区
"MultiTagsCache": false,
// 是否开启持久化标签 (会缓存)
"KeepAlive": true,
// 是否开启组件缓存(此处不同于路由的 keepAlive如果此处为 true 表示设置路由的 keepAlive 起效,反之设置 false 屏蔽平台整体的 keepAlive即使路由设置了keepAlive 也不再起作用)
"Locale": "zh",
// 默认国际化语言 zh 中文、en 英文会缓存max版本额外配置tw 繁體中文、ja 日语、ko 韩语)
"Layout": "vertical",
// 导航菜单模式 vertical 左侧菜单模式、horizontal 顶部菜单模式、mix 混合菜单模式会缓存max版本额外配置double 左侧双栏菜单模式)
"Theme": "light",
// 主题模式(会缓存)
"DarkMode": false,
// 是否开启暗黑模式 (会缓存)
"OverallStyle": "light",
// 整体风格浅色light、深色dark、自动system会缓存更多详情看 https://github.com/pure-admin/vue-pure-admin/commit/dd783136229da9e291b518df93227111f4216ad0#commitcomment-137027417
"Grey": false,
// 灰色模式(会缓存)
"Weak": false,
// 色弱模式(会缓存)
"HideTabs": false,
// 是否隐藏标签页(会缓存)
"HideFooter": false,
// 是否隐藏页脚(会缓存)
"SidebarStatus": true,
// vertical左侧菜单模式模式下侧边栏状态true 展开、false 收起)(会缓存)
"EpThemeColor": "#409EFF",
// 主题色(会缓存)
"ShowLogo": true,
// 是否显示logo会缓存
"ShowModel": "smart",
// 标签页风格smart 灵动模式、card 卡片模式)(会缓存)
"MenuArrowIconNoTransition": false,
// 菜单展开、收起图标是否开启动画,如遇菜单展开、收起卡顿设置成 true 即可(默认 false开启动画
"CachingAsyncRoutes": false,
// 是否开启动态路由缓存本地的全局配置,默认 false
"TooltipEffect": "light",
// 可配置平台主体所有 el-tooltip 的 effect 属性,默认 light不会影响业务代码
"ResponsiveStorageNameSpace": "responsive-",
// 本地响应式存储的命名空间
"MenuSearchHistory": 6
// 菜单搜索历史的最大条目
}
```

View File

@ -1,6 +0,0 @@
import { http } from '@/api/service/request';
import type { BaseResult } from '@/api/service/types';
export const getRouterAsync = () => {
return http.request<BaseResult<any>>('get', 'router/getRouterAsync');
};

View File

@ -12,6 +12,32 @@ type ResultTable = {
pageNo?: number; pageNo?: number;
}; };
/** 系统管理-用户路由获取 */
export const getRouterAsync = () => {
return http.request<BaseResult<any>>('get', 'router/getRouterAsync');
};
/** 系统管理-菜单管理列表 */
export const getMenuList = (data?: any) => {
return http.request<BaseResult<ResultTable>>('get', `router/getMenus`, { data });
};
/** 系统管理-添加菜单 */
export const addMenu = (data?: any) => {
return http.request<BaseResult<any>>('post', `router/addMenu`, { data });
};
/** 系统管理-更新菜单 */
export const updateMenu = (data?: any) => {
return http.request<BaseResult<any>>('put', `router/updateMenu`, { data });
};
/** 系统管理-删除菜单 */
export const deletedMenuByIds = (data?: any) => {
return http.request<BaseResult<any>>('delete', `router/deletedMenuByIds`, { data });
};
// ------------未确认------------
/** 获取系统管理-用户管理列表 */ /** 获取系统管理-用户管理列表 */
export const getUserList = (data?: object) => { export const getUserList = (data?: object) => {
return http.request<BaseResult<ResultTable>>('post', '/user', { data }); return http.request<BaseResult<ResultTable>>('post', '/user', { data });
@ -32,12 +58,6 @@ export const getRoleList = (data?: object) => {
return http.request<ResultTable>('post', '/role', { data }); return http.request<ResultTable>('post', '/role', { data });
}; };
/** 获取系统管理-菜单管理列表 */
export const getMenuList = (data?: any) => {
// /${data.page}/${data.limit}
return http.request<BaseResult<ResultTable>>('get', `router/getMenus`, { data });
};
/** 获取系统管理-部门管理列表 */ /** 获取系统管理-部门管理列表 */
export const getDeptList = (data?: object) => { export const getDeptList = (data?: object) => {
return http.request<any>('post', '/dept', { data }); return http.request<any>('post', '/dept', { data });

View File

@ -1,52 +1,52 @@
import axios from "axios"; import axios from 'axios';
import type { App } from "vue"; import type { App } from 'vue';
let config: object = {}; let config: object = {};
const { VITE_PUBLIC_PATH } = import.meta.env; const { VITE_PUBLIC_PATH } = import.meta.env;
const setConfig = (cfg?: unknown) => { const setConfig = (cfg?: unknown) => {
config = Object.assign(config, cfg); config = Object.assign(config, cfg);
}; };
const getConfig = (key?: string): PlatformConfigs => { const getConfig = (key?: string): PlatformConfigs => {
if (typeof key === "string") { if (typeof key === 'string') {
const arr = key.split("."); const arr = key.split('.');
if (arr && arr.length) { if (arr && arr.length) {
let data = config; let data = config;
arr.forEach(v => { arr.forEach(v => {
if (data && typeof data[v] !== "undefined") { if (data && typeof data[v] !== 'undefined') {
data = data[v]; data = data[v];
} else { } else {
data = null; data = null;
} }
}); });
return data; return data;
} }
} }
return config; return config;
}; };
/** 获取项目动态全局配置 */ /** 获取项目动态全局配置 */
export const getPlatformConfig = async (app: App): Promise<undefined> => { export const getPlatformConfig = async (app: App): Promise<undefined> => {
app.config.globalProperties.$config = getConfig(); app.config.globalProperties.$config = getConfig();
return axios({ return axios({
method: "get", method: 'get',
url: `${VITE_PUBLIC_PATH}platform-config.json` url: `${VITE_PUBLIC_PATH}platform-config.json`,
}) })
.then(({ data: config }) => { .then(({ data: config }) => {
let $config = app.config.globalProperties.$config; let $config = app.config.globalProperties.$config;
// 自动注入系统配置 // 自动注入系统配置
if (app && $config && typeof config === "object") { if (app && $config && typeof config === 'object') {
$config = Object.assign($config, config); $config = Object.assign($config, config);
app.config.globalProperties.$config = $config; app.config.globalProperties.$config = $config;
// 设置全局配置 // 设置全局配置
setConfig($config); setConfig($config);
} }
return $config; return $config;
}) })
.catch(() => { .catch(() => {
throw "请在public文件夹下添加platform-config.json配置文件"; throw '请在public文件夹下添加platform-config.json配置文件';
}); });
}; };
/** 本地响应式存储的命名空间 */ /** 本地响应式存储的命名空间 */

View File

@ -1,38 +1,38 @@
import { $t } from "@/plugins/i18n"; import { $t } from '@/plugins/i18n';
export default { export default {
path: "/error", path: '/error',
redirect: "/error/403", redirect: '/error/403',
meta: { meta: {
icon: "ri:information-line", icon: 'ri:information-line',
showLink: false, showLink: false,
title: "menus.pureAbnormal", title: 'menus.pureAbnormal',
rank: 9 rank: 9,
}, },
children: [ children: [
{ {
path: "/error/403", path: '/error/403',
name: "403", name: '403',
component: () => import("@/views/error/403.vue"), component: () => import('@/components/error/403.vue'),
meta: { meta: {
title: $t("menus.pureFourZeroOne") title: $t('menus.pureFourZeroOne'),
} },
}, },
{ {
path: "/error/404", path: '/error/404',
name: "404", name: '404',
component: () => import("@/views/error/404.vue"), component: () => import('@/components/error/404.vue'),
meta: { meta: {
title: $t("menus.pureFourZeroFour") title: $t('menus.pureFourZeroFour'),
} },
}, },
{ {
path: "/error/500", path: '/error/500',
name: "500", name: '500',
component: () => import("@/views/error/500.vue"), component: () => import('@/components/error/500.vue'),
meta: { meta: {
title: $t("menus.pureFive") title: $t('menus.pureFive'),
} },
} },
] ],
} satisfies RouteConfigsTable; } satisfies RouteConfigsTable;

View File

@ -10,7 +10,7 @@ import { type menuType, routerArrays } from '@/layout/types';
import { useMultiTagsStoreHook } from '@/store/multiTags'; import { useMultiTagsStoreHook } from '@/store/multiTags';
import { usePermissionStoreHook } from '@/store/permission'; import { usePermissionStoreHook } from '@/store/permission';
// 动态路由 // 动态路由
import { getRouterAsync } from '@/api/v1/routes'; import { getRouterAsync } from '@/api/v1/system';
// import { getAsyncRoutes } from '@/api/routes'; // import { getAsyncRoutes } from '@/api/routes';
const IFrame = () => import('@/layout/frame.vue'); const IFrame = () => import('@/layout/frame.vue');

View File

@ -0,0 +1,38 @@
import { defineStore } from 'pinia';
import { addMenu, deletedMenuByIds, updateMenu } from '@/api/v1/system';
import { storeMessage } from '@/utils/message';
export const userRouterStore = defineStore('routerStore', {
state() {
return {};
},
getters: {},
actions: {
/**
* *
* @param data
*/
async addMenu(data: object) {
const result = await addMenu(data);
return storeMessage(result);
},
/**
* *
* @param data
*/
async updateMenu(data: object) {
const result = await updateMenu(data);
return storeMessage(result);
},
/**
* *
* @param data
*/
async deletedMenuByIds(data: object) {
const result = await deletedMenuByIds(data);
return storeMessage(result);
},
},
});

View File

@ -1,6 +1,7 @@
import type { VNode } from 'vue'; import type { VNode } from 'vue';
import { isFunction } from '@pureadmin/utils'; import { isFunction } from '@pureadmin/utils';
import { ElMessage, type MessageHandler } from 'element-plus'; import { ElMessage, type MessageHandler } from 'element-plus';
import type { BaseResult } from '@/api/service/types';
type messageStyle = 'el' | 'antd'; type messageStyle = 'el' | 'antd';
type messageTypes = 'info' | 'success' | 'warning' | 'error'; type messageTypes = 'info' | 'success' | 'warning' | 'error';
@ -35,7 +36,7 @@ interface MessageParams {
/** /**
* `Message` * `Message`
*/ */
const message = (message: string | VNode | (() => VNode), params?: MessageParams): MessageHandler => { export const message = (message: string | VNode | (() => VNode), params?: MessageParams): MessageHandler => {
if (!params) { if (!params) {
return ElMessage({ return ElMessage({
message, message,
@ -77,6 +78,16 @@ const message = (message: string | VNode | (() => VNode), params?: MessageParams
/** /**
* `Message` * `Message`
*/ */
const closeAllMessage = (): void => ElMessage.closeAll(); export const closeAllMessage = (): void => ElMessage.closeAll();
export { message, closeAllMessage }; /**
*
* @param result
*/
export const storeMessage = (result: BaseResult<any>) => {
if (result.code !== 200) {
return false;
}
message(result.message, { type: 'success' });
return true;
};

View File

@ -6,7 +6,7 @@ import { FormProps } from './utils/types';
import { IconSelect } from '@/components/ReIcon'; import { IconSelect } from '@/components/ReIcon';
import Segmented from '@/components/ReSegmented'; import Segmented from '@/components/ReSegmented';
import ReAnimateSelector from '@/components/AnimateSelector'; import ReAnimateSelector from '@/components/AnimateSelector';
import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from '../../enums/enums'; import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from '@/enums';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
const props = withDefaults(defineProps<FormProps>(), { const props = withDefaults(defineProps<FormProps>(), {
@ -36,7 +36,7 @@ const props = withDefaults(defineProps<FormProps>(), {
const ruleFormRef = ref(); const ruleFormRef = ref();
const newFormInline = ref(props.formInline); const newFormInline = ref(props.formInline);
defineExpose({ getRef: ruleFormRef.value }); defineExpose({ menuFormRef: ruleFormRef });
</script> </script>
<template> <template>

View File

@ -8,13 +8,21 @@ import { h, reactive, ref } from 'vue';
import type { FormItemProps } from '../utils/types'; import type { FormItemProps } from '../utils/types';
import { cloneDeep, deviceDetection, isAllEmpty } from '@pureadmin/utils'; import { cloneDeep, deviceDetection, isAllEmpty } from '@pureadmin/utils';
import { userRouterStore } from '@/store/modules/router';
const routerStore = userRouterStore();
export const form = reactive({ export const form = reactive({
title: '', title: '',
}); });
export const formRef = ref(); export const formRef = ref();
export const dataList = ref([]); export const dataList = ref([]);
export const loading = ref(true); export const loading = ref(true);
/**
*
* @param type
* @param text
*/
export const getMenuType = (type, text = false) => { export const getMenuType = (type, text = false) => {
switch (type) { switch (type) {
case 0: case 0:
@ -26,21 +34,29 @@ export const getMenuType = (type, text = false) => {
} }
}; };
/**
*
* @param val
*/
export const handleSelectionChange = val => { export const handleSelectionChange = val => {
console.log('handleSelectionChange', val); console.log('handleSelectionChange', val);
}; };
/**
*
* @param formEl
*/
export const resetForm = async formEl => { export const resetForm = async formEl => {
if (!formEl) return; if (!formEl) return;
formEl.resetFields(); formEl.resetFields();
await onSearch(); await onSearch();
}; };
export async function onSearch() { export const onSearch = async () => {
loading.value = true; loading.value = true;
// 获取菜单数据 // 获取菜单数据
const result = await getMenuList(); const result: any = await getMenuList();
if (result.code !== 200) message(result.message, { type: 'error' }); if (result.code !== 200) message(result.message, { type: 'error' });
// 前端搜索菜单名称 // 前端搜索菜单名称
@ -52,9 +68,9 @@ export async function onSearch() {
dataList.value = handleTree(result.data); dataList.value = handleTree(result.data);
loading.value = false; loading.value = false;
} };
export function formatHigherMenuOptions(treeList) { export const formatHigherMenuOptions = (treeList: any) => {
if (!treeList || !treeList.length) return; if (!treeList || !treeList.length) return;
const newTreeList = []; const newTreeList = [];
for (let i = 0; i < treeList.length; i++) { for (let i = 0; i < treeList.length; i++) {
@ -63,7 +79,7 @@ export function formatHigherMenuOptions(treeList) {
newTreeList.push(treeList[i]); newTreeList.push(treeList[i]);
} }
return newTreeList; return newTreeList;
} };
export function openDialog(title = '新增', row?: FormItemProps) { export function openDialog(title = '新增', row?: FormItemProps) {
addDialog({ addDialog({
@ -98,28 +114,24 @@ export function openDialog(title = '新增', row?: FormItemProps) {
closeOnClickModal: false, closeOnClickModal: false,
contentRenderer: () => h(editForm, { ref: formRef, formInline: null }), contentRenderer: () => h(editForm, { ref: formRef, formInline: null }),
beforeSure: (done, { options }) => { beforeSure: (done, { options }) => {
const FormRef = formRef.value.getRef(); const FormRef = formRef.value.menuFormRef;
const curData = options.props.formInline as FormItemProps; const curData = options.props.formInline as FormItemProps;
FormRef.validate(async (valid: any) => {
FormRef.validate(async valid => {
if (valid) { if (valid) {
console.log('curData', curData); delete curData.higherMenuOptions;
// 表单规则校验通过 let result = false;
if (title === '新增') { if (title === '新增') {
// 实际开发先调用新增接口,再进行下面操作 result = await routerStore.addMenu(curData);
message(`${title}了菜单名称为${$t(curData.title)}的这条数据`, {
type: 'success',
});
done(); // 关闭弹框
await onSearch(); // 刷新表格数据
} else { } else {
// 实际开发先调用修改接口,再进行下面操作 curData.id = row.id;
message(`${title}了菜单名称为${$t(curData.title)}的这条数据`, { result = await routerStore.updateMenu(curData);
type: 'success', }
});
done(); // 关闭弹框 // 刷新表格数据
await onSearch(); // 刷新表格数据 if (result) {
done();
await onSearch();
} }
} }
}); });
@ -127,9 +139,11 @@ export function openDialog(title = '新增', row?: FormItemProps) {
}); });
} }
/**
* *
* @param row
*/
export const handleDelete = async row => { export const handleDelete = async row => {
message(`您删除了菜单名称为${$t(row.title)}的这条数据`, { await routerStore.deletedMenuByIds([row.id]);
type: 'success',
});
await onSearch(); await onSearch();
}; };

View File

@ -1,5 +1,6 @@
interface FormItemProps { interface FormItemProps {
/** 菜单类型0代表菜单、1代表iframe、2代表外链、3代表按钮*/ /** 菜单类型0代表菜单、1代表iframe、2代表外链、3代表按钮*/
id: string;
menuType: number; menuType: number;
higherMenuOptions: Record<string, unknown>[]; higherMenuOptions: Record<string, unknown>[];
parentId: number; parentId: number;