Преглед изворни кода

feat: 综合实例、权限管理重构

陈凯龙 пре 3 година
родитељ
комит
a4bd2068a5
29 измењених фајлова са 1715 додато и 242 уклоњено
  1. 1 0
      components.d.ts
  2. 1 0
      package.json
  3. 2 2
      src/hooks/work/useWork.ts
  4. 37 99
      src/mock/role/admin-role.ts
  5. 2 42
      src/mock/role/test-role.ts
  6. 97 97
      src/router/index.ts
  7. 1 0
      src/router/types.ts
  8. 4 2
      src/store/modules/permission.ts
  9. 17 0
      src/views/example-demo/example-dialog/api.ts
  10. 96 0
      src/views/example-demo/example-dialog/components/Detail.vue
  11. 158 0
      src/views/example-demo/example-dialog/components/InfoWrite.vue
  12. 18 0
      src/views/example-demo/example-dialog/components/types.ts
  13. 135 0
      src/views/example-demo/example-dialog/index.vue
  14. 17 0
      src/views/example-demo/example-page/api.ts
  15. 98 0
      src/views/example-demo/example-page/components/Detail.vue
  16. 160 0
      src/views/example-demo/example-page/components/InfoWrite.vue
  17. 18 0
      src/views/example-demo/example-page/components/types.ts
  18. 11 0
      src/views/example-demo/example-page/example-add.vue
  19. 10 0
      src/views/example-demo/example-page/example-detail.vue
  20. 17 0
      src/views/example-demo/example-page/example-edit.vue
  21. 139 0
      src/views/example-demo/example-page/index.vue
  22. 13 0
      src/views/role-demo/role/api.ts
  23. 201 0
      src/views/role-demo/role/components/InfoWrite.vue
  24. 225 0
      src/views/role-demo/role/components/InfoWrite2.vue
  25. 124 0
      src/views/role-demo/role/index.vue
  26. 5 0
      src/views/role-demo/user/api.ts
  27. 90 0
      src/views/role-demo/user/index.vue
  28. 13 0
      src/vue-bus/index.ts
  29. 5 0
      yarn.lock

+ 1 - 0
components.d.ts

