diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts index 5e9fd14..f31caaa 100644 --- a/src/api/system/menu.ts +++ b/src/api/system/menu.ts @@ -21,3 +21,42 @@ export function getMenuList(params?) { params, }); } +/** + * 获取菜单详情 + * @param params + */ +export function getMenuDetail(id) { + return http.request({ + url: '/menu/detail/'+id, + method: 'GET', + }); +} +/** + * @description: 添加菜单 + */ +export function menuAdd(data:any) { + return http.request({ + url: '/menu/add', + method: 'POST', + data, + }); +} +/** + * @description: 更新菜单 + */ +export function menuUpdate(data:any) { + return http.request({ + url: '/menu/update', + method: 'PUT', + data + }); +} +/** + * @description: 删除菜单 + */ +export function menuDelete(id) { + return http.request({ + url: '/menu/delete/'+id, + method: 'get', + }); +} \ No newline at end of file diff --git a/src/api/system/user.ts b/src/api/system/user.ts index 5de3490..b8922f3 100644 --- a/src/api/system/user.ts +++ b/src/api/system/user.ts @@ -87,3 +87,50 @@ export function getUserList(params) { params, }); } +/** + * @description: 根据ID获取详情 + */ +export function getUserDetail(userId) { + return http.request({ + url: '/user/detail/'+userId, + method: 'get', + }); +} +/** + * @description: 添加用户 + */ +export function userAdd(data:any) { + return http.request({ + url: '/user/add', + method: 'POST', + data, + }); +} +/** + * @description: 更新用户 + */ +export function userUpdate(data:any) { + return http.request({ + url: '/user/update', + method: 'PUT', + data + }); +} +/** + * @description: 删除用户 + */ +export function userDelete(userId) { + return http.request({ + url: '/user/delete/'+userId, + method: 'DELETE', + }); +} +/** + * @description: 批量删除用户 + */ +export function userBatchDelete(userId) { + return http.request({ + url: '/user/batchDelete/'+userId, + method: 'DELETE', + }); +} \ No newline at end of file diff --git a/src/components/icon/index.ts b/src/components/icon/index.ts new file mode 100644 index 0000000..e769a21 --- /dev/null +++ b/src/components/icon/index.ts @@ -0,0 +1,13 @@ +import * as ElementPlusIcons from '@element-plus/icons-vue' + +export const EL_ICON_PREFIX = 'el-icon-' + +const elIconsName: string[] = [] + +for (const [, component] of Object.entries(ElementPlusIcons)) { + elIconsName.push(`${EL_ICON_PREFIX}${component.name}`) +} + +export function getElementPlusIconNames() { + return elIconsName +} diff --git a/src/components/icon/index.vue b/src/components/icon/index.vue new file mode 100644 index 0000000..cdfb210 --- /dev/null +++ b/src/components/icon/index.vue @@ -0,0 +1,37 @@ + diff --git a/src/components/icon/picker.vue b/src/components/icon/picker.vue new file mode 100644 index 0000000..39864a1 --- /dev/null +++ b/src/components/icon/picker.vue @@ -0,0 +1,182 @@ + + + diff --git a/src/components/icon/svg-icon.vue b/src/components/icon/svg-icon.vue new file mode 100644 index 0000000..3c1f652 --- /dev/null +++ b/src/components/icon/svg-icon.vue @@ -0,0 +1,39 @@ + + + diff --git a/src/directives/permission.ts b/src/directives/permission.ts index cf79c66..17115bd 100644 --- a/src/directives/permission.ts +++ b/src/directives/permission.ts @@ -1,19 +1,23 @@ import { ObjectDirective } from 'vue'; import { usePermission } from '@/hooks/web/usePermission'; -export const permission: ObjectDirective = { +export const perm: ObjectDirective = { mounted(el: HTMLButtonElement, binding) { if (binding.value == undefined) return; const { action, effect } = binding.value; - const { hasPermission } = usePermission(); - if (!hasPermission(action)) { - if (effect == 'disabled') { - el.disabled = true; - el.style['disabled'] = 'disabled'; - el.classList.add('is-disabled'); - } else { - el.remove(); - } + const { hasSomePermission } = usePermission(); + if (!hasSomePermission(binding.value)) { + el.parentNode.removeChild(el) + } + }, +}; +export const perms: ObjectDirective = { + mounted(el: HTMLButtonElement, binding) { + if (binding.value == undefined) return; + const { action, effect } = binding.value; + const { hasEveryPermission } = usePermission(); + if (!hasEveryPermission(binding.value)) { + el.parentNode.removeChild(el) } }, }; diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index 66f8e20..2e2c17f 100644 --- a/src/hooks/web/usePermission.ts +++ b/src/hooks/web/usePermission.ts @@ -30,7 +30,7 @@ export function usePermission() { function hasEveryPermission(accesses: string[]): boolean { const permissionsList = userStore.getPermissions; if (Array.isArray(accesses)) { - return permissionsList.every((access: any) => accesses.includes(access.value)); + return accesses.every((access: any) => permissionsList.includes(access)); } throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`); } @@ -43,7 +43,7 @@ export function usePermission() { function hasSomePermission(accesses: string[]): boolean { const permissionsList = userStore.getPermissions; if (Array.isArray(accesses)) { - return permissionsList.some((access: any) => accesses.includes(access.value)); + return accesses.some((access: any) => permissionsList.includes(access)); } throw new Error(`[hasSomePermission]: ${accesses} should be a array !`); } diff --git a/src/main.ts b/src/main.ts index 71f8d71..694d781 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,7 @@ import './styles/index.scss'; import 'element-plus/theme-chalk/display.css'; import 'element-plus/theme-chalk/dark/css-vars.css'; import 'nprogress/nprogress.css'; - +import * as ElementPlusIconsVue from '@element-plus/icons-vue' import { createApp } from 'vue'; import App from './App.vue'; import router, { setupRouter } from './router'; @@ -12,8 +12,10 @@ import { setupStore } from '@/store'; import { setupElement, setupDirectives, setupCustomComponents } from '@/plugins'; async function bootstrap() { - const app = createApp(App); - + const app=createApp(App) + for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) + } // 全局完整引入 element 组件 setupElement(app); diff --git a/src/plugins/directives.ts b/src/plugins/directives.ts index 7c3f4bd..6ebb4f4 100644 --- a/src/plugins/directives.ts +++ b/src/plugins/directives.ts @@ -1,6 +1,6 @@ import { App } from 'vue'; -import { permission } from '@/directives/permission'; +import { perm,perms } from '@/directives/permission'; import { scrollBar } from '@/directives/scrollBar'; /** @@ -8,6 +8,7 @@ import { scrollBar } from '@/directives/scrollBar'; * @param app */ export function setupDirectives(app: App) { - app.directive('permission', permission); // 权限控制指令(演示) + app.directive('perm', perm); // 权限控制指令 (是否包含其中某个权限) + app.directive('perms', perms); // 权限控制指令 (是否包含所有权限) app.directive('scrollBar', scrollBar); // 滚动条 } diff --git a/src/router/index.ts b/src/router/index.ts index 918f5ec..e583a27 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -8,6 +8,11 @@ const modules: any = import.meta.glob('./modules/**/*.ts', { eager: true }); const routeModuleList: RouteRecordRaw[] = []; +const modulesVue = import.meta.glob('/src/views/**/*.vue') +export function getModulesKey() { + return Object.keys(modulesVue).map((item) => item.replace('/src/views/', '').replace('.vue', '')) +} + Object.keys(modules).forEach((key) => { const mod = modules[key].default || {}; const modList = Array.isArray(mod) ? [...mod] : [mod]; diff --git a/src/utils/auth.ts b/src/utils/auth.ts new file mode 100644 index 0000000..8b058dc --- /dev/null +++ b/src/utils/auth.ts @@ -0,0 +1,233 @@ +import {isObject} from "@vue/shared"; +import {cloneDeep} from "lodash"; +import type {RouteLocationNormalizedLoaded} from "vue-router"; +import {RouteLocationNormalized, useRoute} from "vue-router"; +import {watch} from "vue"; +import { + ElLoading, + ElMessage, + ElMessageBox +} from "element-plus"; +import type {LoadingInstance} from "element-plus/es/components/loading/src/loading"; + + +export function isExternal(path: string) { + return /^(https?:|mailto:|tel:)/.test(path); +} + +/** + * @description 获取正确的路经 + * @param {String} path 数据 + */ +export function getNormalPath(path: string) { + if (path.length === 0 || !path || path == "undefined") { + return path; + } + const newPath = path.replace("//", "/"); + const length = newPath.length; + if (newPath[length - 1] === "/") { + return newPath.slice(0, length - 1); + } + return newPath; +} + +export const isEmpty = (value: unknown) => { + return value == null && typeof value == "undefined"; +}; + +/** + * @description对象格式化为Query语法 + * @param { Object } params + * @return {string} Query语法 + */ +export function objectToQuery(params: Record): string { + let query = ""; + for (const props of Object.keys(params)) { + const value = params[props]; + const part = encodeURIComponent(props) + "="; + if (!isEmpty(value)) { + if (isObject(value)) { + for (const key of Object.keys(value)) { + if (!isEmpty(value[key])) { + const params = props + "[" + key + "]"; + const subPart = encodeURIComponent(params) + "="; + query += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + query += part + encodeURIComponent(value) + "&"; + } + } + } + return query.slice(0, -1); +} + +export const addUnit = (value: string | number, unit = "px") => { + return !Object.is(Number(value), NaN) ? `${value}${unit}` : value; +}; + + +export function useWatchRoute(callback: (route: RouteLocationNormalizedLoaded) => void) { + const route = useRoute(); + watch( + route, + () => { + callback(route); + }, + { + immediate: true + } + ); + return { + route + }; +} + + +// declare type MessageType = '' | 'success' | 'warning' | 'info' | 'error'; + +export function confirm(msg: string, type: "warning") { + return ElMessageBox.confirm(msg, "温馨提示", { + confirmButtonText: "确定", + cancelButtonText: "取消", + type: type + }); +} + +export function message(msg: string, type: string = "success") { + ElMessage[type](msg); +} + +let loadingInstance = null; + +// 打开全局loading +export function loading(msg: string) { + loadingInstance = ElLoading.service({ + lock: true, + text: msg + }); +} + +// 关闭全局loading +export function closeLoading() { + loadingInstance?.close(); +} + +export const arrayToTree = ( + data: any[], + props = {id: "id", parentId: "pid", children: "children"} +) => { + data = cloneDeep(data); + const {id, parentId, children} = props; + const result: any[] = []; + const map = new Map(); + data.forEach((item) => { + map.set(item[id], item); + const parent = map.get(item[parentId]); + if (parent) { + parent[children] = parent[children] ?? []; + parent[children].push(item); + } else { + result.push(item); + } + }); + return result; +}; + +/** + * @description 树转数组,队列实现广度优先遍历 + * @param {Array} data 数据 + * @param {Object} props `{ children: 'children' }` + */ + +export const treeToArray = (data: any[], props = {children: "children"}) => { + data = cloneDeep(data); + const {children} = props; + const newData = []; + const queue: any[] = []; + data.forEach((child: any) => queue.push(child)); + while (queue.length) { + const item: any = queue.shift(); + if (item[children]) { + item[children].forEach((child: any) => queue.push(child)); + delete item[children]; + } + newData.push(item); + } + return newData; +}; +/** + * 数组转树型结构 + */ +export const buildTree =(array)=> { + const tree = {}; // 用于存储树形结构的临时对象 + const result = []; // 最终的树形结构数组 + + array.forEach((item) => { + tree[item.id] = { ...item, children: [] }; // 初始化树节点 + }); + + array.forEach((item) => { + if (item.parentId === 0) { + result.push(tree[item.id]); // 根节点直接添加到结果数组 + } else { + tree[item.parentId].children.push(tree[item.id]); // 子节点添加到父节点的children数组 + } + }); + + return result; + } +export const getComponentName = (route: RouteLocationNormalized) => { + return route.matched[route.matched.length - 1]?.components?.default?.name; +}; + + +export function streamFileDownload(file: any, fileName = "文件名称.zip") { + const blob = new Blob([file], {type: "application/octet-stream;charset=UTF-8"}); + const url = window.URL.createObjectURL(blob); + const link = document.createElement("a"); + link.style.display = "none"; + link.href = url; + link.setAttribute("download", fileName); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); // 下载完成移除元素 + window.URL.revokeObjectURL(url); +} + + +export function filterName(list: any, value: any, key: string = "value", label: string = "label") { + let name = ""; + let child = list.find(item => item[key] == value); + name = child ? child[label] : ""; + return name; +} + +export function filterDatetime(value: any, format: string = "yyyy-MM-dd") { + if (value) { + return dateFtt(format, new Date(parseInt(value))); + } else { + return ""; + } +} + + +function dateFtt(fmt, date) { //author: meizz + var o = { + "M+": date.getMonth() + 1, //月份 + "d+": date.getDate(), //日 + "h+": date.getHours(), //小时 + "m+": date.getMinutes(), //分 + "s+": date.getSeconds(), //秒 + "q+": Math.floor((date.getMonth() + 3) / 3), //季度 + "S": date.getMilliseconds() //毫秒 + }; + if (/(y+)/.test(fmt)) + fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); + for (var k in o) + if (new RegExp("(" + k + ")").test(fmt)) + fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); + return fmt; +} + + diff --git a/src/utils/useLockFn.ts b/src/utils/useLockFn.ts new file mode 100644 index 0000000..08759c8 --- /dev/null +++ b/src/utils/useLockFn.ts @@ -0,0 +1,18 @@ +import { ref } from 'vue' + +export function useLockFn(fn: (...args: any[]) => Promise) { + const isLock = ref(false) + const lockFn = async (...args: any[]) => { + if (isLock.value) return + isLock.value = true + try { + await fn(...args) + } finally{ + isLock.value=false + } + } + return { + isLock, + lockFn + } +} diff --git a/src/views/system/menu/edit.vue b/src/views/system/menu/edit.vue new file mode 100644 index 0000000..4f988ce --- /dev/null +++ b/src/views/system/menu/edit.vue @@ -0,0 +1,318 @@ + + diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 522114f..ba11cd0 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -1,258 +1,154 @@