瀏覽代碼

Merge branch 'wheng' of wuheng/eas_webadmin into master

wuheng 1 年之前
父節點
當前提交
7118676006
共有 100 個文件被更改,包括 1705 次插入3528 次删除
  1. 7 5
      .env
  2. 3 1
      build/plugins/index.ts
  3. 2 2
      build/plugins/pwa.ts
  4. 0 32
      docker/.dockerignore
  5. 0 24
      docker/Dockerfile
  6. 0 54
      docker/nginx.conf
  7. 0 128
      mock/api/auth.ts
  8. 0 295
      mock/api/crud/base.ts
  9. 0 5
      mock/api/crud/index.ts
  10. 0 56
      mock/api/crud/modules/demo.ts
  11. 0 46
      mock/api/crud/modules/header-group.ts
  12. 1 6
      mock/api/index.ts
  13. 0 33
      mock/api/management.ts
  14. 0 29
      mock/api/route.ts
  15. 0 40
      mock/model/auth.ts
  16. 0 2
      mock/model/index.ts
  17. 0 1126
      mock/model/route.ts
  18. 12 35
      package.json
  19. 252 68
      pnpm-lock.yaml
  20. 598 1
      public/favicon.svg
  21. 二進制
      public/logo.png
  22. 1 0
      src/assets/svg-icon/academic-cap.svg
  23. 1 0
      src/assets/svg-icon/class.svg
  24. 0 0
      src/assets/svg-icon/classroom.svg
  25. 0 0
      src/assets/svg-icon/lesson.svg
  26. 4 1
      src/assets/svg-icon/logo-fill.svg
  27. 4 1
      src/assets/svg-icon/logo.svg
  28. 1 0
      src/assets/svg-icon/training-class.svg
  29. 120 0
      src/components/View/pdfPreview.vue
  30. 4 5
      src/composables/system.ts
  31. 1 1
      src/config/regexp.ts
  32. 5 3
      src/config/service.ts
  33. 4 31
      src/constants/business.ts
  34. 0 43
      src/context/demo.ts
  35. 0 1
      src/context/index.ts
  36. 1 1
      src/hooks/business/use-table.ts
  37. 0 1
      src/layouts/common/global-content/index.vue
  38. 20 0
      src/layouts/common/global-header/components/drawer-toggle.vue
  39. 0 23
      src/layouts/common/global-header/components/github-site.vue
  40. 3 3
      src/layouts/common/global-header/components/index.ts
  41. 8 2
      src/layouts/common/global-header/components/user-avatar.vue
  42. 4 4
      src/layouts/common/global-header/index.vue
  43. 2 1
      src/layouts/common/setting-drawer/index.vue
  44. 24 62
      src/locales/lang/en.ts
  45. 26 65
      src/locales/lang/zh-cn.ts
  46. 2 0
      src/main.ts
  47. 2 0
      src/plugins/assets.ts
  48. 46 5
      src/plugins/fast-crud/index.tsx
  49. 二進制
      src/public/java.pdf
  50. 二進制
      src/public/分数导入.xlsx
  51. 二進制
      src/public/学员导入.xlsx
  52. 3 2
      src/router/guard/dynamic.ts
  53. 10 9
      src/router/guard/permission.ts
  54. 1 1
      src/router/modules/about.ts
  55. 28 0
      src/router/modules/archives.ts
  56. 0 38
      src/router/modules/auth-demo.ts
  57. 0 48
      src/router/modules/component.ts
  58. 0 45
      src/router/modules/crud.ts
  59. 2 11
      src/router/modules/dashboard.ts
  60. 0 70
      src/router/modules/document.ts
  61. 0 48
      src/router/modules/exception.ts
  62. 0 51
      src/router/modules/function.ts
  63. 52 0
      src/router/modules/groups.ts
  64. 85 0
      src/router/modules/lesson.ts
  65. 0 70
      src/router/modules/management.ts
  66. 0 61
      src/router/modules/multi-menu.ts
  67. 0 149
      src/router/modules/plugin.ts
  68. 76 0
      src/router/modules/system.ts
  69. 0 10
      src/router/routes/index.ts
  70. 29 25
      src/service/api/auth.ts
  71. 0 1
      src/service/api/index.ts
  72. 0 0
      src/service/api/login.ts
  73. 0 0
      src/service/api/man.ts
  74. 0 13
      src/service/api/management.adapter.ts
  75. 0 9
      src/service/api/management.ts
  76. 0 166
      src/service/api/sort.ts
  77. 0 116
      src/service/api/user.ts
  78. 5 5
      src/service/request/helpers.ts
  79. 5 4
      src/service/request/index.ts
  80. 33 20
      src/service/request/instance.ts
  81. 6 5
      src/service/request/request.ts
  82. 1 1
      src/store/modules/app/index.ts
  83. 8 5
      src/store/modules/auth/helpers.ts
  84. 28 46
      src/store/modules/auth/index.ts
  85. 5 27
      src/store/modules/route/index.ts
  86. 6 0
      src/styles/css/global.css
  87. 0 29
      src/typings/api.copy.ts
  88. 2 2
      src/typings/api.d.ts
  89. 8 4
      src/typings/business.d.ts
  90. 11 0
      src/typings/global.d.ts
  91. 38 85
      src/typings/page-route.d.ts
  92. 0 22
      src/typings/sort.ts
  93. 35 65
      src/typings/system.d.ts
  94. 1 1
      src/typings/union-key.d.ts
  95. 34 15
      src/utils/crypto/index.ts
  96. 29 0
      src/utils/form/date.ts
  97. 1 0
      src/utils/form/index.ts
  98. 3 6
      src/utils/form/rule.ts
  99. 1 0
      src/utils/index.ts
  100. 1 2
      src/utils/router/auth.ts

+ 7 - 5
.env

@@ -1,16 +1,16 @@
 VITE_BASE_URL=/
 
-VITE_APP_NAME=SoybeanAdmin
+VITE_APP_NAME=教务系统
 
-VITE_APP_TITLE=Soybean管理系统
+VITE_APP_TITLE=爱扣钉教务系统
 
-VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版
+VITE_APP_DESC=四福科技-爱扣钉-教务
 
 # 权限路由模式: static | dynamic
-VITE_AUTH_ROUTE_MODE=static
+VITE_AUTH_ROUTE_MODE = static
 
 # 路由首页(根路由重定向), 用于static模式的权限路由,dynamic模式取决于后端返回的路由首页
-VITE_ROUTE_HOME_PATH=/dashboard/analysis
+VITE_ROUTE_HOME_PATH=/dashboard/workbench
 
 # iconify图标作为组件的前缀
 VITE_ICON_PREFFIX=icon
@@ -18,3 +18,5 @@ VITE_ICON_PREFFIX=icon
 # 本地SVG图标作为组件的前缀, 请注意一定要包含 VITE_ICON_PREFFIX
 # 格式 {VITE_ICON_PREFFIX}-{本地图标集合名称}
 VITE_ICON_LOCAL_PREFFIX=icon-local
+
+VITE_VERCEL=N

+ 3 - 1
build/plugins/index.ts

@@ -4,6 +4,7 @@ import vueJsx from '@vitejs/plugin-vue-jsx';
 import unocss from '@unocss/vite';
 import progress from 'vite-plugin-progress';
 import pageRoute from '@soybeanjs/vite-plugin-vue-page-route';
+import PurgeIcons from 'vite-plugin-purge-icons';
 import unplugin from './unplugin';
 import mock from './mock';
 import visualizer from './visualizer';
