Selaa lähdekoodia

样式调整,代码格式化,bug修复

zouzs 4 viikkoa sitten
vanhempi
commit
9ca3a4fd66

+ 4 - 1
src/api/routes.ts

@@ -7,5 +7,8 @@ type Result = {
 };
 
 export const getAsyncRoutes = () => {
-  return http.request<Result>("get", baseUrlApi("system/menu/getRouters/authPerm"));
+  return http.request<Result>(
+    "get",
+    baseUrlApi("system/menu/getRouters/authPerm")
+  );
 };

+ 4 - 4
src/components/ExportView/index.vue

@@ -3,10 +3,10 @@
     导出数据
   </el-button>
   <el-dialog
+    v-model="internalVisible"
     center
     :close-on-click-modal="false"
     destroy-on-close
-    v-model="internalVisible"
     :title="dialogTitle"
     width="480px"
     :before-close="handleClose"
@@ -18,8 +18,8 @@
             v-model="form.startPage"
             placeholder="请输入开始页码"
             clearable
-            @keyup.enter="handleExport"
             style="max-width: 120px"
+            @keyup.enter="handleExport"
           />
         </el-form-item>
 
@@ -30,8 +30,8 @@
             v-model="form.endPage"
             placeholder="请输入结束页码"
             clearable
-            @keyup.enter="handleExport"
             style="max-width: 120px"
+            @keyup.enter="handleExport"
           />
         </el-form-item>
       </el-row>
@@ -40,7 +40,7 @@
     <template #footer>
       <div class="dialog-footer">
         <el-button @click="handleClose">取消</el-button>
-        <el-button type="primary" @click="handleExport" :loading="loading">
+        <el-button type="primary" :loading="loading" @click="handleExport">
           导出
         </el-button>
       </div>

+ 45 - 34
src/components/UploadOss/index.vue

@@ -3,58 +3,69 @@ import Clients from "@/utils/upload";
 
 defineOptions({
   name: "UploadOss"
-})
+});
 
 defineProps<{
   drag?: boolean;
   fileList?: [];
-}>()
+}>();
 
 const handleGetOssToken = () => {
-  console.log('获取token');
-}
+  console.log("获取token");
+};
 
-const handleBeforeUpload = async (file: File) => {
-
-}
+const handleBeforeUpload = async (file: File) => {};
 
 const handleUpload = (option: any) => {
-  console.log('上传中', option);
-  const {file} = option
+  console.log("上传中", option);
+  const { file } = option;
   try {
-    const client = new Clients()
-    console.log(client)
-    client.put(`${file.name}`, file).then((res: any) => {
-      console.log('上传成功', res);
-      option.onSuccess(res)
-    }).catch((err: any) => {
-      console.log('上传失败', err);
-      option.onError(err)
-    })
+    const client = new Clients();
+    console.log(client);
+    client
+      .put(`${file.name}`, file)
+      .then((res: any) => {
+        console.log("上传成功", res);
+        option.onSuccess(res);
+      })
+      .catch((err: any) => {
+        console.log("上传失败", err);
+        option.onError(err);
+      });
   } catch (e) {
-    console.log(e)
-    option.onError(e)
+    console.log(e);
+    option.onError(e);
   }
-}
+};
 </script>
 
 <template>
   <el-upload
-      class="upload-demo"
-      drag
-      action="/"
-      :http-request="handleUpload"
-      :before-upload="handleBeforeUpload"
-      :on-preview="(file) => { console.log('preview', file); }"
-      :on-remove="(file, fileList) => { console.log('remove', file, fileList); }"
-      :before-remove="(file, fileList) => { return $confirm(`确定移除 ${file.name}?`); }"
-      :file-list="fileList"
+    class="upload-demo"
+    drag
+    action="/"
+    :http-request="handleUpload"
+    :before-upload="handleBeforeUpload"
+    :on-preview="
+      file => {
+        console.log('preview', file);
+      }
+    "
+    :on-remove="
+      (file, fileList) => {
+        console.log('remove', file, fileList);
+      }
+    "
+    :before-remove="
+      (file, fileList) => {
+        return $confirm(`确定移除 ${file.name}?`);
+      }
+    "
+    :file-list="fileList"
   >
-    <i class="el-icon-upload"></i>
+    <i class="el-icon-upload" />
     <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
   </el-upload>
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 20 - 20
src/main.ts

@@ -1,12 +1,12 @@
 import App from "./App.vue";
 import router from "./router";
-import {setupStore} from "@/store";
-import {getPlatformConfig} from "./config";
-import {MotionPlugin} from "@vueuse/motion";
+import { setupStore } from "@/store";
+import { getPlatformConfig } from "./config";
+import { MotionPlugin } from "@vueuse/motion";
 // import { useEcharts } from "@/plugins/echarts";
-import {createApp, type Directive} from "vue";
-import {useElementPlus} from "@/plugins/elementPlus";
-import {injectResponsiveStorage} from "@/utils/responsive";
+import { createApp, type Directive } from "vue";
+import { useElementPlus } from "@/plugins/elementPlus";
+import { injectResponsiveStorage } from "@/utils/responsive";
 
 import Table from "@pureadmin/table";
 import PureDescriptions from "@pureadmin/descriptions";
@@ -30,14 +30,14 @@ const app = createApp(App);
 import * as directives from "@/directives";
 
 Object.keys(directives).forEach(key => {
-    app.directive(key, (directives as { [key: string]: Directive })[key]);
+  app.directive(key, (directives as { [key: string]: Directive })[key]);
 });
 
 // 全局注册@iconify/vue图标库
 import {
-    IconifyIconOffline,
-    IconifyIconOnline,
-    FontIcon
+  IconifyIconOffline,
+  IconifyIconOnline,
+  FontIcon
 } from "./components/ReIcon";
 
 app.component("IconifyIconOffline", IconifyIconOffline);
@@ -45,8 +45,8 @@ app.component("IconifyIconOnline", IconifyIconOnline);
 app.component("FontIcon", FontIcon);
 
 // 全局注册按钮级别权限组件
-import {Auth} from "@/components/ReAuth";
-import {Perms} from "@/components/RePerms";
+import { Auth } from "@/components/ReAuth";
+import { Perms } from "@/components/RePerms";
 
 app.component("Auth", Auth);
 app.component("Perms", Perms);
@@ -59,12 +59,12 @@ import VueTippy from "vue-tippy";
 app.use(VueTippy);
 
 getPlatformConfig(app).then(async config => {
-    setupStore(app);
-    app.use(router);
-    await router.isReady();
-    injectResponsiveStorage(app, config);
-    app.use(MotionPlugin).use(useElementPlus).use(Table);
-    // .use(PureDescriptions)
-    // .use(useEcharts);
-    app.mount("#app");
+  setupStore(app);
+  app.use(router);
+  await router.isReady();
+  injectResponsiveStorage(app, config);
+  app.use(MotionPlugin).use(useElementPlus).use(Table);
+  // .use(PureDescriptions)
+  // .use(useEcharts);
+  app.mount("#app");
 });

+ 28 - 28
src/router/index.ts