@@ -51,6 +51,7 @@ declare module 'vue' {
     ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
     ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
     ElTooltip: typeof import('element-plus/es')['ElTooltip']
+    ElTree: typeof import('element-plus/es')['ElTree']
     Loading: typeof import('element-plus/es')['ElLoadingDirective']
     ParentView: typeof import('./src/components/ParentView/index.vue')['default']
     Preview: typeof import('./src/components/Preview/index.vue')['default']

+ 1 - 0
package.json

@@ -33,6 +33,7 @@
     "highlight.js": "^11.3.1",
     "intro.js": "^4.2.2",
     "lodash-es": "^4.17.21",
+    "mitt": "^3.0.0",
     "mockjs": "^1.1.0",
     "nprogress": "^0.2.0",
     "path-browserify": "^1.0.1",

+ 2 - 2
src/hooks/work/useWork.ts

@@ -157,7 +157,7 @@ export function useWork(option?: InitOption) {
   }
 
   // 删除多选
-  function dels(item: IObj, single: boolean, callback: Fn) {
+  function dels(item: IObj, single?: boolean, callback?: Fn) {
     delData(
       async () => {
         let ids: Nullable<IObj[]> = null
@@ -190,7 +190,7 @@ export function useWork(option?: InitOption) {
   }
 
   // 打开弹窗
-  function open(row: IObj, component: string, options: DilogOption) {
+  function open(row: Nullable<IObj>, component: string, options?: DilogOption) {
     comName.value = component
     dialogTitle.value =
       (options && options.title) || (!row ? '新增' : component === 'Detail' ? '详情' : '编辑')

+ 37 - 99
src/mock/role/admin-role.ts

@@ -49,19 +49,14 @@ export const checkedNodes = [
         title: '弹窗',
         name: 'DialogDemo'
       },
-      {
-        path: '/components-demo/more',
-        title: '显示更多',
-        name: 'MoreDemo'
-      },
       {
         path: '/components-demo/detail',
-        title: '详情组件',
+        title: '详情',
         name: 'DetailDemo'
       },
       {
         path: '/components-demo/qrcode',
-        title: '二维码组件',
+        title: '二维码',
         name: 'QrcodeDemo'
       },
       {
@@ -78,31 +73,6 @@ export const checkedNodes = [
         path: '/components-demo/watermark',
         name: 'WatermarkDemo',
         title: '水印'
-      },
-      {
-        path: '/components-demo/timer',
-        name: 'TimerDemo',
-        title: '计时器'
-      },
-      {
-        path: '/components-demo/marquee',
-        name: 'MarqueeDemo',
-        title: '无缝滚动'
-      },
-      {
-        path: '/components-demo/water-fall',
-        name: 'WaterFallDemo',
-        title: '瀑布流'
-      },
-      {
-        path: '/components-demo/tree-select',
-        name: 'TreeSelectDemo',
-        title: '瀑布流'
-      },
-      {
-        path: '/components-demo/ellipsis',
-        name: 'Ellipsis',
-        title: '省略号'
       }
     ]
   },
@@ -151,19 +121,14 @@ export const checkedNodes = [
     title: '弹窗',
     name: 'DialogDemo'
   },
-  {
-    path: '/components-demo/more',
-    title: '显示更多',
-    name: 'MoreDemo'
-  },
   {
     path: '/components-demo/detail',
-    title: '详情组件',
+    title: '详情',
     name: 'DetailDemo'
   },
   {
     path: '/components-demo/qrcode',
-    title: '二维码组件',
+    title: '二维码',
     name: 'QrcodeDemo'
   },
   {
@@ -181,31 +146,6 @@ export const checkedNodes = [
     name: 'WatermarkDemo',
     title: '水印'
   },
-  {
-    path: '/components-demo/timer',
-    name: 'TimerDemo',
-    title: '计时器'
-  },
-  {
-    path: '/components-demo/marquee',
-    name: 'MarqueeDemo',
-    title: '无缝滚动'
-  },
-  {
-    path: '/components-demo/water-fall',
-    name: 'WaterFallDemo',
-    title: '瀑布流'
-  },
-  {
-    path: '/components-demo/tree-select',
-    name: 'TreeSelectDemo',
-    title: '瀑布流'
-  },
-  {
-    path: '/components-demo/ellipsis',
-    name: 'Ellipsis',
-    title: '瀑布流'
-  },
   {
     path: '/table-demo',
     title: '表格',
@@ -430,33 +370,6 @@ export const checkedNodes = [
     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: '图标',
@@ -563,6 +476,21 @@ export const checkedNodes = [
         path: '/example-demo/example-page',
         title: '列表综合实例-页面',
         name: 'ExamplePage'
+      },
+      {
+        path: '/example-demo/example-add',
+        title: '列表综合实例-新增',
+        name: 'ExampleAdd'
+      },
+      {
+        path: '/example-demo/example-edit',
+        title: '列表综合实例-编辑',
+        name: 'ExampleEdit'
+      },
+      {
+        path: '/example-demo/example-detail',
+        title: '列表综合实例-详情',
+        name: 'ExampleDetail'
       }
     ]
   },
@@ -576,6 +504,21 @@ export const checkedNodes = [
     title: '列表综合实例-页面',
     name: 'ExamplePage'
   },
+  {
+    path: '/example-demo/example-add',
+    title: '列表综合实例-新增',
+    name: 'ExampleAdd'
+  },
+  {
+    path: '/example-demo/example-edit',
+    title: '列表综合实例-编辑',
+    name: 'ExampleEdit'
+  },
+  {
+    path: '/example-demo/example-detail',
+    title: '列表综合实例-详情',
+    name: 'ExampleDetail'
+  },
   {
     path: '/role-demo',
     title: '权限管理',
@@ -621,11 +564,6 @@ export const checkedkeys = [
   '/components-demo/qrcode',
   '/components-demo/avatars',
   '/components-demo/watermark',
-  '/components-demo/timer',
-  '/components-demo/marquee',
-  '/components-demo/water-fall',
-  '/components-demo/tree-select',
-  '/components-demo/ellipsis',
   '/table-demo',
   '/table-demo/basic-table',
   '/table-demo/page-table',
@@ -649,9 +587,6 @@ export const checkedkeys = [
   '/table-demo/custom-index',
   '/directives-demo',
   '/directives-demo/clipboard',
-  '/hooks-demo',
-  '/hooks-demo/watermark',
-  '/hooks-demo/useScrollTo',
   '/icon/index',
   '/level',
   '/level/menu1',
@@ -662,6 +597,9 @@ export const checkedkeys = [
   '/example-demo',
   '/example-demo/example-dialog',
   '/example-demo/example-page',
+  '/example-demo/example-add',
+  '/example-demo/example-edit',
+  '/example-demo/example-detail',
   '/role-demo',
   '/role-demo/user',
   '/role-demo/role'

+ 2 - 42
src/mock/role/test-role.ts

@@ -71,7 +71,7 @@ export const checkedRoleNodes = [
         component: 'views/components-demo/detail/index',
         name: 'DetailDemo',
         meta: {
-          title: '详情组件'
+          title: '详情'
         }
       },
       {
@@ -79,7 +79,7 @@ export const checkedRoleNodes = [
         component: 'views/components-demo/qrcode/index',
         name: 'QrcodeDemo',
         meta: {
-          title: '二维码组件'
+          title: '二维码'
         }
       },
       {
@@ -105,46 +105,6 @@ export const checkedRoleNodes = [
         meta: {
           title: '水印'
         }
-      },
-      {
-        path: 'timer',
-        component: 'views/components-demo/timer/index',
-        name: 'TimerDemo',
-        meta: {
-          title: '计时器'
-        }
-      },
-      {
-        path: 'marquee',
-        component: 'views/components-demo/marquee/index',
-        name: 'MarqueeDemo',
-        meta: {
-          title: '无缝滚动'
-        }
-      },
-      {
-        path: 'water-fall',
-        component: 'views/components-demo/water-fall/index',
-        name: 'WaterFallDemo',
-        meta: {
-          title: '瀑布流'
-        }
-      },
-      {
-        path: 'tree-select',
-        component: 'views/components-demo/tree-select/index',
-        name: 'TreeSelectDemo',
-        meta: {
-          title: '下拉树'
-        }
-      },
-      {
-        path: 'ellipsis',
-        component: 'views/components-demo/ellipsis/index',
-        name: 'Ellipsis',
-        meta: {
-          title: '省略号'
-        }
       }
     ]
   },

+ 97 - 97
src/router/index.ts

@@ -496,104 +496,104 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
         }
       }
     ]
+  },
+  {
+    path: '/example-demo',
+    component: Layout,
+    name: 'ExampleDemo',
+    redirect: '/example-demo/example-dialog',
+    meta: {
+      alwaysShow: true,
+      icon: 'example',
+      title: '综合实例'
+    },
+    children: [
+      {
+        path: 'example-dialog',
+        component: () => import('_v/example-demo/example-dialog/index.vue'),
+        name: 'ExampleDialog',
+        meta: {
+          title: '列表综合实例-弹窗'
+        }
+      },
+      {
+        path: 'example-page',
+        component: () => import('_v/example-demo/example-page/index.vue'),
+        name: 'ExamplePage',
+        meta: {
+          title: '列表综合实例-页面'
+        }
+      },
+      {
+        path: 'example-add',
+        component: () => import('_v/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: () => import('_v/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: () => import('_v/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: Layout,
+    redirect: '/role-demo/user',
+    name: 'RoleDemo',
+    meta: {
+      title: '权限管理',
+      icon: 'user',
+      alwaysShow: true
+    },
+    children: [
+      {
+        path: 'user',
+        component: () => import('_v/role-demo/user/index.vue'),
+        name: 'User',
+        meta: {
+          title: '用户管理'
+        }
+      },
+      {
+        path: 'role',
+        component: () => import('_v/role-demo/role/index.vue'),
+        name: 'Role',
+        meta: {
+          title: '角色管理'
+        }
+      }
+    ]
   }
-  // {
-  //   path: '/example-demo',
-  //   component: Layout,
-  //   name: 'ExampleDemo',
-  //   redirect: '/example-demo/example-dialog',
-  //   meta: {
-  //     alwaysShow: true,
-  //     icon: 'example',
-  //     title: '综合实例'
-  //   },
-  //   children: [
-  //     {
-  //       path: 'example-dialog',
-  //       component: () => import('_v/example-demo/example-dialog/index.vue'),
-  //       name: 'ExampleDialog',
-  //       meta: {
-  //         title: '列表综合实例-弹窗'
-  //       }
-  //     },
-  //     {
-  //       path: 'example-page',
-  //       component: () => import('_v/example-demo/example-page/index.vue'),
-  //       name: 'ExamplePage',
-  //       meta: {
-  //         title: '列表综合实例-页面'
-  //       }
-  //     },
-  //     {
-  //       path: 'example-add',
-  //       component: () => import('_v/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: () => import('_v/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: () => import('_v/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: Layout,
-  //   redirect: '/role-demo/user',
-  //   name: 'RoleDemo',
-  //   meta: {
-  //     title: '权限管理',
-  //     icon: 'user',
-  //     alwaysShow: true
-  //   },
-  //   children: [
-  //     {
-  //       path: 'user',
-  //       component: () => import('_v/role-demo/user/index.vue'),
-  //       name: 'User',
-  //       meta: {
-  //         title: '用户管理'
-  //       }
-  //     },
-  //     {
-  //       path: 'role',
-  //       component: () => import('_v/role-demo/role/index.vue'),
-  //       name: 'Role',
-  //       meta: {
-  //         title: '角色管理'
-  //       }
-  //     }
-  //   ]
-  // }
 ]
 
 const router = createRouter({

+ 1 - 0
src/router/types.ts

@@ -17,5 +17,6 @@ export interface RouteMeta {
 // @ts-ignore
 export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
   meta: RouteMeta
+  title?: string
   children?: AppRouteRecordRaw[]
 }

+ 4 - 2
src/store/modules/permission.ts

@@ -12,7 +12,8 @@ import { store } from '../index'
 import { useAppStoreWithOut } from '@/store/modules/app'
 const appStore = useAppStoreWithOut()
 
-const modules = import.meta.glob('./src/views/*/*.vue')
+const modules = import.meta.glob('../../views/**/*.vue')
+console.log(modules)
 
 /* Layout */
 const Layout = () => import('@/layout/index.vue')
@@ -100,6 +101,7 @@ function generateRoutesFn(routes: AppRouteRecordRaw[], basePath = '/'): AppRoute
   for (const route of routes) {
     // skip some route
     if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
+      console.log(route)
       continue
     }
 
@@ -162,7 +164,7 @@ function getFilterRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
           ? Layout
           : (route.component as any).includes('##')
           ? getParentLayout((route.component as any).split('##')[1])
-          : modules[`@/${route.component}.vue`]
+          : modules[`../../${route.component}.vue`]
       ) as any
     }
     // recursive child routes

+ 17 - 0
src/views/example-demo/example-dialog/api.ts

@@ -0,0 +1,17 @@
+import fetch from '@/axios-config'
+
+export const getExampleListApi = ({ params }: any) => {
+  return fetch({ url: '/example/list', method: 'get', params })
+}
+
+export const delsExampApi = ({ data }: any) => {
+  return fetch({ url: '/example/delete', method: 'post', data })
+}
+
+export const setExampApi = ({ data }: any) => {
+  return fetch({ url: '/example/save', method: 'post', data })
+}
+
+export const getExampDetApi = ({ params }: any) => {
+  return fetch({ url: '/example/detail', method: 'get', params })
+}

+ 96 - 0
src/views/example-demo/example-dialog/components/Detail.vue

@@ -0,0 +1,96 @@
+<template>
+  <div>
+    <com-detail :data="form" :schema="fromSchema" :collapsed="false" title="文章详情">
+      <template #contentContent="scope">
+        <div v-html="scope.row.content"></div>
+      </template>
+    </com-detail>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="Detail">
+import { PropType, reactive } from 'vue'
+import { getExampDetApi } from '../api'
+import { SchemaConfig } from '_c/ComDetail/types'
+
+const fromSchema: SchemaConfig[] = [
+  {
+    field: 'title',
+    label: '标题',
+    span: 24
+  },
+  {
+    field: 'author',
+    label: '作者'
+  },
+  {
+    field: 'display_time',
+    label: '创建时间'
+  },
+  {
+    field: 'importance',
+    label: '重要性'
+  },
+  {
+    field: 'pageviews',
+    label: '阅读数'
+  },
+  {
+    field: 'content',
+    label: '内容',
+    span: 24
+  }
+]
+
+const props = defineProps({
+  info: {
+    type: Object as PropType<Nullable<IObj>>,
+    default: () => null
+  }
+})
+
+const emit = defineEmits(['close'])
+
+const form = reactive<IObj>({
+  id: '', // id
+  author: '', // 作者
+  title: '', // 标题
+  content: '', // 内容
+  importance: '', // 重要性
+  display_time: '', // 创建时间
+  pageviews: 0 // 阅读数
+})
+
+async function getDet() {
+  if (props.info) {
+    const id = props.info.id
+    try {
+      const res: any = await getExampDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        for (const key in form) {
+          if (key === 'importance') {
+            form[key] = (res.data[key] as number).toString()
+          } else {
+            form[key] = res.data[key]
+          }
+        }
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+function close() {
+  emit('close')
+}
+
+getDet()
+</script>

+ 158 - 0
src/views/example-demo/example-dialog/components/InfoWrite.vue

@@ -0,0 +1,158 @@
+<template>
+  <div>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item prop="title" label="标题">
+            <el-input v-model="form.title" placeholder="请输入标题" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="author" label="作者">
+            <el-input v-model="form.author" placeholder="请输入作者" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="display_time" label="创建时间">
+            <el-date-picker
+              v-model="form.display_time"
+              type="datetime"
+              placeholder="请选择创建时间"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="importance" label="重要性">
+            <el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%">
+              <el-option label="重要" value="3" />
+              <el-option label="良好" value="2" />
+              <el-option label="一般" value="1" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="pageviews" label="阅读数">
+            <el-input-number
+              v-model="form.pageviews"
+              :min="0"
+              :max="99999999"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item prop="content" label="内容">
+            <editor ref="editorRef" :value="form.content" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+      <el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="IfnoWrite">
+import { PropType, reactive, ref } from 'vue'
+import { setExampApi, getExampDetApi } from '../api'
+import Editor from '_c/Editor/index.vue'
+import { Message } from '_c/Message'
+
+const requiredRule: {
+  required: boolean
+  message: string
+} = {
+  required: true,
+  message: '该项为必填项'
+}
+
+const props = defineProps({
+  info: {
+    type: Object as PropType<Nullable<IObj>>,
+    default: () => null
+  }
+})
+
+const emit = defineEmits(['success', 'close'])
+
+const editorRef = ref<Nullable<HTMLElement>>(null)
+const formRef = ref<Nullable<HTMLElement>>(null)
+
+const subLoading = ref<boolean>(false)
+const form = reactive<IObj>({
+  id: '', // id
+  author: '', // 作者
+  title: '', // 标题
+  content: '', // 内容
+  importance: '', // 重要性
+  display_time: '', // 创建时间
+  pageviews: 0 // 阅读数
+})
+const rules = reactive<IObj>({
+  title: [requiredRule],
+  author: [requiredRule],
+  content: [requiredRule],
+  importance: [requiredRule],
+  display_time: [requiredRule],
+  pageviews: [requiredRule]
+})
+
+async function getDet() {
+  if (props.info) {
+    const id = props.info.id
+    try {
+      const res: any = await getExampDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        for (const key in form) {
+          if (key === 'importance') {
+            form[key] = (res.data[key] as number).toString()
+          } else {
+            form[key] = res.data[key]
+          }
+        }
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+// 新增或者编辑
+function setListData() {
+  try {
+    subLoading.value = true
+    form.content = (editorRef.value as any).getHtml()
+    ;(formRef.value as any).validate(async (valid) => {
+      if (valid) {
+        const res = await setExampApi({
+          data: form
+        })
+        if (res) {
+          Message.success(form.id ? '编辑成功' : '新增成功')
+          emit('success', form.id ? 'edit' : 'add')
+        }
+      } else {
+        console.log('error submit!!')
+        return false
+      }
+    })
+  } catch (err) {
+    console.log(err)
+  } finally {
+    subLoading.value = false
+  }
+}
+
+function close() {
+  emit('close')
+}
+
+getDet()
+</script>

+ 18 - 0
src/views/example-demo/example-dialog/components/types.ts

@@ -0,0 +1,18 @@
+export interface InfoWriteParams {
+  title: string
+  id?: string
+  author: string
+  content: string
+  importance: string
+  display_time: string
+  pageviews: number
+}
+
+export interface InfoWriteRules {
+  title?: any[]
+  author?: any[]
+  content?: any[]
+  importance?: any[]
+  display_time?: any[]
+  pageviews?: any[]
+}

+ 135 - 0
src/views/example-demo/example-dialog/index.vue

@@ -0,0 +1,135 @@
+<template>
+  <div>
+    <div class="search__example--wrap">
+      <com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
+    </div>
+
+    <div class="button__example--wrap">
+      <el-button type="primary" icon="el-icon-circle-plus-outline" @click="open(null, 'InfoWrite')">
+        新增
+      </el-button>
+      <el-button type="danger" icon="el-icon-delete" @click="dels">删除</el-button>
+    </div>
+
+    <com-table
+      v-loading="loading"
+      selection
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        currentPage: defaultParams.pageIndex,
+        total: total,
+        onSizeChange: handleSizeChange,
+        onCurrentChange: handleCurrentChange
+      }"
+      @selection-change="handleSelectionChange"
+    >
+      <template #importance="scope">
+        <el-tag
+          :type="
+            scope.row.importance === 3
+              ? 'success'
+              : scope.row.importance === 2
+              ? 'warning'
+              : 'danger'
+          "
+        >
+          {{ scope.row.importance === 3 ? '重要' : scope.row.importance === 2 ? '良好' : '一般' }}
+        </el-tag>
+      </template>
+      <template #action="scope">
+        <el-button type="primary" size="mini" @click="open(scope.row, 'InfoWrite')">编辑</el-button>
+        <el-button type="success" size="mini" @click="open(scope.row, 'Detail')">查看</el-button>
+        <el-button type="danger" size="mini" @click="dels(scope.row)">删除</el-button>
+      </template>
+    </com-table>
+
+    <com-dialog v-model="dialogVisible" :title="dialogTitle">
+      <info-write
+        v-if="comName === 'InfoWrite' && dialogVisible"
+        :info="rowData"
+        @close="toggleVisible"
+        @success="refreshTable"
+      />
+      <detail v-if="comName === 'Detail' && dialogVisible" :info="rowData" @close="toggleVisible" />
+    </com-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="ExampleDialog">
+import { getExampleListApi, delsExampApi } from './api'
+import { useWork } from '@/hooks/work/useWork'
+import InfoWrite from './components/InfoWrite.vue'
+import Detail from './components/Detail.vue'
+const {
+  defaultParams,
+  tableData,
+  loading,
+  total,
+  dialogVisible,
+  dialogTitle,
+  comName,
+  rowData,
+  handleSizeChange,
+  handleCurrentChange,
+  handleSelectionChange,
+  toggleVisible,
+  getList,
+  searchSubmit,
+  resetSubmit,
+  open,
+  refreshTable,
+  dels
+} = useWork({
+  listFun: getExampleListApi,
+  delFun: delsExampApi
+})
+
+const searchData = [
+  {
+    label: '标题',
+    value: '',
+    itemType: 'input',
+    field: 'title',
+    placeholder: '请输入标题',
+    clearable: true
+  }
+]
+
+const columns = [
+  {
+    field: 'title',
+    label: '标题',
+    showOverflowTooltip: true
+  },
+  {
+    field: 'author',
+    label: '作者'
+  },
+  {
+    field: 'display_time',
+    label: '创建时间'
+  },
+  {
+    field: 'importance',
+    label: '重要性',
+    slots: {
+      default: 'importance'
+    }
+  },
+  {
+    field: 'pageviews',
+    label: '阅读数'
+  },
+  {
+    field: 'action',
+    label: '操作',
+    width: '220px',
+    slots: {
+      default: 'action'
+    }
+  }
+]
+
+getList()
+</script>

+ 17 - 0
src/views/example-demo/example-page/api.ts

@@ -0,0 +1,17 @@
+import fetch from '@/axios-config'
+
+export const getExampleListApi = ({ params }: any) => {
+  return fetch({ url: '/example/list2', method: 'get', params })
+}
+
+export const delsExampApi = ({ data }: any) => {
+  return fetch({ url: '/example/delete', method: 'post', data })
+}
+
+export const setExampApi = ({ data }: any) => {
+  return fetch({ url: '/example/save', method: 'post', data })
+}
+
+export const getExampDetApi = ({ params }: any) => {
+  return fetch({ url: '/example/detail', method: 'get', params })
+}

+ 98 - 0
src/views/example-demo/example-page/components/Detail.vue

@@ -0,0 +1,98 @@
+<template>
+  <div>
+    <com-detail :data="form" :schema="fromSchema" :collapsed="false" title="文章详情">
+      <template #contentContent="scope">
+        <div v-html="scope.row.content"></div>
+      </template>
+    </com-detail>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="Detail">
+import { PropType, reactive } from 'vue'
+import { getExampDetApi } from '../api'
+import { useRouter } from 'vue-router'
+const { push } = useRouter()
+
+const props = defineProps({
+  id: {
+    type: String as PropType<string>,
+    default: ''
+  }
+})
+
+const fromSchema = [
+  {
+    field: 'title',
+    label: '标题',
+    span: 24
+  },
+  {
+    field: 'author',
+    label: '作者'
+  },
+  {
+    field: 'display_time',
+    label: '创建时间'
+  },
+  {
+    field: 'importance',
+    label: '重要性'
+  },
+  {
+    field: 'pageviews',
+    label: '阅读数'
+  },
+  {
+    field: 'content',
+    label: '内容',
+    span: 24,
+    slots: {
+      default: 'content'
+    }
+  }
+]
+
+const form = reactive<IObj>({
+  id: '', // id
+  author: '', // 作者
+  title: '', // 标题
+  content: '', // 内容
+  importance: '', // 重要性
+  display_time: '', // 创建时间
+  pageviews: 0 // 阅读数
+})
+
+async function getDet() {
+  if (props.id) {
+    const id = props.id
+    try {
+      const res: any = await getExampDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        for (const key in form) {
+          if (key === 'importance') {
+            form[key] = (res.data[key] as number).toString()
+          } else {
+            form[key] = res.data[key]
+          }
+        }
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+function close() {
+  push('/example-demo/example-page')
+}
+
+getDet()
+</script>

+ 160 - 0
src/views/example-demo/example-page/components/InfoWrite.vue

@@ -0,0 +1,160 @@
+<template>
+  <div>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item prop="title" label="标题">
+            <el-input v-model="form.title" placeholder="请输入标题" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="author" label="作者">
+            <el-input v-model="form.author" placeholder="请输入作者" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="display_time" label="创建时间">
+            <el-date-picker
+              v-model="form.display_time"
+              type="datetime"
+              placeholder="请选择创建时间"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="importance" label="重要性">
+            <el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%">
+              <el-option label="重要" value="3" />
+              <el-option label="良好" value="2" />
+              <el-option label="一般" value="1" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item prop="pageviews" label="阅读数">
+            <el-input-number
+              v-model="form.pageviews"
+              :min="0"
+              :max="99999999"
+              style="width: 100%"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item prop="content" label="内容">
+            <editor ref="editorRef" :value="form.content" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+      <el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="InfoWrite">
+import { PropType, ref, reactive } from 'vue'
+import { setExampApi, getExampDetApi } from '../api'
+import Editor from '_c/Editor/index.vue'
+import { Message } from '_c/Message'
+import { useRouter } from 'vue-router'
+const { push } = useRouter()
+
+const requiredRule: {
+  required: boolean
+  message: string
+} = {
+  required: true,
+  message: '该项为必填项'
+}
+
+const props = defineProps({
+  id: {
+    type: String as PropType<string>,
+    default: ''
+  }
+})
+
+const emit = defineEmits(['success'])
+
+const editorRef = ref<Nullable<HTMLElement>>(null)
+const formRef = ref<Nullable<HTMLElement>>(null)
+
+const subLoading = ref<boolean>(false)
+const form = reactive<IObj>({
+  id: '', // id
+  author: '', // 作者
+  title: '', // 标题
+  content: '', // 内容
+  importance: '', // 重要性
+  display_time: '', // 创建时间
+  pageviews: 0 // 阅读数
+})
+const rules = reactive<IObj>({
+  title: [requiredRule],
+  author: [requiredRule],
+  content: [requiredRule],
+  importance: [requiredRule],
+  display_time: [requiredRule],
+  pageviews: [requiredRule]
+})
+
+async function getDet() {
+  if (props.id) {
+    const id = props.id
+    try {
+      const res: any = await getExampDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        for (const key in form) {
+          if (key === 'importance') {
+            form[key] = (res.data[key] as number).toString()
+          } else {
+            form[key] = res.data[key]
+          }
+        }
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+// 新增或者编辑
+function setListData() {
+  try {
+    subLoading.value = true
+    form.content = (editorRef.value as any).getHtml()
+    ;(formRef.value as any).validate(async (valid) => {
+      if (valid) {
+        const res = await setExampApi({
+          data: form
+        })
+        if (res) {
+          Message.success(form.id ? '编辑成功' : '新增成功')
+          emit('success', form.id ? 'edit' : 'add')
+        }
+      } else {
+        console.log('error submit!!')
+        return false
+      }
+    })
+  } catch (err) {
+    console.log(err)
+  } finally {
+    subLoading.value = false
+  }
+}
+
+function close() {
+  push('/example-demo/example-page')
+}
+
+getDet()
+</script>

+ 18 - 0
src/views/example-demo/example-page/components/types.ts

@@ -0,0 +1,18 @@
+export interface InfoWriteParams {
+  title: string
+  id?: string
+  author: string
+  content: string
+  importance: string
+  display_time: string
+  pageviews: number
+}
+
+export interface InfoWriteRules {
+  title?: any[]
+  author?: any[]
+  content?: any[]
+  importance?: any[]
+  display_time?: any[]
+  pageviews?: any[]
+}

+ 11 - 0
src/views/example-demo/example-page/example-add.vue

@@ -0,0 +1,11 @@
+<template>
+  <info-write @success="success" />
+</template>
+
+<script setup lang="ts" name="ExampleAdd">
+import InfoWrite from './components/InfoWrite.vue'
+import bus from '@/vue-bus'
+function success(type: string) {
+  bus.$emit('success', type)
+}
+</script>

+ 10 - 0
src/views/example-demo/example-page/example-detail.vue

@@ -0,0 +1,10 @@
+<template>
+  <detail :id="id" />
+</template>
+
+<script setup lang="ts" name="ExampleDetail">
+import Detail from './components/Detail.vue'
+import { useRoute } from 'vue-router'
+const { query } = useRoute()
+const id = query.id as string
+</script>

+ 17 - 0
src/views/example-demo/example-page/example-edit.vue

@@ -0,0 +1,17 @@
+<template>
+  <info-write :id="id" @success="success" />
+</template>
+
+<script setup lang="ts">
+import InfoWrite from './components/InfoWrite.vue'
+import bus from '@/vue-bus'
+import { useRoute } from 'vue-router'
+const { query } = useRoute()
+const id = query.id as string
+
+// 成功之后的回调
+function success(type: string) {
+  // 由于使用的是页面跳转,所以只能通过vueBus去进行通信
+  bus.$emit('success', type)
+}
+</script>

+ 139 - 0
src/views/example-demo/example-page/index.vue

@@ -0,0 +1,139 @@
+<template>
+  <div>
+    <div class="search__example--wrap">
+      <com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
+    </div>
+
+    <div class="button__example--wrap">
+      <el-button type="primary" icon="el-icon-circle-plus-outline" @click="open(null)">
+        新增
+      </el-button>
+      <el-button type="danger" icon="el-icon-delete" @click="dels">删除</el-button>
+    </div>
+
+    <com-table
+      v-loading="loading"
+      selection
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        currentPage: defaultParams.pageIndex,
+        total: total,
+        onSizeChange: handleSizeChange,
+        onCurrentChange: handleCurrentChange
+      }"
+      @selection-change="handleSelectionChange"
+    >
+      <template #importance="scope">
+        <el-tag
+          :type="
+            scope.row.importance === 3
+              ? 'success'
+              : scope.row.importance === 2
+              ? 'warning'
+              : 'danger'
+          "
+        >
+          {{ scope.row.importance === 3 ? '重要' : scope.row.importance === 2 ? '良好' : '一般' }}
+        </el-tag>
+      </template>
+      <template #action="scope">
+        <el-button type="primary" size="mini" @click="open(scope.row)">编辑</el-button>
+        <el-button type="success" size="mini" @click="open(scope.row, 'Detail')">查看</el-button>
+        <el-button type="danger" size="mini" @click="dels(scope.row)">删除</el-button>
+      </template>
+    </com-table>
+  </div>
+</template>
+
+<script setup lang="ts" name="ExampleDialog">
+import { onBeforeUnmount } from 'vue'
+import { getExampleListApi, delsExampApi } from './api'
+import { useWork } from '@/hooks/work/useWork'
+import { useRouter } from 'vue-router'
+const { push } = useRouter()
+import bus from '@/vue-bus'
+const {
+  defaultParams,
+  tableData,
+  loading,
+  total,
+  handleSizeChange,
+  handleCurrentChange,
+  handleSelectionChange,
+  getList,
+  searchSubmit,
+  resetSubmit,
+  refreshTable,
+  dels
+} = useWork({
+  listFun: getExampleListApi,
+  delFun: delsExampApi
+})
+
+const searchData = [
+  {
+    label: '标题',
+    value: '',
+    itemType: 'input',
+    field: 'title',
+    placeholder: '请输入标题',
+    clearable: true
+  }
+]
+
+const columns = [
+  {
+    field: 'title',
+    label: '标题',
+    showOverflowTooltip: true
+  },
+  {
+    field: 'author',
+    label: '作者'
+  },
+  {
+    field: 'display_time',
+    label: '创建时间'
+  },
+  {
+    field: 'importance',
+    label: '重要性',
+    slots: {
+      default: 'importance'
+    }
+  },
+  {
+    field: 'pageviews',
+    label: '阅读数'
+  },
+  {
+    field: 'action',
+    label: '操作',
+    width: '220px',
+    slots: {
+      default: 'action'
+    }
+  }
+]
+
+function open(row: Nullable<IObj>, component?: string) {
+  push(
+    !row
+      ? `/example-demo/example-add`
+      : component
+      ? `/example-demo/example-detail?id=${row.id}`
+      : `/example-demo/example-edit?id=${row.id}`
+  )
+}
+
+getList()
+
+bus.$on('success', (type: string) => {
+  refreshTable(type)
+})
+
+onBeforeUnmount(() => {
+  bus.$off('success')
+})
+</script>

+ 13 - 0
src/views/role-demo/role/api.ts

@@ -0,0 +1,13 @@
+import fetch from '@/axios-config'
+
+export const getRoleListApi = ({ params }: any) => {
+  return fetch({ url: '/role/list', method: 'get', params })
+}
+
+export const setRoleApi = ({ data }: any) => {
+  return fetch({ url: '/role/save', method: 'post', data })
+}
+
+export const getRoleDetApi = ({ params }: any) => {
+  return fetch({ url: '/role/detail', method: 'get', params })
+}

+ 201 - 0
src/views/role-demo/role/components/InfoWrite.vue

@@ -0,0 +1,201 @@
+<template>
+  <div>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item prop="roleName" label="角色名">
+            <el-input v-model="form.roleName" disabled placeholder="请输入角色名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="24">
+          <el-form-item label="角色权限">
+            <el-tree
+              ref="tree"
+              :check-strictly="false"
+              :data="routesData as any"
+              :props="defaultProps as any"
+              show-checkbox
+              accordion
+              node-key="path"
+              highlight-current
+              class="permission-tree"
+            />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+      <el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="InfoWrite">
+import { PropType, computed, nextTick, reactive, ref } from 'vue'
+import path from 'path-browserify'
+import { setRoleApi, getRoleDetApi } from '../api'
+import { asyncRouterMap } from '@/router'
+import { isExternal } from '@/utils/validate'
+import { Message } from '_c/Message'
+import { AppRouteRecordRaw } from '@/router/types'
+
+const requiredRule: {
+  required: boolean
+  message: string
+} = {
+  required: true,
+  message: '该项为必填项'
+}
+
+const props = defineProps({
+  info: {
+    type: Object as PropType<Nullable<IObj>>,
+    default: () => null
+  }
+})
+
+const emit = defineEmits(['success', 'close'])
+
+const tree = ref<Nullable<HTMLElement>>(null)
+const formRef = ref<Nullable<HTMLElement>>(null)
+const subLoading = ref<boolean>(false)
+const form = reactive<IObj>({
+  id: '', // id
+  roleName: '', // 角色名
+  checkedNodes: [], // 被选中的节点
+  checkedkeys: [] // 被选中的keys
+})
+const rules = reactive<IObj>({
+  roleName: [requiredRule]
+})
+const routes = ref<IObj>([])
+const defaultProps = reactive<IObj>({
+  children: 'children',
+  label: 'title'
+})
+
+const routesData = computed(() => routes.value)
+
+async function getDet() {
+  if (props.info) {
+    const id = props.info.id
+    try {
+      const res: any = await getRoleDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        for (const key in form) {
+          form[key] = res.data[key]
+        }
+        nextTick(() => {
+          ;(tree.value as any).setCheckedKeys(form.checkedkeys)
+        })
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+// 新增或者编辑
+function setListData() {
+  try {
+    subLoading.value = true
+    ;(formRef.value as any).validate(async (valid) => {
+      if (valid) {
+        // 获取所有被选中节点,由于是前端渲染,所以只要保存一维数组就行
+        form.checkedNodes = (tree.value as any).getCheckedNodes(false, true)
+        console.log(JSON.stringify(form.checkedNodes))
+        // 获取所有被选中的keys,便于渲染是否选中
+        form.checkedkeys = (tree.value as any).getCheckedKeys()
+        console.log(JSON.stringify(form.checkedkeys))
+        const res = await setRoleApi({
+          data: form
+        })
+        if (res) {
+          Message.success(
+            form.id ? '编辑成功,请重新退出登录后查看效果' : '新增成功,请重新退出登录后查看效果'
+          )
+          emit('success', form.id ? 'edit' : 'add')
+        }
+      } else {
+        console.log('error submit!!')
+        return false
+      }
+    })
+  } catch (err) {
+    console.log(err)
+  } finally {
+    subLoading.value = false
+  }
+}
+
+function generateRoutes(routes: AppRouteRecordRaw[], basePath = '/') {
+  const res: AppRouteRecordRaw[] = []
+
+  for (let route of routes) {
+    // skip some route
+    if (route.meta && route.meta.hidden) {
+      continue
+    }
+
+    const onlyOneShowingChild = onlyOneShowingChildFn(
+      route.children,
+      route,
+      path.resolve(basePath, route.path)
+    )
+
+    if (route.children && onlyOneShowingChild && !(route.meta && route.meta.alwaysShow)) {
+      route = onlyOneShowingChild
+    }
+
+    const data = {
+      path: isExternal(route.path) ? route.path : path.resolve(basePath, route.path),
+      title: route.meta && route.meta.title,
+      name: route.name
+    }
+    // recursive child routes
+    if (route.children) {
+      ;(data as any).children = generateRoutes(route.children, data.path)
+    }
+    res.push(data as any)
+  }
+  return res
+}
+
+function onlyOneShowingChildFn(
+  children: AppRouteRecordRaw[] = [],
+  parent: AppRouteRecordRaw,
+  basePath: string
+) {
+  let onlyOneChild: Nullable<AppRouteRecordRaw | any> = null
+  const showingChildren = children.filter((item) => !(item.meta && item.meta.hidden))
+  // When there is only one child route, the child route is displayed by default
+  if (showingChildren.length === 1) {
+    onlyOneChild = showingChildren[0]
+    onlyOneChild.path = isExternal(onlyOneChild.path)
+      ? onlyOneChild.path
+      : path.resolve(basePath, onlyOneChild.path)
+    return onlyOneChild
+  }
+
+  // Show parent if there are no child route to display
+  if (showingChildren.length === 0) {
+    onlyOneChild = { ...parent, path: '', noShowingChildren: true }
+    return onlyOneChild
+  }
+
+  return false
+}
+
+function close() {
+  emit('close')
+}
+
+const oldRoutes = [...asyncRouterMap]
+routes.value = generateRoutes(oldRoutes)
+getDet()
+</script>

+ 225 - 0
src/views/role-demo/role/components/InfoWrite2.vue

@@ -0,0 +1,225 @@
+<template>
+  <div>
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="130px">
+      <el-row>
+        <el-col :span="24">
+          <el-form-item prop="roleName" label="角色名">
+            <el-input v-model="form.roleName" disabled placeholder="请输入角色名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="角色权限">
+            <el-tree
+              ref="tree"
+              :check-strictly="false"
+              :expand-on-click-node="false"
+              :data="routesData as any"
+              :props="defaultProps as any"
+              accordion
+              node-key="path"
+              highlight-current
+              class="permission-tree"
+              @node-click="handleNodeClick"
+            />
+          </el-form-item>
+        </el-col>
+        <el-col v-if="seletTreeData" :span="12">
+          <el-form-item label="title">
+            <el-input v-model="seletTreeData.title" />
+          </el-form-item>
+          <el-form-item label="component">
+            <el-input v-model="seletTreeData.component" />
+          </el-form-item>
+          <el-form-item label="redirect">
+            <el-input v-model="seletTreeData.redirect" />
+          </el-form-item>
+          <el-form-item label="activeMenu">
+            <el-input v-model="seletTreeData.meta.activeMenu" />
+          </el-form-item>
+          <el-form-item label="name">
+            <el-input v-model="seletTreeData.name" />
+          </el-form-item>
+          <el-form-item label="icon">
+            <el-input v-model="seletTreeData.meta.icon" />
+          </el-form-item>
+          <el-form-item label="hidden">
+            <el-switch v-model="seletTreeData.meta.hidden" />
+          </el-form-item>
+          <el-form-item label="alwaysShow">
+            <el-switch v-model="seletTreeData.meta.alwaysShow" />
+          </el-form-item>
+          <el-form-item label="noCache">
+            <el-switch v-model="seletTreeData.meta.noCache" />
+          </el-form-item>
+          <el-form-item label="breadcrumb">
+            <el-switch v-model="seletTreeData.meta.breadcrumb" />
+          </el-form-item>
+          <el-form-item label="affix">
+            <el-switch v-model="seletTreeData.meta.affix" />
+          </el-form-item>
+          <el-form-item label="noTagsView">
+            <el-switch v-model="seletTreeData.meta.noTagsView" />
+          </el-form-item>
+          <el-form-item label="showMainRoute">
+            <el-switch v-model="seletTreeData.meta.showMainRoute" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+    <div class="dialong__button--wrap">
+      <el-button @click="close">取消</el-button>
+      <el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" name="InfoWrite2">
+import { PropType, computed, nextTick, reactive, ref } from 'vue'
+import { setRoleApi, getRoleDetApi } from '../api'
+import { Message } from '_c/Message'
+import { AppRouteRecordRaw } from '@/router/types'
+
+const requiredRule: {
+  required: boolean
+  message: string
+} = {
+  required: true,
+  message: '该项为必填项'
+}
+
+const props = defineProps({
+  info: {
+    type: Object as PropType<Nullable<IObj>>,
+    default: () => null
+  }
+})
+
+const emit = defineEmits(['success', 'close'])
+
+const tree = ref<Nullable<HTMLElement>>(null)
+const formRef = ref<Nullable<HTMLElement>>(null)
+const subLoading = ref<boolean>(false)
+const form = reactive<IObj>({
+  id: '', // id
+  roleName: '', // 角色名
+  checkedNodes: [], // 被选中的节点
+  checkedkeys: [] // 被选中的keys
+})
+const rules = reactive<IObj>({
+  roleName: [requiredRule]
+})
+const routes = ref<IObj>([])
+const defaultProps = reactive<IObj>({
+  children: 'children',
+  label: 'title'
+})
+const seletTreeData = ref<Nullable<IObj>>(null)
+const routesData = computed(() => routes.value)
+
+async function getDet() {
+  if (props.info) {
+    const id = props.info.id
+    try {
+      const res: any = await getRoleDetApi({
+        params: {
+          id: id
+        }
+      })
+      if (res) {
+        console.log(res)
+        for (const key in form) {
+          form[key] = res.data[key]
+        }
+        routes.value = generateRoutes(form.checkedNodes)
+        nextTick(() => {
+          ;(tree.value as any).setCheckedKeys(form.checkedkeys)
+        })
+      }
+    } catch (e) {
+      console.log(e)
+    }
+  }
+}
+
+// 树形点击
+function handleNodeClick(data: IObj) {
+  seletTreeData.value = data
+}
+
+// 新增或者编辑
+function setListData() {
+  try {
+    subLoading.value = true
+    ;(formRef.value as any).validate(async (valid) => {
+      if (valid) {
+        console.log(routesData.value)
+        // 获取所有被选中节点
+        // const checkedNodes = this.$refs.tree.getCheckedNodes(false, true).filter(v => {
+        //   if (v.path.includes('/')) return v
+        // })
+        // // 获取所有被选中的keys,便于渲染是否选中
+        // this.form.checkedkeys = this.$refs.tree.getCheckedKeys()
+        // console.log(JSON.stringify(this.form.checkedkeys))
+
+        // this.form.checkedNodes = this.getFilterNodes(checkedNodes)
+        // console.log(JSON.stringify(this.form.checkedNodes))
+        const res = await setRoleApi({
+          data: Object.assign(form, { checkedNodes: routesData.value })
+        })
+        if (res) {
+          Message.success(
+            form.id ? '编辑成功,请重新退出登录后查看效果' : '新增成功,请重新退出登录后查看效果'
+          )
+          emit('success', form.id ? 'edit' : 'add')
+        }
+      } else {
+        console.log('error submit!!')
+        return false
+      }
+    })
+  } catch (err) {
+    console.log(err)
+  } finally {
+    subLoading.value = false
+  }
+}
+
+function close() {
+  emit('close')
+}
+
+// 树形渲染过滤
+function generateRoutes(routes: AppRouteRecordRaw[]) {
+  const res: AppRouteRecordRaw[] = []
+
+  for (const route of routes) {
+    const data: AppRouteRecordRaw = {
+      path: route.path,
+      name: route.name,
+      redirect: route.redirect || '',
+      title: (route as any).title || (route.meta && route.meta.title),
+      component: (route as any).component || '',
+      meta: {
+        title: (route as any).title || (route.meta && route.meta.title),
+        alwaysShow: route.meta && route.meta.alwaysShow,
+        hidden: route.meta && route.meta.hidden,
+        icon: route.meta && route.meta.icon,
+        noCache: route.meta && route.meta.noCache,
+        breadcrumb: route.meta && route.meta.breadcrumb,
+        affix: route.meta && route.meta.affix,
+        noTagsView: route.meta && route.meta.noTagsView,
+        activeMenu: route.meta && route.meta.activeMenu,
+        showMainRoute: route.meta && route.meta.showMainRoute
+      }
+    }
+    // recursive child routes
+    if (route.children) {
+      data.children = generateRoutes(route.children)
+    }
+    res.push(data)
+  }
+  return res
+}
+
+getDet()
+</script>

+ 124 - 0
src/views/role-demo/role/index.vue

@@ -0,0 +1,124 @@
+<template>
+  <div>
+    <el-alert
+      effect="dark"
+      :closable="false"
+      title="由于是模拟数据,所以只提供了两种不同权限的角色,开发者可根据实际情况自行改造结合。"
+      type="info"
+      style="margin-bottom: 20px"
+    />
+
+    <div class="search__example--wrap">
+      <com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
+    </div>
+
+    <com-table
+      v-loading="loading"
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        currentPage: defaultParams.pageIndex,
+        total: total,
+        onSizeChange: handleSizeChange,
+        onCurrentChange: handleCurrentChange
+      }"
+    >
+      <template #remark="scope">
+        <span>模拟</span>
+        <el-tag
+          :type="scope.row.roleName === 'admin' ? 'success' : 'warning'"
+          style="margin: 0 15px"
+        >
+          {{ scope.row.roleName === 'admin' ? '前端' : '后端' }}
+        </el-tag>
+        <span>角色</span>
+      </template>
+
+      <template #action="scope">
+        <el-button
+          type="primary"
+          size="mini"
+          @click="open(scope.row, scope.row.roleName === 'admin' ? 'InfoWrite' : 'InfoWrite2')"
+        >
+          编辑
+        </el-button>
+      </template>
+    </com-table>
+
+    <com-dialog v-model="dialogVisible" :title="dialogTitle">
+      <info-write
+        v-if="comName === 'InfoWrite' && dialogVisible"
+        :info="rowData"
+        @close="toggleVisible"
+        @success="refreshTable"
+      />
+      <info-write2
+        v-if="comName === 'InfoWrite2' && dialogVisible"
+        :info="rowData"
+        @close="toggleVisible"
+        @success="refreshTable"
+      />
+    </com-dialog>
+  </div>
+</template>
+
+<script setup lang="ts" name="Role">
+import { getRoleListApi } from './api'
+import { useWork } from '@/hooks/work/useWork'
+import InfoWrite from './components/InfoWrite.vue'
+import InfoWrite2 from './components/InfoWrite2.vue'
+const {
+  defaultParams,
+  tableData,
+  loading,
+  total,
+  dialogVisible,
+  dialogTitle,
+  comName,
+  rowData,
+  handleSizeChange,
+  handleCurrentChange,
+  toggleVisible,
+  getList,
+  searchSubmit,
+  resetSubmit,
+  open,
+  refreshTable
+} = useWork({
+  listFun: getRoleListApi
+})
+
+const searchData = [
+  {
+    label: '角色名',
+    value: '',
+    itemType: 'input',
+    field: 'roleName',
+    placeholder: '请输入角色名',
+    clearable: true
+  }
+]
+
+const columns = [
+  {
+    field: 'roleName',
+    label: '角色名'
+  },
+  {
+    label: '备注',
+    slots: {
+      default: 'remark'
+    }
+  },
+  {
+    field: 'action',
+    label: '操作',
+    width: '80px',
+    slots: {
+      default: 'action'
+    }
+  }
+]
+
+getList()
+</script>

+ 5 - 0
src/views/role-demo/user/api.ts

@@ -0,0 +1,5 @@
+import fetch from '@/axios-config'
+
+export const getUserListApi = ({ params }: any) => {
+  return fetch({ url: '/user/list', method: 'get', params })
+}

+ 90 - 0
src/views/role-demo/user/index.vue

@@ -0,0 +1,90 @@
+<template>
+  <div>
+    <el-alert
+      effect="dark"
+      :closable="false"
+      title="由于是模拟数据,所以只提供了两种不同权限的帐号,开发者可根据实际情况自行改造结合。"
+      type="info"
+      style="margin-bottom: 20px"
+    />
+
+    <div class="search__example--wrap">
+      <com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
+    </div>
+
+    <com-table
+      v-loading="loading"
+      :columns="columns"
+      :data="tableData"
+      :pagination="{
+        currentPage: defaultParams.pageIndex,
+        total: total,
+        onSizeChange: handleSizeChange,
+        onCurrentChange: handleCurrentChange
+      }"
+    >
+      <template #remark="scope">
+        <span>模拟</span>
+        <el-tag
+          :type="scope.row.userName === 'admin' ? 'success' : 'warning'"
+          style="margin: 0 15px"
+        >
+          {{ scope.row.userName === 'admin' ? '前端' : '后端' }}
+        </el-tag>
+        <span>控制路由权限</span>
+      </template>
+    </com-table>
+  </div>
+</template>
+
+<script setup lang="ts" name="User">
+import { getUserListApi } from './api'
+import { useWork } from '@/hooks/work/useWork'
+const {
+  defaultParams,
+  tableData,
+  loading,
+  total,
+  handleSizeChange,
+  handleCurrentChange,
+  getList,
+  searchSubmit,
+  resetSubmit
+} = useWork({
+  listFun: getUserListApi
+})
+
+const searchData = [
+  {
+    label: '帐号',
+    value: '',
+    itemType: 'input',
+    field: 'userName',
+    placeholder: '请输入帐号',
+    clearable: true
+  }
+]
+
+const columns = [
+  {
+    field: 'userName',
+    label: '帐号'
+  },
+  {
+    field: 'password',
+    label: '密码'
+  },
+  {
+    field: 'role',
+    label: '角色'
+  },
+  {
+    label: '备注',
+    slots: {
+      default: 'remark'
+    }
+  }
+]
+
+getList()
+</script>

+ 13 - 0
src/vue-bus/index.ts

@@ -0,0 +1,13 @@
+// 通过mitt实现vue-bus通信
+
+import mitt from 'mitt'
+
+const bus: any = {}
+
+const emitter = mitt()
+
+bus.$on = emitter.on
+bus.$off = emitter.off
+bus.$emit = emitter.emit
+
+export default bus

+ 5 - 0
yarn.lock

@@ -5115,6 +5115,11 @@ minimist@1.2.5, minimist@^1.2.0, minimist@^1.2.5:
   resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
   integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
 
+mitt@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.nlark.com/mitt/download/mitt-3.0.0.tgz?cache=0&sync_timestamp=1624481394885&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fmitt%2Fdownload%2Fmitt-3.0.0.tgz#69ef9bd5c80ff6f57473e8d89326d01c414be0bd"
+  integrity sha1-ae+b1cgP9vV0c+jYkybQHEFL4L0=
+
 mixin-deep@^1.2.0:
   version "1.3.2"
   resolved "https://registry.nlark.com/mixin-deep/download/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"