@@ -26,7 +27,8 @@ export function setupVitePlugins(viteEnv: ImportMetaEnv): (PluginOption | Plugin
     unocss(),
     mock(viteEnv),
     progress(),
-    pageRoute()
+    pageRoute(),
+    PurgeIcons()
   ];
 
   if (viteEnv.VITE_VISUALIZER === 'Y') {

+ 2 - 2
build/plugins/pwa.ts

@@ -5,8 +5,8 @@ export default function setupVitePwa() {
     registerType: 'autoUpdate',
     includeAssets: ['favicon.ico'],
     manifest: {
-      name: 'SoybeanAdmin',
-      short_name: 'SoybeanAdmin',
+      name: '爱扣钉教务系统',
+      short_name: '爱扣钉教务系统',
       theme_color: '#fff',
       icons: [
         {

+ 0 - 32
docker/.dockerignore

@@ -1,32 +0,0 @@
-node_modules
-.DS_Store
-dist
-.npmrc
-.cache
-
-tests/server/static
-tests/server/static/upload
-
-.local
-# local env files
-.env.local
-.env.*.local
-.eslintcache
-
-# Log files
-npm-debug.log*
-yarn-debug.log*
-yarn-error.log*
-pnpm-debug.log*
-
-# Editor directories and files
-.idea
-# .vscode
-*.suo
-*.ntvs*
-*.njsproj
-*.sln
-*.sw?
-yarn.lock
-pnpm-lock.yaml
-/vite-profile.cpuprofile

+ 0 - 24
docker/Dockerfile

@@ -1,24 +0,0 @@
-FROM node:16.17.0 as builder
-
-ENV WORKDIR=/soybean-admin
-
-WORKDIR $WORKDIR
-
-COPY ./ $WORKDIR/
-
-ARG version
-ENV COMMITID=$version
-
-RUN npm i -g pnpm
-
-RUN pnpm install
-RUN pnpm build
-
-FROM nginx:alpine as prod
-
-RUN mkdir /soybean
-
-COPY --from=builder /soybean-admin/dist /soybean-admin
-COPY --from=builder /soybean-admin/docker/nginx.conf /etc/nginx/nginx.conf
-
-EXPOSE 80

+ 0 - 54
docker/nginx.conf

@@ -1,54 +0,0 @@
-user  nginx;
-worker_processes  1;
-error_log  /var/log/nginx/error.log warn;
-pid        /var/run/nginx.pid;
-
-events {
-  worker_connections  1024;
-}
-
-http {
-  include       /etc/nginx/mime.types;
-  default_type  application/octet-stream;
-  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
-                    '$status $body_bytes_sent "$http_referer" '
-                    '"$http_user_agent" "$http_x_forwarded_for"';
-  access_log  /var/log/nginx/access.log  main;
-  sendfile        on;
-  keepalive_timeout  65;
-
-  server {
-    listen       80;
-    server_name  localhost;
-
-    location / {
-      # 不缓存html,防止程序更新后缓存继续生效
-      if ($request_filename ~* .*\.(?:htm|html)$) {
-        add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
-        access_log on;
-      }
-      root   /soybean-admin/;
-      index  index.html index.htm;
-      try_files $uri $uri/ /index.html;
-    }
-
-    # location /soybean/soybean-webserver/v1 {
-    #     proxy_set_header Host $host;
-    #     proxy_set_header X-Real-IP $remote_addr;
-    #     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
-    #     proxy_set_header REMOTE-HOST $remote_addr;
-
-    #     # 后台接口地址
-    #     proxy_pass http://192.168.1.99:30597/v1;
-    #     proxy_redirect default;
-    #     add_header Access-Control-Allow-Origin *;
-    #     add_header Access-Control-Allow-Headers X-Requested-With;
-    #     add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
-    # }
-
-    error_page   500 502 503 504  /50x.html;
-    location = /50x.html {
-      root   /usr/share/nginx/html;
-    }
-  }
-}

+ 0 - 128
mock/api/auth.ts

@@ -1,128 +0,0 @@
-import type { MockMethod } from 'vite-plugin-mock';
-import { userModel } from '../model';
-
-/** 参数错误的状态码 */
-const ERROR_PARAM_CODE = 10000;
-
-const ERROR_PARAM_MSG = '参数校验失败!';
-
-const apis: MockMethod[] = [
-  // 获取验证码
-  {
-    url: '/mock/getSmsCode',
-    method: 'post',
-    response: (): Service.MockServiceResult<boolean> => {
-      return {
-        code: 200,
-        message: 'ok',
-        data: true
-      };
-    }
-  },
-  // 用户+密码 登录
-  {
-    url: '/mock/login',
-    method: 'post',
-    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
-      const { userName = undefined, password = undefined } = options.body;
-
-      if (!userName || !password) {
-        return {
-          code: ERROR_PARAM_CODE,
-          message: ERROR_PARAM_MSG,
-          data: null
-        };
-      }
-
-      const findItem = userModel.find(item => item.userName === userName && item.password === password);
-
-      if (findItem) {
-        return {
-          code: 200,
-          message: 'ok',
-          data: {
-            token: findItem.token,
-            refreshToken: findItem.refreshToken
-          }
-        };
-      }
-      return {
-        code: 1000,
-        message: '用户名或密码错误!',
-        data: null
-      };
-    }
-  },
-  // 获取用户信息(请求头携带token, 根据token获取用户信息)
-  {
-    url: '/mock/getUserInfo',
-    method: 'get',
-    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.UserInfo | null> => {
-      // 这里的mock插件得到的字段是authorization, 前端传递的是Authorization字段
-      const { authorization = '' } = options.headers;
-      const REFRESH_TOKEN_CODE = 66666;
-
-      if (!authorization) {
-        return {
-          code: REFRESH_TOKEN_CODE,
-          message: '用户已失效或不存在!',
-          data: null
-        };
-      }
-      const userInfo: Auth.UserInfo = {
-        userId: '',
-        userName: '',
-        userRole: 'user'
-      };
-      const isInUser = userModel.some(item => {
-        const flag = item.token === authorization;
-        if (flag) {
-          const { userId: itemUserId, userName, userRole } = item;
-          Object.assign(userInfo, { userId: itemUserId, userName, userRole });
-        }
-        return flag;
-      });
-
-      if (isInUser) {
-        return {
-          code: 200,
-          message: 'ok',
-          data: userInfo
-        };
-      }
-
-      return {
-        code: REFRESH_TOKEN_CODE,
-        message: '用户信息异常!',
-        data: null
-      };
-    }
-  },
-  {
-    url: '/mock/updateToken',
-    method: 'post',
-    response: (options: Service.MockOption): Service.MockServiceResult<ApiAuth.Token | null> => {
-      const { refreshToken = '' } = options.body;
-
-      const findItem = userModel.find(item => item.refreshToken === refreshToken);
-
-      if (findItem) {
-        return {
-          code: 200,
-          message: 'ok',
-          data: {
-            token: findItem.token,
-            refreshToken: findItem.refreshToken
-          }
-        };
-      }
-      return {
-        code: 3000,
-        message: '用户已失效或不存在!',
-        data: null
-      };
-    }
-  }
-];
-
-export default apis;

+ 0 - 295
mock/api/crud/base.ts

@@ -1,295 +0,0 @@
-export type ListItem = {
-  id?: number;
-  children?: ListItem[];
-  [key: string]: any;
-};
-export type BaseMockOptions = { name: string; copyTimes?: number; list: ListItem[]; idGenerator: number };
-type CopyListParams = { originList: ListItem[]; newList: ListItem[]; options: BaseMockOptions; parentId?: number };
-
-function copyList(props: CopyListParams) {
-  const { originList, newList, options, parentId } = props;
-  for (const item of originList) {
-    const newItem: ListItem = { ...item, parentId };
-    newItem.id = options.idGenerator;
-    options.idGenerator += 1;
-    newList.push(newItem);
-    if (item.children) {
-      newItem.children = [];
-      copyList({
-        originList: item.children,
-        newList: newItem.children,
-        options,
-        parentId: newItem.id
-      });
-    }
-  }
-}
-
-function delById(req: Service.MockOption, list: any[]) {
-  for (let i = 0; i < list.length; i += 1) {
-    const item = list[i];
-    if (item.id === parseInt(req.query.id, 10)) {
-      list.splice(i, 1);
-      break;
-    }
-    if (item.children && item.children.length > 0) {
-      delById(req, item.children);
-    }
-  }
-}
-
-function findById(id: number, list: ListItem[]): any {
-  for (const item of list) {
-    if (item.id === id) {
-      return item;
-    }
-    if (item.children && item.children.length > 0) {
-      const sub = findById(id, item.children);
-      if (sub !== null && sub !== undefined) {
-        return sub;
-      }
-    }
-  }
-  return null;
-}
-
-function matchWithArrayCondition(value: any[], item: ListItem, key: string) {
-  if (value.length === 0) {
-    return true;
-  }
-  let matched = false;
-  for (const i of value) {
-    if (item[key] instanceof Array) {
-      for (const j of item[key]) {
-        if (i === j) {
-          matched = true;
-          break;
-        }
-      }
-      if (matched) {
-        break;
-      }
-    } else if (item[key] === i || (typeof item[key] === 'string' && item[key].indexOf(`${i}`) >= 0)) {
-      matched = true;
-      break;
-    }
-    if (matched) {
-      break;
-    }
-  }
-  return matched;
-}
-
-function matchWithObjectCondition(value: any, item: ListItem, key: string) {
-  let matched = true;
-  for (const key2 of Object.keys(value)) {
-    const v = value[key2];
-    if (v && item[key] && v !== item[key][key2]) {
-      matched = false;
-      break;
-    }
-  }
-  return matched;
-}
-
-function searchFromList(list: ListItem[], query: any) {
-  const filter = (item: ListItem) => {
-    let allFound = true; // 是否所有条件都符合
-    for (const key of Object.keys(query)) {
-      const value = query[key];
-      if (value === undefined || value === null || value === '') {
-        // no nothing
-      } else if (value instanceof Array) {
-        // 如果条件中的value是数组的话,只要查到一个就行
-        const matched = matchWithArrayCondition(value, item, key);
-        if (!matched) {
-          allFound = false;
-        }
-      } else if (value instanceof Object) {
-        // 如果条件中的value是对象的话,需要每个key都匹配
-        const matched = matchWithObjectCondition(value, item, key);
-        if (!matched) {
-          allFound = false;
-        }
-      } else if (item[key] !== value) {
-        allFound = false;
-      }
-    }
-    return allFound;
-  };
-  return list.filter(filter);
-}
-
-export default {
-  buildMock(options: BaseMockOptions) {
-    const name = options.name;
-    if (!options.copyTimes) {
-      options.copyTimes = 29;
-    }
-    const list: any[] = [];
-    for (let i = 0; i < options.copyTimes; i += 1) {
-      copyList({
-        originList: options.list,
-        newList: list,
-        options
-      });
-    }
-    options.list = list;
-    return [
-      {
-        path: `/mock/${name}/page`,
-        method: 'post',
-        handle(req: Service.MockOption) {
-          let data = [...list];
-          let limit = 20;
-          let offset = 0;
-          for (const item of list) {
-            if (item.children && item.children.length === 0) {
-              item.hasChildren = false;
-              item.lazy = false;
-            }
-          }
-          let orderAsc: any;
-          let orderProp: any;
-          if (req && req.body) {
-            const { page, query } = req.body;
-            if (page.limit) {
-              limit = parseInt(page.limit, 10);
-            }
-            if (page.offset) {
-              offset = parseInt(page.offset, 10);
-            }
-            if (Object.keys(query).length > 0) {
-              data = searchFromList(list, query);
-            }
-          }
-
-          const start = offset;
-          let end = offset + limit;
-          if (data.length < end) {
-            end = data.length;
-          }
-
-          if (orderProp) {
-            // 排序
-            data.sort((a, b) => {
-              let ret = 0;
-              if (a[orderProp] > b[orderProp]) {
-                ret = 1;
-              } else if (a[orderProp] < b[orderProp]) {
-                ret = -1;
-              }
-              return orderAsc ? ret : -ret;
-            });
-          }
-
-          const records = data.slice(start, end);
-          const lastOffset = data.length - (data.length % limit);
-          if (offset > lastOffset) {
-            offset = lastOffset;
-          }
-          return {
-            code: 200,
-            message: 'success',
-            data: {
-              records,
-              total: data.length,
-              limit,
-              offset
-            }
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/get`,
-        method: 'get',
-        handle(req: Service.MockOption) {
-          let id = req.query.id;
-          id = parseInt(id, 10);
-          let current = null;
-          for (const item of list) {
-            if (item.id === id) {
-              current = item;
-              break;
-            }
-          }
-          return {
-            code: 200,
-            message: 'success',
-            data: current
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/add`,
-        method: 'post',
-        handle(req: Service.MockOption) {
-          req.body.id = options.idGenerator;
-          options.idGenerator += 1;
-          list.unshift(req.body);
-          return {
-            code: 200,
-            message: 'success',
-            data: req.body.id
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/update`,
-        method: 'post',
-        handle(req: Service.MockOption) {
-          const item = findById(req.body.id, list);
-          if (item) {
-            Object.assign(item, req.body);
-          }
-          return {
-            code: 200,
-            message: 'success',
-            data: null
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/delete`,
-        method: 'post',
-        handle(req: Service.MockOption) {
-          delById(req, list);
-          return {
-            code: 200,
-            message: 'success',
-            data: null
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/batchDelete`,
-        method: 'post',
-        handle(req: Service.MockOption) {
-          const ids = req.body.ids;
-          for (let i = list.length - 1; i >= 0; i -= 1) {
-            const item = list[i];
-            if (ids.indexOf(item.id) >= 0) {
-              list.splice(i, 1);
-            }
-          }
-          return {
-            code: 200,
-            message: 'success',
-            data: null
-          };
-        }
-      },
-      {
-        path: `/mock/${name}/all`,
-        method: 'post',
-        handle() {
-          return {
-            code: 200,
-            message: 'success',
-            data: list
-          };
-        }
-      }
-    ];
-  }
-};

+ 0 - 5
mock/api/crud/index.ts

@@ -1,5 +0,0 @@
-import demo from './modules/demo';
-import headerGroup from './modules/header-group';
-
-const crudApis = [...demo, ...headerGroup];
-export default crudApis;

+ 0 - 56
mock/api/crud/modules/demo.ts

@@ -1,56 +0,0 @@
-import type { MethodType, MockMethod } from 'vite-plugin-mock';
-import type { BaseMockOptions } from '../base';
-import mockBase from '../base';
-import MockOption = Service.MockOption;
-
-const options: BaseMockOptions = {
-  name: 'crud/demo',
-  idGenerator: 0,
-  list: [
-    {
-      select: '1',
-      text: '文本测试',
-      copyable: '文本可复制',
-      avatar: 'http://greper.handsfree.work/extends/avatar.jpg',
-      richtext: '富文本',
-      datetime: '2023-01-30 11:11:11'
-    },
-    {
-      select: '2'
-    },
-    {
-      select: '0'
-    }
-  ]
-};
-const mockedApis = mockBase.buildMock(options);
-
-const apis: MockMethod[] = [
-  {
-    url: `/mock/${options.name}/dict`,
-    method: 'get',
-    response: () => {
-      return {
-        code: 200,
-        message: '',
-        data: [
-          { value: '0', label: '关', color: 'warning' },
-          { value: '1', label: '开', color: 'success' },
-          { value: '2', label: '停' }
-        ]
-      };
-    }
-  }
-];
-
-for (const mockedApi of mockedApis) {
-  apis.push({
-    url: mockedApi.path,
-    method: mockedApi.method as MethodType,
-    response: (request: MockOption) => {
-      return mockedApi.handle(request);
-    }
-  });
-}
-
-export default apis;

+ 0 - 46
mock/api/crud/modules/header-group.ts

@@ -1,46 +0,0 @@
-import type { MethodType, MockMethod } from 'vite-plugin-mock';
-import type { BaseMockOptions } from '../base';
-import mockBase from '../base';
-import MockOption = Service.MockOption;
-
-const options: BaseMockOptions = {
-  name: 'crud/header-group',
-  idGenerator: 0,
-  list: [
-    {
-      name: '张三',
-      age: 18,
-      province: '广东省',
-      city: '深圳市',
-      county: '南山区',
-      street: '粤海街道'
-    },
-    {
-      name: '李四',
-      age: 26,
-      province: '浙江省',
-      city: '杭州市',
-      county: '西湖区',
-      street: '西湖街道'
-    },
-    {
-      name: '王五',
-      age: 24
-    }
-  ]
-};
-const mockedApis = mockBase.buildMock(options);
-
-const apis: MockMethod[] = [];
-
-for (const mockedApi of mockedApis) {
-  apis.push({
-    url: mockedApi.path,
-    method: mockedApi.method as MethodType,
-    response: (request: MockOption) => {
-      return mockedApi.handle(request);
-    }
-  });
-}
-
-export default apis;

+ 1 - 6
mock/api/index.ts

@@ -1,6 +1 @@
-import auth from './auth';
-import route from './route';
-import management from './management';
-import crud from './crud';
-
-export default [...auth, ...route, ...management, ...crud];
+export default [];

+ 0 - 33
mock/api/management.ts

@@ -1,33 +0,0 @@
-import { mock } from 'mockjs';
-import type { MockMethod } from 'vite-plugin-mock';
-
-const apis: MockMethod[] = [
-  {
-    url: '/mock/getAllUserList',
-    method: 'post',
-    response: (): Service.MockServiceResult<ApiUserManagement.User[]> => {
-      const data = mock({
-        'list|1000': [
-          {
-            id: '@id',
-            userName: '@cname',
-            'age|18-56': 56,
-            'gender|1': ['0', '1', null],
-            phone:
-              /^[1](([3][0-9])|([4][01456789])|([5][012356789])|([6][2567])|([7][0-8])|([8][0-9])|([9][012356789]))[0-9]{8}$/,
-            'email|1': ['@email("qq.com")', null],
-            'userStatus|1': ['1', '2', '3', '4', null]
-          }
-        ]
-      });
-
-      return {
-        code: 200,
-        message: 'ok',
-        data: data.list
-      };
-    }
-  }
-];
-
-export default apis;

+ 0 - 29
mock/api/route.ts

@@ -1,29 +0,0 @@
-import type { MockMethod } from 'vite-plugin-mock';
-import { routeModel, userModel } from '../model';
-
-const apis: MockMethod[] = [
-  {
-    url: '/mock/getUserRoutes',
-    method: 'post',
-    response: (options: Service.MockOption): Service.MockServiceResult => {
-      const { userId = undefined } = options.body;
-
-      const routeHomeName: AuthRoute.LastDegreeRouteKey = 'dashboard_analysis';
-
-      const role = userModel.find(item => item.userId === userId)?.userRole || 'user';
-
-      const filterRoutes = routeModel[role];
-
-      return {
-        code: 200,
-        message: 'ok',
-        data: {
-          routes: filterRoutes,
-          home: routeHomeName
-        }
-      };
-    }
-  }
-];
-
-export default apis;

+ 0 - 40
mock/model/auth.ts

@@ -1,40 +0,0 @@
-interface UserModel extends Auth.UserInfo {
-  token: string;
-  refreshToken: string;
-  password: string;
-}
-
-export const userModel: UserModel[] = [
-  {
-    token: '__TOKEN_SOYBEAN__',
-    refreshToken: '__REFRESH_TOKEN_SOYBEAN__',
-    userId: '0',
-    userName: 'Soybean',
-    userRole: 'super',
-    password: 'soybean123'
-  },
-  {
-    token: '__TOKEN_SUPER__',
-    refreshToken: '__REFRESH_TOKEN_SUPER__',
-    userId: '1',
-    userName: 'Super',
-    userRole: 'super',
-    password: 'super123'
-  },
-  {
-    token: '__TOKEN_ADMIN__',
-    refreshToken: '__REFRESH_TOKEN_ADMIN__',
-    userId: '2',
-    userName: 'Admin',
-    userRole: 'admin',
-    password: 'admin123'
-  },
-  {
-    token: '__TOKEN_USER01__',
-    refreshToken: '__REFRESH_TOKEN_USER01__',
-    userId: '3',
-    userName: 'User01',
-    userRole: 'user',
-    password: 'user01123'
-  }
-];

+ 0 - 2
mock/model/index.ts

@@ -1,2 +0,0 @@
-export * from './auth';
-export * from './route';

+ 0 - 1126
mock/model/route.ts

@@ -1,1126 +0,0 @@
-export const routeModel: Record<Auth.RoleType, AuthRoute.Route[]> = {
-  super: [
-    {
-      name: 'dashboard',
-      path: '/dashboard',
-      component: 'basic',
-      children: [
-        {
-          name: 'dashboard_analysis',
-          path: '/dashboard/analysis',
-          component: 'self',
-          meta: {
-            title: '分析页',
-            requiresAuth: true,
-            icon: 'icon-park-outline:analysis'
-          }
-        },
-        {
-          name: 'dashboard_workbench',
-          path: '/dashboard/workbench',
-          component: 'self',
-          meta: {
-            title: '工作台',
-            requiresAuth: true,
-            icon: 'icon-park-outline:workbench'
-          }
-        }
-      ],
-      meta: {
-        title: '仪表盘',
-        icon: 'mdi:monitor-dashboard',
-        order: 1
-      }
-    },
-    {
-      name: 'document',
-      path: '/document',
-      component: 'basic',
-      children: [
-        {
-          name: 'document_vue',
-          path: '/document/vue',
-          component: 'self',
-          meta: {
-            title: 'vue文档',
-            requiresAuth: true,
-            icon: 'logos:vue'
-          }
-        },
-        {
-          name: 'document_vite',
-          path: '/document/vite',
-          component: 'self',
-          meta: {
-            title: 'vite文档',
-            requiresAuth: true,
-            icon: 'logos:vitejs'
-          }
-        },
-        {
-          name: 'document_naive',
-          path: '/document/naive',
-          component: 'self',
-          meta: {
-            title: 'naive文档',
-            requiresAuth: true,
-            icon: 'logos:naiveui'
-          }
-        },
-        {
-          name: 'document_project',
-          path: '/document/project',
-          component: 'self',
-          meta: {
-            title: '项目文档',
-            requiresAuth: true,
-            localIcon: 'logo'
-          }
-        },
-        {
-          name: 'document_project-link',
-          path: '/document/project-link',
-          meta: {
-            title: '项目文档(外链)',
-            requiresAuth: true,
-            localIcon: 'logo',
-            href: 'https://docs.soybean.pro/'
-          }
-        }
-      ],
-      meta: {
-        title: '文档',
-        icon: 'mdi:file-document-multiple-outline',
-        order: 2
-      }
-    },
-    {
-      name: 'component',
-      path: '/component',
-      component: 'basic',
-      children: [
-        {
-          name: 'component_button',
-          path: '/component/button',
-          component: 'self',
-          meta: {
-            title: '按钮',
-            requiresAuth: true,
-            icon: 'mdi:button-cursor'
-          }
-        },
-        {
-          name: 'component_card',
-          path: '/component/card',
-          component: 'self',
-          meta: {
-            title: '卡片',
-            requiresAuth: true,
-            icon: 'mdi:card-outline'
-          }
-        },
-        {
-          name: 'component_table',
-          path: '/component/table',
-          component: 'self',
-          meta: {
-            title: '表格',
-            requiresAuth: true,
-            icon: 'mdi:table-large'
-          }
-        }
-      ],
-      meta: {
-        title: '组件示例',
-        icon: 'cib:app-store',
-        order: 3
-      }
-    },
-    {
-      name: 'plugin',
-      path: '/plugin',
-      component: 'basic',
-      children: [
-        {
-          name: 'plugin_charts',
-          path: '/plugin/charts',
-          component: 'multi',
-          children: [
-            {
-              name: 'plugin_charts_echarts',
-              path: '/plugin/charts/echarts',
-              component: 'self',
-              meta: {
-                title: 'ECharts',
-                requiresAuth: true,
-                icon: 'simple-icons:apacheecharts'
-              }
-            },
-            {
-              name: 'plugin_charts_antv',
-              path: '/plugin/charts/antv',
-              component: 'self',
-              meta: {
-                title: 'AntV',
-                requiresAuth: true,
-                icon: 'simple-icons:antdesign'
-              }
-            }
-          ],
-          meta: {
-            title: '图表',
-            icon: 'mdi:chart-areaspline'
-          }
-        },
-        {
-          name: 'plugin_map',
-          path: '/plugin/map',
-          component: 'self',
-          meta: {
-            title: '地图',
-            requiresAuth: true,
-            icon: 'mdi:map'
-          }
-        },
-        {
-          name: 'plugin_video',
-          path: '/plugin/video',
-          component: 'self',
-          meta: {
-            title: '视频',
-            requiresAuth: true,
-            icon: 'mdi:video'
-          }
-        },
-        {
-          name: 'plugin_editor',
-          path: '/plugin/editor',
-          component: 'multi',
-          children: [
-            {
-              name: 'plugin_editor_quill',
-              path: '/plugin/editor/quill',
-              component: 'self',
-              meta: {
-                title: '富文本编辑器',
-                requiresAuth: true,
-                icon: 'mdi:file-document-edit-outline'
-              }
-            },
-            {
-              name: 'plugin_editor_markdown',
-              path: '/plugin/editor/markdown',
-              component: 'self',
-              meta: {
-                title: 'markdown编辑器',
-                requiresAuth: true,
-                icon: 'ri:markdown-line'
-              }
-            }
-          ],
-          meta: {
-            title: '编辑器',
-            icon: 'icon-park-outline:editor'
-          }
-        },
-        {
-          name: 'plugin_swiper',
-          path: '/plugin/swiper',
-          component: 'self',
-          meta: {
-            title: 'Swiper插件',
-            requiresAuth: true,
-            icon: 'simple-icons:swiper'
-          }
-        },
-        {
-          name: 'plugin_copy',
-          path: '/plugin/copy',
-          component: 'self',
-          meta: {
-            title: '剪贴板',
-            requiresAuth: true,
-            icon: 'mdi:clipboard-outline'
-          }
-        },
-        {
-          name: 'plugin_icon',
-          path: '/plugin/icon',
-          component: 'self',
-          meta: {
-            title: '图标',
-            requiresAuth: true,
-            localIcon: 'custom-icon'
-          }
-        },
-        {
-          name: 'plugin_print',
-          path: '/plugin/print',
-          component: 'self',
-          meta: {
-            title: '打印',
-            requiresAuth: true,
-            icon: 'mdi:printer'
-          }
-        }
-      ],
-      meta: {
-        title: '插件示例',
-        icon: 'clarity:plugin-line',
-        order: 4
-      }
-    },
-    {
-      name: 'auth-demo',
-      path: '/auth-demo',
-      component: 'basic',
-      children: [
-        {
-          name: 'auth-demo_permission',
-          path: '/auth-demo/permission',
-          component: 'self',
-          meta: {
-            title: '权限切换',
-            requiresAuth: true,
-            icon: 'ic:round-construction'
-          }
-        },
-        {
-          name: 'auth-demo_super',
-          path: '/auth-demo/super',
-          component: 'self',
-          meta: {
-            title: '超级管理员可见',
-            requiresAuth: true,
-            icon: 'ic:round-supervisor-account'
-          }
-        }
-      ],
-      meta: {
-        title: '权限示例',
-        icon: 'ic:baseline-security',
-        order: 5
-      }
-    },
-    {
-      name: 'function',
-      path: '/function',
-      component: 'basic',
-      children: [
-        {
-          name: 'function_tab',
-          path: '/function/tab',
-          component: 'self',
-          meta: {
-            title: 'Tab',
-            requiresAuth: true,
-            icon: 'ic:round-tab'
-          }
-        },
-        {
-          name: 'function_tab-detail',
-          path: '/function/tab-detail',
-          component: 'self',
-          meta: {
-            title: 'Tab Detail',
-            requiresAuth: true,
-            hide: true,
-            activeMenu: 'function_tab',
-            icon: 'ic:round-tab'
-          }
-        },
-        {
-          name: 'function_tab-multi-detail',
-          path: '/function/tab-multi-detail',
-          component: 'self',
-          meta: {
-            title: 'Tab Multi Detail',
-            requiresAuth: true,
-            hide: true,
-            multiTab: true,
-            activeMenu: 'function_tab',
-            icon: 'ic:round-tab'
-          }
-        }
-      ],
-      meta: {
-        title: '功能',
-        icon: 'icon-park-outline:all-application',
-        order: 6
-      }
-    },
-    {
-      name: 'exception',
-      path: '/exception',
-      component: 'basic',
-      children: [
-        {
-          name: 'exception_403',
-          path: '/exception/403',
-          component: 'self',
-          meta: {
-            title: '异常页403',
-            requiresAuth: true,
-            icon: 'ic:baseline-block'
-          }
-        },
-        {
-          name: 'exception_404',
-          path: '/exception/404',
-          component: 'self',
-          meta: {
-            title: '异常页404',
-            requiresAuth: true,
-            icon: 'ic:baseline-web-asset-off'
-          }
-        },
-        {
-          name: 'exception_500',
-          path: '/exception/500',
-          component: 'self',
-          meta: {
-            title: '异常页500',
-            requiresAuth: true,
-            icon: 'ic:baseline-wifi-off'
-          }
-        }
-      ],
-      meta: {
-        title: '异常页',
-        icon: 'ant-design:exception-outlined',
-        order: 7
-      }
-    },
-    {
-      name: 'multi-menu',
-      path: '/multi-menu',
-      component: 'basic',
-      children: [
-        {
-          name: 'multi-menu_first',
-          path: '/multi-menu/first',
-          component: 'multi',
-          children: [
-            {
-              name: 'multi-menu_first_second',
-              path: '/multi-menu/first/second',
-              component: 'self',
-              meta: {
-                title: '二级菜单',
-                requiresAuth: true,
-                icon: 'mdi:menu'
-              }
-            },
-            {
-              name: 'multi-menu_first_second-new',
-              path: '/multi-menu/first/second-new',
-              component: 'multi',
-              children: [
-                {
-                  name: 'multi-menu_first_second-new_third',
-                  path: '/multi-menu/first/second-new/third',
-                  component: 'self',
-                  meta: {
-                    title: '三级菜单',
-                    requiresAuth: true,
-                    icon: 'mdi:menu'
-                  }
-                }
-              ],
-              meta: {
-                title: '二级菜单(有子菜单)',
-                icon: 'mdi:menu'
-              }
-            }
-          ],
-          meta: {
-            title: '一级菜单',
-            icon: 'mdi:menu'
-          }
-        }
-      ],
-      meta: {
-        title: '多级菜单',
-        icon: 'carbon:menu',
-        order: 8
-      }
-    },
-    {
-      name: 'management',
-      path: '/management',
-      component: 'basic',
-      children: [
-        {
-          name: 'management_auth',
-          path: '/management/auth',
-          component: 'self',
-          meta: {
-            title: '权限管理',
-            requiresAuth: true,
-            icon: 'ic:baseline-security'
-          }
-        },
-        {
-          name: 'management_role',
-          path: '/management/role',
-          component: 'self',
-          meta: {
-            title: '角色管理',
-            requiresAuth: true,
-            icon: 'carbon:user-role'
-          }
-        },
-        {
-          name: 'management_user',
-          path: '/management/user',
-          component: 'self',
-          meta: {
-            title: '用户管理',
-            requiresAuth: true,
-            icon: 'ic:round-manage-accounts'
-          }
-        },
-        {
-          name: 'management_route',
-          path: '/management/route',
-          component: 'self',
-          meta: {
-            title: '路由管理',
-            requiresAuth: true,
-            icon: 'material-symbols:route'
-          }
-        }
-      ],
-      meta: {
-        title: '系统管理',
-        icon: 'carbon:cloud-service-management',
-        order: 9
-      }
-    },
-    {
-      name: 'about',
-      path: '/about',
-      component: 'self',
-      meta: {
-        title: '关于',
-        requiresAuth: true,
-        singleLayout: 'basic',
-        icon: 'fluent:book-information-24-regular',
-        order: 10
-      }
-    }
-  ],
-  admin: [
-    {
-      name: 'dashboard',
-      path: '/dashboard',
-      component: 'basic',
-      children: [
-        {
-          name: 'dashboard_analysis',
-          path: '/dashboard/analysis',
-          component: 'self',
-          meta: {
-            title: '分析页',
-            requiresAuth: true,
-            icon: 'icon-park-outline:analysis'
-          }
-        },
-        {
-          name: 'dashboard_workbench',
-          path: '/dashboard/workbench',
-          component: 'self',
-          meta: {
-            title: '工作台',
-            requiresAuth: true,
-            icon: 'icon-park-outline:workbench'
-          }
-        }
-      ],
-      meta: {
-        title: '仪表盘',
-        icon: 'mdi:monitor-dashboard',
-        order: 1
-      }
-    },
-    {
-      name: 'document',
-      path: '/document',
-      component: 'basic',
-      children: [
-        {
-          name: 'document_vue',
-          path: '/document/vue',
-          component: 'self',
-          meta: {
-            title: 'vue文档',
-            requiresAuth: true,
-            icon: 'logos:vue'
-          }
-        },
-        {
-          name: 'document_vite',
-          path: '/document/vite',
-          component: 'self',
-          meta: {
-            title: 'vite文档',
-            requiresAuth: true,
-            icon: 'logos:vitejs'
-          }
-        },
-        {
-          name: 'document_naive',
-          path: '/document/naive',
-          component: 'self',
-          meta: {
-            title: 'naive文档',
-            requiresAuth: true,
-            icon: 'logos:naiveui'
-          }
-        },
-        {
-          name: 'document_project',
-          path: '/document/project',
-          component: 'self',
-          meta: {
-            title: '项目文档',
-            requiresAuth: true,
-            localIcon: 'logo'
-          }
-        },
-        {
-          name: 'document_project-link',
-          path: '/document/project-link',
-          meta: {
-            title: '项目文档(外链)',
-            requiresAuth: true,
-            localIcon: 'logo',
-            href: 'https://docs.soybean.pro/'
-          }
-        }
-      ],
-      meta: {
-        title: '文档',
-        icon: 'mdi:file-document-multiple-outline',
-        order: 2
-      }
-    },
-    {
-      name: 'component',
-      path: '/component',
-      component: 'basic',
-      children: [
-        {
-          name: 'component_button',
-          path: '/component/button',
-          component: 'self',
-          meta: {
-            title: '按钮',
-            requiresAuth: true,
-            icon: 'mdi:button-cursor'
-          }
-        },
-        {
-          name: 'component_card',
-          path: '/component/card',
-          component: 'self',
-          meta: {
-            title: '卡片',
-            requiresAuth: true,
-            icon: 'mdi:card-outline'
-          }
-        },
-        {
-          name: 'component_table',
-          path: '/component/table',
-          component: 'self',
-          meta: {
-            title: '表格',
-            requiresAuth: true,
-            icon: 'mdi:table-large'
-          }
-        }
-      ],
-      meta: {
-        title: '组件示例',
-        icon: 'cib:app-store',
-        order: 3
-      }
-    },
-    {
-      name: 'plugin',
-      path: '/plugin',
-      component: 'basic',
-      children: [
-        {
-          name: 'plugin_charts',
-          path: '/plugin/charts',
-          component: 'multi',
-          children: [
-            {
-              name: 'plugin_charts_echarts',
-              path: '/plugin/charts/echarts',
-              component: 'self',
-              meta: {
-                title: 'ECharts',
-                requiresAuth: true,
-                icon: 'simple-icons:apacheecharts'
-              }
-            },
-            {
-              name: 'plugin_charts_antv',
-              path: '/plugin/charts/antv',
-              component: 'self',
-              meta: {
-                title: 'AntV',
-                requiresAuth: true,
-                icon: 'simple-icons:antdesign'
-              }
-            }
-          ],
-          meta: {
-            title: '图表',
-            icon: 'mdi:chart-areaspline'
-          }
-        },
-        {
-          name: 'plugin_map',
-          path: '/plugin/map',
-          component: 'self',
-          meta: {
-            title: '地图',
-            requiresAuth: true,
-            icon: 'mdi:map'
-          }
-        },
-        {
-          name: 'plugin_video',
-          path: '/plugin/video',
-          component: 'self',
-          meta: {
-            title: '视频',
-            requiresAuth: true,
-            icon: 'mdi:video'
-          }
-        },
-        {
-          name: 'plugin_editor',
-          path: '/plugin/editor',
-          component: 'multi',
-          children: [
-            {
-              name: 'plugin_editor_quill',
-              path: '/plugin/editor/quill',
-              component: 'self',
-              meta: {
-                title: '富文本编辑器',
-                requiresAuth: true,
-                icon: 'mdi:file-document-edit-outline'
-              }
-            },
-            {
-              name: 'plugin_editor_markdown',
-              path: '/plugin/editor/markdown',
-              component: 'self',
-              meta: {
-                title: 'markdown编辑器',
-                requiresAuth: true,
-                icon: 'ri:markdown-line'
-              }
-            }
-          ],
-          meta: {
-            title: '编辑器',
-            icon: 'icon-park-outline:editor'
-          }
-        },
-        {
-          name: 'plugin_swiper',
-          path: '/plugin/swiper',
-          component: 'self',
-          meta: {
-            title: 'Swiper插件',
-            requiresAuth: true,
-            icon: 'simple-icons:swiper'
-          }
-        },
-        {
-          name: 'plugin_copy',
-          path: '/plugin/copy',
-          component: 'self',
-          meta: {
-            title: '剪贴板',
-            requiresAuth: true,
-            icon: 'mdi:clipboard-outline'
-          }
-        },
-        {
-          name: 'plugin_icon',
-          path: '/plugin/icon',
-          component: 'self',
-          meta: {
-            title: '图标',
-            requiresAuth: true,
-            localIcon: 'custom-icon'
-          }
-        },
-        {
-          name: 'plugin_print',
-          path: '/plugin/print',
-          component: 'self',
-          meta: {
-            title: '打印',
-            requiresAuth: true,
-            icon: 'mdi:printer'
-          }
-        }
-      ],
-      meta: {
-        title: '插件示例',
-        icon: 'clarity:plugin-line',
-        order: 4
-      }
-    },
-    {
-      name: 'auth-demo',
-      path: '/auth-demo',
-      component: 'basic',
-      children: [
-        {
-          name: 'auth-demo_permission',
-          path: '/auth-demo/permission',
-          component: 'self',
-          meta: {
-            title: '权限切换',
-            requiresAuth: true,
-            icon: 'ic:round-construction'
-          }
-        }
-      ],
-      meta: {
-        title: '权限示例',
-        icon: 'ic:baseline-security',
-        order: 5
-      }
-    },
-    {
-      name: 'function',
-      path: '/function',
-      component: 'basic',
-      children: [
-        {
-          name: 'function_tab',
-          path: '/function/tab',
-          component: 'self',
-          meta: {
-            title: 'Tab',
-            requiresAuth: true,
-            icon: 'ic:round-tab'
-          }
-        },
-        {
-          name: 'function_tab-detail',
-          path: '/function/tab-detail',
-          component: 'self',
-          meta: {
-            title: 'Tab Detail',
-            requiresAuth: true,
-            hide: true,
-            activeMenu: 'function_tab',
-            icon: 'ic:round-tab'
-          }
-        },
-        {
-          name: 'function_tab-multi-detail',
-          path: '/function/tab-multi-detail',
-          component: 'self',
-          meta: {
-            title: 'Tab Multi Detail',
-            requiresAuth: true,
-            hide: true,
-            multiTab: true,
-            activeMenu: 'function_tab',
-            icon: 'ic:round-tab'
-          }
-        }
-      ],
-      meta: {
-        title: '功能',
-        icon: 'icon-park-outline:all-application',
-        order: 6
-      }
-    },
-    {
-      name: 'exception',
-      path: '/exception',
-      component: 'basic',
-      children: [
-        {
-          name: 'exception_403',
-          path: '/exception/403',
-          component: 'self',
-          meta: {
-            title: '异常页403',
-            requiresAuth: true,
-            icon: 'ic:baseline-block'
-          }
-        },
-        {
-          name: 'exception_404',
-          path: '/exception/404',
-          component: 'self',
-          meta: {
-            title: '异常页404',
-            requiresAuth: true,
-            icon: 'ic:baseline-web-asset-off'
-          }
-        },
-        {
-          name: 'exception_500',
-          path: '/exception/500',
-          component: 'self',
-          meta: {
-            title: '异常页500',
-            requiresAuth: true,
-            icon: 'ic:baseline-wifi-off'
-          }
-        }
-      ],
-      meta: {
-        title: '异常页',
-        icon: 'ant-design:exception-outlined',
-        order: 7
-      }
-    },
-    {
-      name: 'multi-menu',
-      path: '/multi-menu',
-      component: 'basic',
-      children: [
-        {
-          name: 'multi-menu_first',
-          path: '/multi-menu/first',
-          component: 'multi',
-          children: [
-            {
-              name: 'multi-menu_first_second',
-              path: '/multi-menu/first/second',
-              component: 'self',
-              meta: {
-                title: '二级菜单',
-                requiresAuth: true,
-                icon: 'mdi:menu'
-              }
-            },
-            {
-              name: 'multi-menu_first_second-new',
-              path: '/multi-menu/first/second-new',
-              component: 'multi',
-              children: [
-                {
-                  name: 'multi-menu_first_second-new_third',
-                  path: '/multi-menu/first/second-new/third',
-                  component: 'self',
-                  meta: {
-                    title: '三级菜单',
-                    requiresAuth: true,
-                    icon: 'mdi:menu'
-                  }
-                }
-              ],
-              meta: {
-                title: '二级菜单(有子菜单)',
-                icon: 'mdi:menu'
-              }
-            }
-          ],
-          meta: {
-            title: '一级菜单',
-            icon: 'mdi:menu'
-          }
-        }
-      ],
-      meta: {
-        title: '多级菜单',
-        icon: 'carbon:menu',
-        order: 8
-      }
-    },
-    {
-      name: 'management',
-      path: '/management',
-      component: 'basic',
-      children: [
-        {
-          name: 'management_auth',
-          path: '/management/auth',
-          component: 'self',
-          meta: {
-            title: '权限管理',
-            requiresAuth: true,
-            icon: 'ic:baseline-security'
-          }
-        },
-        {
-          name: 'management_role',
-          path: '/management/role',
-          component: 'self',
-          meta: {
-            title: '角色管理',
-            requiresAuth: true,
-            icon: 'carbon:user-role'
-          }
-        },
-        {
-          name: 'management_user',
-          path: '/management/user',
-          component: 'self',
-          meta: {
-            title: '用户管理',
-            requiresAuth: true,
-            icon: 'ic:round-manage-accounts'
-          }
-        },
-        {
-          name: 'management_route',
-          path: '/management/route',
-          component: 'self',
-          meta: {
-            title: '路由管理',
-            requiresAuth: true,
-            icon: 'material-symbols:route'
-          }
-        }
-      ],
-      meta: {
-        title: '系统管理',
-        icon: 'carbon:cloud-service-management',
-        order: 9
-      }
-    },
-    {
-      name: 'about',
-      path: '/about',
-      component: 'self',
-      meta: {
-        title: '关于',
-        requiresAuth: true,
-        singleLayout: 'basic',
-        icon: 'fluent:book-information-24-regular',
-        order: 10
-      }
-    }
-  ],
-  user: [
-    {
-      name: 'dashboard',
-      path: '/dashboard',
-      component: 'basic',
-      children: [
-        {
-          name: 'dashboard_analysis',
-          path: '/dashboard/analysis',
-          component: 'self',
-          meta: {
-            title: '分析页',
-            requiresAuth: true,
-            icon: 'icon-park-outline:analysis'
-          }
-        }
-      ],
-      meta: {
-        title: '仪表盘',
-        icon: 'mdi:monitor-dashboard',
-        order: 1
-      }
-    },
-    {
-      name: 'auth-demo',
-      path: '/auth-demo',
-      component: 'basic',
-      children: [
-        {
-          name: 'auth-demo_permission',
-          path: '/auth-demo/permission',
-          component: 'self',
-          meta: {
-            title: '权限切换',
-            requiresAuth: true,
-            icon: 'ic:round-construction'
-          }
-        }
-      ],
-      meta: {
-        title: '权限示例',
-        icon: 'ic:baseline-security',
-        order: 5
-      }
-    },
-    {
-      name: 'multi-menu',
-      path: '/multi-menu',
-      component: 'basic',
-      children: [
-        {
-          name: 'multi-menu_first',
-          path: '/multi-menu/first',
-          component: 'multi',
-          children: [
-            {
-              name: 'multi-menu_first_second',
-              path: '/multi-menu/first/second',
-              component: 'self',
-              meta: {
-                title: '二级菜单',
-                requiresAuth: true,
-                icon: 'mdi:menu'
-              }
-            },
-            {
-              name: 'multi-menu_first_second-new',
-              path: '/multi-menu/first/second-new',
-              component: 'multi',
-              children: [
-                {
-                  name: 'multi-menu_first_second-new_third',
-                  path: '/multi-menu/first/second-new/third',
-                  component: 'self',
-                  meta: {
-                    title: '三级菜单',
-                    requiresAuth: true,
-                    icon: 'mdi:menu'
-                  }
-                }
-              ],
-              meta: {
-                title: '二级菜单(有子菜单)',
-                icon: 'mdi:menu'
-              }
-            }
-          ],
-          meta: {
-            title: '一级菜单',
-            icon: 'mdi:menu'
-          }
-        }
-      ],
-      meta: {
-        title: '多级菜单',
-        icon: 'carbon:menu',
-        order: 7
-      }
-    },
-    {
-      name: 'about',
-      path: '/about',
-      component: 'self',
-      meta: {
-        title: '关于',
-        requiresAuth: true,
-        singleLayout: 'basic',
-        icon: 'fluent:book-information-24-regular',
-        order: 8
-      }
-    }
-  ]
-};

+ 12 - 35
package.json

@@ -1,39 +1,8 @@
 {
-  "name": "soybean-admin",
+  "name": "eas-admin",
   "version": "0.9.9",
-  "description": "A fresh and elegant admin template, based on Vue3、Vite3、TypeScript、NaiveUI and UnoCSS. 一个基于Vue3、Vite3、TypeScript、NaiveUI and UnoCSS的清新优雅的中后台模版。",
-  "author": {
-    "name": "Soybean",
-    "email": "honghuangdc@gmail.com",
-    "url": "https://github.com/honghuangdc"
-  },
-  "license": "MIT",
-  "homepage": "https://github.com/honghuangdc/soybean-admin",
-  "repository": {
-    "url": "https://github.com/honghuangdc/soybean-admin.git"
-  },
-  "bugs": {
-    "url": "https://github.com/honghuangdc/soybean-admin/issues"
-  },
-  "keywords": [
-    "Vue",
-    "Vue3",
-    "admin",
-    "admin-template",
-    "vue-admin",
-    "vue-admin-template",
-    "Vite3",
-    "Vite",
-    "vite-admin",
-    "TypeScript",
-    "TS",
-    "NaiveUI",
-    "naive-ui",
-    "naive-admin",
-    "NaiveUI-Admin",
-    "naive-ui-admin",
-    "UnoCSS"
-  ],
+  "description": "",
+  "keywords": [],
   "scripts": {
     "dev": "cross-env VITE_SERVICE_ENV=dev vite",
     "dev:test": "cross-env VITE_SERVICE_ENV=test vite",
@@ -63,8 +32,12 @@
     "@fast-crud/fast-extends": "^1.13.6",
     "@fast-crud/ui-interface": "^1.13.6",
     "@fast-crud/ui-naive": "^1.13.6",
+    "@iconify/iconify": "2.0.1",
+    "@purge-icons/generated": "0.7.0",
     "@soybeanjs/vue-materials": "^0.1.9",
+    "@vicons/antd": "^0.12.0",
     "@vueuse/core": "^10.1.2",
+    "ant-design-vue": "4.0.0-rc.6",
     "axios": "1.4.0",
     "clipboard": "^2.0.11",
     "colord": "^2.9.3",
@@ -80,14 +53,18 @@
     "swiper": "^9.3.2",
     "ua-parser-js": "^1.0.35",
     "vditor": "^3.9.2",
+    "vite-plugin-purge-icons": "0.7.0",
     "vue": "3.3.4",
     "vue-i18n": "^9.2.2",
     "vue-monoplasty-slide-verify": "^1.3.1",
+    "vue-pdf-embed": "^1.1.6",
     "vue-router": "^4.2.1",
     "vue-slider-vertify": "^0.0.1",
+    "vue3-pdfjs": "^0.1.6",
     "vuedraggable": "^4.1.0",
     "wangeditor": "^4.7.15",
-    "xgplayer": "^3.0.2"
+    "xgplayer": "^3.0.2",
+    "xlsx": "^0.18.5"
   },
   "devDependencies": {
     "@amap/amap-jsapi-types": "^0.0.13",

文件差異過大導致無法顯示
+ 252 - 68
pnpm-lock.yaml


+ 598 - 1
public/favicon.svg

@@ -1 +1,598 @@
-<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><path d="M81.28 55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1 8.1 0 014-8.61 7.89 7.89 0 019.3 1.23 35.999 35.999 0 015.9 8.83 75.18 75.18 0 018.44 28.58 83.211 83.211 0 01-5.23 36.74 102.983 102.983 0 01-3 7.28 1.2 1.2 0 000 1.41c9.58 13.3 21.76 23 37.85 27.24a54.37 54.37 0 0019.68 1.57 7.72 7.72 0 018.36 6.9 7.903 7.903 0 01-6.7 9 64.744 64.744 0 01-23-1.33 77.68 77.68 0 01-36.93-19.88 93.628 93.628 0 01-11.91-13.71 2.18 2.18 0 00-2.3-1.06 72.744 72.744 0 00-27.38 7.55c-11.6 6-20.67 14.58-26.4 26.45a10.134 10.134 0 01-3.7 4.7 8 8 0 01-9.19-.7 7.86 7.86 0 01-2.36-9.28 60.324 60.324 0 018.72-14.52c12.2-15.43 28.21-24.59 47.32-28.57A85.085 85.085 0 0173.07 87c.524.015 1-.307 1.18-.8a76.06 76.06 0 006.53-22.3c.351-2.652.518-5.325.5-8z" fill="#1890ff"/><path d="M136.26 108.34a44.742 44.742 0 01-11.13-2.87 46.108 46.108 0 01-19.66-13.76 8 8 0 015.72-13.22 7.93 7.93 0 016.54 2.93 33.27 33.27 0 0018.87 10.75c1.546.155 3.058.553 4.48 1.18a8.08 8.08 0 013.84 9.21c-.92 3.52-4.13 5.81-8.66 5.78zm-80.6-75.02a7.61 7.61 0 016.64 5 49.139 49.139 0 013.64 17 46.33 46.33 0 01-2.46 17.28c-2 5.77-8.24 7.79-12.89 4.15a8.1 8.1 0 01-2.39-9 31.679 31.679 0 001.68-12.36 35.77 35.77 0 00-2.43-11c-2.1-5.45 1.75-11.07 8.21-11.07zm22.26 93.25a8 8 0 01-6.68 7.86 32.88 32.88 0 00-19.7 12.19 8.13 8.13 0 01-11.21 1.62 8 8 0 01-1.41-11.58A51.043 51.043 0 0154 123.81a45.842 45.842 0 0114-5.1c5.35-1.04 9.91 2.56 9.92 7.86z" fill="#1890ff"/></svg>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="400px" height="400px" viewBox="0 0 400 400" enable-background="new 0 0 400 400" xml:space="preserve">  <image id="image0"  x="0" y="0"
+    href="data:image/png;base64,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
+MMt2hmYAAAAASUVORK5CYII=" />
+</svg>

二進制
public/logo.png


+ 1 - 0
src/assets/svg-icon/academic-cap.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 20 20"><path fill="currentColor" d="M10.394 2.08a1 1 0 0 0-.788 0l-7 3a1 1 0 0 0 0 1.84L5.25 8.051a.999.999 0 0 1 .356-.257l4-1.714a1 1 0 1 1 .788 1.838l-2.727 1.17l1.94.831a1 1 0 0 0 .787 0l7-3a1 1 0 0 0 0-1.838l-7-3ZM3.31 9.397L5 10.12v4.102a8.969 8.969 0 0 0-1.05-.174a1 1 0 0 1-.89-.89a11.115 11.115 0 0 1 .25-3.762Zm5.99 7.176A9.026 9.026 0 0 0 7 14.935v-3.957l1.818.78a3 3 0 0 0 2.364 0l5.508-2.361a11.026 11.026 0 0 1 .25 3.762a1 1 0 0 1-.89.89a8.968 8.968 0 0 0-5.35 2.524a1 1 0 0 1-1.4 0ZM6 18a1 1 0 0 0 1-1v-2.065a8.935 8.935 0 0 0-2-.712V17a1 1 0 0 0 1 1Z"/></svg>

+ 1 - 0
src/assets/svg-icon/class.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1692672982685" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11328" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M297.6 387.9c-65.6 0-119-52-119-115.9s53.4-115.9 119-115.9 119 52 119 115.9-53.4 115.9-119 115.9z m0-191.8c-43.6 0-79 34-79 75.9 0 41.8 35.5 75.9 79 75.9s79-34 79-75.9c0-41.8-35.5-75.9-79-75.9z" fill="#1C1C1C" p-id="11329"></path><path d="M485.5 568.3h-40V489c0-53.7-43.7-97.4-97.4-97.4H246.9c-53.7 0-97.4 43.7-97.4 97.4v79.3h-40V489c0-75.7 61.6-137.4 137.4-137.4h101.3c75.7 0 137.4 61.6 137.4 137.4v79.3zM740.8 387.9c-65.6 0-119-52-119-115.9s53.4-115.9 119-115.9 119 52 119 115.9-53.4 115.9-119 115.9z m0-191.8c-43.6 0-79 34-79 75.9 0 41.8 35.5 75.9 79 75.9s79-34 79-75.9c0-41.8-35.5-75.9-79-75.9z" fill="#1C1C1C" p-id="11330"></path><path d="M928.8 568.3h-40V489c0-53.7-43.7-97.4-97.4-97.4H690.1c-53.7 0-97.4 43.7-97.4 97.4v79.3h-40V489c0-75.7 61.6-137.4 137.4-137.4h101.3c75.7 0 137.4 61.6 137.4 137.4v79.3z" fill="#1C1C1C" p-id="11331"></path><path d="M996.5 722.1H33.6c-11 0-20-9-20-20V584.5c0-11 9-20 20-20h962.9c11 0 20 9 20 20v117.7c0 11-9 19.9-20 19.9z m-942.9-40h922.9v-77.7H53.6v77.7z" fill="#1C1C1C" p-id="11332"></path><path d="M125.4 926.3c-11 0-20-9-20-20V720.1c0-11 9-20 20-20s20 9 20 20v186.2c0 11.1-8.9 20-20 20zM904.6 926.3c-11 0-20-9-20-20V702.1c0-11 9-20 20-20s20 9 20 20v204.2c0 11.1-9 20-20 20z" fill="#1C1C1C" p-id="11333"></path></svg>

文件差異過大導致無法顯示
+ 0 - 0
src/assets/svg-icon/classroom.svg


文件差異過大導致無法顯示
+ 0 - 0
src/assets/svg-icon/lesson.svg


+ 4 - 1
src/assets/svg-icon/logo-fill.svg

@@ -1 +1,4 @@
-<svg xmlns="http://www.w3.org/2000/svg"><path d="M0 0h160v160H0V0z" fill="currentColor"/><path d="M94.322 51.888A69.12 69.12 0 0187.806 80.9a1.732 1.732 0 00.191 2.014c6.124 8.338 13.677 14.894 23.356 18.821a46.564 46.564 0 0017.273 3.414 29.101 29.101 0 003.364-.252 6.245 6.245 0 017.051 5.156 6.112 6.112 0 01-5.187 7.19 50.758 50.758 0 01-18.19-1.007c-15.964-3.686-28.2-12.84-37.709-25.88a2.165 2.165 0 00-2.246-1.098c-14.1 1.38-26.357 6.475-35.754 17.331a38.721 38.721 0 00-6.275 9.808 6.255 6.255 0 01-8.229 3.444 6.184 6.184 0 01-3.293-8.258 49.662 49.662 0 019.699-14.722c10.636-11.52 23.97-17.663 39.37-19.677a14.06 14.06 0 012.86-.342c1.622.14 2.197-.735 2.75-2.014a54.752 54.752 0 004.865-23.463 44.302 44.302 0 00-8.057-25.175 6.152 6.152 0 01-.655-6.506 6.043 6.043 0 015.318-3.564 6.386 6.386 0 015.7 3.02 53.98 53.98 0 017.222 14.38 59.734 59.734 0 013.092 18.368z" fill="#fff"/><path d="M47.257 119.468a6.04 6.04 0 011.36-3.907 38.165 38.165 0 0122.66-14.098 6.124 6.124 0 016.699 2.487 6.223 6.223 0 01-3.868 9.698 26.276 26.276 0 00-15.823 9.838 6.245 6.245 0 01-11.028-4.028v.01zm77.935-26.01a34.908 34.908 0 01-9.89-2.498 35.717 35.717 0 01-14.756-10.523 6.233 6.233 0 012.861-10 5.832 5.832 0 016.486 1.742 26.986 26.986 0 0016.628 8.912 6.042 6.042 0 015.036 5.58 6.253 6.253 0 01-4.32 6.504 6.588 6.588 0 01-2.045.282zM69.817 53.65a33.69 33.69 0 01-2.286 12.607 6.255 6.255 0 01-11.018 1.007 6.132 6.132 0 01-.655-5.438 26.178 26.178 0 00-.534-18.377 6.256 6.256 0 0111.572-4.753 40.515 40.515 0 012.921 14.954z" fill="#fff"/></svg>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024"><path fill="currentColor" d="M516 673c0 4.4 3.4 8 7.5 8h185c4.1 0 7.5-3.6 7.5-8v-48c0-4.4-3.4-8-7.5-8h-185c-4.1 0-7.5 3.6-7.5 8v48zm-194.9 6.1l192-161c3.8-3.2 3.8-9.1 0-12.3l-192-160.9A7.95 7.95 0 0 0 308 351v62.7c0 2.4 1 4.6 2.9 6.1L420.7 512l-109.8 92.2a8.1 8.1 0 0 0-2.9 6.1V673c0 6.8 7.9 10.5 13.1 6.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"/></svg>

+ 4 - 1
src/assets/svg-icon/logo.svg

@@ -1 +1,4 @@
-<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><path d="M81.28 55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1 8.1 0 014-8.61 7.89 7.89 0 019.3 1.23 35.999 35.999 0 015.9 8.83 75.18 75.18 0 018.44 28.58 83.211 83.211 0 01-5.23 36.74 102.983 102.983 0 01-3 7.28 1.2 1.2 0 000 1.41c9.58 13.3 21.76 23 37.85 27.24a54.37 54.37 0 0019.68 1.57 7.72 7.72 0 018.36 6.9 7.903 7.903 0 01-6.7 9 64.744 64.744 0 01-23-1.33 77.68 77.68 0 01-36.93-19.88 93.628 93.628 0 01-11.91-13.71 2.18 2.18 0 00-2.3-1.06 72.744 72.744 0 00-27.38 7.55c-11.6 6-20.67 14.58-26.4 26.45a10.134 10.134 0 01-3.7 4.7 8 8 0 01-9.19-.7 7.86 7.86 0 01-2.36-9.28 60.324 60.324 0 018.72-14.52c12.2-15.43 28.21-24.59 47.32-28.57A85.085 85.085 0 0173.07 87c.524.015 1-.307 1.18-.8a76.06 76.06 0 006.53-22.3c.351-2.652.518-5.325.5-8z" fill="currentColor"/><path d="M136.26 108.34a44.742 44.742 0 01-11.13-2.87 46.108 46.108 0 01-19.66-13.76 8 8 0 015.72-13.22 7.93 7.93 0 016.54 2.93 33.27 33.27 0 0018.87 10.75c1.546.155 3.058.553 4.48 1.18a8.08 8.08 0 013.84 9.21c-.92 3.52-4.13 5.81-8.66 5.78zm-80.6-75.02a7.61 7.61 0 016.64 5 49.139 49.139 0 013.64 17 46.33 46.33 0 01-2.46 17.28c-2 5.77-8.24 7.79-12.89 4.15a8.1 8.1 0 01-2.39-9 31.679 31.679 0 001.68-12.36 35.77 35.77 0 00-2.43-11c-2.1-5.45 1.75-11.07 8.21-11.07zm22.26 93.25a8 8 0 01-6.68 7.86 32.88 32.88 0 00-19.7 12.19 8.13 8.13 0 01-11.21 1.62 8 8 0 01-1.41-11.58A51.043 51.043 0 0154 123.81a45.842 45.842 0 0114-5.1c5.35-1.04 9.91 2.56 9.92 7.86z" fill="currentColor"/></svg>
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024"><path fill="currentColor" d="M516 673c0 4.4 3.4 8 7.5 8h185c4.1 0 7.5-3.6 7.5-8v-48c0-4.4-3.4-8-7.5-8h-185c-4.1 0-7.5 3.6-7.5 8v48zm-194.9 6.1l192-161c3.8-3.2 3.8-9.1 0-12.3l-192-160.9A7.95 7.95 0 0 0 308 351v62.7c0 2.4 1 4.6 2.9 6.1L420.7 512l-109.8 92.2a8.1 8.1 0 0 0-2.9 6.1V673c0 6.8 7.9 10.5 13.1 6.1zM880 112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V144c0-17.7-14.3-32-32-32zm-40 728H184V184h656v656z"/></svg>
+

+ 1 - 0
src/assets/svg-icon/training-class.svg

@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 48 48"><path fill="currentColor" fill-rule="evenodd" d="M6 6h31v5h-2V8H8v23h21.387v2H6V6Zm30 13a3 3 0 1 0 0-6a3 3 0 0 0 0 6Zm2.031 2.01c1.299 0 2.327.584 3 1.486c.629.845.895 1.89.955 2.855a7.626 7.626 0 0 1-.397 2.92c-.3.87-.807 1.77-1.589 2.387V40.5a1.5 1.5 0 0 1-2.98.247L35.73 33h-.298l-1.458 7.776A1.5 1.5 0 0 1 31 40.5V26.233a63.223 63.223 0 0 0-.592.919l-.078.123l-.02.032l-.005.009a1.5 1.5 0 0 1-1.274.707h-5a1.5 1.5 0 1 1 0-3h4.177c.243-.376.563-.864.899-1.354c.35-.511.736-1.052 1.08-1.476c.167-.207.354-.423.542-.6c.092-.087.22-.2.376-.3a1.72 1.72 0 0 1 .926-.282h6Z" clip-rule="evenodd"/></svg>

+ 120 - 0
src/components/View/pdfPreview.vue

@@ -0,0 +1,120 @@
+<template>
+  <div class="pdf-preview">
+    <div class="pdf-wrap">
+      <vue-pdf-embed :source="state.source" :style="scale" class="vue-pdf-embed" :page="state.pageNum" />
+    </div>
+    <div class="page-tool">
+      <div class="page-tool-item" @click="lastPage">上一页</div>
+      <div class="page-tool-item" @click="nextPage">下一页</div>
+      <div class="page-tool-item">{{ state.pageNum }}/{{ state.numPages }}</div>
+      <div class="page-tool-item" @click="pageZoomOut">放大</div>
+      <div class="page-tool-item" @click="pageZoomIn">缩小</div>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import { reactive, onMounted, computed } from 'vue';
+import VuePdfEmbed from 'vue-pdf-embed';
+import { createLoadingTask } from 'vue3-pdfjs';
+
+const props = defineProps({
+  pdfUrl: {
+    type: String,
+    required: true
+  }
+});
+const state = reactive({
+  source: props.pdfUrl,
+  pageNum: 1,
+  scale: 1, // 缩放比例
+  numPages: 0 // 总页数
+});
+
+const scale = computed(() => `transform:scale(${state.scale})`);
+function lastPage() {
+  if (state.pageNum > 1) {
+    state.pageNum -= 1;
+  }
+}
+function nextPage() {
+  if (state.pageNum < state.numPages) {
+    state.pageNum += 1;
+  }
+}
+function pageZoomOut() {
+  if (state.scale < 2) {
+    state.scale += 0.1;
+  }
+}
+function pageZoomIn() {
+  if (state.scale > 1) {
+    state.scale -= 0.1;
+  }
+}
+
+onMounted(() => {
+  const loadingTask = createLoadingTask(state.source);
+  loadingTask.promise.then((pdf: { numPages: number }) => {
+    state.numPages = pdf.numPages;
+  });
+});
+</script>
+<style lang="css" scoped>
+.pdf-preview {
+  position: relative;
+  height: 100vh;
+  padding: 20px 0;
+  box-sizing: border-box;
+  background: rgb(66, 66, 66);
+}
+
+.vue-pdf-embed {
+  text-align: center;
+  width: 515px;
+  border: 1px solid #e5e5e5;
+  margin: 0 auto;
+  box-sizing: border-box;
+}
+
+.pdf-preview {
+  position: relative;
+  height: 100vh;
+  padding: 20px 0;
+  box-sizing: border-box;
+  background-color: e9e9e9;
+}
+
+.pdf-wrap {
+  overflow-y: auto;
+}
+
+.vue-pdf-embed {
+  text-align: center;
+  width: 515px;
+  border: 1px solid #e5e5e5;
+  margin: 0 auto;
+  box-sizing: border-box;
+}
+
+.page-tool {
+  position: absolute;
+  bottom: 35px;
+  padding-left: 15px;
+  padding-right: 15px;
+  display: flex;
+  align-items: center;
+  background: rgb(66, 66, 66);
+  color: white;
+  border-radius: 19px;
+  z-index: 100;
+  cursor: pointer;
+  margin-left: 50%;
+  transform: translateX(-50%);
+}
+
+.page-tool-item {
+  padding: 8px 15px;
+  padding-left: 10px;
+  cursor: pointer;
+}
+</style>

+ 4 - 5
src/composables/system.ts

@@ -34,15 +34,14 @@ export function usePermission() {
   const auth = useAuthStore();
 
   function hasPermission(permission: Auth.RoleType | Auth.RoleType[]) {
-    const { userRole } = auth.userInfo;
-
-    let has = userRole === 'super';
+    const { userType } = auth.userInfo;
+    let has = userType === 'admin';
     if (!has) {
       if (isArray(permission)) {
-        has = (permission as Auth.RoleType[]).includes(userRole);
+        has = (permission as Auth.RoleType[]).includes(userType);
       }
       if (isString(permission)) {
-        has = (permission as Auth.RoleType) === userRole;
+        has = (permission as Auth.RoleType) === userType;
       }
     }
     return has;

+ 1 - 1
src/config/regexp.ts

@@ -7,7 +7,7 @@ export const REGEXP_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
 
 /** 密码正则(密码为6-18位数字/字符/符号的组合) */
 export const REGEXP_PWD = /^([0-9]{6,18}|[a-zA-Z]{6,18}|[^0-9a-zA-Z]{6,18})$/;
-  // /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){6,18}$/;
+// /^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){6,18}$/;
 
 /** 6位数字验证码正则 */
 export const REGEXP_CODE_SIX = /^\d{6}$/;

+ 5 - 3
src/config/service.ts

@@ -40,7 +40,9 @@ export const ERROR_STATUS = {
 };
 
 /** 不弹出错误信息的code */
-export const NO_ERROR_MSG_CODE: (string | number)[] = [];
+export const NO_ERROR_MSG_CODE: (string | number | null)[] = [];
 
-/** token失效需要刷新token的code(这里的66666只是个例子,需要将后端表示token过期的code填进来) */
-export const REFRESH_TOKEN_CODE: (string | number)[] = ['token'];
+/** token失效需要刷新token的code(需要将后端表示token过期的code填进来) */
+export const REFRESH_TOKEN_CODE: (string | number)[] = [9988];
+
+export const REFRESH_TOKEN_COUNT: (string | number)[] = [];

+ 4 - 31
src/constants/business.ts

@@ -1,41 +1,14 @@
 export const loginModuleLabels: Record<UnionKey.LoginModule, string> = {
-  'pwd-login': '账密登录',
-  'code-login': '手机验证码登录',
-  register: '注册',
-  'reset-pwd': '重置密码',
-  'bind-wechat': '微信绑定'
+  'pwd-login': '账密登录'
 };
 
-export const userRoleLabels: Record<Auth.RoleType, string> = {
-  super: '超级管理员',
-  admin: '管理员',
-  user: '普通用户'
-};
-
-export const userRoleOptions: Common.OptionWithKey<Auth.RoleType>[] = [
-  { value: 'super', label: userRoleLabels.super },
-  { value: 'admin', label: userRoleLabels.admin },
-  { value: 'user', label: userRoleLabels.user }
-];
-
-/** 用户性别 */
-export const genderLabels: Record<UserManagement.GenderKey, string> = {
-  0: '女',
-  1: '男'
-};
-
-export const genderOptions: Common.OptionWithKey<UserManagement.GenderKey>[] = [
-  { value: '0', label: genderLabels['0'] },
-  { value: '1', label: genderLabels['1'] }
-];
-
 /** 用户状态 */
 export const userStatusLabels: Record<UserManagement.UserStatusKey, string> = {
   Y: 'Y',
-  N: 'N',
+  N: 'N'
 };
 
 export const userStatusOptions: Common.OptionWithKey<UserManagement.UserStatusKey>[] = [
-  { value: 'Y', label: userStatusLabels['Y'] },
-  { value: 'N', label: userStatusLabels['N'] },
+  { value: 'Y', label: userStatusLabels.Y },
+  { value: 'N', label: userStatusLabels.N }
 ];

+ 0 - 43
src/context/demo.ts

@@ -1,43 +0,0 @@
-import { ref } from 'vue';
-import type { Ref } from 'vue';
-import { useContext } from '@/hooks';
-
-interface DemoContext {
-  counts: Ref<number>;
-  setCounts: (count: number) => void;
-}
-
-const { useProvide: useDemoProvide, useInject: useDemoInject } = useContext<DemoContext>();
-
-export function useDemoContext() {
-  const counts = ref(0);
-
-  function setCounts(count: number) {
-    counts.value = count;
-  }
-
-  const demoContext: DemoContext = {
-    counts,
-    setCounts
-  };
-
-  function useProvide() {
-    return useDemoProvide(demoContext);
-  }
-
-  return {
-    useProvide,
-    useInject: useDemoInject
-  };
-}
-
-// 示例用法: A.vue为父组件, B.vue为子孙组件 C.vue为子孙组件
-// A.vue
-// import { useDemoContext } from '@/context';
-// const { useProvide } = useDemoContext();
-// const { counts, setCounts } = useProvide();
-
-// B.vue 和 C.vue : 共享状态 counts
-// import { useDemoContext } from '@/context';
-// const { useInject } = useDemoContext();
-// const { counts, setCounts } = useInject();

+ 0 - 1
src/context/index.ts

@@ -1 +0,0 @@
-export * from './demo';

+ 1 - 1
src/hooks/business/use-table.ts

@@ -32,7 +32,7 @@ type ApiFn<Params = ApiParams, TableData = Record<string, unknown>> = (
  */
 type TransformedTableData<TableData = Record<string, unknown>> = {
   data: TableData[];
-	status:boolean;
+  status: boolean;
   pageNum: number;
   pageSize: number;
   total: number;

+ 0 - 1
src/layouts/common/global-content/index.vue

@@ -2,7 +2,6 @@
   <router-view v-slot="{ Component, route }">
     <transition
       :name="theme.pageAnimateMode"
-      mode="out-in"
       :appear="true"
       @before-leave="app.setDisableMainXScroll(true)"
       @after-enter="app.setDisableMainXScroll(false)"

+ 20 - 0
src/layouts/common/global-header/components/drawer-toggle.vue

@@ -0,0 +1,20 @@
+<template>
+  <hover-container
+    class="w-40px h-full"
+    tooltip-content="主题设置"
+    :inverted="theme.header.inverted"
+    @click="app.toggleSettingDrawerVisible"
+  >
+    <icon-ant-design-setting-outlined class="text-18px" />
+  </hover-container>
+</template>
+
+<script lang="ts" setup>
+defineOptions({ name: 'DrawerToggle' });
+import { useAppStore, useThemeStore } from '@/store';
+
+const app = useAppStore();
+const theme = useThemeStore();
+</script>
+
+<style scoped></style>

+ 0 - 23
src/layouts/common/global-header/components/github-site.vue

@@ -1,23 +0,0 @@
-<template>
-  <hover-container
-    tooltip-content="github"
-    class="w-40px h-full"
-    :inverted="theme.header.inverted"
-    @click="handleClickLink"
-  >
-    <icon-mdi-github class="text-20px" />
-  </hover-container>
-</template>
-
-<script lang="ts" setup>
-import { useThemeStore } from '@/store';
-
-defineOptions({ name: 'GithubSite' });
-
-const theme = useThemeStore();
-function handleClickLink() {
-  window.open('https://github.com/honghuangdc/soybean-admin', '_blank');
-}
-</script>
-
-<style scoped></style>

+ 3 - 3
src/layouts/common/global-header/components/index.ts

@@ -1,23 +1,23 @@
 import MenuCollapse from './menu-collapse.vue';
 import GlobalBreadcrumb from './global-breadcrumb.vue';
 import HeaderMenu from './header-menu.vue';
-import GithubSite from './github-site.vue';
 import FullScreen from './full-screen.vue';
 import ThemeMode from './theme-mode.vue';
 import UserAvatar from './user-avatar.vue';
 import SystemMessage from './system-message.vue';
 import SettingButton from './setting-button.vue';
 import ToggleLang from './toggle-lang.vue';
+import DrawerToggle from './drawer-toggle.vue';
 
 export {
   MenuCollapse,
   GlobalBreadcrumb,
   HeaderMenu,
-  GithubSite,
   FullScreen,
   ThemeMode,
   UserAvatar,
   SystemMessage,
   SettingButton,
-  ToggleLang
+  ToggleLang,
+  DrawerToggle
 };

+ 8 - 2
src/layouts/common/global-header/components/user-avatar.vue

@@ -2,12 +2,13 @@
   <n-dropdown :options="options" @select="handleDropdown">
     <hover-container class="px-12px" :inverted="theme.header.inverted">
       <icon-local-avatar class="text-32px" />
-      <span class="pl-8px text-16px font-medium">{{ auth.userInfo.userName }}</span>
+      <span class="pl-8px text-16px font-medium">{{ auth.userInfo.username }}</span>
     </hover-container>
   </n-dropdown>
 </template>
 
 <script lang="ts" setup>
+import { useRouter } from 'vue-router';
 import type { DropdownOption } from 'naive-ui';
 import { useAuthStore, useThemeStore } from '@/store';
 import { useIconRender } from '@/composables';
@@ -17,7 +18,7 @@ defineOptions({ name: 'UserAvatar' });
 const auth = useAuthStore();
 const theme = useThemeStore();
 const { iconRender } = useIconRender();
-
+const router = useRouter();
 const options: DropdownOption[] = [
   {
     label: '用户中心',
@@ -39,6 +40,11 @@ type DropdownKey = 'user-center' | 'logout';
 
 function handleDropdown(optionKey: string) {
   const key = optionKey as DropdownKey;
+  if (key === 'user-center') {
+    router.push({
+      path: '/system/profile'
+    });
+  }
   if (key === 'logout') {
     window.$dialog?.info({
       title: '提示',

+ 4 - 4
src/layouts/common/global-header/index.vue

@@ -8,11 +8,11 @@
     <header-menu v-else />
     <div class="flex justify-end h-full">
       <global-search />
-      <github-site />
       <full-screen />
       <theme-mode />
+      <drawer-toggle />
       <toggle-lang />
-      <system-message />
+      <system-message v-if="false" />
       <setting-button v-if="showButton" />
       <user-avatar />
     </div>
@@ -26,7 +26,6 @@ import GlobalLogo from '../global-logo/index.vue';
 import GlobalSearch from '../global-search/index.vue';
 import {
   FullScreen,
-  GithubSite,
   GlobalBreadcrumb,
   HeaderMenu,
   MenuCollapse,
@@ -34,7 +33,8 @@ import {
   SystemMessage,
   ThemeMode,
   UserAvatar,
-  ToggleLang
+  ToggleLang,
+  DrawerToggle
 } from './components';
 
 defineOptions({ name: 'GlobalHeader' });

+ 2 - 1
src/layouts/common/setting-drawer/index.vue

@@ -20,7 +20,8 @@ defineOptions({ name: 'SettingDrawer' });
 
 const app = useAppStore();
 
-const showButton = import.meta.env.DEV || import.meta.env.VITE_VERCEL === 'Y';
+// const showButton = import.meta.env.DEV || import.meta.env.VITE_VERCEL === 'Y';
+const showButton = import.meta.env.VITE_VERCEL === 'Y';
 </script>
 
 <style scoped></style>

+ 24 - 62
src/locales/lang/en.ts

@@ -8,74 +8,36 @@ const locale: LocaleMessages<I18nType.Schema> = {
     routes: {
       dashboard: {
         dashboard: 'Dashboard',
-        analysis: 'Analysis',
         workbench: 'Workbench'
       },
-      document: {
-        _value: 'Document',
-        vue: 'Vue Document',
-        vite: 'Vite Document',
-        naive: 'NaiveUI Document',
-        project: 'Project Document',
-        'project-link': 'Project Document(href)'
-      },
-      component: {
-        _value: 'Component',
-        button: 'Button',
-        card: 'Card',
-        table: 'Table'
-      },
-      plugin: {
-        _value: 'Plugin',
-        charts: {
-          _value: 'Chart',
-          echarts: 'ECharts',
-          antv: 'AntV'
-        },
-        copy: 'Copy',
-        editor: {
-          _value: 'Editor',
-          quill: 'Quill',
-          markdown: 'Markdown'
-        },
-        icon: 'Icon',
-        map: 'Map',
-        print: 'Print',
-        swiper: 'Swiper',
-        video: 'Video'
-      },
-      'auth-demo': {
-        _value: 'Auth Demo',
-        permission: 'Toggle Permission',
-        super: 'Super Auth'
-      },
-      function: {
-        _value: 'Function',
-        tab: 'System Tab'
-      },
-      exception: {
-        _value: 'Exception',
-        403: '403',
-        404: '404',
-        500: '500'
-      },
-      'multi-menu': {
-        _value: 'Multi Degree Menu',
-        first: {
-          _value: 'First Degree',
-          second: 'Second Degree',
-          'second-new': {
-            _value: 'Second Degree With Children',
-            third: 'Third Degree'
-          }
-        }
-      },
-      management: {
+      system: {
         _value: 'System Management',
         auth: 'Auth',
         role: 'Role',
         route: 'Route',
-        user: 'User'
+        user: 'User',
+        sort: 'category',
+        student: 'Student',
+        setting: 'setting',
+        profile: 'profile'
+      },
+      lesson: {
+        _value: 'Lesson Management',
+        schedule: 'schedule',
+        calendar: 'calendar',
+        checkin: 'student sign in',
+        attendance: 'student attendance',
+        score: 'score'
+      },
+      group: {
+        _value: 'group',
+        group: 'class',
+        classroom: 'classroom',
+        student: 'group student'
+      },
+      archives: {
+        _value: 'archives',
+        students: 'students info'
       },
       about: 'About'
     }

+ 26 - 65
src/locales/lang/zh-cn.ts

@@ -1,82 +1,43 @@
- import type { LocaleMessages } from 'vue-i18n';
+import type { LocaleMessages } from 'vue-i18n';
 
 const locale: LocaleMessages<I18nType.Schema> = {
   message: {
     system: {
-      title: 'Soybean管理系统'
+      title: '爱扣钉教务系统'
     },
     routes: {
       dashboard: {
         dashboard: '仪表盘',
-        analysis: '分析页',
         workbench: '工作台'
       },
-      document: {
-        _value: '文档',
-        vue: 'Vue文档',
-        vite: 'Vite文档',
-        naive: 'NaiveUI文档',
-        project: '项目文档',
-        'project-link': '项目文档(外链)'
-      },
-      component: {
-        _value: '组件示例',
-        button: '按钮',
-        card: '卡片',
-        table: '表格'
-      },
-      plugin: {
-        _value: '插件示例',
-        charts: {
-          _value: '图表',
-          echarts: 'ECharts',
-          antv: 'AntV'
-        },
-        copy: '剪贴板',
-        editor: {
-          _value: '编辑器',
-          quill: '富文本',
-          markdown: 'Markdown'
-        },
-        icon: '图标',
-        map: '地图',
-        print: '打印',
-        swiper: 'Swiper',
-        video: '视频'
-      },
-      'auth-demo': {
-        _value: '权限示例',
-        permission: '切换权限',
-        super: '超级管理员可见'
-      },
-      function: {
-        _value: '功能',
-        tab: 'Tab页签'
-      },
-      exception: {
-        _value: '异常页',
-        403: '403',
-        404: '404',
-        500: '500'
-      },
-      'multi-menu': {
-        _value: '多级菜单',
-        first: {
-          _value: '一级菜单',
-          second: '二级菜单',
-          'second-new': {
-            _value: '二级菜单(有子菜单)',
-            third: '三级菜单'
-          }
-        }
-      },
-      management: {
+      system: {
         _value: '系统管理',
         auth: '权限管理',
         role: '角色管理',
-        route: '路由管理',
+        route: '课程分类',
         user: '用户管理',
-        sort: '课程分类'
+        sort: '课程分类',
+        student: '学生管理',
+        setting: '个人设置',
+        profile: '个人信息'
+      },
+      lesson: {
+        _value: '课程管理',
+        schedule: '排课管理',
+        calendar: '课程日历',
+        checkin: '学员签到',
+        attendance: '学员出勤',
+        score: '学生考核'
+      },
+      group: {
+        _value: '班级管理',
+        student: '班级学员',
+        classroom: '教室管理',
+        group: '班级管理'
+      },
+      archives: {
+        _value: '档案管理',
+        students: '档案搜索'
       },
       about: '关于'
     }

+ 2 - 0
src/main.ts

@@ -1,4 +1,5 @@
 import { createApp } from 'vue';
+import DatePicker from 'ant-design-vue/lib/date-picker';
 import App from './App.vue';
 import AppLoading from './components/common/app-loading.vue';
 import { setupDirectives } from './directives';
@@ -18,6 +19,7 @@ async function setupApp() {
 
   const app = createApp(App);
 
+  app.use(DatePicker);
   // store plugin: pinia
   setupStore(app);
 

+ 2 - 0
src/plugins/assets.ts

@@ -5,6 +5,8 @@ import 'swiper/css/navigation';
 import 'swiper/css/pagination';
 import 'virtual:svg-icons-register';
 import '../styles/css/global.css';
+import 'ant-design-vue/lib/date-picker/style';
+import '@iconify/iconify/dist/iconify';
 
 /** import static assets: css, js , font and so on. - [引入静态资源,css、js和字体文件等] */
 export default function setupAssets() {}

+ 46 - 5
src/plugins/fast-crud/index.tsx

@@ -1,10 +1,10 @@
 import type { App } from 'vue';
 import type { FsSetupOptions, PageQuery } from '@fast-crud/fast-crud';
 // eslint-disable-next-line import/order
-import { FastCrud } from '@fast-crud/fast-crud';
+import { FastCrud, useTypes } from '@fast-crud/fast-crud';
 import '@fast-crud/fast-crud/dist/style.css';
 import './common.scss';
-
+import locale from 'ant-design-vue/es/date-picker/locale/zh_CN';
 import type { FsUploaderOptions } from '@fast-crud/fast-extends';
 import {
   FsExtendsCopyable,
@@ -122,7 +122,7 @@ function install(app: App, options: FsSetupOpts = {}) {
   const uploaderOptions: FsUploaderOptions = {
     defaultType: 'form',
     form: {
-      action: 'http://www.docmirror.cn:7070/api/upload/form/upload',
+      action: '/upload/form/upload',
       name: 'file',
       withCredentials: false,
       uploadRequest: async props => {
@@ -144,7 +144,7 @@ function install(app: App, options: FsSetupOpts = {}) {
       async successHandle(ret: string) {
         // 上传完成后的结果处理, 此处应转换格式为{url:xxx,key:xxx}
         return {
-          url: `http://www.docmirror.cn:7070${ret}`,
+          url: ret,
           key: ret.replace('/api/upload/form/download?key=', '')
         };
       }
@@ -160,8 +160,49 @@ function install(app: App, options: FsSetupOpts = {}) {
   app.use(FsExtendsJson);
   app.use(FsExtendsTime);
   app.use(FsExtendsCopyable);
-}
 
+  const { addTypes } = useTypes();
+  const easDate = {
+    easDate: {
+      form: {
+        component: {
+          locale,
+          name: 'a-date-picker',
+          format: 'YYYY-MM-DD',
+          valueFormat: 'YYYY-MM-DD',
+          vModel: 'value'
+        }
+      },
+      column: {
+        component: {
+          name: 'fs-date-format',
+          format: 'YYYY-MM-DD'
+        }
+      }
+    }
+  };
+  const easDateTime = {
+    easDateTime: {
+      form: {
+        component: {
+          locale,
+          name: 'a-date-picker',
+          format: 'YYYY-MM-DD hh:mm',
+          valueFormat: 'YYYY-MM-DD hh:mm',
+          vModel: 'value'
+        }
+      },
+      column: {
+        component: {
+          name: 'fs-date-format',
+          format: 'YYYY-MM-DD hh:mm'
+        }
+      }
+    }
+  };
+  addTypes(easDate);
+  addTypes(easDateTime);
+}
 export default {
   install
 };

二進制
src/public/java.pdf


二進制
src/public/分数导入.xlsx


二進制
src/public/学员导入.xlsx


+ 3 - 2
src/router/guard/dynamic.ts

@@ -1,7 +1,7 @@
 import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
 import { routeName } from '@/router';
 import { useRouteStore } from '@/store';
-import { localStg } from '@/utils';
+// import { localStg } from '@/utils';
 
 /**
  * 动态路由
@@ -12,7 +12,8 @@ export async function createDynamicRouteGuard(
   next: NavigationGuardNext
 ) {
   const route = useRouteStore();
-  const isLogin = Boolean(localStg.get('token'));
+  // const isLogin = Boolean(localStg.get('token'));
+  const isLogin = Boolean(localStorage.getItem('token'));
 
   // 初始化权限路由
   if (!route.isInitAuthRoute) {

+ 10 - 9
src/router/guard/permission.ts

@@ -1,7 +1,7 @@
 import type { NavigationGuardNext, RouteLocationNormalized } from 'vue-router';
 import { routeName } from '@/router';
 import { useAuthStore } from '@/store';
-import { exeStrategyActions, localStg } from '@/utils';
+import { exeStrategyActions } from '@/utils';
 import { createDynamicRouteGuard } from './dynamic';
 
 /** 处理路由页面的权限 */
@@ -22,19 +22,20 @@ export async function createPermissionGuard(
   }
 
   const auth = useAuthStore();
-  const isLogin = Boolean(localStg.get('token'));
+  // const isLogin = Boolean(localStg.get('token'));
+  const isLogin = Boolean(localStorage.getItem('token'));
   const permissions = to.meta.permissions || [];
   const needLogin = Boolean(to.meta?.requiresAuth) || Boolean(permissions.length);
-  const hasPermission = !permissions.length || permissions.includes(auth.userInfo.userRole);
+  const hasPermission = !permissions.length || permissions.includes(auth.userInfo.userType);
 
   const actions: Common.StrategyAction[] = [
     // 已登录状态跳转登录页,跳转至首页
-    [
-      isLogin && to.name === routeName('login'),
-      () => {
-        next({ name: routeName('root') });
-      }
-    ],
+    // [
+    //   isLogin && to.name === routeName('login'),
+    //   () => {
+    //     next({ name: routeName('root') });
+    //   }
+    // ],
     // 不需要登录权限的页面直接通行
     [
       !needLogin,

+ 1 - 1
src/router/modules/about.ts

@@ -8,7 +8,7 @@ const about1: AuthRoute.Route = {
     requiresAuth: true,
     keepAlive: true,
     singleLayout: 'basic',
-    permissions: ['super', 'admin', 'user'],
+    permissions: ['admin', 'teacher'],
     icon: 'fluent:book-information-24-regular',
     order: 10
   }

+ 28 - 0
src/router/modules/archives.ts

@@ -0,0 +1,28 @@
+const archives: AuthRoute.Route = {
+  name: 'archives',
+  path: '/archives',
+  component: 'basic',
+  children: [
+    {
+      name: 'archives_students',
+      path: '/archives/students',
+      component: 'self',
+      meta: {
+        title: '档案搜索',
+        permissions: ['admin', 'teacher', 'member'],
+        i18nTitle: 'message.routes.archives.students',
+        requiresAuth: true,
+        icon: 'icons8:student'
+      }
+    }
+  ],
+  meta: {
+    title: '档案管理',
+    i18nTitle: 'message.routes.archives._value',
+    icon: 'vaadin:archives',
+    permissions: ['admin', 'teacher', 'member'],
+    order: 2
+  }
+};
+
+export default archives;

+ 0 - 38
src/router/modules/auth-demo.ts

@@ -1,38 +0,0 @@
-const authDemo: AuthRoute.Route = {
-  name: 'auth-demo',
-  path: '/auth-demo',
-  component: 'basic',
-  children: [
-    {
-      name: 'auth-demo_permission',
-      path: '/auth-demo/permission',
-      component: 'self',
-      meta: {
-        title: '权限切换',
-        i18nTitle: 'message.routes.auth-demo.permission',
-        requiresAuth: true,
-        icon: 'ic:round-construction'
-      }
-    },
-    {
-      name: 'auth-demo_super',
-      path: '/auth-demo/super',
-      component: 'self',
-      meta: {
-        title: '超级管理员可见',
-        i18nTitle: 'message.routes.auth-demo.super',
-        requiresAuth: true,
-        permissions: ['super'],
-        icon: 'ic:round-supervisor-account'
-      }
-    }
-  ],
-  meta: {
-    title: '权限示例',
-    i18nTitle: 'message.routes.auth-demo._value',
-    icon: 'ic:baseline-security',
-    order: 5
-  }
-};
-
-export default authDemo;

+ 0 - 48
src/router/modules/component.ts

@@ -1,48 +0,0 @@
-const component: AuthRoute.Route = {
-  name: 'component',
-  path: '/component',
-  component: 'basic',
-  children: [
-    {
-      name: 'component_button',
-      path: '/component/button',
-      component: 'self',
-      meta: {
-        title: '按钮',
-        i18nTitle: 'message.routes.component.button',
-        requiresAuth: true,
-        icon: 'mdi:button-cursor'
-      }
-    },
-    {
-      name: 'component_card',
-      path: '/component/card',
-      component: 'self',
-      meta: {
-        title: '卡片',
-        i18nTitle: 'message.routes.component.card',
-        requiresAuth: true,
-        icon: 'mdi:card-outline'
-      }
-    },
-    {
-      name: 'component_table',
-      path: '/component/table',
-      component: 'self',
-      meta: {
-        title: '表格',
-        i18nTitle: 'message.routes.component.table',
-        requiresAuth: true,
-        icon: 'mdi:table-large'
-      }
-    }
-  ],
-  meta: {
-    title: '组件示例',
-    i18nTitle: 'message.routes.component._value',
-    icon: 'cib:app-store',
-    order: 3
-  }
-};
-
-export default component;

+ 0 - 45
src/router/modules/crud.ts

@@ -1,45 +0,0 @@
-const component: any = {
-  name: 'crud',
-  path: '/crud',
-  component: 'basic',
-  meta: {
-    title: 'CRUD示例',
-    requiresAuth: true,
-    icon: 'mdi:table-large',
-    order: 4
-  },
-  children: [
-    {
-      name: 'crud_demo',
-      path: '/crud/demo',
-      component: 'self',
-      meta: {
-        title: '基本示例',
-        requiresAuth: true,
-        icon: 'mdi:button-cursor'
-      }
-    },
-    {
-      name: 'crud_header_group',
-      path: '/crud/header_group',
-      component: 'self',
-      meta: {
-        title: '多级表头',
-        requiresAuth: true,
-        icon: 'mdi:button-cursor'
-      }
-    },
-    {
-      name: 'crud_doc',
-      path: '/crud/doc',
-      component: 'self',
-      meta: {
-        title: 'FastCrud文档',
-        requiresAuth: true,
-        icon: 'logos:vue'
-      }
-    }
-  ]
-};
-
-export default component;

+ 2 - 11
src/router/modules/dashboard.ts

@@ -3,17 +3,6 @@ const dashboard: AuthRoute.Route = {
   path: '/dashboard',
   component: 'basic',
   children: [
-    {
-      name: 'dashboard_analysis',
-      path: '/dashboard/analysis',
-      component: 'self',
-      meta: {
-        title: '分析页',
-        requiresAuth: true,
-        icon: 'icon-park-outline:analysis',
-        i18nTitle: 'message.routes.dashboard.analysis'
-      }
-    },
     {
       name: 'dashboard_workbench',
       path: '/dashboard/workbench',
@@ -21,6 +10,7 @@ const dashboard: AuthRoute.Route = {
       meta: {
         title: '工作台',
         requiresAuth: true,
+        permissions: ['admin', 'teacher', 'member'],
         icon: 'icon-park-outline:workbench',
         i18nTitle: 'message.routes.dashboard.workbench'
       }
@@ -30,6 +20,7 @@ const dashboard: AuthRoute.Route = {
     title: '仪表盘',
     icon: 'mdi:monitor-dashboard',
     order: 1,
+    permissions: ['admin', 'teacher', 'member'],
     i18nTitle: 'message.routes.dashboard.dashboard'
   }
 };

+ 0 - 70
src/router/modules/document.ts

@@ -1,70 +0,0 @@
-const document: AuthRoute.Route = {
-  name: 'document',
-  path: '/document',
-  component: 'basic',
-  children: [
-    {
-      name: 'document_vue',
-      path: '/document/vue',
-      component: 'self',
-      meta: {
-        title: 'vue文档',
-        i18nTitle: 'message.routes.document.vue',
-        requiresAuth: true,
-        icon: 'logos:vue'
-      }
-    },
-    {
-      name: 'document_vite',
-      path: '/document/vite',
-      component: 'self',
-      meta: {
-        title: 'vite文档',
-        i18nTitle: 'message.routes.document.vite',
-        requiresAuth: true,
-        icon: 'logos:vitejs'
-      }
-    },
-    {
-      name: 'document_naive',
-      path: '/document/naive',
-      component: 'self',
-      meta: {
-        title: 'naive文档',
-        i18nTitle: 'message.routes.document.naive',
-        requiresAuth: true,
-        icon: 'logos:naiveui'
-      }
-    },
-    {
-      name: 'document_project',
-      path: '/document/project',
-      component: 'self',
-      meta: {
-        title: '项目文档',
-        i18nTitle: 'message.routes.document.project',
-        requiresAuth: true,
-        localIcon: 'logo'
-      }
-    },
-    {
-      name: 'document_project-link',
-      path: '/document/project-link',
-      meta: {
-        title: '项目文档(外链)',
-        i18nTitle: 'message.routes.document.project-link',
-        requiresAuth: true,
-        localIcon: 'logo',
-        href: 'https://docs.soybean.pro/'
-      }
-    }
-  ],
-  meta: {
-    title: '文档',
-    i18nTitle: 'message.routes.document._value',
-    icon: 'mdi:file-document-multiple-outline',
-    order: 2
-  }
-};
-
-export default document;

+ 0 - 48
src/router/modules/exception.ts

@@ -1,48 +0,0 @@
-const exception: AuthRoute.Route = {
-  name: 'exception',
-  path: '/exception',
-  component: 'basic',
-  children: [
-    {
-      name: 'exception_403',
-      path: '/exception/403',
-      component: 'self',
-      meta: {
-        title: '异常页403',
-        i18nTitle: 'message.routes.exception.403',
-        requiresAuth: true,
-        icon: 'ic:baseline-block'
-      }
-    },
-    {
-      name: 'exception_404',
-      path: '/exception/404',
-      component: 'self',
-      meta: {
-        title: '异常页404',
-        i18nTitle: 'message.routes.exception.404',
-        requiresAuth: true,
-        icon: 'ic:baseline-web-asset-off'
-      }
-    },
-    {
-      name: 'exception_500',
-      path: '/exception/500',
-      component: 'self',
-      meta: {
-        title: '异常页500',
-        i18nTitle: 'message.routes.exception.500',
-        requiresAuth: true,
-        icon: 'ic:baseline-wifi-off'
-      }
-    }
-  ],
-  meta: {
-    i18nTitle: 'message.routes.exception._value',
-    title: '异常页',
-    icon: 'ant-design:exception-outlined',
-    order: 7
-  }
-};
-
-export default exception;

+ 0 - 51
src/router/modules/function.ts

@@ -1,51 +0,0 @@
-const functionRoute: AuthRoute.Route = {
-  name: 'function',
-  path: '/function',
-  component: 'basic',
-  children: [
-    {
-      name: 'function_tab',
-      path: '/function/tab',
-      component: 'self',
-      meta: {
-        title: 'Tab',
-        i18nTitle: 'message.routes.function.tab',
-        requiresAuth: true,
-        icon: 'ic:round-tab'
-      }
-    },
-    {
-      name: 'function_tab-detail',
-      path: '/function/tab-detail',
-      component: 'self',
-      meta: {
-        title: 'Tab Detail',
-        requiresAuth: true,
-        hide: true,
-        activeMenu: 'function_tab',
-        icon: 'ic:round-tab'
-      }
-    },
-    {
-      name: 'function_tab-multi-detail',
-      path: '/function/tab-multi-detail',
-      component: 'self',
-      meta: {
-        title: 'Tab Multi Detail',
-        requiresAuth: true,
-        hide: true,
-        multiTab: true,
-        activeMenu: 'function_tab',
-        icon: 'ic:round-tab'
-      }
-    }
-  ],
-  meta: {
-    title: '功能',
-    i18nTitle: 'message.routes.function._value',
-    icon: 'icon-park-outline:all-application',
-    order: 6
-  }
-};
-
-export default functionRoute;

+ 52 - 0
src/router/modules/groups.ts

@@ -0,0 +1,52 @@
+const group: AuthRoute.Route = {
+  name: 'group',
+  path: '/group',
+  component: 'basic',
+  children: [
+    {
+      name: 'group_group',
+      path: '/group/group',
+      component: 'self',
+      meta: {
+        title: '班级管理',
+        i18nTitle: 'message.routes.group.group',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        localIcon: 'class'
+      }
+    },
+    {
+      name: 'group_student',
+      path: '/group/student',
+      component: 'self',
+      meta: {
+        title: '班级学员',
+        i18nTitle: 'message.routes.group.student',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        icon: 'typcn:group'
+      }
+    },
+    {
+      name: 'group_classroom',
+      path: '/group/classroom',
+      component: 'self',
+      meta: {
+        title: '教室管理',
+        i18nTitle: 'message.routes.group.classroom',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        localIcon: 'classroom'
+      }
+    }
+  ],
+  meta: {
+    title: '班级管理',
+    i18nTitle: 'message.routes.group._value',
+    localIcon: 'lesson',
+    permissions: ['admin', 'teacher'],
+    order: 8
+  }
+};
+
+export default group;

+ 85 - 0
src/router/modules/lesson.ts

@@ -0,0 +1,85 @@
+const lesson: AuthRoute.Route = {
+  name: 'lesson',
+  path: '/lesson',
+  component: 'basic',
+  children: [
+    {
+      name: 'lesson_schedule',
+      path: '/lesson/schedule',
+      component: 'self',
+      meta: {
+        title: '排课管理',
+        i18nTitle: 'message.routes.lesson.schedule',
+        requiresAuth: true,
+        icon: 'healthicons:i-schedule-school-date-time'
+      }
+    },
+    {
+      name: 'lesson_calendar',
+      path: '/lesson/calendar',
+      component: 'self',
+      meta: {
+        title: '排课日历',
+        i18nTitle: 'message.routes.lesson.calendar',
+        requiresAuth: true,
+        icon: 'radix-icons:calendar'
+      }
+    },
+    {
+      name: 'lesson_checkin',
+      path: '/lesson/checkin',
+      component: 'self',
+      meta: {
+        title: '课程签到',
+        i18nTitle: 'message.routes.lesson.checkin',
+        requiresAuth: true,
+        icon: 'mdi:sign',
+        hide: true
+      }
+    },
+    {
+      name: 'system_profile',
+      path: '/system/profile',
+      component: 'self',
+      meta: {
+        title: '个人中心',
+        i18nTitle: 'message.routes.system.profile',
+        requiresAuth: true,
+        icon: 'uil:setting',
+        hide: true
+      }
+    },
+    {
+      name: 'lesson_score',
+      path: '/lesson/score',
+      component: 'self',
+      meta: {
+        title: '课程考核',
+        i18nTitle: 'message.routes.lesson.score',
+        requiresAuth: true,
+        icon: 'healthicons:i-exam-qualification-outline'
+      }
+    },
+    {
+      name: 'lesson_attendance',
+      path: '/lesson/attendance',
+      component: 'self',
+      meta: {
+        title: '学员出勤',
+        permissions: ['admin', 'teacher'],
+        i18nTitle: 'message.routes.lesson.attendance',
+        requiresAuth: true,
+        icon: 'emojione:kiss-mark'
+      }
+    }
+  ],
+  meta: {
+    title: '课程管理',
+    i18nTitle: 'message.routes.lesson._value',
+    icon: 'carbon:book',
+    permissions: ['admin', 'teacher', 'member'],
+    order: 9
+  }
+};
+
+export default lesson;

+ 0 - 70
src/router/modules/management.ts

@@ -1,70 +0,0 @@
-const management: AuthRoute.Route = {
-  name: 'management',
-  path: '/management',
-  component: 'basic',
-  children: [
-    {
-      name: 'management_auth',
-      path: '/management/auth',
-      component: 'self',
-      meta: {
-        title: '权限管理',
-        i18nTitle: 'message.routes.management.auth',
-        requiresAuth: true,
-        icon: 'ic:baseline-security'
-      }
-    },
-    {
-      name: 'management_role',
-      path: '/management/role',
-      component: 'self',
-      meta: {
-        title: '角色管理',
-        i18nTitle: 'message.routes.management.role',
-        requiresAuth: true,
-        icon: 'carbon:user-role'
-      }
-    },
-    {
-      name: 'management_user',
-      path: '/management/user',
-      component: 'self',
-      meta: {
-        title: '用户管理',
-        i18nTitle: 'message.routes.management.user',
-        requiresAuth: true,
-        icon: 'ic:round-manage-accounts'
-      }
-    },
-    {
-      name: 'management_sort',
-      path: '/management/sort',
-      component: 'self',
-      meta: {
-        title: '课程分类',
-        i18nTitle: 'message.routes.management.sort',
-        requiresAuth: true,
-        icon: 'material-symbols:sort'
-      }
-    },
-    {
-      name: 'management_route',
-      path: '/management/route',
-      component: 'self',
-      meta: {
-        title: '路由管理',
-        i18nTitle: 'message.routes.management.route',
-        requiresAuth: true,
-        icon: 'material-symbols:route'
-      }
-    }
-  ],
-  meta: {
-    title: '系统管理',
-    i18nTitle: 'message.routes.management._value',
-    icon: 'carbon:cloud-service-management',
-    order: 9
-  }
-};
-
-export default management;

+ 0 - 61
src/router/modules/multi-menu.ts

@@ -1,61 +0,0 @@
-const multiMenu: AuthRoute.Route = {
-  name: 'multi-menu',
-  path: '/multi-menu',
-  component: 'basic',
-  children: [
-    {
-      name: 'multi-menu_first',
-      path: '/multi-menu/first',
-      component: 'multi',
-      children: [
-        {
-          name: 'multi-menu_first_second',
-          path: '/multi-menu/first/second',
-          component: 'self',
-          meta: {
-            title: '二级菜单',
-            i18nTitle: 'message.routes.multi-menu.first.second',
-            requiresAuth: true,
-            icon: 'mdi:menu'
-          }
-        },
-        {
-          name: 'multi-menu_first_second-new',
-          path: '/multi-menu/first/second-new',
-          component: 'multi',
-          children: [
-            {
-              name: 'multi-menu_first_second-new_third',
-              path: '/multi-menu/first/second-new/third',
-              component: 'self',
-              meta: {
-                title: '三级菜单',
-                i18nTitle: 'message.routes.multi-menu.first.second-new.third',
-                requiresAuth: true,
-                icon: 'mdi:menu'
-              }
-            }
-          ],
-          meta: {
-            title: '二级菜单(有子菜单)',
-            i18nTitle: 'message.routes.multi-menu.first.second-new._value',
-            icon: 'mdi:menu'
-          }
-        }
-      ],
-      meta: {
-        title: '一级菜单',
-        i18nTitle: 'message.routes.multi-menu.first._value',
-        icon: 'mdi:menu'
-      }
-    }
-  ],
-  meta: {
-    title: '多级菜单',
-    i18nTitle: 'message.routes.multi-menu._value',
-    icon: 'carbon:menu',
-    order: 8
-  }
-};
-
-export default multiMenu;

+ 0 - 149
src/router/modules/plugin.ts

@@ -1,149 +0,0 @@
-const plugin: AuthRoute.Route = {
-  name: 'plugin',
-  path: '/plugin',
-  component: 'basic',
-  children: [
-    {
-      name: 'plugin_charts',
-      path: '/plugin/charts',
-      component: 'multi',
-      children: [
-        {
-          name: 'plugin_charts_echarts',
-          path: '/plugin/charts/echarts',
-          component: 'self',
-          meta: {
-            title: 'ECharts',
-            i18nTitle: 'message.routes.plugin.charts.echarts',
-            requiresAuth: true,
-            icon: 'simple-icons:apacheecharts'
-          }
-        },
-        {
-          name: 'plugin_charts_antv',
-          path: '/plugin/charts/antv',
-          component: 'self',
-          meta: {
-            title: 'AntV',
-            i18nTitle: 'message.routes.plugin.charts.antv',
-            requiresAuth: true,
-            icon: 'simple-icons:antdesign'
-          }
-        }
-      ],
-      meta: {
-        title: '图表',
-        i18nTitle: 'message.routes.plugin.charts._value',
-        icon: 'mdi:chart-areaspline'
-      }
-    },
-    {
-      name: 'plugin_map',
-      path: '/plugin/map',
-      component: 'self',
-      meta: {
-        title: '地图',
-        i18nTitle: 'message.routes.plugin.map',
-        requiresAuth: true,
-        icon: 'mdi:map'
-      }
-    },
-    {
-      name: 'plugin_video',
-      path: '/plugin/video',
-      component: 'self',
-      meta: {
-        title: '视频',
-        i18nTitle: 'message.routes.plugin.video',
-        requiresAuth: true,
-        icon: 'mdi:video'
-      }
-    },
-    {
-      name: 'plugin_editor',
-      path: '/plugin/editor',
-      component: 'multi',
-      children: [
-        {
-          name: 'plugin_editor_quill',
-          path: '/plugin/editor/quill',
-          component: 'self',
-          meta: {
-            title: '富文本编辑器',
-            i18nTitle: 'message.routes.plugin.editor.quill',
-            requiresAuth: true,
-            icon: 'mdi:file-document-edit-outline'
-          }
-        },
-        {
-          name: 'plugin_editor_markdown',
-          path: '/plugin/editor/markdown',
-          component: 'self',
-          meta: {
-            title: 'markdown编辑器',
-            i18nTitle: 'message.routes.plugin.editor.markdown',
-            requiresAuth: true,
-            icon: 'ri:markdown-line'
-          }
-        }
-      ],
-      meta: {
-        title: '编辑器',
-        i18nTitle: 'message.routes.plugin.editor._value',
-        icon: 'icon-park-outline:editor'
-      }
-    },
-    {
-      name: 'plugin_swiper',
-      path: '/plugin/swiper',
-      component: 'self',
-      meta: {
-        title: 'Swiper插件',
-        i18nTitle: 'message.routes.plugin.swiper',
-        requiresAuth: true,
-        icon: 'simple-icons:swiper'
-      }
-    },
-    {
-      name: 'plugin_copy',
-      path: '/plugin/copy',
-      component: 'self',
-      meta: {
-        title: '剪贴板',
-        i18nTitle: 'message.routes.plugin.copy',
-        requiresAuth: true,
-        icon: 'mdi:clipboard-outline'
-      }
-    },
-    {
-      name: 'plugin_icon',
-      path: '/plugin/icon',
-      component: 'self',
-      meta: {
-        title: '图标',
-        i18nTitle: 'message.routes.plugin.icon',
-        requiresAuth: true,
-        localIcon: 'custom-icon'
-      }
-    },
-    {
-      name: 'plugin_print',
-      path: '/plugin/print',
-      component: 'self',
-      meta: {
-        title: '打印',
-        i18nTitle: 'message.routes.plugin.print',
-        requiresAuth: true,
-        icon: 'mdi:printer'
-      }
-    }
-  ],
-  meta: {
-    title: '插件示例',
-    i18nTitle: 'message.routes.plugin._value',
-    icon: 'clarity:plugin-line',
-    order: 4
-  }
-};
-
-export default plugin;

+ 76 - 0
src/router/modules/system.ts

@@ -0,0 +1,76 @@
+const system: AuthRoute.Route = {
+  name: 'system',
+  path: '/system',
+  component: 'basic',
+  children: [
+    {
+      name: 'system_role',
+      path: '/system/role',
+      component: 'self',
+      meta: {
+        title: '角色管理',
+        i18nTitle: 'message.routes.system.role',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        icon: 'carbon:user-role'
+      }
+    },
+    {
+      name: 'system_user',
+      path: '/system/user',
+      component: 'self',
+      meta: {
+        title: '用户管理',
+        i18nTitle: 'message.routes.system.user',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        icon: 'ic:round-manage-accounts'
+      }
+    },
+    {
+      name: 'system_student',
+      path: '/system/student',
+      component: 'self',
+      meta: {
+        title: '学员管理',
+        i18nTitle: 'message.routes.system.student',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        localIcon: 'academic-cap'
+      }
+    },
+    {
+      name: 'system_category',
+      path: '/system/category',
+      component: 'self',
+      meta: {
+        title: '课程分类',
+        i18nTitle: 'message.routes.system.sort',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        icon: 'material-symbols:sort'
+      }
+    },
+    {
+      name: 'system_subject',
+      path: '/system/subject',
+      component: 'self',
+      meta: {
+        title: '系列分类',
+        i18nTitle: 'message.routes.system.route',
+        permissions: ['admin', 'teacher'],
+        requiresAuth: true,
+        icon: 'material-symbols:route'
+      }
+    }
+  ],
+  meta: {
+    title: '系统管理',
+    i18nTitle: 'message.routes.system._value',
+    permissions: ['admin', 'teacher'],
+    icon: 'carbon:cloud-service-management',
+    order: 9
+  }
+};
+
+export default system;

+ 0 - 10
src/router/routes/index.ts

@@ -29,15 +29,6 @@ export const constantRoutes: AuthRoute.Route[] = [
       singleLayout: 'blank'
     }
   },
-  {
-    name: 'constant-page',
-    path: '/constant-page',
-    component: 'self',
-    meta: {
-      title: '固定页面',
-      singleLayout: 'blank'
-    }
-  },
   {
     name: '403',
     path: '/403',
@@ -65,7 +56,6 @@ export const constantRoutes: AuthRoute.Route[] = [
       singleLayout: 'blank'
     }
   },
-  // 匹配无效路径的路由
   {
     name: 'not-found',
     path: '/:pathMatch(.*)*',

+ 29 - 25
src/service/api/auth.ts

@@ -1,4 +1,4 @@
-import { request,mockRequest } from '../request';
+import { request } from '../request';
 
 /**
  * 获取验证码
@@ -11,9 +11,9 @@ export function fetchSmsCode(phone: string) {
 
 // 参数接口
 export interface AdminLoginParams {
-  username?: string;
-  passwd?: string;
-	captchaVerification?: string
+  username: string;
+  passwd: string;
+  captchaVerification: string;
 }
 
 // 响应接口
@@ -22,37 +22,33 @@ export interface AdminLoginRes {
   msg: string;
   data: Record<string, unknown>;
 }
+
 /**
- * 管理员登录
- * @param {object} params AdminPojo
- * @param {string} params.username
- * @param {string} params.passwd
- * @returns
+ * 登录
+ * @param userName - 用户名
+ * @param password - 密码
+ * @param captchaVerification - 验证码
  */
-// export function adminLogin(params: AdminLoginParams) {
-//   return request.post(`/adminLogin`, params);
-// }
+export function fetchLogin(params: AdminLoginParams) {
+  const res = request.post('/login/adminLogin', params);
+  return res;
+}
+
 /**
  * 登录
  * @param userName - 用户名
  * @param password - 密码
+ * @param captchaVerification - 验证码
  */
-export function fetchLogin(params: AdminLoginParams) {
-  return request.post<ApiAuth.Token>('/adminLogin',params);
+export function fetchStudentLogin(params: AdminLoginParams) {
+  const res = request.post('/login/studentLogin', params);
+  return res;
 }
 
 /** 获取用户信息 */
 export function fetchUserInfo() {
-  return mockRequest.get<ApiAuth.UserInfo>('/getUserInfo');
-}
-
-/**
- * 获取用户路由数据
- * @param userId - 用户id
- * @description 后端根据用户id查询到对应的角色类型,并将路由筛选出对应角色的路由数据返回前端
- */
-export function fetchUserRoutes(userId: string) {
-  return mockRequest.post<ApiRoute.Route>('/getUserRoutes', { userId });
+  const res = request.get('/login/getUserInfo');
+  return res;
 }
 
 /**
@@ -60,5 +56,13 @@ export function fetchUserRoutes(userId: string) {
  * @param refreshToken
  */
 export function fetchUpdateToken(refreshToken: string) {
-  return request.post<ApiAuth.Token>('/refreshToken',  refreshToken );
+  return request.post<ApiAuth.Token>(
+    '/login/refreshToken',
+    {},
+    {
+      headers: {
+        Authorization: refreshToken
+      }
+    }
+  );
 }

+ 0 - 1
src/service/api/index.ts

@@ -1,2 +1 @@
 export * from './auth';
-export * from './management';

+ 0 - 0
src/service/api/login.ts


+ 0 - 0
src/service/api/man.ts


+ 0 - 13
src/service/api/management.adapter.ts

@@ -1,13 +0,0 @@
-export function adapterOfFetchUserList(data: ApiUserManagement.User[] | null): UserManagement.User[] {
-  if (!data) return [];
-
-  return data.map((item, index) => {
-    const user: UserManagement.User = {
-      index: index + 1,
-      key: item.id,
-      ...item
-    };
-
-    return user;
-  });
-}

+ 0 - 9
src/service/api/management.ts

@@ -1,9 +0,0 @@
-import { adapter } from '@/utils';
-import { mockRequest } from '../request';
-import { adapterOfFetchUserList } from './management.adapter';
-
-/** 获取用户列表 */
-export const fetchUserList = async () => {
-  const data = await mockRequest.post<ApiUserManagement.User[] | null>('/getAllUserList');
-  return adapter(adapterOfFetchUserList, data);
-};

+ 0 - 166
src/service/api/sort.ts

@@ -1,166 +0,0 @@
-import { request } from '../request';
-
-
-// 查询全部课程类别
-// 响应接口
-export interface SelectAll_1Res {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-}
-
-/**
- * 查询全部课程类别
- * @returns
- */
-export function selectAll_1() {
-  return request.get(`/selectAll`);
-}
-
-
-// 添加课程类别
-// 参数接口
-export interface AddEasEduCategoryParams {
-  id?: number|null;
-  name?: string|null;
-  description?: string|null;
-  createTime?: string;
-  modifyTime?: string;
-  createUid?: number|null;
-  disabled?: string|null;
-}
-
-// 响应接口
-export interface AddEasEduCategoryRes {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-}
-
-/**
- * 添加课程类别
- * @param {object} params EasEduCategory
- * @param {number} params.id
- * @param {string} params.name 学科名称
- * @param {string} params.description 学科描述
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @returns
- */
-export function addEasEduCategory(params: AddEasEduCategoryParams){
-  return request.post(`/addEasEduCategory`, params);
-}
-
-
-// 根据Id删除课程类别
-// 响应接口
-export interface DeleteByIdRes {
-  status: boolean;
-  msg: string;
-  data: string;
-}
-
-/**
- * 根据Id删除课程类别
- * @param {string} id
-  * @returns
- */
-export function deleteById(id: number){
-  return request.delete(`/deleteById/${id}`);
-}
-
-
-
-// 课程类别更新
-// 参数接口
-export interface UpdateEasEduCategoryParams {
-  id?: number|null;
-  name?: string|null;
-  description?: string|null;
-  createTime?:  string;
-  modifyTime?: string;
-  createUid?: number|null;
-  disabled?: string|null;
-}
-
-// 响应接口
-export interface UpdateEasEduCategoryRes {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-}
-
-/**
- * 课程类别更新
- * @param {object} params EasEduCategory
- * @param {number} params.id
- * @param {string} params.name 学科名称
- * @param {string} params.description 学科描述
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @returns
- */
-export function updateEasEduCategory(params: UpdateEasEduCategoryParams) {
-  return request.post(`/updateEasEduCategory`, params);
-}
-
-
-// 根据Id查询对应课程类别
-// 响应接口
-export interface SelectById_1Res {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-}
-
-/**
- * 根据Id查询对应课程类别
- * @param {string} id
-  * @returns
- */
-export function selectById_1(id: number) {
-  return request.get(`/selectById?id=${id}`);
-}
-
-
-// 根据条件进行查询课程类别
-// 参数接口
-export interface SelectByCondition_1Params {
-  id?: number;
-  name?: string;
-  description?: string;
-  createTime?: Record<string, unknown>;
-  modifyTime?: Record<string, unknown>;
-  createUid?: number;
-  disabled?: string;
-}
-
-// 响应接口
-export interface SelectByCondition_1Res {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-  total: number;
-}
-
-/**
- * 根据条件进行查询课程类别
- * @param {string} pageNum
- * @param {string} pageSize
- * @param {object} params EasEduCategory
- * @param {number} params.id
- * @param {string} params.name 学科名称
- * @param {string} params.description 学科描述
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @returns
- */
-export function selectByCondition_1(pageNum: number, pageSize: number, params: SelectByCondition_1Params){
-  return request.post(`/selectByCondition?pageNum=${pageNum}&pageSize=${pageSize}`, params);
-}

+ 0 - 116
src/service/api/user.ts

@@ -1,116 +0,0 @@
-import { request } from '../request';
-
-// 参数接口
-export interface UpdateParams {
-  id?: number;
-  name?: string;
-  isActive?: Record<string, unknown>;
-  createTime?: Record<string, unknown>;
-  modifyTime?: Record<string, unknown>;
-  createUid?: number;
-  disabled?: string;
-  description?: string;
-}
-// 响应接口
-export interface UpdateRes {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-}
-
-/**
- * 更新权限
- * @param {object} params EasSysPermission
- * @param {number} params.id ID
- * @param {string} params.name 权限名称
- * @param {object} params.isActive 是否激活
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @param {string} params.description 权限描述
- * @returns
- */
-export function update(params: UpdateParams) {
-  return request.put(`/permission/update`, params);
-}
-// 参数接口
-export interface QueryParams {
-  id?: number;
-  name?: string;
-  description?: string;
-  isActive?: string;
-  createTime?: string;
-  modifyTime?: string;
-  createUid?: number;
-  disabled?: string;
-}
-
-// 响应接口
-export interface QueryRes {
-  total: number;
-  data: Record<string, unknown>;
-}
-
-/**
- * 查询权限
- * @param {string} pageNum
- * @param {string} pageSize
- * @param {object} params EasSysPermission
- * @param {number} params.id ID
- * @param {string} params.name 权限名称
- * @param {object} params.isActive 是否激活
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @param {string} params.description 权限描述
- * @returns
- */
-export function query(pageNum: number, pageSize: number, params: QueryParams) {
-  return request.post(`/permission/query?pageNum=${pageNum}&pageSize=${pageSize}`, params);
-}
-// 参数接口
-export interface Query_1Params {
-  id?: number;
-  depname?: string;
-  address?: string;
-  phone?: string;
-  email?: string;
-  manager?: string;
-  createTime?: string;
-  modifyTime?: string;
-  createUid?: number;
-  disabled?: string;
-}
-
-// 响应接口
-export interface Query_1Res {
-  status: boolean;
-  msg: string;
-  data: Record<string, unknown>;
-  total: number;
-}
-
-/**
- * 查询部门
- * @param {string} pageNum
- * @param {string} pageSize
- * @param {object} params EasSysDepartment
- * @param {number} params.id
- * @param {string} params.depname 部门名称
- * @param {string} params.address 部门地址
- * @param {string} params.phone 部门电话
- * @param {string} params.email 部门电子邮箱
- * @param {string} params.manager 部门负责人
- * @param {object} params.createTime 创建时间
- * @param {object} params.modifyTime 修改时间
- * @param {number} params.createUid 创建用户ID
- * @param {string} params.disabled 状态
- * @returns
- */
-export function query_1(pageNum: number, pageSize: number, params: Query_1Params) {
-  return request.post(`/department/query?pageNum=${pageNum}&pageSize=${pageSize}`, params);
-}
-
-

+ 5 - 5
src/service/request/helpers.ts

@@ -10,14 +10,14 @@ import { fetchUpdateToken } from '../api';
 export async function handleRefreshToken(axiosConfig: AxiosRequestConfig) {
   const { resetAuthStore } = useAuthStore();
   const refreshToken = localStg.get('refreshToken') || '';
-  const { data } = await fetchUpdateToken(refreshToken);
-  if (data) {
-    localStg.set('token', data.token);
-    localStg.set('refreshToken', data.refreshToken);
+  const response = await fetchUpdateToken(refreshToken);
+  if (response && response.data && response.data.token) {
+    localStg.set('token', response.data.token);
+    localStg.set('refreshToken', response.data.refreshToken);
 
     const config = { ...axiosConfig };
     if (config.headers) {
-      config.headers.Authorization = data.token;
+      config.headers.Authorization = response.data.token;
     }
     return config;
   }

+ 5 - 4
src/service/request/index.ts

@@ -5,11 +5,12 @@ const { url, proxyPattern } = getServiceEnvConfig(import.meta.env);
 
 const isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'Y';
 
-export const request = createRequest({ baseURL: isHttpProxy ? proxyPattern : url,
-	headers: {
-		'X-Requested-With': 'XMLHttpRequest',
+export const request = createRequest({
+  baseURL: isHttpProxy ? proxyPattern : url,
+  headers: {
+    'X-Requested-With': 'XMLHttpRequest',
     'Content-Type': 'application/json; charset=UTF-8'
-	}
+  }
 });
 
 export const mockRequest = createRequest({ baseURL: '/mock' });

+ 33 - 20
src/service/request/instance.ts

@@ -1,6 +1,6 @@
 import axios from 'axios';
 import type { AxiosResponse, AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios';
-import { REFRESH_TOKEN_CODE } from '@/config';
+import { REFRESH_TOKEN_CODE, REFRESH_TOKEN_COUNT } from '@/config';
 import {
   localStg,
   handleAxiosError,
@@ -28,10 +28,12 @@ export default class CustomAxiosInstance {
   constructor(
     axiosConfig: AxiosRequestConfig,
     backendConfig: Service.BackendResultConfig = {
-      codeKey: 'status',
+      statusKey: 'status',
       dataKey: 'data',
-      msgKey: 'code',
-      successCode: true
+      msgKey: 'msg',
+      successCode: true,
+      total: 'total',
+      codeKey: 'code'
     }
   ) {
     this.backendConfig = backendConfig;
@@ -43,52 +45,63 @@ export default class CustomAxiosInstance {
   setInterceptor() {
     this.instance.interceptors.request.use(
       async config => {
-				console.log("--------------------", config);
         const handleConfig = { ...config };
         if (handleConfig.headers) {
           // 数据转换
-					console.log(handleConfig.headers);
           const contentType = handleConfig.headers['Content-Type'] as UnionKey.ContentType;
-					// console.log(contentType);
           handleConfig.data = await transformRequestData(handleConfig.data, contentType);
           // 设置token
-          handleConfig.headers.Authorization = localStg.get('token') ||'';
+          const token = localStg.get('token');
+          if (token !== null && !handleConfig.headers.Authorization) {
+            handleConfig.headers.Authorization = token;
+          }
         }
-				console.log(handleConfig);
         return handleConfig;
       },
       (axiosError: AxiosError) => {
         const error = handleAxiosError(axiosError);
-        return handleServiceResult(error, null);
+        return handleServiceResult(error, null, null);
       }
     );
     this.instance.interceptors.response.use(
       (async response => {
         const { status } = response;
-        if (status === 200 || status < 300 || status === 304 ) {
+        if (status === 200 || status < 300 || status === 304) {
           const backend = response.data;
-          const { codeKey, dataKey, successCode } = this.backendConfig;
+          const { codeKey, dataKey, successCode, statusKey } = this.backendConfig;
           // 请求成功
-          if (backend[codeKey] === successCode ) {
-            return handleServiceResult(null, backend[dataKey]);
+          if (backend[statusKey] === successCode) {
+            return handleServiceResult(null, backend[dataKey], backend);
           }
+
+          // 如果已经失效一次则说明刷新Token也失效了
           // token失效, 刷新token
+
           if (REFRESH_TOKEN_CODE.includes(backend[codeKey])) {
-            const config = await handleRefreshToken(response.config);
-            if (config) {
-              return this.instance.request(config);
+            if (REFRESH_TOKEN_COUNT.length === 0) {
+              REFRESH_TOKEN_COUNT.push(1);
+              const config = await handleRefreshToken(response.config);
+              if (config) {
+                // eslint-disable-next-line max-depth
+                if (REFRESH_TOKEN_COUNT.length > 0) {
+                  REFRESH_TOKEN_COUNT.length = 0;
+                }
+                return this.instance.request(config);
+              }
+            } else {
+              return Promise.resolve();
             }
           }
 
           const error = handleBackendError(backend, this.backendConfig);
-          return handleServiceResult(error, null);
+          return handleServiceResult(error, null, backend);
         }
         const error = handleResponseError(response);
-        return handleServiceResult(error, null);
+        return handleServiceResult(error, null, response.data);
       }) as (response: AxiosResponse<any, any>) => Promise<AxiosResponse<any, any>>,
       (axiosError: AxiosError) => {
         const error = handleAxiosError(axiosError);
-        return handleServiceResult(error, null);
+        return handleServiceResult(error, null, null);
       }
     );
   }

+ 6 - 5
src/service/request/request.ts

@@ -19,9 +19,7 @@ interface RequestParam {
  * @param backendConfig - 后端接口字段配置
  */
 export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
-	console.log( "axiosConfig",  axiosConfig )
   const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);
-
   /**
    * 异步promise请求
    * @param param - 请求参数
@@ -29,6 +27,7 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
    * - method: 请求方法(默认get)
    * - data: 请求的body的data
    * - axiosConfig: axios配置
+   * - total
    */
   async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> {
     const { url } = param;
@@ -41,7 +40,6 @@ export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: S
       data: param.data,
       config: param.axiosConfig
     })) as Service.RequestResult<T>;
-
     return res;
   }
 
@@ -95,6 +93,7 @@ interface RequestResultHook<T = any> {
   error: Ref<Service.RequestError | null>;
   loading: Ref<boolean>;
   network: Ref<boolean>;
+  total: Ref<number>;
 }
 
 /**
@@ -120,11 +119,12 @@ export function createHookRequest(axiosConfig: AxiosRequestConfig, backendConfig
     startLoading();
     const data = ref<T | null>(null) as Ref<T | null>;
     const error = ref<Service.RequestError | null>(null);
-
+    const total = ref<number>() as Ref<number>;
     function handleRequestResult(response: any) {
       const res = response as Service.RequestResult<T>;
       data.value = res.data;
       error.value = res.error;
+      total.value = Number(res.total);
       endLoading();
       setNetwork(window.navigator.onLine);
     }
@@ -141,7 +141,8 @@ export function createHookRequest(axiosConfig: AxiosRequestConfig, backendConfig
       data,
       error,
       loading,
-      network
+      network,
+      total
     };
   }
 

+ 1 - 1
src/store/modules/app/index.ts

@@ -48,7 +48,7 @@ export const useAppStore = defineStore('app-store', {
      * 重载页面
      * @param duration - 重载的延迟时间(ms)
      */
-    async reloadPage(duration = 0) {
+    async reloadPage(duration = 100) {
       this.reloadFlag = false;
       await nextTick();
       if (duration) {

+ 8 - 5
src/store/modules/auth/helpers.ts

@@ -2,18 +2,21 @@ import { localStg } from '@/utils';
 
 /** 获取token */
 export function getToken() {
-  return localStg.get('token') || '';
+  return localStorage.getItem('token');
 }
 
 /** 获取用户信息 */
 export function getUserInfo() {
   const emptyInfo: Auth.UserInfo = {
-    userId: '',
-    userName: '',
-    userRole: 'user'
+    id: null,
+    username: '',
+    phone: null,
+    email: '',
+    permissions: [],
+    departments: [],
+    userType: ''
   };
   const userInfo: Auth.UserInfo = localStg.get('userInfo') || emptyInfo;
-
   return userInfo;
 }
 

+ 28 - 46
src/store/modules/auth/index.ts

@@ -1,7 +1,9 @@
 import { unref, nextTick } from 'vue';
 import { defineStore } from 'pinia';
+import { REFRESH_TOKEN_COUNT } from '@/config';
 import { router } from '@/router';
-import { fetchLogin, fetchUserInfo,AdminLoginParams } from '@/service';
+import type { AdminLoginParams } from '@/service';
+import { fetchLogin, fetchUserInfo, fetchStudentLogin } from '@/service';
 import { useRouterPush } from '@/composables';
 import { localStg } from '@/utils';
 import { useTabStore } from '../tab';
@@ -20,7 +22,7 @@ interface AuthState {
 export const useAuthStore = defineStore('auth-store', {
   state: (): AuthState => ({
     userInfo: getUserInfo(),
-    token: getToken(),
+    token: getToken() as string,
     loginLoading: false
   }),
   getters: {
@@ -38,6 +40,7 @@ export const useAuthStore = defineStore('auth-store', {
       const route = unref(router.currentRoute);
 
       clearAuthStorage();
+      REFRESH_TOKEN_COUNT.length = 0;
       this.$reset();
 
       if (route.meta.requiresAuth) {
@@ -61,7 +64,6 @@ export const useAuthStore = defineStore('auth-store', {
 
       if (loginSuccess) {
         await route.initAuthRoute();
-
         // 跳转登录后的地址
         toLoginRedirect();
 
@@ -69,7 +71,7 @@ export const useAuthStore = defineStore('auth-store', {
         if (route.isInitAuthRoute) {
           window.$notification?.success({
             title: '登录成功!',
-            content: `欢迎回来,${this.userInfo.userName}!`,
+            content: `欢迎回来,${this.userInfo.username}!`,
             duration: 3000
           });
         }
@@ -84,72 +86,52 @@ export const useAuthStore = defineStore('auth-store', {
      * 根据token进行登录
      * @param backendToken - 返回的token
      */
+
     async loginByToken(backendToken: ApiAuth.Token) {
       let successFlag = false;
-
       // 先把token存储到缓存中(后面接口的请求头需要token)
       const { token, refreshToken } = backendToken;
       localStg.set('token', token);
       localStg.set('refreshToken', refreshToken);
-
       // 获取用户信息
       const { data } = await fetchUserInfo();
       if (data) {
         // 成功后把用户信息存储到缓存中
-        localStg.set('userInfo', data);
-
+        localStg.set('userInfo', data as Auth.UserInfo);
         // 更新状态
-        this.userInfo = data;
+        this.userInfo = data as Auth.UserInfo;
         this.token = token;
-
         successFlag = true;
       }
-
       return successFlag;
     },
-		
+
+    async studentLogin(params: AdminLoginParams) {
+      this.loginLoading = true;
+      const { data, status, code } = await fetchStudentLogin(params);
+      if (status && code === 200) {
+        await this.handleActionAfterLogin(data as ApiAuth.Token);
+        return true;
+      }
+      this.loginLoading = false;
+      return false;
+    },
+
     /**
      * 登录
      * @param userName - 用户名
      * @param password - 密码
      */
 
-    async login(params:AdminLoginParams) {
+    async login(params: AdminLoginParams) {
       this.loginLoading = true;
-      const { data } = await fetchLogin(params);
-      if (data) {
-        await this.handleActionAfterLogin(data);
+      const { code, status, data } = await fetchLogin(params);
+      if (status && code === 200) {
+        await this.handleActionAfterLogin(data as ApiAuth.Token);
+        return true;
       }
       this.loginLoading = false;
-    },
-    /**
-     * 更换用户权限(切换账号)
-     * @param userRole
-     */
-    // async updateUserRole(userRole: Auth.RoleType) {
-    //   const { resetRouteStore, initAuthRoute } = useRouteStore();
-
-    //   const accounts: Record<Auth.RoleType, { userName: string; password: string }> = {
-    //     super: {
-    //       userName: 'Super',
-    //       password: 'super123'
-    //     },
-    //     admin: {
-    //       userName: 'Admin',
-    //       password: 'admin123'
-    //     },
-    //     user: {
-    //       userName: 'User01',
-    //       password: 'user01123'
-    //     }
-    //   };
-    //   const { userName, password } = accounts[userRole];
-    //   const { data } = await fetchLogin(userName, password);
-    //   if (data) {
-    //     await this.loginByToken(data);
-    //     resetRouteStore();
-    //     initAuthRoute();
-    //   }
-    // }
+      return false;
+    }
   }
 });

+ 5 - 27
src/store/modules/route/index.ts

@@ -1,8 +1,6 @@
 import { defineStore } from 'pinia';
 import { ROOT_ROUTE, constantRoutes, router, routes as staticRoutes } from '@/router';
-import { fetchUserRoutes } from '@/service';
 import {
-  localStg,
   filterAuthRoutesByUserPermission,
   getCacheRoutes,
   getConstantRouteNames,
@@ -11,8 +9,7 @@ import {
   transformAuthRouteToMenu,
   transformAuthRouteToSearchMenus,
   transformRouteNameToRoutePath,
-  transformRoutePathToRouteName,
-  sortRoutes
+  transformRoutePathToRouteName
 } from '@/utils';
 import { useAuthStore } from '../auth';
 import { useTabStore } from '../tab';
@@ -91,7 +88,6 @@ export const useRouteStore = defineStore('route-store', {
       vueRoutes.forEach(route => {
         router.addRoute(route);
       });
-
       this.cacheRoutes = getCacheRoutes(vueRoutes);
     },
     /** 动态路由模式下:更新根路由的重定向 */
@@ -108,38 +104,20 @@ export const useRouteStore = defineStore('route-store', {
     /** 初始化动态路由 */
     async initDynamicRoute() {
       const { resetAuthStore } = useAuthStore();
-      const { initHomeTab } = useTabStore();
-
-      const { userId } = localStg.get('userInfo') || {};
 
-      if (!userId) {
+      const id = localStorage.getItem('userInfo');
+      if (!id) {
         throw new Error('userId 不能为空!');
       }
-
-      const { error, data } = await fetchUserRoutes(userId);
-
-      if (!error) {
-        this.routeHomeName = data.home;
-        this.handleUpdateRootRedirect(data.home);
-        this.handleAuthRoute(sortRoutes(data.routes));
-
-        initHomeTab(data.home, router);
-
-        this.isInitAuthRoute = true;
-      } else {
-        resetAuthStore();
-      }
+      resetAuthStore();
     },
     /** 初始化静态路由 */
     async initStaticRoute() {
       const { initHomeTab } = useTabStore();
       const auth = useAuthStore();
-
-      const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userRole);
+      const routes = filterAuthRoutesByUserPermission(staticRoutes, auth.userInfo.userType);
       this.handleAuthRoute(routes);
-
       initHomeTab(this.routeHomeName, router);
-
       this.isInitAuthRoute = true;
     },
     /** 初始化权限路由 */

+ 6 - 0
src/styles/css/global.css

@@ -6,3 +6,9 @@ body,
 #app {
 	height: 100%;
 }
+.ant-picker-dropdown {
+	z-index: 3000;
+}
+.ant-picker {
+	border-radius: 4px;
+}

+ 0 - 29
src/typings/api.copy.ts

@@ -1,29 +0,0 @@
-declare namespace ApiUserMa {
-	interface User {
-		/** 用户id */
-		id?: number;
-		name: number;
-		/** 用户名 */
-		description: string | null;
-		/** 用户年龄 */
-		createTime: number | null;
-		modifyTime: string | null;
-		createUid: number;
-		// /**
-		//  * 用户性别
-		//  * - 0: 女
-		//  * - 1: 男
-		//  */
-		// gender: '0' | '1' | null;
-		// /** 用户手机号码 */
-		// phone: string;
-		// /** 用户邮箱 */
-		// email: string | null;
-		/**
-		 * 用户状态
-		 * - N: 启用
-		 * - Y: 禁用
-		 */
-		disabled: "N" | "Y" | null;
-	}
-}

+ 2 - 2
src/typings/api.d.ts

@@ -25,7 +25,7 @@ declare namespace ApiRoute {
 declare namespace ApiUserManagement {
   interface User {
     /** 用户id */
-    id: number | null;
+    id: number;
     /** 用户名 */
     name: string | null;
     /** 用户年龄 */
@@ -40,6 +40,6 @@ declare namespace ApiUserManagement {
      * - Y: 启用
      * - N: 禁用
      */
-    disabled: 'Y' | 'N'|null ;
+    disabled: 'Y' | 'N' | null;
   }
 }

+ 8 - 4
src/typings/business.d.ts

@@ -6,16 +6,20 @@ declare namespace Auth {
    * - admin: 管理员
    * - user: 用户
    */
-  type RoleType = 'super' | 'admin' | 'user';
+  type RoleType = 'admin' | 'teacher' | 'member' | 'disable' | '';
 
   /** 用户信息 */
   interface UserInfo {
     /** 用户id */
-    userId: string;
+    id: number | null;
     /** 用户名 */
-    userName: string;
+    username: string;
+    phone: string | null;
+    email: string;
+    permissions: [];
+    departments: [];
     /** 用户角色类型 */
-    userRole: RoleType;
+    userType: RoleType;
   }
 }
 

+ 11 - 0
src/typings/global.d.ts

@@ -19,3 +19,14 @@ declare namespace Common {
 
 /** 构建时间 */
 declare const PROJECT_BUILD_TIME: string;
+
+declare type Nullable<T> = T | null;
+declare type Recordable<T = any> = Record<string, T>;
+declare type ReadonlyRecordable<T = any> = {
+  readonly [key: string]: T;
+};
+
+declare module '*.xlsx' {
+  const src: string;
+  export default src;
+}

+ 38 - 85
src/typings/page-route.d.ts

@@ -19,64 +19,36 @@ declare namespace PageRoute {
     | '403'
     | '404'
     | '500'
-    | 'constant-page'
     | 'login'
     | 'not-found'
     | 'about'
-    | 'auth-demo'
-    | 'auth-demo_permission'
-    | 'auth-demo_super'
-    | 'component'
-    | 'component_button'
-    | 'component_card'
-    | 'component_table'
-    | 'crud'
-    | 'crud_demo'
-    | 'crud_doc'
-    | 'crud_header'
-    | 'crud_header_group'
-    | 'crud_source'
+    | 'archives'
+    | 'archives_students'
+    | 'archives_students_component'
     | 'dashboard'
-    | 'dashboard_analysis'
     | 'dashboard_workbench'
-    | 'document'
-    | 'document_naive'
-    | 'document_project-link'
-    | 'document_project'
-    | 'document_vite'
-    | 'document_vue'
     | 'exception'
     | 'exception_403'
     | 'exception_404'
     | 'exception_500'
-    | 'function'
-    | 'function_tab-detail'
-    | 'function_tab-multi-detail'
-    | 'function_tab'
-    | 'management'
-    | 'management_auth'
-    | 'management_role'
-    | 'management_route'
-    | 'management_sort'
-    | 'management_user'
-    | 'multi-menu'
-    | 'multi-menu_first'
-    | 'multi-menu_first_second-new'
-    | 'multi-menu_first_second-new_third'
-    | 'multi-menu_first_second'
-    | 'plugin'
-    | 'plugin_charts'
-    | 'plugin_charts_antv'
-    | 'plugin_charts_echarts'
-    | 'plugin_copy'
-    | 'plugin_editor'
-    | 'plugin_editor_markdown'
-    | 'plugin_editor_quill'
-    | 'plugin_icon'
-    | 'plugin_map'
-    | 'plugin_print'
-    | 'plugin_swiper'
-    | 'plugin_video';
+    | 'group'
+    | 'group_classroom'
+    | 'group_group'
+    | 'group_student'
+    | 'lesson'
+    | 'lesson_attendance'
+    | 'lesson_calendar'
+    | 'lesson_checkin'
+    | 'lesson_schedule'
+    | 'lesson_score'
+    | 'system'
+    | 'system_auth'
+    | 'system_category'
+    | 'system_profile'
+    | 'system_role'
+    | 'system_student'
+    | 'system_subject'
+    | 'system_user';
 
   /**
    * last degree route key, which has the page file
@@ -87,48 +59,29 @@ declare namespace PageRoute {
     | '403'
     | '404'
     | '500'
-    | 'constant-page'
     | 'login'
     | 'not-found'
     | 'about'
-    | 'auth-demo_permission'
-    | 'auth-demo_super'
-    | 'component_button'
-    | 'component_card'
-    | 'component_table'
-    | 'crud_demo'
-    | 'crud_doc'
-    | 'crud_header_group'
-    | 'crud_source'
-    | 'dashboard_analysis'
+    | 'archives_students_component'
+    | 'archives_students'
     | 'dashboard_workbench'
-    | 'document_naive'
-    | 'document_project-link'
-    | 'document_project'
-    | 'document_vite'
-    | 'document_vue'
     | 'exception_403'
     | 'exception_404'
     | 'exception_500'
-    | 'function_tab-detail'
-    | 'function_tab-multi-detail'
-    | 'function_tab'
-    | 'management_auth'
-    | 'management_role'
-    | 'management_route'
-    | 'management_sort'
-    | 'management_user'
-    | 'multi-menu_first_second-new_third'
-    | 'multi-menu_first_second'
-    | 'plugin_charts_antv'
-    | 'plugin_charts_echarts'
-    | 'plugin_copy'
-    | 'plugin_editor_markdown'
-    | 'plugin_editor_quill'
-    | 'plugin_icon'
-    | 'plugin_map'
-    | 'plugin_print'
-    | 'plugin_swiper'
-    | 'plugin_video'
+    | 'group_classroom'
+    | 'group_group'
+    | 'group_student'
+    | 'lesson_attendance'
+    | 'lesson_calendar'
+    | 'lesson_checkin'
+    | 'lesson_schedule'
+    | 'lesson_score'
+    | 'system_auth'
+    | 'system_category'
+    | 'system_profile'
+    | 'system_role'
+    | 'system_student'
+    | 'system_subject'
+    | 'system_user'
   >;
 }

+ 0 - 22
src/typings/sort.ts

@@ -1,22 +0,0 @@
-declare namespace CourseSort {
-  interface User extends ApiUserMa.User {
-    /** 序号 */
-    index: number;
-    /** 表格的key(id) */
-    key: string;
-  }
-
-  /**
-   * 用户性别
-   * - 0: 女
-   * - 1: 男
-   */
-  // type GenderKey = NonNullable<User['gender']>;
-
-  /**
-   * 用户状态
-   * - 1: 启用
-   * - 2: 禁用
-   */
-  type UserStatusKey = NonNullable<User['disabled']>;
-}

+ 35 - 65
src/typings/system.d.ts

@@ -13,9 +13,9 @@ declare namespace Service {
     /** 请求服务的错误类型 */
     type: RequestErrorType;
     /** 错误码 */
-    code: string | number;
+    code: number | string | null;
     /** 错误信息 */
-    msg: string;
+    msg: string | null;
   }
 
   /** 后端接口返回的数据结构配置 */
@@ -28,22 +28,32 @@ declare namespace Service {
     msgKey: string;
     /** 后端业务上定义的成功请求的状态 */
     successCode: boolean;
+    total: string;
+    statusKey: string;
   }
 
   /** 自定义的请求成功结果 */
   interface SuccessResult<T = any> {
     /** 请求错误 */
-    error: null;
+    error: RequestError | null;
     /** 请求数据 */
     data: T;
+    status: boolean;
+    total: number | null;
+    msg: string | null;
+    code: number | string | null;
   }
 
   /** 自定义的请求失败结果 */
   interface FailedResult {
     /** 请求错误 */
-    error: RequestError;
+    error: RequestError | null;
     /** 请求数据 */
     data: null;
+    total: null | number;
+    status: boolean;
+    msg: string | null;
+    code: number | string | null;
   }
 
   /** 自定义的请求结果 */
@@ -311,76 +321,36 @@ declare namespace I18nType {
     routes: {
       dashboard: {
         dashboard: string;
-        analysis: string;
         workbench: string;
       };
-      document: {
-        _value: string;
-        vue: string;
-        vite: string;
-        naive: string;
-        project: string;
-        'project-link': string;
-      };
-      component: {
-        _value: string;
-        button: string;
-        card: string;
-        table: string;
-      };
-      plugin: {
-        _value: string;
-        charts: {
-          _value: string;
-          antv: string;
-          echarts: string;
-        };
-        copy: string;
-        editor: {
-          _value: string;
-          markdown: string;
-          quill: string;
-        };
-        icon: string;
-        map: string;
-        print: string;
-        swiper: string;
-        video: string;
-      };
-      'auth-demo': {
-        _value: string;
-        permission: string;
-        super: string;
-      };
-      function: {
+      system: {
         _value: string;
-        tab: string;
+        auth: string;
+        role: string;
+        route: string;
+        user: string;
+        sort: string;
+        student: string;
+        profile: string;
+        setting: string;
       };
-      exception: {
+      lesson: {
         _value: string;
-        403: string;
-        404: string;
-        500: string;
+        schedule: string;
+        calendar: string;
+        checkin: string;
+        attendance: string;
+        score: string;
       };
-      'multi-menu': {
+      group: {
         _value: string;
-        first: {
-          _value: string;
-          second: string;
-          'second-new': {
-            _value: string;
-            third: string;
-          };
-        };
+        group: string;
+        student: string;
+        classroom: string;
       };
-      management: {
+      archives: {
         _value: string;
-        auth: string;
-        role: string;
-        route: string;
-        user: string;
-			  sort: string;
-				usdt:string;
+        students: string;
       };
       about: string;
     };

+ 1 - 1
src/typings/union-key.d.ts

@@ -17,7 +17,7 @@ declare namespace UnionKey {
    * - reset-pwd: 重置密码
    * - bind-wechat: 微信绑定
    */
-  type LoginModule = 'pwd-login' | 'code-login' | 'register' | 'reset-pwd' | 'bind-wechat';
+  type LoginModule = 'pwd-login';
 
   /**
    * 布局模式

+ 34 - 15
src/utils/crypto/index.ts

@@ -6,31 +6,50 @@ const keyword = 'eas-key-password';
  * 加密数据
  * @param data - 数据
  */
-export function encrypto(data: any) {
-	const time = Date.now();
-	 //转码
-	 const wordStr = CryptoJS.enc.Utf8.parse(time + "" + data);
-	 const key = CryptoJS.enc.Utf8.parse(keyword);
-  // const newData = JSON.stringify(data);
-	//加密
+export function encryption(data: any) {
+  const time = Date.now();
+  // 转码
+  const wordStr = CryptoJS.enc.Utf8.parse(String(time) + data);
+  const key = CryptoJS.enc.Utf8.parse(keyword);
   const cryptoStr = CryptoJS.AES.encrypt(wordStr, key, {
-    mode: CryptoJS.mode.ECB, //模式
-    padding: CryptoJS.pad.Pkcs7, //补零
+    mode: CryptoJS.mode.ECB, // 模式
+    padding: CryptoJS.pad.Pkcs7 // 补零
   });
- return cryptoStr.toString();
+  return cryptoStr.toString();
 }
 
 /**
  * 解密数据
  * @param cipherText - 密文
  */
-export function decrypto(cipherText: string) {
-	const key = CryptoJS.enc.Utf8.parse(keyword);
-	//解密
+export function decryption(cipherText: string) {
+  const key = CryptoJS.enc.Utf8.parse(keyword);
+  // 解密
   const cryptoStr = CryptoJS.AES.decrypt(cipherText, key, {
-    mode: CryptoJS.mode.ECB, //模式
-    padding: CryptoJS.pad.Pkcs7, //补零
+    mode: CryptoJS.mode.ECB, // 模式
+    padding: CryptoJS.pad.Pkcs7 // 补零
   });
   return CryptoJS.enc.Utf8.stringify(cryptoStr).toString();
+}
+
+/**
+ * 加密数据
+ * @param data - 数据
+ */
+export function encryptJson(data: any) {
+  const newData = JSON.stringify(data);
+  return CryptoJS.AES.encrypt(newData, keyword).toString();
+}
+
+/**
+ * 解密数据
+ * @param cipherText - 密文
+ */
+export function decryptJson(cipherText: string) {
+  const bytes = CryptoJS.AES.decrypt(cipherText, keyword);
+  const originalText = bytes.toString(CryptoJS.enc.Utf8);
+  if (originalText) {
+    return JSON.parse(originalText);
+  }
   return null;
 }

+ 29 - 0
src/utils/form/date.ts

@@ -0,0 +1,29 @@
+export const formatTimestamp = (timestamp: number): string => {
+  const date = new Date(timestamp);
+
+  const year = date.getFullYear();
+  const month = `0${date.getMonth() + 1}`.slice(-2);
+  const day = `0${date.getDate()}`.slice(-2);
+  const hour = `0${date.getHours()}`.slice(-2);
+  const minute = `0${date.getMinutes()}`.slice(-2);
+
+  return `${year}-${month}-${day} ${hour}:${minute}`;
+};
+
+export const formatDate = (date: Date): string => {
+  const year = date.getFullYear();
+  const month = `0${date.getMonth() + 1}`.slice(-2);
+  const day = `0${date.getDate()}`.slice(-2);
+  const hour = `0${date.getHours()}`.slice(-2);
+  const minute = `0${date.getMinutes()}`.slice(-2);
+
+  return `${year}-${month}-${day} ${hour}:${minute}`;
+};
+
+export function getLastDayOfMonth(date: Date) {
+  return new Date(date.getFullYear(), date.getMonth() + 1, 0, 23, 59, 59);
+}
+
+export function getFirstDayOfMonth(date: Date) {
+  return new Date(date.getFullYear(), date.getMonth(), 1);
+}

+ 1 - 0
src/utils/form/index.ts

@@ -1 +1,2 @@
 export * from './rule';
+export * from './date';

+ 3 - 6
src/utils/form/rule.ts

@@ -11,16 +11,13 @@ export const requiredFormRule = createRequiredFormRule();
 interface CustomFormRules {
   /** 手机号码 */
   createTime: FormItemRule[];
-	pwd:FormItemRule[];
+  pwd: FormItemRule[];
 }
 
 /** 表单规则 */
 export const formRules: CustomFormRules = {
-  createTime: [
-    createRequiredFormRule('请输入创建时间'),
-    { pattern: '', message: '不能为空', trigger: 'input' }
-  ],
-	pwd: [{ pattern: REGEXP_PWD, message: '密码格式错误', trigger: 'blur' }]
+  createTime: [createRequiredFormRule('请输入创建时间'), { pattern: '', message: '不能为空', trigger: 'input' }],
+  pwd: [{ pattern: REGEXP_PWD, message: '密码格式错误', trigger: 'blur' }]
 };
 
 /** 是否为空字符串 */

+ 1 - 0
src/utils/index.ts

@@ -3,3 +3,4 @@ export * from './storage';
 export * from './service';
 export * from './router';
 export * from './form';
+export * from './tools';

+ 1 - 2
src/utils/router/auth.ts

@@ -15,8 +15,7 @@ export function filterAuthRoutesByUserPermission(routes: AuthRoute.Route[], perm
 function filterAuthRouteByUserPermission(route: AuthRoute.Route, permission: Auth.RoleType): AuthRoute.Route[] {
   const filterRoute = { ...route };
   const hasPermission =
-    !route.meta.permissions || permission === 'super' || route.meta.permissions.includes(permission);
-
+    !route.meta.permissions || permission === 'admin' || route.meta.permissions.includes(permission);
   if (filterRoute.children) {
     const filterChildren = filterRoute.children.map(item => filterAuthRouteByUserPermission(item, permission)).flat(1);
     Object.assign(filterRoute, { children: filterChildren });

部分文件因文件數量過多而無法顯示