@@ -158,38 +158,38 @@ router.beforeEach((to: ToRouteType, _from, next) => {
         to.path !== "/login"
       ) {
         initRouter().then((router: Router) => {
-        /*// 使用下面方法替换initRouter
+          /*// 使用下面方法替换initRouter
         usePermissionStoreHook().handleWholeMenus([]);
         addPathMatch();*/
-        if (!useMultiTagsStoreHook().getMultiTagsCache) {
-          const { path } = to;
-          const route = findRouteByPath(
-            path,
-            router.options.routes[0].children
-          );
-          getTopMenu(true);
-          // query、params模式路由传参数的标签页不在此处处理
-          if (route && route.meta?.title) {
-            if (isAllEmpty(route.parentId) && route.meta?.backstage) {
-              // 此处为动态顶级路由(目录)
-              const { path, name, meta } = route.children[0];
-              useMultiTagsStoreHook().handleTags("push", {
-                path,
-                name,
-                meta
-              });
-            } else {
-              const { path, name, meta } = route;
-              useMultiTagsStoreHook().handleTags("push", {
-                path,
-                name,
-                meta
-              });
+          if (!useMultiTagsStoreHook().getMultiTagsCache) {
+            const { path } = to;
+            const route = findRouteByPath(
+              path,
+              router.options.routes[0].children
+            );
+            getTopMenu(true);
+            // query、params模式路由传参数的标签页不在此处处理
+            if (route && route.meta?.title) {
+              if (isAllEmpty(route.parentId) && route.meta?.backstage) {
+                // 此处为动态顶级路由(目录)
+                const { path, name, meta } = route.children[0];
+                useMultiTagsStoreHook().handleTags("push", {
+                  path,
+                  name,
+                  meta
+                });
+              } else {
+                const { path, name, meta } = route;
+                useMultiTagsStoreHook().handleTags("push", {
+                  path,
+                  name,
+                  meta
+                });
+              }
             }
           }
-        }
-        // 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次)
-        if (isAllEmpty(to.name)) router.push(to.fullPath);
+          // 确保动态路由完全加入路由列表并且不影响静态路由(注意:动态路由刷新时router.beforeEach可能会触发两次,第一次触发动态路由还未完全添加,第二次动态路由才完全添加到路由列表,如果需要在router.beforeEach做一些判断可以在to.name存在的条件下去判断,这样就只会触发一次)
+          if (isAllEmpty(to.name)) router.push(to.fullPath);
         });
       }
       toCorrectRoute();

+ 20 - 20
src/router/modules/home.ts

@@ -1,25 +1,25 @@
-const {VITE_HIDE_HOME} = import.meta.env;
+const { VITE_HIDE_HOME } = import.meta.env;
 const Layout = () => import("@/layout/index.vue");
 
 export default {
-    path: "/",
-    name: "Home",
-    component: Layout,
-    redirect: "/welcome",
-    meta: {
-        icon: "ep/home-filled",
+  path: "/",
+  name: "Home",
+  component: Layout,
+  redirect: "/welcome",
+  meta: {
+    icon: "ep/home-filled",
+    title: "首页",
+    rank: 0
+  },
+  children: [
+    {
+      path: "/welcome",
+      name: "Welcome",
+      component: () => import("@/views/welcome/index.vue"),
+      meta: {
         title: "首页",
-        rank: 0
-    },
-    children: [
-        {
-            path: "/welcome",
-            name: "Welcome",
-            component: () => import("@/views/welcome/index.vue"),
-            meta: {
-                title: "首页",
-                showLink: VITE_HIDE_HOME === "true" ? false : true
-            }
-        }
-    ]
+        showLink: VITE_HIDE_HOME === "true" ? false : true
+      }
+    }
+  ]
 } satisfies RouteConfigsTable;

+ 16 - 16
src/router/modules/testFingerPrints.ts

@@ -1,19 +1,19 @@
 // 最简代码,也就是这些字段必须有
 export default {
-    path: "/testFingerPrints",
-    meta: {
-        title: "测试信息获取",
-        showLink: false,
-    },
-    children: [
-        {
-            path: "/testFingerPrints/information",
-            name: "Information",
-            component: () => import("@/views/testFingerPrints/information/index.vue"),
-            meta: {
-                title: "fingerprints,浏览器指纹",
-                showParent: true
-            }
-        },
-    ]
+  path: "/testFingerPrints",
+  meta: {
+    title: "测试信息获取",
+    showLink: false
+  },
+  children: [
+    {
+      path: "/testFingerPrints/information",
+      name: "Information",
+      component: () => import("@/views/testFingerPrints/information/index.vue"),
+      meta: {
+        title: "fingerprints,浏览器指纹",
+        showParent: true
+      }
+    }
+  ]
 };

+ 24 - 24
src/router/modules/testStorage.ts

@@ -1,28 +1,28 @@
 // 最简代码,也就是这些字段必须有
 export default {
-    path: "/testStorage",
-    meta: {
-        title: "测试存储",
-        showLink: false,
+  path: "/testStorage",
+  meta: {
+    title: "测试存储",
+    showLink: false
+  },
+  children: [
+    {
+      path: "/testStorage/local",
+      name: "Local",
+      component: () => import("@/views/testStorage/local/index.vue"),
+      meta: {
+        title: "本地存储",
+        showParent: true
+      }
     },
-    children: [
-        {
-            path: "/testStorage/local",
-            name: "Local",
-            component: () => import("@/views/testStorage/local/index.vue"),
-            meta: {
-                title: "本地存储",
-                showParent: true
-            }
-        },
-        {
-            path: "/testStorage/cookie",
-            name: "Cookie",
-            component: () => import("@/views/testStorage/cookie/index.vue"),
-            meta: {
-                title: "cookie存储",
-                showParent: true
-            }
-        }
-    ]
+    {
+      path: "/testStorage/cookie",
+      name: "Cookie",
+      component: () => import("@/views/testStorage/cookie/index.vue"),
+      meta: {
+        title: "cookie存储",
+        showParent: true
+      }
+    }
+  ]
 };

+ 23 - 23
src/router/modules/testTable.ts

@@ -1,28 +1,28 @@
 // 最简代码,也就是这些字段必须有
 export default {
-    path: "/testTable",
-    meta: {
+  path: "/testTable",
+  meta: {
+    title: "封装表格",
+    showLink: false
+  },
+  children: [
+    {
+      path: "/testTable/splitTable",
+      name: "SplitTable",
+      component: () => import("@/views/testTable/splitTable/index.vue"),
+      meta: {
         title: "封装表格",
-        showLink: false,
+        showParent: true
+      }
     },
-    children: [
-        {
-            path: "/testTable/splitTable",
-            name: "SplitTable",
-            component: () => import("@/views/testTable/splitTable/index.vue"),
-            meta: {
-                title: "封装表格",
-                showParent: true
-            }
-        },
-        {
-            path: "/testTable/PageTable",
-            name: "PageTable",
-            component: () => import("@/views/testTable/pageTable/index.vue"),
-            meta: {
-                title: "整体封装",
-                showParent: true
-            }
-        }
-    ]
+    {
+      path: "/testTable/PageTable",
+      name: "PageTable",
+      component: () => import("@/views/testTable/pageTable/index.vue"),
+      meta: {
+        title: "整体封装",
+        showParent: true
+      }
+    }
+  ]
 };

+ 24 - 24
src/router/modules/testUpload.ts

@@ -1,28 +1,28 @@
 // 最简代码,也就是这些字段必须有
 export default {
-    path: "/testUpload",
-    meta: {
-        title: "上传文件",
-        showLink: false,
+  path: "/testUpload",
+  meta: {
+    title: "上传文件",
+    showLink: false
+  },
+  children: [
+    {
+      path: "/testUpload/upload",
+      name: "Upload",
+      component: () => import("@/views/testUpload/upload/index.vue"),
+      meta: {
+        title: "测试上传",
+        showParent: true
+      }
     },
-    children: [
-        {
-            path: "/testUpload/upload",
-            name: "Upload",
-            component: () => import("@/views/testUpload/upload/index.vue"),
-            meta: {
-                title: "测试上传",
-                showParent: true
-            }
-        },
-        {
-            path: "/testUpload/upload-oss",
-            name: "UploadOss",
-            component: () => import("@/views/testUpload/upload-oss/index.vue"),
-            meta: {
-                title: "上传到oss",
-                showParent: true
-            }
-        }
-    ]
+    {
+      path: "/testUpload/upload-oss",
+      name: "UploadOss",
+      component: () => import("@/views/testUpload/upload-oss/index.vue"),
+      meta: {
+        title: "上传到oss",
+        showParent: true
+      }
+    }
+  ]
 };

+ 113 - 113
src/utils/auth.ts

@@ -1,39 +1,39 @@
 import Cookies from "js-cookie";
-import {useUserStoreHook} from "@/store/modules/user";
-import {storageLocal, isString, isIncludeAllChildren} from "@pureadmin/utils";
+import { useUserStoreHook } from "@/store/modules/user";
+import { storageLocal, isString, isIncludeAllChildren } from "@pureadmin/utils";
 
 export interface DataInfo<T> {
-    /** token */
-    accessToken: string;
-    /** `accessToken`的过期时间(时间戳) */
-    expires: T;
-    /** 用于调用刷新accessToken的接口时所需的token */
-    refreshToken: string;
-    /** 头像 */
-    avatar?: string;
-    /** 用户名 */
-    username?: string;
-    /** 昵称 */
-    nickname?: string;
-    /** 当前登录用户的角色 */
-    roles?: Array<string>;
-    /** 当前登录用户的按钮级别权限 */
-    permissions?: Array<string>;
+  /** token */
+  accessToken: string;
+  /** `accessToken`的过期时间(时间戳) */
+  expires: T;
+  /** 用于调用刷新accessToken的接口时所需的token */
+  refreshToken: string;
+  /** 头像 */
+  avatar?: string;
+  /** 用户名 */
+  username?: string;
+  /** 昵称 */
+  nickname?: string;
+  /** 当前登录用户的角色 */
+  roles?: Array<string>;
+  /** 当前登录用户的按钮级别权限 */
+  permissions?: Array<string>;
 
-    admUserId: number;
-    admUserName: string;
-    id: number;
-    loginName: string;
-    operationRole: number;
-    roleId: number;
-    roleName: string;
-    token: string;
-    userMenuTree: object;
-    userModule: string;
-    _businessDockingId: number;
+  admUserId: number;
+  admUserName: string;
+  id: number;
+  loginName: string;
+  operationRole: number;
+  roleId: number;
+  roleName: string;
+  token: string;
+  userMenuTree: object;
+  userModule: string;
+  _businessDockingId: number;
 
-    access_token: string;
-    expires_in: number;
+  access_token: string;
+  expires_in: number;
 }
 
 export const userKey = "user-info";
@@ -48,10 +48,10 @@ export const multipleTabsKey = "multiple-tabs";
 
 /** 获取`token` */
 export function getToken(): DataInfo<number> {
-    // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
-    return Cookies.get(TokenKey)
-        ? JSON.parse(Cookies.get(TokenKey))
-        : storageLocal().getItem(userKey);
+  // 此处与`TokenKey`相同,此写法解决初始化时`Cookies`中不存在`TokenKey`报错
+  return Cookies.get(TokenKey)
+    ? JSON.parse(Cookies.get(TokenKey))
+    : storageLocal().getItem(userKey);
 }
 
 /**
@@ -61,98 +61,98 @@ export function getToken(): DataInfo<number> {
  * 将`avatar`、`username`、`nickname`、`roles`、`permissions`、`refreshToken`、`expires`这七条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
  */
 export function setToken(data: DataInfo<number>) {
-    const expires = 0;
-    // const { accessToken, refreshToken } = data;
-    const {access_token, expires_in} = data;
-    const {isRemembered, loginDay} = useUserStoreHook();
-    // expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
-    // const cookieString = JSON.stringify({ token, expires, refreshToken });
-    const cookieString = JSON.stringify({access_token, expires_in});
+  const expires = 0;
+  // const { accessToken, refreshToken } = data;
+  const { access_token, expires_in } = data;
+  const { isRemembered, loginDay } = useUserStoreHook();
+  // expires = new Date(data.expires).getTime(); // 如果后端直接设置时间戳,将此处代码改为expires = data.expires,然后把上面的DataInfo<Date>改成DataInfo<number>即可
+  // const cookieString = JSON.stringify({ token, expires, refreshToken });
+  const cookieString = JSON.stringify({ access_token, expires_in });
 
-    expires_in > 0
-        ? Cookies.set(TokenKey, cookieString, {
-            expires: expires_in
-        })
-        : Cookies.set(TokenKey, cookieString);
+  expires_in > 0
+    ? Cookies.set(TokenKey, cookieString, {
+        expires: expires_in
+      })
+    : Cookies.set(TokenKey, cookieString);
 
-    Cookies.set(
-        multipleTabsKey,
-        "true",
-        isRemembered
-            ? {
-                expires: loginDay
-            }
-            : {}
-    );
+  Cookies.set(
+    multipleTabsKey,
+    "true",
+    isRemembered
+      ? {
+          expires: loginDay
+        }
+      : {}
+  );
 
-    function setUserKey({avatar, username, nickname, roles, permissions}) {
-        useUserStoreHook().SET_AVATAR(avatar);
-        useUserStoreHook().SET_USERNAME(username);
-        useUserStoreHook().SET_NICKNAME(nickname);
-        useUserStoreHook().SET_ROLES(roles);
-        useUserStoreHook().SET_PERMS(permissions);
-        storageLocal().setItem(userKey, {
-            // refreshToken,
-            expires,
-            avatar,
-            username,
-            nickname,
-            roles,
-            permissions
-        });
-    }
+  function setUserKey({ avatar, username, nickname, roles, permissions }) {
+    useUserStoreHook().SET_AVATAR(avatar);
+    useUserStoreHook().SET_USERNAME(username);
+    useUserStoreHook().SET_NICKNAME(nickname);
+    useUserStoreHook().SET_ROLES(roles);
+    useUserStoreHook().SET_PERMS(permissions);
+    storageLocal().setItem(userKey, {
+      // refreshToken,
+      expires,
+      avatar,
+      username,
+      nickname,
+      roles,
+      permissions
+    });
+  }
 
-    if (data.username && data.roles) {
-        const {username, roles} = data;
-        setUserKey({
-            avatar: data?.avatar ?? "",
-            username,
-            nickname: data?.nickname ?? "",
-            roles,
-            permissions: data?.permissions ?? []
-        });
-    } else {
-        const avatar =
-            storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "";
-        const username =
-            storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "";
-        const nickname =
-            storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
-        const roles =
-            storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
-        const permissions =
-            storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
-        setUserKey({
-            avatar,
-            username,
-            nickname,
-            roles,
-            permissions
-        });
-    }
+  if (data.username && data.roles) {
+    const { username, roles } = data;
+    setUserKey({
+      avatar: data?.avatar ?? "",
+      username,
+      nickname: data?.nickname ?? "",
+      roles,
+      permissions: data?.permissions ?? []
+    });
+  } else {
+    const avatar =
+      storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "";
+    const username =
+      storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "";
+    const nickname =
+      storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "";
+    const roles =
+      storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [];
+    const permissions =
+      storageLocal().getItem<DataInfo<number>>(userKey)?.permissions ?? [];
+    setUserKey({
+      avatar,
+      username,
+      nickname,
+      roles,
+      permissions
+    });
+  }
 }
 
 /** 删除`token`以及key值为`user-info`的localStorage信息 */
 export function removeToken() {
-    Cookies.remove(TokenKey);
-    Cookies.remove(multipleTabsKey);
-    storageLocal().removeItem(userKey);
+  Cookies.remove(TokenKey);
+  Cookies.remove(multipleTabsKey);
+  storageLocal().removeItem(userKey);
 }
 
 /** 格式化token(jwt格式) */
 export const formatToken = (token: string): string => {
-    return "Bearer " + token;
+  return "Bearer " + token;
 };
 
 /** 是否有按钮级别的权限(根据登录接口返回的`permissions`字段进行判断)*/
 export const hasPerms = (value: string | Array<string>): boolean => {
-    if (!value) return false;
-    const allPerms = "*:*:*";
-    const {permissions} = useUserStoreHook();
-    if (!permissions) return false;
-    if (permissions.length === 1 && permissions[0] === allPerms) return true;
-    const isAuths = isString(value)
-        ? permissions.includes(value)
-        : isIncludeAllChildren(value, permissions);
-    return isAuths ? true : false;
+  if (!value) return false;
+  const allPerms = "*:*:*";
+  const { permissions } = useUserStoreHook();
+  if (!permissions) return false;
+  if (permissions.length === 1 && permissions[0] === allPerms) return true;
+  const isAuths = isString(value)
+    ? permissions.includes(value)
+    : isIncludeAllChildren(value, permissions);
+  return isAuths ? true : false;
 };

+ 207 - 207
src/utils/http/index.ts

@@ -1,89 +1,89 @@
 import Axios, {
-    type AxiosInstance,
-    type AxiosRequestConfig,
-    type CustomParamsSerializer
+  type AxiosInstance,
+  type AxiosRequestConfig,
+  type CustomParamsSerializer
 } from "axios";
 import type {
-    PureHttpError,
-    RequestMethods,
-    PureHttpResponse,
-    PureHttpRequestConfig,
-    FileHttpRequestConfig
+  PureHttpError,
+  RequestMethods,
+  PureHttpResponse,
+  PureHttpRequestConfig,
+  FileHttpRequestConfig
 } from "./types.d";
-import {stringify} from "qs";
+import { stringify } from "qs";
 import NProgress from "../progress";
-import {getToken, formatToken} from "@/utils/auth";
-import {useUserStoreHook} from "@/store/modules/user";
+import { getToken, formatToken } from "@/utils/auth";
+import { useUserStoreHook } from "@/store/modules/user";
 
 // 相关配置请参考:www.axios-js.com/zh-cn/docs/#axios-request-config-1
 const defaultConfig: AxiosRequestConfig = {
-    // 请求超时时间
-    timeout: 10000,
-    headers: {
-        Accept: "application/json, text/plain, */*",
-        "Content-Type": "application/json",
-        "X-Requested-With": "XMLHttpRequest"
-    },
-    // 数组格式参数序列化(https://github.com/axios/axios/issues/5142)
-    paramsSerializer: {
-        serialize: stringify as unknown as CustomParamsSerializer
-    }
+  // 请求超时时间
+  timeout: 10000,
+  headers: {
+    Accept: "application/json, text/plain, */*",
+    "Content-Type": "application/json",
+    "X-Requested-With": "XMLHttpRequest"
+  },
+  // 数组格式参数序列化(https://github.com/axios/axios/issues/5142)
+  paramsSerializer: {
+    serialize: stringify as unknown as CustomParamsSerializer
+  }
 };
 
 class PureHttp {
-    constructor() {
-        this.httpInterceptorsRequest();
-        this.httpInterceptorsResponse();
-    }
-
-    /** `token`过期后,暂存待执行的请求 */
-    private static requests = [];
-
-    /** 防止重复刷新`token` */
-    private static isRefreshing = false;
-
-    /** 初始化配置对象 */
-    private static initConfig: PureHttpRequestConfig = {};
-
-    /** 保存当前`Axios`实例对象 */
-    private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);
-
-    /** 重连原始请求 */
-    private static retryOriginalRequest(config: PureHttpRequestConfig) {
-        return new Promise(resolve => {
-            PureHttp.requests.push((token: string) => {
-                config.headers["Authorization"] = formatToken(token);
-                resolve(config);
-            });
-        });
-    }
-
-    /** 请求拦截 */
-    private httpInterceptorsRequest(): void {
-        PureHttp.axiosInstance.interceptors.request.use(
-            async (config: PureHttpRequestConfig): Promise<any> => {
-                // 开启进度条动画
-                NProgress.start();
-                // 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
-                if (typeof config.beforeRequestCallback === "function") {
-                    config.beforeRequestCallback(config);
-                    return config;
-                }
-                if (PureHttp.initConfig.beforeRequestCallback) {
-                    PureHttp.initConfig.beforeRequestCallback(config);
-                    return config;
-                }
-                /** 请求白名单,放置一些不需要`token`的接口(通过设置请求白名单,防止`token`过期后再请求造成的死循环问题) */
-                const whiteList = ["/refresh-token", "/login"];
-                return whiteList.some(url => config.url.endsWith(url))
-                    ? config
-                    : new Promise(resolve => {
-                        const data = getToken();
-                        if (data) {
-                            const now = new Date().getTime();
-                            const expired = parseInt(data.expires_in) - now <= 0;
-                            if (expired) {
-                                /*if (!PureHttp.isRefreshing) {
+  constructor() {
+    this.httpInterceptorsRequest();
+    this.httpInterceptorsResponse();
+  }
+
+  /** `token`过期后,暂存待执行的请求 */
+  private static requests = [];
+
+  /** 防止重复刷新`token` */
+  private static isRefreshing = false;
+
+  /** 初始化配置对象 */
+  private static initConfig: PureHttpRequestConfig = {};
+
+  /** 保存当前`Axios`实例对象 */
+  private static axiosInstance: AxiosInstance = Axios.create(defaultConfig);
+
+  /** 重连原始请求 */
+  private static retryOriginalRequest(config: PureHttpRequestConfig) {
+    return new Promise(resolve => {
+      PureHttp.requests.push((token: string) => {
+        config.headers["Authorization"] = formatToken(token);
+        resolve(config);
+      });
+    });
+  }
+
+  /** 请求拦截 */
+  private httpInterceptorsRequest(): void {
+    PureHttp.axiosInstance.interceptors.request.use(
+      async (config: PureHttpRequestConfig): Promise<any> => {
+        // 开启进度条动画
+        NProgress.start();
+        // 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
+        if (typeof config.beforeRequestCallback === "function") {
+          config.beforeRequestCallback(config);
+          return config;
+        }
+        if (PureHttp.initConfig.beforeRequestCallback) {
+          PureHttp.initConfig.beforeRequestCallback(config);
+          return config;
+        }
+        /** 请求白名单,放置一些不需要`token`的接口(通过设置请求白名单,防止`token`过期后再请求造成的死循环问题) */
+        const whiteList = ["/refresh-token", "/login"];
+        return whiteList.some(url => config.url.endsWith(url))
+          ? config
+          : new Promise(resolve => {
+              const data = getToken();
+              if (data) {
+                const now = new Date().getTime();
+                const expired = parseInt(data.expires_in) - now <= 0;
+                if (expired) {
+                  /*if (!PureHttp.isRefreshing) {
                                   PureHttp.isRefreshing = true;
                                   // token过期刷新
                                   useUserStoreHook()
@@ -98,140 +98,140 @@ class PureHttp {
                                       PureHttp.isRefreshing = false;
                                     });
                                 }*/
-                                config.headers["authorization"] = formatToken(
-                                    data.access_token
-                                );
-                                resolve(config);
-                            } else {
-                                config.headers["authorization"] = formatToken(
-                                    data.access_token
-                                );
-                                resolve(config);
-                            }
-                        } else {
-                            resolve(config);
-                        }
-                    });
-            },
-            error => {
-                return Promise.reject(error);
-            }
-        );
-    }
-
-    /** 响应拦截 */
-    private httpInterceptorsResponse(): void {
-        const instance = PureHttp.axiosInstance;
-        instance.interceptors.response.use(
-            (response: PureHttpResponse) => {
-                const $config = response.config;
-                // 关闭进度条动画
-                NProgress.done();
-                // 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
-                if (typeof $config.beforeResponseCallback === "function") {
-                    $config.beforeResponseCallback(response);
-                    return response.data;
-                }
-                if (PureHttp.initConfig.beforeResponseCallback) {
-                    PureHttp.initConfig.beforeResponseCallback(response);
-                    return response.data;
-                }
-                if (response.data.code === 401) {
-                    console.log('用户未登录')
-                    // 用户未登录
-                    useUserStoreHook().logOut();
-                    return Promise.reject(new Error("用户未登录"));
+                  config.headers["authorization"] = formatToken(
+                    data.access_token
+                  );
+                  resolve(config);
+                } else {
+                  config.headers["authorization"] = formatToken(
+                    data.access_token
+                  );
+                  resolve(config);
                 }
-                return response.data;
-            },
-            (error: PureHttpError) => {
-                const $error = error;
-                $error.isCancelRequest = Axios.isCancel($error);
-                if ($error.status === 401) {
-                    console.log('登录状态已过期')
-                    // 用户未登录
-                    useUserStoreHook().logOut();
-                    return Promise.reject(new Error("登录状态已过期"));
-                }
-                // 关闭进度条动画
-                NProgress.done();
-                // 所有的响应异常 区分来源为取消请求/非取消请求
-                return Promise.reject($error);
-            }
-        );
-    }
-
-    /** 通用请求工具函数 */
-    public request<T>(
-        method: RequestMethods,
-        url: string,
-        param?: AxiosRequestConfig,
-        axiosConfig?: PureHttpRequestConfig
-    ): Promise<T> {
-        const config = {
-            method,
-            url,
-            ...param,
-            ...axiosConfig
-        } as PureHttpRequestConfig;
-
-        // 单独处理自定义请求/响应回调
-        return new Promise((resolve, reject) => {
-            PureHttp.axiosInstance
-                .request(config)
-                .then((response: undefined) => {
-                    resolve(response);
-                })
-                .catch(error => {
-                    reject(error);
-                });
+              } else {
+                resolve(config);
+              }
+            });
+      },
+      error => {
+        return Promise.reject(error);
+      }
+    );
+  }
+
+  /** 响应拦截 */
+  private httpInterceptorsResponse(): void {
+    const instance = PureHttp.axiosInstance;
+    instance.interceptors.response.use(
+      (response: PureHttpResponse) => {
+        const $config = response.config;
+        // 关闭进度条动画
+        NProgress.done();
+        // 优先判断post/get等方法是否传入回调,否则执行初始化设置等回调
+        if (typeof $config.beforeResponseCallback === "function") {
+          $config.beforeResponseCallback(response);
+          return response.data;
+        }
+        if (PureHttp.initConfig.beforeResponseCallback) {
+          PureHttp.initConfig.beforeResponseCallback(response);
+          return response.data;
+        }
+        if (response.data.code === 401) {
+          console.log("用户未登录");
+          // 用户未登录
+          useUserStoreHook().logOut();
+          return Promise.reject(new Error("用户未登录"));
+        }
+        return response.data;
+      },
+      (error: PureHttpError) => {
+        const $error = error;
+        $error.isCancelRequest = Axios.isCancel($error);
+        if ($error.status === 401) {
+          console.log("登录状态已过期");
+          // 用户未登录
+          useUserStoreHook().logOut();
+          return Promise.reject(new Error("登录状态已过期"));
+        }
+        // 关闭进度条动画
+        NProgress.done();
+        // 所有的响应异常 区分来源为取消请求/非取消请求
+        return Promise.reject($error);
+      }
+    );
+  }
+
+  /** 通用请求工具函数 */
+  public request<T>(
+    method: RequestMethods,
+    url: string,
+    param?: AxiosRequestConfig,
+    axiosConfig?: PureHttpRequestConfig
+  ): Promise<T> {
+    const config = {
+      method,
+      url,
+      ...param,
+      ...axiosConfig
+    } as PureHttpRequestConfig;
+
+    // 单独处理自定义请求/响应回调
+    return new Promise((resolve, reject) => {
+      PureHttp.axiosInstance
+        .request(config)
+        .then((response: undefined) => {
+          resolve(response);
+        })
+        .catch(error => {
+          reject(error);
         });
-    }
-
-    /** 单独抽离的`post`工具函数 */
-    public post<T, P>(
-        url: string,
-        params?: AxiosRequestConfig<P>,
-        config?: PureHttpRequestConfig
-    ): Promise<T> {
-        return this.request<T>("post", url, params, config);
-    }
-
-    /** 单独抽离的`get`工具函数 */
-    public get<T, P>(
-        url: string,
-        params?: AxiosRequestConfig<P>,
-        config?: PureHttpRequestConfig
-    ): Promise<T> {
-        return this.request<T>("get", url, params, config);
-    }
-
-    /** 单独抽离的`put`工具函数 */
-    public put<T, P>(
-        url: string,
-        params?: AxiosRequestConfig<P>,
-        config?: PureHttpRequestConfig
-    ): Promise<T> {
-        return this.request<T>("put", url, params, config);
-    }
-
-    /* ----------------------------------- 导出封装 ----------------------------------- */
-
-    public postExport<T, P>(
-        url: string,
-        params?: AxiosRequestConfig<P>,
-        config?: FileHttpRequestConfig
-    ): Promise<T> {
-        const exportConfig = {
-            responseType: "blob",
-            ...params,
-            ...config
-        } as PureHttpRequestConfig;
-
-        return this.request<T>("post", url, exportConfig);
-    }
-
-    /* ------------------------------------------------------------------------------- */
+    });
+  }
+
+  /** 单独抽离的`post`工具函数 */
+  public post<T, P>(
+    url: string,
+    params?: AxiosRequestConfig<P>,
+    config?: PureHttpRequestConfig
+  ): Promise<T> {
+    return this.request<T>("post", url, params, config);
+  }
+
+  /** 单独抽离的`get`工具函数 */
+  public get<T, P>(
+    url: string,
+    params?: AxiosRequestConfig<P>,
+    config?: PureHttpRequestConfig
+  ): Promise<T> {
+    return this.request<T>("get", url, params, config);
+  }
+
+  /** 单独抽离的`put`工具函数 */
+  public put<T, P>(
+    url: string,
+    params?: AxiosRequestConfig<P>,
+    config?: PureHttpRequestConfig
+  ): Promise<T> {
+    return this.request<T>("put", url, params, config);
+  }
+
+  /* ----------------------------------- 导出封装 ----------------------------------- */
+
+  public postExport<T, P>(
+    url: string,
+    params?: AxiosRequestConfig<P>,
+    config?: FileHttpRequestConfig
+  ): Promise<T> {
+    const exportConfig = {
+      responseType: "blob",
+      ...params,
+      ...config
+    } as PureHttpRequestConfig;
+
+    return this.request<T>("post", url, exportConfig);
+  }
+
+  /* ------------------------------------------------------------------------------- */
 }
 
 export const http = new PureHttp();

+ 52 - 48
src/utils/upload.ts

@@ -1,68 +1,72 @@
-import {http} from "@/utils/http";
-import {baseUrlApi} from "@/api/utils";
+import { http } from "@/utils/http";
+import { baseUrlApi } from "@/api/utils";
 import OSS from "ali-oss";
-import {AxiosInstance} from "axios";
+import type { AxiosInstance } from "axios";
 
 type Result = {
-    msg: boolean;
-    data: any;
-    code: number;
+  msg: boolean;
+  data: any;
+  code: number;
 };
 
 interface UploadOptions {
+  accessKeyId: string;
+  accessKeySecret: string;
+  stsToken: string;
+  bucket: string;
+  region?: string;
+  endpoint?: string;
+  secure?: boolean;
+  cname?: boolean;
+  timeout?: number;
+  refreshSTSToken?: () => Promise<{
     accessKeyId: string;
     accessKeySecret: string;
     stsToken: string;
-    bucket: string;
-    region?: string;
-    endpoint?: string;
-    secure?: boolean;
-    cname?: boolean;
-    timeout?: number;
-    refreshSTSToken?: () => Promise<{
-        accessKeyId: string;
-        accessKeySecret: string;
-        stsToken: string;
-    }>;
-    refreshSTSTokenInterval?: number;
-    axiosOptions?: AxiosInstance;
+  }>;
+  refreshSTSTokenInterval?: number;
+  axiosOptions?: AxiosInstance;
 }
 
 export const getAliossToken = () => {
-    return http.request<Result>("post", baseUrlApi("/alioss/token"));
+  return http.request<Result>("post", baseUrlApi("/alioss/token"));
 };
 
 export default class Clients {
-    client: OSS
+  client: OSS;
 
-    constructor() {
-        this.client = null;
-        // this.getClient()
-    }
+  constructor() {
+    this.client = null;
+    // this.getClient()
+  }
 
-    async getClient() {
-        let params: UploadOptions = {accessKeyId: "", accessKeySecret: "", bucket: "", stsToken: ""}
-        let res = await getAliossToken()
-        if (res.code === 0) {
-            params.accessKeyId = res.data.accessKeyId;
-            params.accessKeySecret = res.data.accessKeySecret;
-            params.stsToken = res.data.securityToken;
-        }
-        //后端提供数据
-        this.client = new OSS({
-            region: 'oss-cn-hangzhou', //oss-cn-beijing-internal.aliyuncs.com
-            accessKeyId: params.accessKeyId,
-            accessKeySecret: params.accessKeySecret,
-            stsToken: params.stsToken,
-            bucket: 'loansm'
-        })
+  async getClient() {
+    const params: UploadOptions = {
+      accessKeyId: "",
+      accessKeySecret: "",
+      bucket: "",
+      stsToken: ""
+    };
+    const res = await getAliossToken();
+    if (res.code === 0) {
+      params.accessKeyId = res.data.accessKeyId;
+      params.accessKeySecret = res.data.accessKeySecret;
+      params.stsToken = res.data.securityToken;
     }
+    //后端提供数据
+    this.client = new OSS({
+      region: "oss-cn-hangzhou", //oss-cn-beijing-internal.aliyuncs.com
+      accessKeyId: params.accessKeyId,
+      accessKeySecret: params.accessKeySecret,
+      stsToken: params.stsToken,
+      bucket: "loansm"
+    });
+  }
 
-    async put(fileName: string, file: File | Blob | string) {
-        if (!this.client) {
-            await this.getClient()
-        }
-        return await this.client.put(fileName, file);
+  async put(fileName: string, file: File | Blob | string) {
+    if (!this.client) {
+      await this.getClient();
     }
-
-}
+    return await this.client.put(fileName, file);
+  }
+}

+ 125 - 0
src/views/system/role/components/deptTree.vue

@@ -0,0 +1,125 @@
+<script setup lang="ts">
+import { onMounted, ref, defineEmits, watch, nextTick } from "vue";
+import { getMenuListTree } from "@/api/system/menu";
+import { type CheckboxValueType } from "element-plus";
+
+onMounted(() => {
+  getMenuList();
+  console.log(model.value);
+});
+
+const emit = defineEmits(["update:modelValue"]);
+
+const menuList = ref([]);
+const getMenuList = async () => {
+  let res = await getMenuListTree();
+  menuList.value = res.data;
+};
+
+const model = defineModel();
+
+watch(
+  model,
+  (value: number[]) => {
+    nextTick(() => {
+      setCheckedKeys(value, true);
+      const allKeys = getAllKeys(menuList.value);
+      menuNodeAll.value = value.length === allKeys.length;
+      isIndeterminate.value = value.length > 0 && value.length < allKeys.length;
+    });
+  },
+  { immediate: true, deep: true }
+);
+
+const menuRef = ref(null);
+
+const menuCheckStrictly = ref(true);
+const menuNodeAll = ref(false);
+const menuExpand = ref(false);
+const isIndeterminate = ref(false);
+const handleCheckedTreeExpand = (value: CheckboxValueType) => {
+  // 递归展开
+  for (let i = 0; i < menuList.value.length; i++) {
+    menuRef.value.store.nodesMap[menuList.value[i].id].expanded = value;
+  }
+};
+
+const handleCheckedTreeNodeAll = (value: CheckboxValueType) => {
+  menuRef.value.setCheckedNodes(value ? menuList.value : []);
+  isIndeterminate.value = false;
+  const allKeys = getAllKeys(menuList.value);
+  model.value = value ? allKeys : [];
+  console.log(model.value);
+  console.log(menuNodeAll.value);
+};
+
+const handleTreeCheck = (_: any, checkedState: any) => {
+  const allKeys = getAllKeys(menuList.value);
+  const checkedCount = checkedState.checkedKeys.length;
+  menuNodeAll.value = checkedCount === allKeys.length;
+  isIndeterminate.value = checkedCount > 0 && checkedCount < allKeys.length;
+  model.value = checkedState.checkedKeys;
+  emit("update:modelValue", checkedState.checkedKeys);
+};
+
+// 递归获取所有节点key
+const getAllKeys = (data: any) => {
+  return data.reduce((keys: any, node: any) => {
+    keys.push(node.id);
+    if (node.children && menuCheckStrictly) {
+      keys.push(...getAllKeys(node.children));
+    }
+    return keys;
+  }, []);
+};
+
+const setCheckedKeys = (keys: number[], leafOnly: boolean) => {
+  return menuRef.value.setCheckedKeys(keys, leafOnly);
+};
+</script>
+
+<template>
+  <div style="width: 100%">
+    <el-row type="flex" justify="space-between" align="middle">
+      <el-col :span="4">
+        <el-checkbox
+          v-model="menuExpand"
+          @change="handleCheckedTreeExpand($event)"
+          >全部展开/折叠
+        </el-checkbox>
+      </el-col>
+      <el-col :span="4">
+        <el-checkbox
+          v-model="menuNodeAll"
+          :indeterminate="isIndeterminate"
+          @change="handleCheckedTreeNodeAll($event)"
+          >全选/全不选
+        </el-checkbox>
+      </el-col>
+      <el-col :span="4">
+        <el-checkbox v-model="menuCheckStrictly">级联下级 </el-checkbox>
+      </el-col>
+    </el-row>
+    <el-tree
+      ref="menuRef"
+      class="tree-border"
+      :data="menuList"
+      show-checkbox
+      node-key="id"
+      empty-text="加载中,请稍后"
+      :check-strictly="!menuCheckStrictly"
+      @check="handleTreeCheck"
+    />
+  </div>
+</template>
+
+<style scoped lang="scss">
+/* tree border */
+.tree-border {
+  width: 100%;
+  margin-top: 5px;
+  border: 1px solid #e5e6e7;
+  background: #ffffff none;
+  border-radius: 4px;
+}
+</style>

+ 52 - 32
src/views/system/role/components/menuTree.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { onMounted, ref, defineEmits, watch, nextTick } from "vue";
+import { onMounted, ref, defineEmits, watch, nextTick, computed } from "vue";
 import { getMenuListTree } from "@/api/system/menu";
 import { type CheckboxValueType } from "element-plus";
 
@@ -16,16 +16,13 @@ const getMenuList = async () => {
   menuList.value = res.data;
 };
 
-const model = defineModel();
+const model = defineModel<number[]>();
 
 watch(
   model,
   (value: number[]) => {
     nextTick(() => {
       setCheckedKeys(value, true);
-      const allKeys = getAllKeys(menuList.value);
-      menuNodeAll.value = value.length === allKeys.length;
-      isIndeterminate.value = value.length > 0 && value.length < allKeys.length;
     });
   },
   { immediate: true, deep: true }
@@ -34,9 +31,7 @@ watch(
 const menuRef = ref(null);
 
 const menuCheckStrictly = ref(true);
-const menuNodeAll = ref(false);
 const menuExpand = ref(false);
-const isIndeterminate = ref(false);
 const handleCheckedTreeExpand = (value: CheckboxValueType) => {
   // 递归展开
   for (let i = 0; i < menuList.value.length; i++) {
@@ -44,38 +39,66 @@ const handleCheckedTreeExpand = (value: CheckboxValueType) => {
   }
 };
 
-const handleCheckedTreeNodeAll = (value: CheckboxValueType) => {
-  menuRef.value.setCheckedNodes(value ? menuList.value : []);
-  isIndeterminate.value = false;
-  const allKeys = getAllKeys(menuList.value);
-  model.value = value ? allKeys : [];
-  console.log(model.value);
-  console.log(menuNodeAll.value);
+// 只返回所有叶子节点 id
+const getLeafKeys = (data: any[]) => {
+  const keys: number[] = [];
+  data.forEach(node => {
+    if (node.children && node.children.length) {
+      keys.push(...getLeafKeys(node.children));
+    } else {
+      keys.push(node.id);
+    }
+  });
+  return keys;
 };
 
-const handleTreeCheck = (_: any, checkedState: any) => {
-  const allKeys = getAllKeys(menuList.value);
-  const checkedCount = checkedState.checkedKeys.length;
-  menuNodeAll.value = checkedCount === allKeys.length;
-  isIndeterminate.value = checkedCount > 0 && checkedCount < allKeys.length;
-  model.value = checkedState.checkedKeys;
-  emit("update:modelValue", checkedState.checkedKeys);
+// 叶子节点总集合(随菜单树变化动态计算)
+const allLeafKeys = computed(() => getLeafKeys(menuList.value));
+
+// 全选状态:由 model 与叶子总集合计算;设置时驱动 model 更新
+const allSelected = computed({
+  get() {
+    const leafKeys = allLeafKeys.value;
+    if (!leafKeys.length) return false;
+    // model 中的所有 key 都在叶子集合,并且数量一致视为全选
+    return (
+      model.value.length === leafKeys.length &&
+      model.value.every(k => leafKeys.includes(k))
+    );
+  },
+  set(checked: boolean) {
+    model.value = checked ? [...allLeafKeys.value] : [];
+  }
+});
+
+// 半选状态:仅基于数量计算
+const indeterminate = computed(() => {
+  const total = allLeafKeys.value.length;
+  const count = model.value.length;
+  return count > 0 && count < total;
+});
+
+// 通过 watch(model) 来同步 UI 勾选状态,因此这里不再需要手动方法
+
+const handleTreeCheck = (_: any, _checkedState: any) => {
+  const leafKeys = menuRef.value?.getCheckedKeys(true) ?? [];
+  model.value = leafKeys;
+  emit("update:modelValue", leafKeys);
 };
 
 // 递归获取所有节点key
 const getAllKeys = (data: any) => {
-  return data.reduce((keys: any, node: any) => {
-    keys.push(node.id);
-    if (node.children && menuCheckStrictly) {
-      keys.push(...getAllKeys(node.children));
-    }
-    return keys;
-  }, []);
+  return getLeafKeys(data);
 };
 
 const setCheckedKeys = (keys: number[], leafOnly: boolean) => {
   return menuRef.value.setCheckedKeys(keys, leafOnly);
 };
+
+// 级联开关变化时,重新应用当前 model 的叶子集合到树上
+watch(menuCheckStrictly, () => {
+  nextTick(() => setCheckedKeys(model.value, true));
+});
 </script>
 
 <template>
@@ -89,10 +112,7 @@ const setCheckedKeys = (keys: number[], leafOnly: boolean) => {
         </el-checkbox>
       </el-col>
       <el-col :span="4">
-        <el-checkbox
-          v-model="menuNodeAll"
-          :indeterminate="isIndeterminate"
-          @change="handleCheckedTreeNodeAll($event)"
+        <el-checkbox v-model="allSelected" :indeterminate="indeterminate"
           >全选/全不选
         </el-checkbox>
       </el-col>

+ 5 - 5
src/views/system/role/index.vue

@@ -247,7 +247,7 @@ interface State {
     roleName: string;
     roleSort: number;
     status: string;
-    isMenuCheckStrictly: string;
+    // isMenuCheckStrictly: string;
     dataScope: string;
   };
   rules: FormRules;
@@ -268,7 +268,7 @@ const state = reactive<State>({
     roleName: "",
     roleSort: null,
     status: "",
-    isMenuCheckStrictly: "0",
+    // isMenuCheckStrictly: "0",
     dataScope: ""
   },
   rules: {
@@ -450,7 +450,7 @@ const handleCreate = (): void => {
     roleName: "",
     roleSort: null,
     status: "0",
-    isMenuCheckStrictly: "0",
+    // isMenuCheckStrictly: "0",
     dataScope: null
   };
   state.isCreate = true;
@@ -504,7 +504,7 @@ const handleSubmit = async (values: FieldValues) => {
   if (state.isCreate) {
     try {
       let params = form.value;
-      params.isMenuCheckStrictly = "0";
+      // params.isMenuCheckStrictly = "0";
       let res = await addSystemRole(params);
       if (res.code === 200) {
         ElMessage.success("新增成功");
@@ -521,7 +521,7 @@ const handleSubmit = async (values: FieldValues) => {
     // 编辑
     try {
       let params = form.value;
-      params.isMenuCheckStrictly = "0";
+      // params.isMenuCheckStrictly = "0";
       let res = await updateSystemRole(params);
       if (res.code === 200) {
         ElMessage.success("修改成功");

+ 6 - 10
src/views/testFingerPrints/information/index.vue

@@ -1,10 +1,10 @@
 <script setup lang="ts">
-import {load} from '@fingerprintjs/fingerprintjs';
-import {ref} from "vue";
+import { load } from "@fingerprintjs/fingerprintjs";
+import { ref } from "vue";
 
 defineOptions({
   name: "Information"
-})
+});
 
 let fingerId = ref<string>();
 let userId = ref<string[]>();
@@ -14,14 +14,13 @@ const finger = async () => {
   const visitorId = await result.get();
   console.log(visitorId.visitorId); // 打印生成的指纹
   fingerId.value = visitorId.visitorId;
-}
-
+};
 
 const userAgent = () => {
   console.log("userAgent", navigator.userAgent);
   userId.value = navigator.userAgent.split("/");
   return navigator.userAgent;
-}
+};
 </script>
 
 <template>
@@ -35,9 +34,6 @@ const userAgent = () => {
       <span class="ml-5">{{ userId }}</span>
     </el-card>
   </div>
-
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 7 - 9
src/views/testStorage/cookie/index.vue

@@ -1,19 +1,17 @@
 <script setup lang="ts">
-import {CookieStorage} from '@/utils/cookieStorage';
+import { CookieStorage } from "@/utils/cookieStorage";
 
 defineOptions({
   name: "Cookie"
-})
-const cookie = new CookieStorage()
-let json = JSON.stringify({name: 'lisi'})
-cookie.setItem('name', json)
-console.log(cookie.getItem('name'));
+});
+const cookie = new CookieStorage();
+let json = JSON.stringify({ name: "lisi" });
+cookie.setItem("name", json);
+console.log(cookie.getItem("name"));
 </script>
 
 <template>
   <div>cookie</div>
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 9 - 9
src/views/testStorage/local/index.vue

@@ -1,21 +1,21 @@
 <script setup lang="ts">
-import {localForage} from "@/utils/localforage";
+import { localForage } from "@/utils/localforage";
 
 defineOptions({
   name: "Local"
-})
+});
 
-localForage().setItem('name', 'lisi', 1000 * 60 * 60 * 24 * 7)
+localForage().setItem("name", "lisi", 1000 * 60 * 60 * 24 * 7);
 
-localForage().getItem('name').then((res) => {
-  console.log(res);
-})
+localForage()
+  .getItem("name")
+  .then(res => {
+    console.log(res);
+  });
 </script>
 
 <template>
   <div>local</div>
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 196 - 183
src/views/testTable/pageTable/index.vue

@@ -1,51 +1,59 @@
 <template>
   <div>
     <PlusPage
-        :columns="tableConfig"
-        :request="getList"
-        :before-search-submit="handleBeforeSearch"
-        :is-card="true"
-        :table="{
-          isSelection: true,
-          actionBar: { buttons, type: 'button', width: 240 },
-          adaptive: { offsetBottom: 50 },
-          onSortChange: handleSortChange,
-          onSelectionChange: handleSelect,
-        }"
-        :defaultPageInfo="{ page: 1, pageSize: 10 }"
-        :defaultPageSizeList="[10, 20, 50, 100]"
+      :columns="tableConfig"
+      :request="getList"
+      :before-search-submit="handleBeforeSearch"
+      :is-card="true"
+      :table="{
+        isSelection: true,
+        actionBar: { buttons, type: 'button', width: 240 },
+        adaptive: { offsetBottom: 50 },
+        onSortChange: handleSortChange,
+        onSelectionChange: handleSelect
+      }"
+      :defaultPageInfo="{ page: 1, pageSize: 10 }"
+      :defaultPageSizeList="[10, 20, 50, 100]"
     >
       <template #table-title>
         <el-row class="button-row">
           <el-button type="primary" @click="handleCreate"> 添加</el-button>
-          <el-button type="danger" @click="handleBatchDelete"> 批量删除</el-button>
+          <el-button type="danger" @click="handleBatchDelete">
+            批量删除</el-button
+          >
         </el-row>
       </template>
       <template #table-toolbar>
         <ExportView
-            title="导出"
-            url="''"
-            file-name="订单列表"
-            :query-params="{}"
+          title="导出"
+          url="''"
+          file-name="订单列表"
+          :query-params="{}"
         />
       </template>
     </PlusPage>
     <!-- 弹窗编辑 -->
     <PlusDialogForm
-        ref="dialogForm"
-        v-model:visible="visible"
-        @change="handleChange"
-        @confirm="handleSubmit"
-        @submit-error="handleSubmitError"
-        @close="handleClose"
-        :form="{ columns, labelPosition: 'top', rules, rowProps: {gutter: 20}, colProps: {span: 12} }"
-        :dialog="{ title: dialogTitle + '用户组', width: 800, confirmLoading }"
+      ref="dialogForm"
+      v-model:visible="visible"
+      :form="{
+        columns,
+        labelPosition: 'top',
+        rules,
+        rowProps: { gutter: 20 },
+        colProps: { span: 12 }
+      }"
+      :dialog="{ title: dialogTitle + '用户组', width: 800, confirmLoading }"
+      @change="handleChange"
+      @confirm="handleSubmit"
+      @submit-error="handleSubmitError"
+      @close="handleClose"
     />
   </div>
 </template>
 
 <script lang="ts" setup>
-import {computed, defineOptions, reactive, ref, toRefs} from "vue";
+import { computed, defineOptions, reactive, ref, toRefs } from "vue";
 import {
   type PageInfo,
   type PlusColumn,
@@ -54,8 +62,8 @@ import {
   PlusPage,
   useTable
 } from "plus-pro-components";
-import {cloneDeep} from "lodash-es";
-import {ElMessage} from "element-plus";
+import { cloneDeep } from "lodash-es";
+import { ElMessage } from "element-plus";
 import ExportView from "@/components/ExportView/index.vue";
 
 defineOptions({
@@ -71,26 +79,26 @@ interface TableRow {
 }
 
 const getList = async (
-    query: PageInfo & {
-      status?: string;
-      name?: string;
-    }
+  query: PageInfo & {
+    status?: string;
+    name?: string;
+  }
 ) => {
-  const {page = 1, pageSize = 20, status, name} = query || {};
+  const { page = 1, pageSize = 20, status, name } = query || {};
   const total = 100;
-  const List = Array.from({length: total}).map((_, index) => {
+  const List = Array.from({ length: total }).map((_, index) => {
     return {
       id: index,
       name: index === 0 ? "name".repeat(99) : index + "name",
       status: String(index % 3),
       tag:
-          index === 1
-              ? "success"
-              : index === 2
-                  ? "warning"
-                  : index === 3
-                      ? "info"
-                      : "danger",
+        index === 1
+          ? "success"
+          : index === 2
+            ? "warning"
+            : index === 3
+              ? "info"
+              : "danger",
       progress: Number((Math.random() * 100).toFixed(2)),
       rate: index > 3 ? 2 : 3.5,
       switch: index % 2 === 0,
@@ -109,7 +117,7 @@ const getList = async (
   });
 
   const pageList = mockList.filter(
-      (_, index) => index < pageSize * page && index >= pageSize * (page - 1)
+    (_, index) => index < pageSize * page && index >= pageSize * (page - 1)
   );
 
   // 等待2s
@@ -121,7 +129,7 @@ const getList = async (
 
   console.log(pageList);
 
-  return {data: pageList, success: true, total: mockList.length};
+  return { data: pageList, success: true, total: mockList.length };
 };
 
 // 搜索之前函数
@@ -152,9 +160,9 @@ const disabledDate = (time: any) => {
     // 当前日期 + one = 7天之后
     const maxTime = choiceDate.value + one;
     return (
-        time.getTime() < minTime || time.getTime() > maxTime
-        // 限制不能选择今天及以后
-        // || time.getTime() + 1 * 24 * 3600 * 1000 > Date.now()
+      time.getTime() < minTime || time.getTime() > maxTime
+      // 限制不能选择今天及以后
+      // || time.getTime() + 1 * 24 * 3600 * 1000 > Date.now()
     );
   } else {
     // 如果没有选择日期,就要限制不能选择今天及以后
@@ -172,8 +180,8 @@ const calendarChange = (obj: any) => {
   if (maxDate) choiceDate.value = null;
 };
 
-const dialogTitle = computed(() => (state.isCreate ? '新增' : '编辑'))
-const {buttons} = useTable<TableRow[]>();
+const dialogTitle = computed(() => (state.isCreate ? "新增" : "编辑"));
+const { buttons } = useTable<TableRow[]>();
 
 // 表格数据
 const tableConfig: PlusColumn[] = [
@@ -217,7 +225,7 @@ const tableConfig: PlusColumn[] = [
     prop: "tag",
     valueType: "tag",
     fieldProps: (value: string) => {
-      return {type: value};
+      return { type: value };
     }
   },
   {
@@ -225,7 +233,11 @@ const tableConfig: PlusColumn[] = [
     prop: "progress",
     valueType: "progress",
     fieldProps: (value: number) => {
-      return value <= 30 ? {status: 'exception'} : value > 30 && value <= 50 ? {status: 'warning'} : {status: 'success'};
+      return value <= 30
+        ? { status: "exception" }
+        : value > 30 && value <= 50
+          ? { status: "warning" }
+          : { status: "success" };
     },
     minWidth: 180
   },
@@ -235,7 +247,7 @@ const tableConfig: PlusColumn[] = [
     valueType: "rate",
     hideInSearch: true,
     editable: true,
-    minWidth: 130,
+    minWidth: 130
   },
   {
     label: "开关",
@@ -273,7 +285,7 @@ const tableConfig: PlusColumn[] = [
     },
     hideInTable: true,
     tableColumnProps: {
-      sortable: false,
+      sortable: false
     },
     minWidth: 120
   }
@@ -286,160 +298,160 @@ const dialogVisible = ref(false);
 const dialogForm = ref(null);
 
 const state = reactive<FieldValues>({
-  status: '0',
-  name: '',
+  status: "0",
+  name: "",
   rate: 4,
   isCreate: true,
   progress: 100,
   confirmLoading: false,
   switch: true,
   time: new Date().toString(),
-  img: 'https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg',
-  query: {groupName: ''},
+  img: "https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg",
+  query: { groupName: "" },
   currentRow: {},
   visible: false,
   detailsVisible: false,
   isBatch: false,
   selectedIds: [],
   form: {
-    groupName: '',
-    remark: '',
+    groupName: "",
+    remark: "",
     userGroupId: undefined
   },
   rules: {
     name: [
       {
         required: true,
-        message: '请输入',
-        trigger: 'blur'
+        message: "请输入",
+        trigger: "blur"
       }
     ]
   }
-})
+});
 
 const columns: PlusColumn[] = [
   {
-    label: '名称',
+    label: "名称",
     width: 120,
-    prop: 'name',
-    valueType: 'copy',
-    tooltip: '名称最多显示6个字符'
+    prop: "name",
+    valueType: "copy",
+    tooltip: "名称最多显示6个字符"
   },
   {
-    label: '状态',
+    label: "状态",
     width: 120,
-    prop: 'status',
-    valueType: 'select',
+    prop: "status",
+    valueType: "select",
     options: [
       {
-        label: '未解决',
-        value: '0',
-        color: 'red'
+        label: "未解决",
+        value: "0",
+        color: "red"
       },
       {
-        label: '已解决',
-        value: '1',
-        color: 'blue'
+        label: "已解决",
+        value: "1",
+        color: "blue"
       },
       {
-        label: '解决中',
-        value: '2',
-        color: 'yellow'
+        label: "解决中",
+        value: "2",
+        color: "yellow"
       },
       {
-        label: '失败',
-        value: '3',
-        color: 'red'
+        label: "失败",
+        value: "3",
+        color: "red"
       }
     ]
   },
   {
-    label: '标签',
+    label: "标签",
     width: 120,
-    prop: 'tag'
+    prop: "tag"
   },
   {
-    label: '执行进度',
+    label: "执行进度",
     width: 200,
-    prop: 'progress'
+    prop: "progress"
   },
   {
-    label: '评分',
+    label: "评分",
     width: 200,
-    prop: 'rate',
-    valueType: 'rate'
+    prop: "rate",
+    valueType: "rate"
   },
   {
-    label: '是否显示',
+    label: "是否显示",
     width: 100,
-    prop: 'switch',
-    valueType: 'switch'
+    prop: "switch",
+    valueType: "switch"
   },
   {
-    label: '图片',
-    prop: 'img',
+    label: "图片",
+    prop: "img",
     width: 100,
-    valueType: 'img'
+    valueType: "img"
   },
   {
-    label: '时间',
-    prop: 'time',
-    valueType: 'date-picker'
+    label: "时间",
+    prop: "time",
+    valueType: "date-picker"
   },
   {
-    label: '数量',
-    prop: 'number',
-    valueType: 'input-number',
-    fieldProps: {precision: 2, step: 2}
+    label: "数量",
+    prop: "number",
+    valueType: "input-number",
+    fieldProps: { precision: 2, step: 2 }
   },
   {
-    label: '城市',
-    prop: 'city',
-    valueType: 'cascader',
+    label: "城市",
+    prop: "city",
+    valueType: "cascader",
     options: [
       {
-        value: '0',
-        label: '陕西',
+        value: "0",
+        label: "陕西",
         children: [
           {
-            value: '0-0',
-            label: '西安',
+            value: "0-0",
+            label: "西安",
             children: [
               {
-                value: '0-0-0',
-                label: '新城区'
+                value: "0-0-0",
+                label: "新城区"
               },
               {
-                value: '0-0-1',
-                label: '高新区'
+                value: "0-0-1",
+                label: "高新区"
               },
               {
-                value: '0-0-2',
-                label: '灞桥区'
+                value: "0-0-2",
+                label: "灞桥区"
               }
             ]
           }
         ]
       },
       {
-        value: '1',
-        label: '山西',
+        value: "1",
+        label: "山西",
         children: [
           {
-            value: '1-0',
-            label: '太原',
+            value: "1-0",
+            label: "太原",
             children: [
               {
-                value: '1-0-0',
-                label: '小店区'
+                value: "1-0-0",
+                label: "小店区"
               },
               {
-                value: '1-0-1',
-                label: '古交市'
+                value: "1-0-1",
+                label: "古交市"
               },
               {
-                value: '1-0-2',
-                label: '万柏林区'
+                value: "1-0-2",
+                label: "万柏林区"
               }
             ]
           }
@@ -448,112 +460,112 @@ const columns: PlusColumn[] = [
     ]
   },
   {
-    label: '地区',
-    prop: 'place',
-    tooltip: '请精确到门牌号',
+    label: "地区",
+    prop: "place",
+    tooltip: "请精确到门牌号",
     fieldProps: {
-      placeholder: '请精确到门牌号'
+      placeholder: "请精确到门牌号"
     }
   },
   {
-    label: '要求',
-    prop: 'demand',
-    valueType: 'checkbox',
+    label: "要求",
+    prop: "demand",
+    valueType: "checkbox",
     options: [
       {
-        label: '四六级',
-        value: '0'
+        label: "四六级",
+        value: "0"
       },
       {
-        label: '计算机二级证书',
-        value: '1'
+        label: "计算机二级证书",
+        value: "1"
       },
       {
-        label: '普通话证书',
-        value: '2'
+        label: "普通话证书",
+        value: "2"
       }
     ]
   },
   {
-    label: '梦想',
-    prop: 'gift',
-    valueType: 'radio',
+    label: "梦想",
+    prop: "gift",
+    valueType: "radio",
     options: [
       {
-        label: '诗',
-        value: '0'
+        label: "诗",
+        value: "0"
       },
       {
-        label: '远方',
-        value: '1'
+        label: "远方",
+        value: "1"
       }
     ]
   },
   {
-    label: '到期时间',
-    prop: 'endTime',
-    valueType: 'date-picker',
+    label: "到期时间",
+    prop: "endTime",
+    valueType: "date-picker",
     fieldProps: {
-      type: 'datetimerange',
-      startPlaceholder: '请选择开始时间',
-      endPlaceholder: '请选择结束时间'
+      type: "datetimerange",
+      startPlaceholder: "请选择开始时间",
+      endPlaceholder: "请选择结束时间"
     }
   },
   {
-    label: '说明',
-    prop: 'desc',
-    valueType: 'textarea',
+    label: "说明",
+    prop: "desc",
+    valueType: "textarea",
     fieldProps: {
       maxlength: 10,
       showWordLimit: true,
-      autosize: {minRows: 2, maxRows: 4}
+      autosize: { minRows: 2, maxRows: 4 }
     }
   }
-]
+];
 
 // 创建
 const handleCreate = (): void => {
-  state.currentRow = {}
+  state.currentRow = {};
   state.form = {
-    groupName: '',
-    remark: '',
+    groupName: "",
+    remark: "",
     userGroupId: undefined
-  }
-  state.isCreate = true
-  state.visible = true
-}
+  };
+  state.isCreate = true;
+  state.visible = true;
+};
 
 // 批量删除
 const handleBatchDelete = async () => {
-  console.log('批量删除')
+  console.log("批量删除");
   if (!state.selectedIds.length) {
-    ElMessage.warning('请选择数据!')
-    return
+    ElMessage.warning("请选择数据!");
+    return;
   }
-  console.log(state.selectedIds)
-}
+  console.log(state.selectedIds);
+};
 // 选择
 const handleSelect = (data: any) => {
-  state.selectedIds = [...data].map(item => item.id)
-}
+  state.selectedIds = [...data].map(item => item.id);
+};
 const handleChange = (values: FieldValues) => {
-  console.log(values, 'change')
-}
+  console.log(values, "change");
+};
 const handleSubmit = (values: FieldValues) => {
-  console.log(values, 'Submit')
-  confirmLoading.value = true
+  console.log(values, "Submit");
+  confirmLoading.value = true;
   setTimeout(() => {
-    confirmLoading.value = false
-    visible.value = false
-  }, 2000)
-}
+    confirmLoading.value = false;
+    visible.value = false;
+  }, 2000);
+};
 const handleSubmitError = (err: any) => {
-  console.log(err, 'err')
-}
+  console.log(err, "err");
+};
 const handleClose = () => {
   // 重置表单
-  dialogForm.value.formInstance.resetFields()
-}
+  dialogForm.value.formInstance.resetFields();
+};
 
 buttons.value = [
   {
@@ -582,7 +594,7 @@ buttons.value = [
     text: "删除",
     code: "delete",
     // props v0.1.16 版本新增计算属性支持
-    props: computed(() => ({type: "danger"})),
+    props: computed(() => ({ type: "danger" })),
     confirm: {
       options: {
         draggable: true,
@@ -592,5 +604,6 @@ buttons.value = [
   }
 ];
 
-const {form, confirmLoading, rules, currentRow, visible, detailsVisible} = toRefs(state)
+const { form, confirmLoading, rules, currentRow, visible, detailsVisible } =
+  toRefs(state);
 </script>

+ 1 - 2
src/views/testTable/splitTable/index.vue

@@ -314,8 +314,7 @@ const handleClickButton = (data: ButtonsCallBackParams) => {
         @paginationChange="handlePaginationChange"
         @sort-change="handleSrotChange"
         @clickAction="handleClickButton"
-      >
-      </PlusTable>
+      />
     </el-card>
   </div>
 </template>

+ 12 - 14
src/views/testUpload/upload-oss/index.vue

@@ -1,29 +1,27 @@
 <script setup lang="ts">
-import {onMounted, ref} from "vue";
-import UploadOss from '@/components/UploadOss/index.vue'
+import { onMounted, ref } from "vue";
+import UploadOss from "@/components/UploadOss/index.vue";
 
 defineOptions({
   name: "UploadOss"
-})
+});
 
-onMounted(async () => {
+onMounted(async () => {});
 
-})
-
-let fileList = ref()
+let fileList = ref();
 </script>
 
 <template>
   <div>
     <el-card class="box-card">
-      <div slot="header" class="clearfix">
-        <span>上传组件</span>
-      </div>
-      <UploadOss :file-list="fileList" class="mt-5"></UploadOss>
+      <template v-slot:header>
+        <div class="clearfix">
+          <span>上传组件</span>
+        </div>
+      </template>
+      <UploadOss :file-list="fileList" class="mt-5" />
     </el-card>
   </div>
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>

+ 54 - 22
src/views/testUpload/upload/index.vue

@@ -1,7 +1,7 @@
 <script setup lang="ts">
 defineOptions({
   name: "Upload"
-})
+});
 import { ref } from "vue";
 const fileList = ref([
   {
@@ -15,50 +15,84 @@ const fileList = ref([
 ]);
 
 const form = ref({
-  name: '',
+  name: "",
   file: null
-})
+});
 </script>
 
 <template>
   <div>
     <el-card class="box-card">
-      <div slot="header" class="clearfix">
-        <span>上传组件</span>
-      </div>
+      <template v-slot:header>
+        <div class="clearfix">
+          <span>上传组件</span>
+        </div>
+      </template>
       <el-upload
         class="upload-demo"
         drag
         action="https://jsonplaceholder.typicode.com/posts/"
-        :on-preview="(file) => { console.log('preview', file); }"
-        :on-remove="(file, fileList) => { console.log('remove', file, fileList); }"
-        :before-remove="(file, fileList) => { return $confirm(`确定移除 ${file.name}?`); }"
+        :on-preview="
+          file => {
+            console.log('preview', file);
+          }
+        "
+        :on-remove="
+          (file, fileList) => {
+            console.log('remove', file, fileList);
+          }
+        "
+        :before-remove="
+          (file, fileList) => {
+            return $confirm(`确定移除 ${file.name}?`);
+          }
+        "
         :file-list="fileList"
       >
-        <i class="el-icon-upload"></i>
+        <i class="el-icon-upload" />
         <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
-        <div class="el-upload__tip" slot="tip">只能上传jpg/png文件,且不超过500kb</div>
+        <template v-slot:tip>
+          <div class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+        </template>
       </el-upload>
     </el-card>
 
     <el-card class="box-card mt-5">
-      <div slot="header" class="clearfix">
-        <span>表单中的上传组件</span>
-      </div>
+      <template v-slot:header>
+        <div class="clearfix">
+          <span>表单中的上传组件</span>
+        </div>
+      </template>
       <el-form :model="{ name: '', file: null }" label-width="80px">
         <el-form-item label="姓名">
-          <el-input v-model="form.name"></el-input>
+          <el-input v-model="form.name" />
         </el-form-item>
         <el-form-item label="上传文件">
           <el-upload
             action="https://jsonplaceholder.typicode.com/posts/"
-            :on-preview="(file) => { console.log('preview', file); }"
-            :on-remove="(file, fileList) => { console.log('remove', file, fileList); }"
-            :before-remove="(file, fileList) => { return $confirm(`确定移除 ${file.name}?`); }"
+            :on-preview="
+              file => {
+                console.log('preview', file);
+              }
+            "
+            :on-remove="
+              (file, fileList) => {
+                console.log('remove', file, fileList);
+              }
+            "
+            :before-remove="
+              (file, fileList) => {
+                return $confirm(`确定移除 ${file.name}?`);
+              }
+            "
             :file-list="fileList"
           >
             <el-button size="small" type="primary">点击上传</el-button>
-            <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
+            <template v-slot:tip>
+              <div class="el-upload__tip">
+                只能上传jpg/png文件,且不超过500kb
+              </div>
+            </template>
           </el-upload>
         </el-form-item>
         <el-form-item>
@@ -70,6 +104,4 @@ const form = ref({
   </div>
 </template>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>