From fa8b33acbe1641e805f7d4f21628abd35de09a79 Mon Sep 17 00:00:00 2001 From: Ah jung <735878602@qq.com> Date: Wed, 21 Jul 2021 18:33:02 +0800 Subject: [PATCH] fix Bug or add example --- CHANGELOG.md | 12 + README.md | 3 + mock/system/menu.ts | 93 ++++++++ mock/system/role.ts | 27 ++- mock/user/user.ts | 8 + package.json | 6 +- src/api/system/menu.ts | 23 +- src/components/Table/README.md | 6 +- src/components/Table/index.ts | 4 + .../Table/src/components/TableAction.vue | 145 ++++++++++++ src/components/Table/src/hooks/useColumns.ts | 33 ++- src/components/Table/src/props.ts | 6 +- src/components/Table/src/types/tableAction.ts | 26 +++ src/enums/pageEnum.ts | 2 + src/hooks/web/usePermission.ts | 2 +- src/layout/components/Footer/index.vue | 2 +- src/layout/components/TagsView/index.vue | 67 +++--- src/plugins/naive.ts | 6 +- src/router/index.ts | 2 +- src/router/modules/list.ts | 9 + src/utils/index.ts | 31 +++ src/views/form/basicForm/index.vue | 25 +- src/views/list/basicList/columns.ts | 67 +++--- src/views/list/basicList/index.vue | 88 ++++++- src/views/list/basicList/info.vue | 38 ++++ src/views/login/index.vue | 3 +- src/views/system/menu/CreateDrawer.vue | 137 +++++++++++ src/views/system/menu/menu.vue | 214 ++++++++++++------ src/views/system/role/columns.ts | 30 --- src/views/system/role/role.vue | 187 ++++++++++++--- tailwind.config.js | 26 +-- yarn.lock | 55 +++-- 32 files changed, 1096 insertions(+), 287 deletions(-) create mode 100644 mock/system/menu.ts create mode 100644 src/components/Table/src/components/TableAction.vue create mode 100644 src/components/Table/src/types/tableAction.ts create mode 100644 src/views/list/basicList/info.vue create mode 100644 src/views/system/menu/CreateDrawer.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 602d69c..53ac8ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 1.4 (2021-07-21) +### 🐛 Bug Fixes +- vite降至2.3.6 +- 多标签页交互优化 + +- ### ✨ Features +- 新增 `TableAction` 组件 +- 新增 `菜单权限管理` 示例 +- 新增 `角色权限管理` 示例 +- 持续更新更多实用组件及示例,感谢Star + + # 1.3 (2021-07-19) ### 🐛 Bug Fixes - 修复多标签页左右切换按钮自适应展示 diff --git a/README.md b/README.md index e3b2215..6cca18f 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,9 @@ Naive Ui Admin 是一个免费开源的中后台模版,使用了最新的`vue3 - [x] 异常页面 - [x] 结果页面 - [x] 设置页面 +- [x] 系统设置 +- [x] 菜单权限 +- [x] 角色权限 ### 页面组件 #### ProTable diff --git a/mock/system/menu.ts b/mock/system/menu.ts new file mode 100644 index 0000000..90d130a --- /dev/null +++ b/mock/system/menu.ts @@ -0,0 +1,93 @@ +import { resultSuccess } from '../_util' + +const menuList = (() => { + const result: any[] = [ + { + label: 'Dashboard', + key: 'dashboard', + type: 1, + subtitle:'dashboard', + openType:1, + auth:'dashboard', + path:'/dashboard', + children: [ + { + label: '主控台', + key: 'console', + type: 1, + subtitle:'console', + openType:1, + auth:'console', + path:'/dashboard/console', + }, + { + label: '工作台', + key: 'workplace', + type: 1, + subtitle:'workplace', + openType:1, + auth:'workplace', + path:'/dashboard/workplace', + } + ] + }, + { + label: '表单管理', + key: 'form', + type: 1, + subtitle:'form', + openType:1, + auth:'form', + path:'/form', + children: [ + { + label: '基础表单', + key: 'basic-form', + type: 1, + subtitle:'basic-form', + openType:1, + auth:'basic-form', + path:'/form/basic-form', + }, + { + label: '分步表单', + key: 'step-form', + type: 1, + subtitle:'step-form', + openType:1, + auth:'step-form', + path:'/form/step-form', + }, + { + label: '表单详情', + key: 'detail', + type: 1, + subtitle:'detail', + openType:1, + auth:'detail', + path:'/form/detail', + } + ] + } + ] + + return result +}); + + +export default [ + { + url: '/api/menu/list', + timeout: 1000, + method: 'get', + response: () => { + const list = menuList() + return resultSuccess({ + list + } + ); + }, + } +] + + diff --git a/mock/system/role.ts b/mock/system/role.ts index bedef36..2a5796c 100644 --- a/mock/system/role.ts +++ b/mock/system/role.ts @@ -1,14 +1,26 @@ import { resultSuccess, doCustomTimes } from '../_util' + +function getMenuKeys() { + let keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'] + let newKeys = [] + doCustomTimes(parseInt(Math.random()*6), () => { + let key = keys[Math.floor(Math.random() * keys.length)]; + newKeys.push(key) + }) + return Array.from(new Set(newKeys)); +} + const roleList = ((pageSize) => { - const result:any[] = [] - doCustomTimes(pageSize,()=> { + const result: any[] = [] + doCustomTimes(pageSize, () => { result.push({ id: '@integer(10,100)', name: '@cname()', - explain:'@cname()', - isDefault:'@boolean()', - create_date: `@date('yyyy-MM-dd')`, + explain: '@cname()', + isDefault: '@boolean()', + menu_keys: getMenuKeys(), + create_date: `@date('yyyy-MM-dd hh:mm:ss')`, 'status|1': ['normal', 'enable', 'disable'], }); }) @@ -17,7 +29,6 @@ const roleList = ((pageSize) => { export default [ - //表格数据列表 { url: '/api/role/list', timeout: 1000, @@ -26,8 +37,8 @@ export default [ const { page = 1, pageSize = 10 } = query; const list = roleList(Number(pageSize)) return resultSuccess({ - page:Number(page), - pageSize:Number(pageSize), + page: Number(page), + pageSize: Number(pageSize), pageCount: 60, list } diff --git a/mock/user/user.ts b/mock/user/user.ts index e6f2a5f..cf802f7 100644 --- a/mock/user/user.ts +++ b/mock/user/user.ts @@ -25,6 +25,14 @@ const adminInfo = { { roleName: '工作台', value: 'dashboard_workplace', + }, + { + roleName: '基础列表', + value: 'basic_list', + }, + { + roleName: '基础列表删除', + value: 'basic_list_delete', } ], } diff --git a/package.json b/package.json index d3b3226..eb179e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "naive-ui-admin", - "version": "1.3", + "version": "1.4", "author": { "name": "Ahjung", "email": "735878602@qq.com", @@ -80,9 +80,9 @@ "stylelint-scss": "^3.19.0", "tailwindcss": "^2.2.4", "typescript": "^4.3.2", - "vite": "^2.4.2", + "vite": "2.3.6", "vite-plugin-html": "^2.0.7", - "vite-plugin-mock": "^2.9.1", + "vite-plugin-mock": "^2.9.3", "vite-plugin-style-import": "^1.0.1", "vue-eslint-parser": "^7.8.0" }, diff --git a/src/api/system/menu.ts b/src/api/system/menu.ts index c81d1b2..ed186ca 100644 --- a/src/api/system/menu.ts +++ b/src/api/system/menu.ts @@ -1,32 +1,23 @@ import http from '@/utils/http/axios' -import { - GetByUserIdParams, - GetMenuListByUserIdResult, - GetAuthCodeByUserIdResult -} from './model/menuModel' - -enum Api { - adminMenus = '/menus', - GetBtnCodeListByUserId = '/getBtnCodeListByUserId' -} /** * @description: 根据用户id获取用户菜单 */ export function adminMenus() { - return http.request({ - url: Api.adminMenus, + return http.request({ + url: '/menus', method: 'GET' }) } + /** - * 根据用户Id获取权限编码 + * 获取tree菜单列表 * @param params */ -export function getBtnCodeListByUserId(params: GetByUserIdParams) { - return http.request({ - url: Api.GetBtnCodeListByUserId, +export function getMenuList(params) { + return http.request({ + url: '/menu/list', method: 'GET', params }) diff --git a/src/components/Table/README.md b/src/components/Table/README.md index 25d38df..7540989 100644 --- a/src/components/Table/README.md +++ b/src/components/Table/README.md @@ -43,7 +43,11 @@ const columns = [ }, { title: '地址', - key: 'address' + key: 'address', + auth: ['amdin'], // 同时根据权限控制是否显示 + ifShow: (row) => { + return true; // 根据业务控制是否显示 + }, }, { title: '日期', diff --git a/src/components/Table/index.ts b/src/components/Table/index.ts index 4f33e83..fa14d27 100644 --- a/src/components/Table/index.ts +++ b/src/components/Table/index.ts @@ -1 +1,5 @@ export { default as BasicTable } from './src/Table.vue'; +export { default as TableAction } from './src/components/TableAction.vue'; +export * from './src/types/table'; +export * from './src/types/tableAction'; + diff --git a/src/components/Table/src/components/TableAction.vue b/src/components/Table/src/components/TableAction.vue new file mode 100644 index 0000000..368cdf7 --- /dev/null +++ b/src/components/Table/src/components/TableAction.vue @@ -0,0 +1,145 @@ + + + diff --git a/src/components/Table/src/hooks/useColumns.ts b/src/components/Table/src/hooks/useColumns.ts index 7e7312e..2e5bbea 100644 --- a/src/components/Table/src/hooks/useColumns.ts +++ b/src/components/Table/src/hooks/useColumns.ts @@ -2,6 +2,9 @@ import { ref, Ref, ComputedRef, unref, computed, watch, toRaw } from 'vue'; import type { BasicColumn, BasicTableProps } from '../types/table'; import { isEqual, cloneDeep } from 'lodash-es'; import { isArray, isString } from '@/utils/is'; +import { usePermission } from '@/hooks/web/usePermission'; +import { isString, isBoolean, isFunction } from "@/utils/is"; +import { ActionItem } from "@/components/Table"; export function useColumns(propsRef: ComputedRef) { const columnsRef = ref(unref(propsRef).columns) as unknown as Ref; @@ -9,13 +12,34 @@ export function useColumns(propsRef: ComputedRef) { const getColumnsRef = computed(() => { const columns = cloneDeep(unref(columnsRef)); + + handleActionColumn(propsRef, columns); + if (!columns) return []; return columns; }) + const { hasPermission } = usePermission(); + + function isIfShow(action: ActionItem): boolean { + const ifShow = action.ifShow; + + let isIfShow = true; + + if (isBoolean(ifShow)) { + isIfShow = ifShow; + } + if (isFunction(ifShow)) { + isIfShow = ifShow(action); + } + return isIfShow; + } + const getPageColumns = computed(() => { const pageColumns = unref(getColumnsRef); const columns = cloneDeep(pageColumns); - return columns + return columns.filter((column) => { + return hasPermission(column.auth) && isIfShow(column); + }) }) watch( @@ -26,6 +50,13 @@ export function useColumns(propsRef: ComputedRef) { } ); + function handleActionColumn(propsRef: ComputedRef, columns: BasicColumn[]) { + const { actionColumn } = unref(propsRef); + if (!actionColumn) return; + columns.push({ + ...actionColumn + }); + } //设置 function setColumns(columnList: string[]) { diff --git a/src/components/Table/src/props.ts b/src/components/Table/src/props.ts index 0696ca9..3a31e6f 100644 --- a/src/components/Table/src/props.ts +++ b/src/components/Table/src/props.ts @@ -41,5 +41,9 @@ export const basicProps = { showPagination: { type: [String, Boolean], default: 'auto' - } + }, + actionColumn: { + type: Object as PropType, + default: null, + }, } diff --git a/src/components/Table/src/types/tableAction.ts b/src/components/Table/src/types/tableAction.ts new file mode 100644 index 0000000..d635b95 --- /dev/null +++ b/src/components/Table/src/types/tableAction.ts @@ -0,0 +1,26 @@ +import { NButton, NTooltip } from 'naive-ui'; +import { RoleEnum } from '/@/enums/roleEnum'; + +export interface ActionItem extends NButton.props { + onClick?: Fn; + label?: string; + color?: 'success' | 'error' | 'warning'; + icon?: string; + popConfirm?: PopConfirm; + disabled?: boolean; + divider?: boolean; + // 权限编码控制是否显示 + auth?: RoleEnum | RoleEnum[] | string | string[]; + // 业务控制是否显示 + ifShow?: boolean | ((action: ActionItem) => boolean); + tooltip?: string | TooltipProps; +} + +export interface PopConfirm { + title: string; + okText?: string; + cancelText?: string; + confirm: Fn; + cancel?: Fn; + icon?: string; +} diff --git a/src/enums/pageEnum.ts b/src/enums/pageEnum.ts index aa0f800..ce0cf95 100644 --- a/src/enums/pageEnum.ts +++ b/src/enums/pageEnum.ts @@ -7,6 +7,8 @@ export enum PageEnum { REDIRECT_NAME = 'Redirect', // 首页 BASE_HOME = '/dashboard', + //首页跳转默认路由 + BASE_HOME_REDIRECT = '/dashboard/console', // 错误 ERROR_PAGE_NAME = 'ErrorPage', } diff --git a/src/hooks/web/usePermission.ts b/src/hooks/web/usePermission.ts index c03750e..83fbf5a 100644 --- a/src/hooks/web/usePermission.ts +++ b/src/hooks/web/usePermission.ts @@ -19,7 +19,7 @@ export function usePermission() { * 可用于 v-if 显示逻辑 * */ function hasPermission(accesses: string[]): boolean { - if (!accesses.length) return true + if (!accesses ||!accesses.length) return true return _someRoles(accesses) } diff --git a/src/layout/components/Footer/index.vue b/src/layout/components/Footer/index.vue index 88175f8..2ebb690 100644 --- a/src/layout/components/Footer/index.vue +++ b/src/layout/components/Footer/index.vue @@ -12,7 +12,7 @@ diff --git a/src/layout/components/TagsView/index.vue b/src/layout/components/TagsView/index.vue index 46eee16..f2edbd3 100644 --- a/src/layout/components/TagsView/index.vue +++ b/src/layout/components/TagsView/index.vue @@ -20,15 +20,15 @@
- + @@ -73,6 +73,7 @@ import { useAsyncRouteStore } from '@/store/modules/asyncRoute' import { RouteItem } from '@/store/modules/tabsView' import { useProjectSetting } from '@/hooks/setting/useProjectSetting' import { useMessage } from 'naive-ui' +// @ts-ignore import Draggable from 'vuedraggable/src/vuedraggable' import { PageEnum } from '@/enums/pageEnum' import { @@ -86,7 +87,6 @@ import { } from '@vicons/antd' import { renderIcon } from '@/utils/index' import elementResizeDetectorMaker from 'element-resize-detector' -import { useProjectSettingStore } from "@/store/modules/projectSetting"; import { useDesignSetting } from '@/hooks/setting/useDesignSetting' export default defineComponent({ @@ -118,6 +118,7 @@ export default defineComponent({ const navRef: any = ref(null) const navScroll: any = ref(null) const navWrap: any = ref(null) + const isCurrent = ref(false) const state = reactive({ activeKey: route.fullPath, @@ -152,28 +153,34 @@ export default defineComponent({ }) //tags 右侧下拉菜单 - const TabsMenuOptions = [ - { - label: '刷新当前', - key: '1', - icon: renderIcon(ReloadOutlined) - }, - { - label: '关闭当前', - key: '2', - icon: renderIcon(CloseOutlined) - }, - { - label: '关闭其他', - key: '3', - icon: renderIcon(ColumnWidthOutlined) - }, - { - label: '关闭全部', - key: '4', - icon: renderIcon(MinusOutlined) - } - ] + const TabsMenuOptions = computed(() => { + const isDisabled = unref(tabsList).length <= 1 ? true : false + return [ + { + label: '刷新当前', + key: '1', + icon: renderIcon(ReloadOutlined) + }, + { + label: `关闭当前`, + key: '2', + disabled: unref(isCurrent) || isDisabled, + icon: renderIcon(CloseOutlined) + }, + { + label: '关闭其他', + key: '3', + disabled:isDisabled, + icon: renderIcon(ColumnWidthOutlined) + }, + { + label: '关闭全部', + key: '4', + disabled:isDisabled, + icon: renderIcon(MinusOutlined) + } + ] + }) let routes: RouteItem[] = [] @@ -288,7 +295,7 @@ export default defineComponent({ const closeAll = () => { localStorage.removeItem('routes') tabsViewStore.closeAllTabs() - router.replace('/') + router.replace(PageEnum.BASE_HOME) updateNavScroll() } @@ -373,8 +380,9 @@ export default defineComponent({ return state.navStyle }) - function handleContextMenu(e) { + function handleContextMenu(e,item) { e.preventDefault() + isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path state.showDropdown = false nextTick().then(() => { state.showDropdown = true @@ -419,6 +427,7 @@ export default defineComponent({ navScroll, route, tabsList, + baseHome:PageEnum.BASE_HOME_REDIRECT, goPage, closeTabItem, closeLeft, diff --git a/src/plugins/naive.ts b/src/plugins/naive.ts index 95780cd..ed2e731 100644 --- a/src/plugins/naive.ts +++ b/src/plugins/naive.ts @@ -61,7 +61,8 @@ import { NLoadingBarProvider, NModal, NUpload, - NTree + NTree, + NSpin } from 'naive-ui' const naive = create({ @@ -126,7 +127,8 @@ const naive = create({ NLoadingBarProvider, NModal, NUpload, - NTree + NTree, + NSpin ] }) diff --git a/src/router/index.ts b/src/router/index.ts index d71973f..039e008 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -45,7 +45,7 @@ export const asyncRoutes = [ErrorPageRoute, ...routeModuleList]; //普通路由 无需验证权限 -export const constantRouter = [LoginRoute, RootRoute, RedirectRoute] +export const constantRouter:any[] = [LoginRoute, RootRoute, RedirectRoute] const router = createRouter({ history: createWebHashHistory(''), diff --git a/src/router/modules/list.ts b/src/router/modules/list.ts index 538bd17..9b5adc1 100644 --- a/src/router/modules/list.ts +++ b/src/router/modules/list.ts @@ -33,6 +33,15 @@ const routes: Array = [ title: '基础列表', }, component: () => import('@/views/list/basicList/index.vue') + }, + { + path: 'basic-info/:id?', + name: 'basic-info', + meta: { + title: '基础详情', + hidden:true + }, + component: () => import('@/views/list/basicList/info.vue') } ], } diff --git a/src/utils/index.ts b/src/utils/index.ts index 5dac687..8aed354 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -42,3 +42,34 @@ export const withInstall = (component: T, alias?: string) => { }; return component as T & Plugin; }; + +/** + * 找到对应的节点 + * */ +let result = null +export function getTreeItem(data: any[], key?: string | number) { + data.map(item => { + if (item.key === key) { + result = item; + } else { + if (item.children && item.children.length) { + getTreeItem(item.children, key); + } + } + }); + return result +} + +/** + * 找到所有节点 + * */ +let treeAll = [] +export function getTreeAll(data: any[]) { + data.map(item => { + treeAll.push(item.key) + if (item.children && item.children.length) { + getTreeAll(item.children); + } + }); + return treeAll +} diff --git a/src/views/form/basicForm/index.vue b/src/views/form/basicForm/index.vue index 5f1f6a1..ca672bd 100644 --- a/src/views/form/basicForm/index.vue +++ b/src/views/form/basicForm/index.vue @@ -5,7 +5,7 @@ 表单页用于向用户收集或验证信息,基础表单常见于数据项较少的表单场景。表单域标签也可支持响应式。
- + - + @@ -155,16 +155,18 @@ export default defineComponent({ const message = useMessage() const { uploadUrl } = globSetting + const defaultValueRef = () => ({ + name: '', + mobile: '', + remark: '', + sex: 1, + matter: null, + doctor: null, + datetime: [], + }) + const state = reactive({ - formValue: { - name: '', - mobile: '', - remark: '', - sex: 1, - matter: null, - doctor: null, - datetime: [], - }, + formValue: defaultValueRef(), //图片列表 通常查看和编辑使用 绝对路径 | 相对路径都可以 uploadList: [ 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', @@ -188,6 +190,7 @@ export default defineComponent({ function resetForm() { formRef.value.restoreValidation() + state.formValue = Object.assign(state.formValue, defaultValueRef()) } function uploadChange(list: string[]) { diff --git a/src/views/list/basicList/columns.ts b/src/views/list/basicList/columns.ts index dbaec86..d970e5a 100644 --- a/src/views/list/basicList/columns.ts +++ b/src/views/list/basicList/columns.ts @@ -25,7 +25,11 @@ export const columns = [ }, { title: '地址', - key: 'address' + key: 'address', + auth: ['basic_list'], // 同时根据权限控制是否显示 + ifShow: (_column) => { + return true; // 根据业务控制是否显示 + }, }, { title: '开始日期', @@ -39,34 +43,35 @@ export const columns = [ title: '创建时间', key: 'date', }, - { - title: '操作', - key: 'actions', - width: 150, - //简单写一下例子,不建议这么写,过段时间,这里封二次封装 - render() { - return [ - h( - NButton, - { - size: 'small', - type: 'error', - style: 'margin-right:10px', - onClick: () => { - } - }, - { default: () => '删除' } - ), - h( - NButton, - { - size: 'small', - onClick: () => { - } - }, - { default: () => '编辑' } - ) - ] - } - } + // { + // title: '操作', + // key: 'actions', + // width: 150, + // //简单写一下例子,不建议这么写,过段时间,这里封二次封装 + // render() { + // return [ + // h( + // NButton, + // { + // size: 'small', + // type: 'error', + // style: 'margin-right:10px', + // onClick: () => { + // } + // }, + // { default: () => '删除' } + // ), + // h( + // NButton, + // { + // size: 'small', + // onClick: () => { + // + // } + // }, + // { default: () => '编辑' } + // ) + // ] + // } + // } ] diff --git a/src/views/list/basicList/index.vue b/src/views/list/basicList/index.vue index 65b9081..4d290bd 100644 --- a/src/views/list/basicList/index.vue +++ b/src/views/list/basicList/index.vue @@ -5,6 +5,7 @@ :request="loadDataTable" :row-key="row => row.id" ref="actionRef" + :actionColumn="actionColumn" @update:checked-row-keys="onCheckedRow" > + + + @@ -57,10 +63,11 @@ + + diff --git a/src/views/login/index.vue b/src/views/login/index.vue index 7022073..e48c0a9 100644 --- a/src/views/login/index.vue +++ b/src/views/login/index.vue @@ -29,7 +29,6 @@
+ + + + + {{ formParams.type === 1 ? '侧边栏菜单' : '' }} + + + + + + + + + + + + + + 当前窗口 + 新窗口 + + + + + + + + + + + + + + + + + + diff --git a/src/views/system/menu/menu.vue b/src/views/system/menu/menu.vue index a7c4fe8..bde0ff0 100644 --- a/src/views/system/menu/menu.vue +++ b/src/views/system/menu/menu.vue @@ -11,7 +11,7 @@