diff --git a/lint-staged.config.js b/lint-staged.config.js deleted file mode 100644 index 2cff139..0000000 --- a/lint-staged.config.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], - '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], - 'package.json': ['prettier --write'], - '*.vue': ['prettier --write', 'stylelint --fix'], - '*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write'], - '*.md': ['prettier --write'] -} diff --git a/mock/index.ts b/mock/index.ts index 3d3e853..1e0db3b 100644 --- a/mock/index.ts +++ b/mock/index.ts @@ -2,9 +2,13 @@ import Mock from 'mockjs' import { param2Obj } from '@/utils' import example from './example' +import user from './user' +import role from './role' const mocks: any[] = [ - ...example + ...example, + ...user, + ...role ] // for front mock diff --git a/mock/role/admin-role.ts b/mock/role/admin-role.ts new file mode 100644 index 0000000..e3cefab --- /dev/null +++ b/mock/role/admin-role.ts @@ -0,0 +1,420 @@ +export const checkedNodes = [{ + 'path': '/components-demo', + 'title': '功能组件', + 'name': 'ComponentsDemo', + 'children': [{ + 'path': '/components-demo/echarts', + 'title': '图表', + 'name': 'EchartsDemo' + }, { + 'path': '/components-demo/preview', + 'title': '图片预览', + 'name': 'PreviewDemo' + }, { + 'path': '/components-demo/button', + 'title': '按钮', + 'name': 'ButtonDemo' + }, { + 'path': '/components-demo/message', + 'title': '消息提示', + 'name': 'MessageDemo' + }, { + 'path': '/components-demo/count-to', + 'title': '数字动画', + 'name': 'CountToDemo' + }, { + 'path': '/components-demo/search', + 'title': '查询', + 'name': 'SearchDemo' + }, { + 'path': '/components-demo/editor', + 'title': '富文本编辑器', + 'name': 'EditorDemo' + }, { + 'path': '/components-demo/markdown', + 'title': 'markdown编辑器', + 'name': 'MarkdownDemo' + }, { + 'path': '/components-demo/dialog', + 'title': '弹窗', + 'name': 'DialogDemo' + }, { + 'path': '/components-demo/more', + 'title': '显示更多', + 'name': 'MoreDemo' + }, { + 'path': '/components-demo/detail', + 'title': '详情组件', + 'name': 'DetailDemo' + }] +}, { + 'path': '/components-demo/echarts', + 'title': '图表', + 'name': 'EchartsDemo' +}, { + 'path': '/components-demo/preview', + 'title': '图片预览', + 'name': 'PreviewDemo' +}, { + 'path': '/components-demo/button', + 'title': '按钮', + 'name': 'ButtonDemo' +}, { + 'path': '/components-demo/message', + 'title': '消息提示', + 'name': 'MessageDemo' +}, { + 'path': '/components-demo/count-to', + 'title': '数字动画', + 'name': 'CountToDemo' +}, { + 'path': '/components-demo/search', + 'title': '查询', + 'name': 'SearchDemo' +}, { + 'path': '/components-demo/editor', + 'title': '富文本编辑器', + 'name': 'EditorDemo' +}, { + 'path': '/components-demo/markdown', + 'title': 'markdown编辑器', + 'name': 'MarkdownDemo' +}, { + 'path': '/components-demo/dialog', + 'title': '弹窗', + 'name': 'DialogDemo' +}, { + 'path': '/components-demo/more', + 'title': '显示更多', + 'name': 'MoreDemo' +}, { + 'path': '/components-demo/detail', + 'title': '详情组件', + 'name': 'DetailDemo' +}, { + 'path': '/table-demo', + 'title': '表格', + 'name': 'TableDemo', + 'children': [{ + 'path': '/table-demo/basic-table', + 'title': '基础表格', + 'name': 'BasicTable' + }, { + 'path': '/table-demo/page-table', + 'title': '分页表格', + 'name': 'PageTable' + }, { + 'path': '/table-demo/stripe-table', + 'title': '带斑马纹表格', + 'name': 'StripeTable' + }, { + 'path': '/table-demo/border-table', + 'title': '带边框表格', + 'name': 'BorderTable' + }, { + 'path': '/table-demo/state-table', + 'title': '带状态表格', + 'name': 'StateTable' + }, { + 'path': '/table-demo/fixed-header', + 'title': '固定表头', + 'name': 'FixedHeader' + }, { + 'path': '/table-demo/fixed-column', + 'title': '固定列', + 'name': 'FixedColumn' + }, { + 'path': '/table-demo/fixed-column-header', + 'title': '固定列和表头', + 'name': 'FixedColumnHeader' + }, { + 'path': '/table-demo/fluid-height', + 'title': '流体高度', + 'name': 'FluidHeight' + }, { + 'path': '/table-demo/multi-header', + 'title': '多级表头', + 'name': 'MultiHeader' + }, { + 'path': '/table-demo/single-choice', + 'title': '单选', + 'name': 'SingleChoice' + }, { + 'path': '/table-demo/multiple-choice', + 'title': '多选', + 'name': 'MultipleChoice' + }, { + 'path': '/table-demo/sort-table', + 'title': '排序', + 'name': 'SortTable' + }, { + 'path': '/table-demo/screen-table', + 'title': '筛选', + 'name': 'ScreenTable' + }, { + 'path': '/table-demo/expand-row', + 'title': '展开行', + 'name': 'ExpandRow' + }, { + 'path': '/table-demo/tree-and-load', + 'title': '树形数据与懒加载', + 'name': 'TreeAndLoad' + }, { + 'path': '/table-demo/custom-header', + 'title': '自定义表头', + 'name': 'CustomHeader' + }, { + 'path': '/table-demo/total-table', + 'title': '表尾合计行', + 'name': 'TotalTable' + }, { + 'path': '/table-demo/merge-table', + 'title': '合并行或列', + 'name': 'MergeTable' + }, { + 'path': '/table-demo/custom-index', + 'title': '自定义索引', + 'name': 'CustomIndex' + }] +}, { + 'path': '/table-demo/basic-table', + 'title': '基础表格', + 'name': 'BasicTable' +}, { + 'path': '/table-demo/page-table', + 'title': '分页表格', + 'name': 'PageTable' +}, { + 'path': '/table-demo/stripe-table', + 'title': '带斑马纹表格', + 'name': 'StripeTable' +}, { + 'path': '/table-demo/border-table', + 'title': '带边框表格', + 'name': 'BorderTable' +}, { + 'path': '/table-demo/state-table', + 'title': '带状态表格', + 'name': 'StateTable' +}, { + 'path': '/table-demo/fixed-header', + 'title': '固定表头', + 'name': 'FixedHeader' +}, { + 'path': '/table-demo/fixed-column', + 'title': '固定列', + 'name': 'FixedColumn' +}, { + 'path': '/table-demo/fixed-column-header', + 'title': '固定列和表头', + 'name': 'FixedColumnHeader' +}, { + 'path': '/table-demo/fluid-height', + 'title': '流体高度', + 'name': 'FluidHeight' +}, { + 'path': '/table-demo/multi-header', + 'title': '多级表头', + 'name': 'MultiHeader' +}, { + 'path': '/table-demo/single-choice', + 'title': '单选', + 'name': 'SingleChoice' +}, { + 'path': '/table-demo/multiple-choice', + 'title': '多选', + 'name': 'MultipleChoice' +}, { + 'path': '/table-demo/sort-table', + 'title': '排序', + 'name': 'SortTable' +}, { + 'path': '/table-demo/screen-table', + 'title': '筛选', + 'name': 'ScreenTable' +}, { + 'path': '/table-demo/expand-row', + 'title': '展开行', + 'name': 'ExpandRow' +}, { + 'path': '/table-demo/tree-and-load', + 'title': '树形数据与懒加载', + 'name': 'TreeAndLoad' +}, { + 'path': '/table-demo/custom-header', + 'title': '自定义表头', + 'name': 'CustomHeader' +}, { + 'path': '/table-demo/total-table', + 'title': '表尾合计行', + 'name': 'TotalTable' +}, { + 'path': '/table-demo/merge-table', + 'title': '合并行或列', + 'name': 'MergeTable' +}, { + 'path': '/table-demo/custom-index', + 'title': '自定义索引', + 'name': 'CustomIndex' +}, { + 'path': '/directives-demo', + 'title': '自定义指令', + 'name': 'DirectivesDemo', + 'children': [{ + 'path': '/directives-demo/clipboard', + 'title': 'Clipboard', + 'name': 'ClipboardDemo' + }] +}, { + 'path': '/directives-demo/clipboard', + 'title': 'Clipboard', + 'name': 'ClipboardDemo' +}, { + 'path': '/hooks-demo', + 'title': 'Hooks', + 'name': 'HooksDemo', + 'children': [{ + 'path': '/hooks-demo/watermark', + 'title': 'UseWaterMark', + 'name': 'UseWatermarkDemo' + }, { + 'path': '/hooks-demo/useScrollTo', + 'title': 'UseScrollTo', + 'name': 'UseScrollToDemo' + }] +}, { + 'path': '/hooks-demo/watermark', + 'title': 'UseWaterMark', + 'name': 'UseWatermarkDemo' +}, { + 'path': '/hooks-demo/useScrollTo', + 'title': 'UseScrollTo', + 'name': 'UseScrollToDemo' +}, { + 'path': '/icon/index', + 'title': '图标', + 'name': 'Icons' +}, { + 'path': '/level', + 'title': '多级菜单缓存', + 'name': 'Level', + 'children': [{ + 'path': '/level/menu1', + 'title': 'Menu1', + 'name': 'Menu1Demo', + 'children': [{ + 'path': '/level/menu1/menu1-1', + 'title': 'Menu1-1', + 'name': 'Menu11Demo', + 'children': [{ + 'path': '/level/menu1/menu1-1/menu1-1-1', + 'title': 'Menu1-1-1', + 'name': 'Menu111Demo' + }] + }, { + 'path': '/level/menu1/menu1-2', + 'title': 'Menu1-2', + 'name': 'Menu12Demo' + }] + }, { + 'path': '/level/menu2', + 'title': 'Menu2', + 'name': 'Menu2Demo' + }] +}, { + 'path': '/level/menu1', + 'title': 'Menu1', + 'name': 'Menu1Demo', + 'children': [{ + 'path': '/level/menu1/menu1-1', + 'title': 'Menu1-1', + 'name': 'Menu11Demo', + 'children': [{ + 'path': '/level/menu1/menu1-1/menu1-1-1', + 'title': 'Menu1-1-1', + 'name': 'Menu111Demo' + }] + }, { + 'path': '/level/menu1/menu1-2', + 'title': 'Menu1-2', + 'name': 'Menu12Demo' + }] +}, { + 'path': '/level/menu1/menu1-1', + 'title': 'Menu1-1', + 'name': 'Menu11Demo', + 'children': [{ + 'path': '/level/menu1/menu1-1/menu1-1-1', + 'title': 'Menu1-1-1', + 'name': 'Menu111Demo' + }] +}, { + 'path': '/level/menu1/menu1-1/menu1-1-1', + 'title': 'Menu1-1-1', + 'name': 'Menu111Demo' +}, { + 'path': '/level/menu1/menu1-2', + 'title': 'Menu1-2', + 'name': 'Menu12Demo' +}, { + 'path': '/level/menu2', + 'title': 'Menu2', + 'name': 'Menu2Demo' +}, { + 'path': '/example-demo', + 'title': '综合实例', + 'name': 'ExampleDemo', + 'children': [{ + 'path': '/example-demo/example-dialog', + 'title': '列表综合实例-弹窗', + 'name': 'ExampleDialog' + }, { + 'path': '/example-demo/example-page', + 'title': '列表综合实例-页面', + 'name': 'ExamplePage' + }] +}, { + 'path': '/example-demo/example-dialog', + 'title': '列表综合实例-弹窗', + 'name': 'ExampleDialog' +}, { + 'path': '/example-demo/example-page', + 'title': '列表综合实例-页面', + 'name': 'ExamplePage' +}, { + 'path': '/role-demo', + 'title': '权限管理', + 'name': 'RoleDemo', + 'children': [{ + 'path': '/role-demo/user', + 'title': '用户管理', + 'name': 'User' + }, { + 'path': '/role-demo/role', + 'title': '角色管理', + 'name': 'Role' + }] +}, { + 'path': '/role-demo/user', + 'title': '用户管理', + 'name': 'User' +}, { + 'path': '/role-demo/role', + 'title': '角色管理', + 'name': 'Role' +}] + +export const checkedkeys = ['/components-demo', '/components-demo/echarts', '/components-demo/preview', + '/components-demo/button', '/components-demo/message', '/components-demo/count-to', '/components-demo/search', + '/components-demo/editor', '/components-demo/markdown', '/components-demo/dialog', '/components-demo/more', + '/components-demo/detail', '/table-demo', '/table-demo/basic-table', '/table-demo/page-table', + '/table-demo/stripe-table', '/table-demo/border-table', '/table-demo/state-table', '/table-demo/fixed-header', + '/table-demo/fixed-column', '/table-demo/fixed-column-header', '/table-demo/fluid-height', + '/table-demo/multi-header', '/table-demo/single-choice', '/table-demo/multiple-choice', '/table-demo/sort-table', + '/table-demo/screen-table', '/table-demo/expand-row', '/table-demo/tree-and-load', '/table-demo/custom-header', + '/table-demo/total-table', '/table-demo/merge-table', '/table-demo/custom-index', '/directives-demo', + '/directives-demo/clipboard', '/hooks-demo', '/hooks-demo/watermark', '/hooks-demo/useScrollTo', '/icon/index', + '/level', '/level/menu1', '/level/menu1/menu1-1', '/level/menu1/menu1-1/menu1-1-1', '/level/menu1/menu1-2', + '/level/menu2', '/example-demo', '/example-demo/example-dialog', '/example-demo/example-page', '/role-demo', + '/role-demo/user', '/role-demo/role' +] diff --git a/mock/role/index.ts b/mock/role/index.ts new file mode 100644 index 0000000..96078f2 --- /dev/null +++ b/mock/role/index.ts @@ -0,0 +1,97 @@ +import wsCache from '@/cache' +import { Role } from './types' +import { checkedNodes, checkedkeys } from './admin-role' +import { checkedRoleNodes } from './test-role' + +let List: Role[] = wsCache.get('roleList') || [ + { + roleName: 'admin', + id: '1', + checkedNodes: checkedNodes, + checkedkeys: checkedkeys + }, + { + roleName: 'test', + id: '2', + checkedNodes: checkedRoleNodes, + checkedkeys: [] + } +] + +export default [ + // 列表接口 + { + url: 'http://mockjs.test.cn/role/list', + type: 'get', + response: (config: any) => { + const { + roleName, + pageIndex, + pageSize + } = config.query + + const mockList = List.filter(item => { + if (roleName && item.roleName.indexOf(roleName) < 0) return false + return true + }) + const pageList = mockList.filter((item, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)) + + return { + code: '0000', + data: { + total: mockList.length, + list: pageList + } + } + } + }, + + // 详情接口 + { + url: 'http://mockjs.test.cn/role/detail', + type: 'get', + response: (config: any) => { + const { + id + } = config.query + for (const role of List) { + if (role.id === id) { + return { + code: '0000', + data: role + } + } + } + } + }, + + // 保存接口 + { + url: 'http://mockjs.test.cn/role/save', + type: 'post', + response: (config: any) => { + const data = config.body + if (!data.id) { + List = [data].concat(List) + return { + code: '0000', + data: 'success' + } + } else { + List.map(item => { + if (item.id === data.id) { + for (const key in item) { + item[key] = data[key] + } + } + }) + // 存在缓存中,避免刷新没有掉 + wsCache.set('roleList', List) + return { + code: '0000', + data: 'success' + } + } + } + } +] diff --git a/mock/role/test-role.ts b/mock/role/test-role.ts new file mode 100644 index 0000000..7a7e34f --- /dev/null +++ b/mock/role/test-role.ts @@ -0,0 +1,489 @@ +export const checkedRoleNodes = [{ + path: '/components-demo', + component: '#', + redirect: '/components-demo/echarts', + name: 'ComponentsDemo', + meta: { + title: '功能组件', + icon: 'component', + alwaysShow: true + }, + children: [{ + path: 'echarts', + component: 'pages/index/views/components-demo/echarts/index.vue', + name: 'EchartsDemo', + meta: { + title: '图表' + } + }, + { + path: 'preview', + component: 'pages/index/views/components-demo/preview/index.vue', + name: 'PreviewDemo', + meta: { + title: '图片预览' + } + }, + { + path: 'button', + component: 'pages/index/views/components-demo/button/index.vue', + name: 'ButtonDemo', + meta: { + title: '按钮' + } + }, + { + path: 'message', + component: 'pages/index/views/components-demo/message/index.vue', + name: 'MessageDemo', + meta: { + title: '消息提示' + } + }, + { + path: 'count-to', + component: 'pages/index/views/components-demo/count-to/index.vue', + name: 'CountToDemo', + meta: { + title: '数字动画' + } + }, + { + path: 'search', + component: 'pages/index/views/components-demo/search/index.vue', + name: 'SearchDemo', + meta: { + title: '查询' + } + }, + { + path: 'editor', + component: 'pages/index/views/components-demo/editor/index.vue', + name: 'EditorDemo', + meta: { + title: '富文本编辑器' + } + }, + { + path: 'markdown', + component: 'pages/index/views/components-demo/markdown/index.vue', + name: 'MarkdownDemo', + meta: { + title: 'markdown编辑器' + } + }, + { + path: 'dialog', + component: 'pages/index/views/components-demo/dialog/index.vue', + name: 'DialogDemo', + meta: { + title: '弹窗' + } + }, + { + path: 'more', + component: 'pages/index/views/components-demo/more/index.vue', + name: 'MoreDemo', + meta: { + title: '显示更多' + } + }, + { + path: 'detail', + component: 'pages/index/views/components-demo/detail/index.vue', + name: 'DetailDemo', + meta: { + title: '详情组件' + } + } + ] + }, + { + path: '/table-demo', + component: '#', + redirect: '/table-demo/basic-usage', + name: 'TableDemo', + meta: { + title: '表格', + icon: 'table', + alwaysShow: true + }, + children: [ + { + path: 'basic-table', + component: 'pages/index/views/table-demo/basic-table/index.vue', + name: 'BasicTable', + meta: { + title: '基础表格' + } + }, + { + path: 'page-table', + component: 'pages/index/views/table-demo/page-table/index.vue', + name: 'PageTable', + meta: { + title: '分页表格' + } + }, + { + path: 'stripe-table', + component: 'pages/index/views/table-demo/stripe-table/index.vue', + name: 'StripeTable', + meta: { + title: '带斑马纹表格' + } + }, + { + path: 'border-table', + component: 'pages/index/views/table-demo/border-table/index.vue', + name: 'BorderTable', + meta: { + title: '带边框表格' + } + }, + { + path: 'state-table', + component: 'pages/index/views/table-demo/state-table/index.vue', + name: 'StateTable', + meta: { + title: '带状态表格' + } + }, + { + path: 'fixed-header', + component: 'pages/index/views/table-demo/fixed-header/index.vue', + name: 'FixedHeader', + meta: { + title: '固定表头' + } + }, + { + path: 'fixed-column', + component: 'pages/index/views/table-demo/fixed-column/index.vue', + name: 'FixedColumn', + meta: { + title: '固定列' + } + }, + { + path: 'fixed-column-header', + component: 'pages/index/views/table-demo/fixed-column-header/index.vue', + name: 'FixedColumnHeader', + meta: { + title: '固定列和表头' + } + }, + { + path: 'fluid-height', + component: 'pages/index/views/table-demo/fluid-height/index.vue', + name: 'FluidHeight', + meta: { + title: '流体高度' + } + }, + { + path: 'multi-header', + component: 'pages/index/views/table-demo/multi-header/index.vue', + name: 'MultiHeader', + meta: { + title: '多级表头' + } + }, + { + path: 'single-choice', + component: 'pages/index/views/table-demo/single-choice/index.vue', + name: 'SingleChoice', + meta: { + title: '单选' + } + }, + { + path: 'multiple-choice', + component: 'pages/index/views/table-demo/multiple-choice/index.vue', + name: 'MultipleChoice', + meta: { + title: '多选' + } + }, + { + path: 'sort-table', + component: 'pages/index/views/table-demo/sort-table/index.vue', + name: 'SortTable', + meta: { + title: '排序' + } + }, + { + path: 'screen-table', + component: 'pages/index/views/table-demo/screen-table/index.vue', + name: 'ScreenTable', + meta: { + title: '筛选' + } + }, + { + path: 'expand-row', + component: 'pages/index/views/table-demo/expand-row/index.vue', + name: 'ExpandRow', + meta: { + title: '展开行' + } + }, + { + path: 'tree-and-load', + component: 'pages/index/views/table-demo/tree-and-load/index.vue', + name: 'TreeAndLoad', + meta: { + title: '树形数据与懒加载' + } + }, + { + path: 'custom-header', + component: 'pages/index/views/table-demo/custom-header/index.vue', + name: 'CustomHeader', + meta: { + title: '自定义表头' + } + }, + { + path: 'total-table', + component: 'pages/index/views/table-demo/total-table/index.vue', + name: 'TotalTable', + meta: { + title: '表尾合计行' + } + }, + { + path: 'merge-table', + component: 'pages/index/views/table-demo/merge-table/index.vue', + name: 'MergeTable', + meta: { + title: '合并行或列' + } + }, + { + path: 'custom-index', + component: 'pages/index/views/table-demo/custom-index/index.vue', + name: 'CustomIndex', + meta: { + title: '自定义索引' + } + } + ] + }, + { + path: '/directives-demo', + component: '#', + redirect: '/directives-demo/clipboard', + name: 'DirectivesDemo', + meta: { + title: '自定义指令', + icon: 'clipboard', + alwaysShow: true + }, + children: [{ + path: 'clipboard', + component: 'pages/index/views/directives-demo/clipboard/index.vue', + name: 'ClipboardDemo', + meta: { + title: 'Clipboard' + } + }] + }, + { + path: '/hooks-demo', + component: '#', + redirect: '/hooks-demo/watermark', + name: 'HooksDemo', + meta: { + title: 'Hooks', + icon: 'international', + alwaysShow: true + }, + children: [{ + path: 'watermark', + component: 'pages/index/views/hooks-demo/useWatermark/index.vue', + name: 'UseWatermarkDemo', + meta: { + title: 'UseWaterMark' + } + }, + { + path: 'useScrollTo', + component: 'pages/index/views/hooks-demo/useScrollTo/index.vue', + name: 'UseScrollToDemo', + meta: { + title: 'UseScrollTo' + } + } + ] + }, + { + path: '/icon', + component: '#', + name: 'IconsDemo', + meta: { + title: '图标', + icon: 'icon' + }, + children: [{ + path: 'index', + component: 'pages/index/views/icons/index.vue', + name: 'Icons', + meta: { + title: '图标', + icon: 'icon' + } + }] + }, + { + path: '/level', + component: '#', + redirect: '/level/menu1/menu1-1/menu1-1-1', + name: 'Level', + meta: { + title: '多级菜单缓存', + icon: 'nested' + }, + children: [{ + path: 'menu1', + name: 'Menu1Demo', + component: '##Menu1Demo', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'Menu1' + }, + children: [{ + path: 'menu1-1', + name: 'Menu11Demo', + component: '##Menu11Demo', + redirect: '/level/menu1/menu1-1/menu1-1-1', + meta: { + title: 'Menu1-1', + alwaysShow: true + }, + children: [{ + path: 'menu1-1-1', + name: 'Menu111Demo', + component: 'pages/index/views/level/Menu111.vue', + meta: { + title: 'Menu1-1-1' + } + }] + }, + { + path: 'menu1-2', + name: 'Menu12Demo', + component: 'pages/index/views/level/Menu12.vue', + meta: { + title: 'Menu1-2' + } + } + ] + }, + { + path: 'menu2', + name: 'Menu2Demo', + component: 'pages/index/views/level/Menu2.vue', + meta: { + title: 'Menu2' + } + } + ] + }, + { + path: '/example-demo', + component: '#', + name: 'ExampleDemo', + redirect: '/example-demo/example-dialog', + meta: { + alwaysShow: true, + icon: 'example', + title: '综合实例' + }, + children: [{ + path: 'example-dialog', + component: 'pages/index/views/example-demo/example-dialog/index.vue', + name: 'ExampleDialog', + meta: { + title: '列表综合实例-弹窗' + } + }, + { + path: 'example-page', + component: 'pages/index/views/example-demo/example-page/index.vue', + name: 'ExamplePage', + meta: { + title: '列表综合实例-页面' + } + }, + { + path: 'example-add', + component: 'pages/index/views/example-demo/example-page/example-add.vue', + name: 'ExampleAdd', + meta: { + title: '列表综合实例-新增', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example-demo/example-page' + } + }, + { + path: 'example-edit', + component: 'pages/index/views/example-demo/example-page/example-edit.vue', + name: 'ExampleEdit', + meta: { + title: '列表综合实例-编辑', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example-demo/example-page' + } + }, + { + path: 'example-detail', + component: 'pages/index/views/example-demo/example-page/example-detail.vue', + name: 'ExampleDetail', + meta: { + title: '列表综合实例-详情', + noTagsView: true, + noCache: true, + hidden: true, + showMainRoute: true, + activeMenu: '/example-demo/example-page' + } + } + ] + }, + { + path: '/role-demo', + component: '#', + redirect: '/role-demo/user', + name: 'RoleDemo', + meta: { + title: '权限管理', + icon: 'user', + alwaysShow: true + }, + children: [{ + path: 'user', + component: 'pages/index/views/role-demo/user/index.vue', + name: 'User', + meta: { + title: '用户管理' + } + }, + { + path: 'role', + component: 'pages/index/views/role-demo/role/index.vue', + name: 'Role', + meta: { + title: '角色管理' + } + } + ] + } +] diff --git a/mock/role/types.ts b/mock/role/types.ts new file mode 100644 index 0000000..29bc772 --- /dev/null +++ b/mock/role/types.ts @@ -0,0 +1,6 @@ +export interface Role { + roleName: String + id: String + checkedNodes: any[] + checkedkeys: any[] +} diff --git a/mock/user/index.ts b/mock/user/index.ts new file mode 100644 index 0000000..6c9632c --- /dev/null +++ b/mock/user/index.ts @@ -0,0 +1,70 @@ +import { User } from './types' + +const List: User[] = [ + { + userName: 'admin', + password: 'admin', + role: 'admin', + roleId: '1' + }, + { + userName: 'test', + password: 'test', + role: 'test', + roleId: '2' + } +] + +export default [ + // 列表接口 + { + url: 'http://mockjs.test.cn/user/list', + type: 'get', + response: (config: any) => { + const { + userName, + pageIndex, + pageSize + } = config.query + + const mockList = List.filter(item => { + if (userName && item.userName.indexOf(userName) < 0) return false + return true + }) + const pageList = mockList.filter((item, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)) + + return { + code: '0000', + data: { + total: mockList.length, + list: pageList + } + } + } + }, + + // 登录接口 + { + url: 'http://mockjs.test.cn/user/login', + type: 'post', + response: (config: any) => { + const data = config.body + let hasUser = false + for (const user of List) { + if (user.userName === data.userName && user.password === data.passWord) { + hasUser = true + return { + code: '0000', + data: user + } + } + } + if (!hasUser) { + return { + code: '500', + message: '账号或密码错误' + } + } + } + } +] diff --git a/mock/user/types.ts b/mock/user/types.ts new file mode 100644 index 0000000..78b7d20 --- /dev/null +++ b/mock/user/types.ts @@ -0,0 +1,6 @@ +export interface User { + userName: String + password: String + role: String + roleId: String +} diff --git a/package.json b/package.json index fe84d13..e093a20 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "clipboard": "^2.0.6", "core-js": "^3.6.5", "echarts": "^4.9.0", - "element-plus": "1.0.1-beta.14", + "element-plus": "1.0.1-beta.26", "highlight.js": "^10.4.0", "lodash-es": "^4.17.15", "mitt": "^2.1.0", @@ -93,10 +93,5 @@ "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } - }, - "lint-staged": { - "*.{js,vue}": [ - "vue-cli-service lint" - ] } } diff --git a/src/assets/icons/svgo.yml b/src/assets/icons/svgo.yml deleted file mode 100644 index d11906a..0000000 --- a/src/assets/icons/svgo.yml +++ /dev/null @@ -1,22 +0,0 @@ -# replace default config - -# multipass: true -# full: true - -plugins: - - # - name - # - # or: - # - name: false - # - name: true - # - # or: - # - name: - # param1: 1 - # param2: 2 - -- removeAttrs: - attrs: - - 'fill' - - 'fill-rule' diff --git a/src/components/Detail/index.vue b/src/components/Detail/index.vue index 6b16e54..5caad2d 100644 --- a/src/components/Detail/index.vue +++ b/src/components/Detail/index.vue @@ -33,12 +33,14 @@ :class="{'detail__content--flex': !vertical}" >
- - + + {{ item.label }} +
- - + + {{ data[item.field] }} +
diff --git a/src/components/Dialog/index.vue b/src/components/Dialog/index.vue index 35bee5d..4d55d45 100644 --- a/src/components/Dialog/index.vue +++ b/src/components/Dialog/index.vue @@ -1,16 +1,33 @@ diff --git a/src/components/Table/index.vue b/src/components/Table/index.vue index 0b607a0..b55287e 100644 --- a/src/components/Table/index.vue +++ b/src/components/Table/index.vue @@ -53,10 +53,6 @@ :index="scope.$index" /> - - diff --git a/src/components/UserInfo/index.vue b/src/components/UserInfo/index.vue index 65e4d2f..ab8feaf 100644 --- a/src/components/UserInfo/index.vue +++ b/src/components/UserInfo/index.vue @@ -25,12 +25,14 @@ import { resetRouter } from '_p/index/router' import wsCache from '@/cache' import { useRouter } from 'vue-router' import { tagsViewStore } from '_p/index/store/modules/tagsView' +import { appStore } from '_p/index/store/modules/app' export default defineComponent({ name: 'UserInfo', setup() { const { replace, push } = useRouter() async function loginOut(): Promise { - wsCache.clear() + // wsCache.clear() + wsCache.delete(appStore.userInfo) await resetRouter() // 重置静态路由表 await tagsViewStore.delAllViews() // 删除所有的tags标签页 replace('/login') diff --git a/src/pages/index/api/index.ts b/src/pages/index/api/index.ts index f515480..e7ecd45 100644 --- a/src/pages/index/api/index.ts +++ b/src/pages/index/api/index.ts @@ -1,14 +1,14 @@ -import { EmptyObj } from '@/types/glob' +// import { EmptyObj } from '@/types/glob' -const modulesFiles: any = require.context('./modules', true, /\.ts$/) +// const modulesFiles: any = require.context('./modules', true, /\.ts$/) -const modules: EmptyObj = modulesFiles.keys().reduce((modules: EmptyObj, modulePath: string): Object => { - const moduleName: string = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') - const value: EmptyObj = modulesFiles(modulePath) - modules[moduleName] = value.default - return modules -}, {}) +// const modules: EmptyObj = modulesFiles.keys().reduce((modules: EmptyObj, modulePath: string): Object => { +// const moduleName: string = modulePath.replace(/^\.\/(.*)\.\w+$/, '$1') +// const value: EmptyObj = modulesFiles(modulePath) +// modules[moduleName] = value.default +// return modules +// }, {}) -export default { - ...modules -} +// export default { +// ...modules +// } diff --git a/src/pages/index/router/index.ts b/src/pages/index/router/index.ts index 221957f..1b5e78d 100644 --- a/src/pages/index/router/index.ts +++ b/src/pages/index/router/index.ts @@ -26,6 +26,7 @@ const Layout = () => import('../layout/index.vue') activeMenu: '/dashboard' 显示高亮的路由路径 followAuth: '/dashboard' 跟随哪个路由进行权限过滤 showMainRoute: true 设置为true即使hidden为true,也依然可以进行路由跳转(默认 false) + followRoute: '/dashboard' 为路由设置跟随其他路由的权限 } **/ @@ -573,6 +574,35 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [ } } ] + }, + { + path: '/role-demo', + component: Layout, + redirect: '/role-demo/user', + name: 'RoleDemo', + meta: { + title: '权限管理', + icon: 'user', + alwaysShow: true + }, + children: [ + { + path: 'user', + component: () => import('_p/index/views/role-demo/user/index.vue'), + name: 'User', + meta: { + title: '用户管理' + } + }, + { + path: 'role', + component: () => import('_p/index/views/role-demo/role/index.vue'), + name: 'Role', + meta: { + title: '角色管理' + } + } + ] } ] diff --git a/src/pages/index/router/types.d.ts b/src/pages/index/router/types.d.ts index 43586ec..ec67fb6 100644 --- a/src/pages/index/router/types.d.ts +++ b/src/pages/index/router/types.d.ts @@ -12,6 +12,7 @@ export interface RouteMeta { noTagsView?: boolean followAuth?: string showMainRoute?: boolean + followRoute?: string } export interface AppRouteRecordRaw extends Omit { diff --git a/src/pages/index/store/modules/permission.ts b/src/pages/index/store/modules/permission.ts index d3c7a95..97e8ddf 100644 --- a/src/pages/index/store/modules/permission.ts +++ b/src/pages/index/store/modules/permission.ts @@ -4,6 +4,15 @@ import { deepClone } from '@/utils' import store from '../index' import { VuexModule, getModule, Module, Mutation, Action } from 'vuex-module-decorators' import { AppRouteRecordRaw } from '_p/index/router/types' +import wsCache from '@/cache' +import { isExternal } from '@/utils/validate' +import path from 'path' +import { getParentLayout } from '_p/index/router/utils' + +import { appStore } from '_p/index/store/modules/app' + +/* Layout */ +const Layout = () => import('_p/index/layout/index.vue') export interface PermissionState { routers: AppRouteRecordRaw[] @@ -19,8 +28,7 @@ class Permission extends VuexModule implements PermissionState { @Mutation private SET_ROUTERS(routers: AppRouteRecordRaw[]): void { - // const flatRoutes: AppRouteRecordRaw[] = getFlatRoutes(deepClone(asyncRouterMap, ['component'])) - // const flatRoutes: AppRouteRecordRaw[] = deepClone(asyncRouterMap, ['component']) + // 动态路由,404一定要放到最后面 this.addRouters = routers.concat([{ path: '/:path(.*)*', redirect: '/404', @@ -30,6 +38,7 @@ class Permission extends VuexModule implements PermissionState { breadcrumb: false } }]) + // 渲染菜单的所有路由 this.routers = deepClone(constantRouterMap, ['component']).concat(routers) } @Mutation @@ -40,7 +49,16 @@ class Permission extends VuexModule implements PermissionState { @Action public GenerateRoutes(): Promise { return new Promise(resolve => { - const routerMap: AppRouteRecordRaw[] = generateRoutes(deepClone(asyncRouterMap, ['component'])) + // 路由权限控制 + let routerMap: AppRouteRecordRaw[] = [] + if (wsCache.get(appStore.userInfo).roleName === 'admin') { + // 模拟前端控制权限 + routerMap = generateRoutes(deepClone(asyncRouterMap, ['component'])) + } else { + // 模拟后端控制权限 + routerMap = getFilterRoutes(wsCache.get(appStore.userInfo).checkedNodes) + } + // const routerMap: AppRouteRecordRaw[] = generateRoutes(deepClone(asyncRouterMap, ['component'])) this.SET_ROUTERS(routerMap) resolve() }) @@ -52,7 +70,7 @@ class Permission extends VuexModule implements PermissionState { } // 路由过滤,主要用于权限控制 -function generateRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { +function generateRoutes(routes: AppRouteRecordRaw[], basePath = '/'): AppRouteRecordRaw[] { const res: AppRouteRecordRaw[] = [] for (const route of routes) { @@ -61,12 +79,37 @@ function generateRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { continue } + let onlyOneChild = null + + if (route.children && route.children.length === 1 && !route.meta.alwaysShow) { + onlyOneChild = isExternal(route.children[0].path) + ? route.children[0].path + : path.resolve(path.resolve(basePath, route.path), route.children[0].path) + } + let data: any = null - data = Object.assign({}, route) + + // 如不需要路由权限,可注释以下逻辑 + // 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。 + const list = wsCache.get(appStore.userInfo).checkedNodes + // 开发者可以根据实际情况进行扩展 + for (const item of list) { + // 通过路径去匹配 + if (isExternal(item.path) && (onlyOneChild === item.path || route.path === item.path)) { + data = Object.assign({}, route) + } else { + const routePath = path.resolve(basePath, onlyOneChild || route.path) + if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) { + data = Object.assign({}, route) + } + } + } + // 如不需要路由权限,解注释下面一行 + // data = Object.assign({}, route) // recursive child routes if (route.children && data) { - data.children = generateRoutes(route.children) + data.children = generateRoutes(route.children, path.resolve(basePath, data.path)) } if (data) { res.push(data as AppRouteRecordRaw) @@ -75,4 +118,34 @@ function generateRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] { return res } +// 模拟后端过滤路由 +function getFilterRoutes(routes: any[]): any[] { + const res = [] + + for (const route of routes) { + const data: any = { + path: route.path, + name: route.name, + redirect: route.redirect + } + data.meta = Object.assign({}, route.meta || {}, { title: route.title }) + if (route.component) { + // 动态加载路由文件,可根据实际情况进行自定义逻辑 + data.component = route.component === '#' + ? Layout + : (route.component.includes('##') + ? getParentLayout(route.component.split('##')[1]) + : () => new Promise((resolve) => { + resolve(import(`${route.component}`)) + })) + } + // recursive child routes + if (route.children) { + data.children = getFilterRoutes(route.children) + } + res.push(data) + } + return res +} + export const permissionStore = getModule(Permission) diff --git a/src/pages/index/views/components-demo/detail/index.vue b/src/pages/index/views/components-demo/detail/index.vue index 591b1e8..68f45d2 100644 --- a/src/pages/index/views/components-demo/detail/index.vue +++ b/src/pages/index/views/components-demo/detail/index.vue @@ -62,7 +62,7 @@ -