mirror of
				https://github.com/pure-admin/vue-pure-admin.git
				synced 2025-11-03 13:44:47 +08:00 
			
		
		
		
	Merge branch 'main' into gitee
This commit is contained in:
		
						commit
						07dc4942ba
					
				@ -1,4 +1,5 @@
 | 
			
		||||
buttons:
 | 
			
		||||
  pureAccountSettings: Account Settings
 | 
			
		||||
  pureLoginOut: LoginOut
 | 
			
		||||
  pureLogin: Login
 | 
			
		||||
  pureSystemSet: Open ProjectConfig
 | 
			
		||||
@ -175,4 +176,4 @@ login:
 | 
			
		||||
  purePassWordRuleReg: The password format should be any combination of 8-18 digits
 | 
			
		||||
  purePassWordSureReg: Please enter confirm password
 | 
			
		||||
  purePassWordDifferentReg: The two passwords do not match!
 | 
			
		||||
  purePassWordUpdateReg: Password has been updated
 | 
			
		||||
  purePassWordUpdateReg: Password has been updated
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
buttons:
 | 
			
		||||
  pureAccountSettings: 账户设置
 | 
			
		||||
  pureLoginOut: 退出系统
 | 
			
		||||
  pureLogin: 登录
 | 
			
		||||
  pureSystemSet: 打开项目配置
 | 
			
		||||
@ -175,4 +176,4 @@ login:
 | 
			
		||||
  purePassWordRuleReg: 密码格式应为8-18位数字、字母、符号的任意两种组合
 | 
			
		||||
  purePassWordSureReg: 请输入确认密码
 | 
			
		||||
  purePassWordDifferentReg: 两次密码不一致!
 | 
			
		||||
  purePassWordUpdateReg: 修改密码成功
 | 
			
		||||
  purePassWordUpdateReg: 修改密码成功
 | 
			
		||||
@ -10,7 +10,9 @@ export default defineFakeRoute([
 | 
			
		||||
        return {
 | 
			
		||||
          success: true,
 | 
			
		||||
          data: {
 | 
			
		||||
            avatar: "https://avatars.githubusercontent.com/u/44761321",
 | 
			
		||||
            username: "admin",
 | 
			
		||||
            nickname: "小铭",
 | 
			
		||||
            // 一个用户可能有多个角色
 | 
			
		||||
            roles: ["admin"],
 | 
			
		||||
            accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
 | 
			
		||||
@ -22,8 +24,9 @@ export default defineFakeRoute([
 | 
			
		||||
        return {
 | 
			
		||||
          success: true,
 | 
			
		||||
          data: {
 | 
			
		||||
            avatar: "https://avatars.githubusercontent.com/u/52823142",
 | 
			
		||||
            username: "common",
 | 
			
		||||
            // 一个用户可能有多个角色
 | 
			
		||||
            nickname: "小林",
 | 
			
		||||
            roles: ["common"],
 | 
			
		||||
            accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
 | 
			
		||||
            refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										59
									
								
								mock/mine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								mock/mine.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
			
		||||
import { defineFakeRoute } from "vite-plugin-fake-server/client";
 | 
			
		||||
import { faker } from "@faker-js/faker/locale/zh_CN";
 | 
			
		||||
 | 
			
		||||
export default defineFakeRoute([
 | 
			
		||||
  // 账户设置-个人信息
 | 
			
		||||
  {
 | 
			
		||||
    url: "/mine",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      return {
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: {
 | 
			
		||||
          avatar: "https://avatars.githubusercontent.com/u/44761321",
 | 
			
		||||
          username: "admin",
 | 
			
		||||
          nickname: "小铭",
 | 
			
		||||
          email: "pureadmin@163.com",
 | 
			
		||||
          phone: "15888886789",
 | 
			
		||||
          description: "一个热爱开源的前端工程师"
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  // 账户设置-个人安全日志
 | 
			
		||||
  {
 | 
			
		||||
    url: "/mine-logs",
 | 
			
		||||
    method: "get",
 | 
			
		||||
    response: () => {
 | 
			
		||||
      let list = [
 | 
			
		||||
        {
 | 
			
		||||
          id: 1,
 | 
			
		||||
          ip: faker.internet.ipv4(),
 | 
			
		||||
          address: "中国河南省信阳市",
 | 
			
		||||
          system: "macOS",
 | 
			
		||||
          browser: "Chrome",
 | 
			
		||||
          summary: "账户登录", // 详情
 | 
			
		||||
          operatingTime: new Date() // 时间
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          id: 2,
 | 
			
		||||
          ip: faker.internet.ipv4(),
 | 
			
		||||
          address: "中国广东省深圳市",
 | 
			
		||||
          system: "Windows",
 | 
			
		||||
          browser: "Firefox",
 | 
			
		||||
          summary: "绑定了手机号码",
 | 
			
		||||
          operatingTime: new Date().setDate(new Date().getDate() - 1)
 | 
			
		||||
        }
 | 
			
		||||
      ];
 | 
			
		||||
      return {
 | 
			
		||||
        success: true,
 | 
			
		||||
        data: {
 | 
			
		||||
          list,
 | 
			
		||||
          total: list.length, // 总条目数
 | 
			
		||||
          pageSize: 10, // 每页显示条目个数
 | 
			
		||||
          currentPage: 1 // 当前页数
 | 
			
		||||
        }
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
]);
 | 
			
		||||
@ -9,9 +9,9 @@ export default defineFakeRoute([
 | 
			
		||||
    response: ({ body }) => {
 | 
			
		||||
      let list = [
 | 
			
		||||
        {
 | 
			
		||||
          username: "admin",
 | 
			
		||||
          nickname: "admin",
 | 
			
		||||
          avatar: "https://avatars.githubusercontent.com/u/44761321",
 | 
			
		||||
          username: "admin",
 | 
			
		||||
          nickname: "小铭",
 | 
			
		||||
          phone: "15888886789",
 | 
			
		||||
          email: faker.internet.email(),
 | 
			
		||||
          sex: 0,
 | 
			
		||||
@ -27,9 +27,9 @@ export default defineFakeRoute([
 | 
			
		||||
          createTime: 1605456000000
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          username: "common",
 | 
			
		||||
          nickname: "common",
 | 
			
		||||
          avatar: "https://avatars.githubusercontent.com/u/52823142",
 | 
			
		||||
          username: "common",
 | 
			
		||||
          nickname: "小林",
 | 
			
		||||
          phone: "18288882345",
 | 
			
		||||
          email: faker.internet.email(),
 | 
			
		||||
          sex: 1,
 | 
			
		||||
@ -397,6 +397,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -420,6 +421,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -443,6 +445,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -466,6 +469,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -489,6 +493,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -512,6 +517,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -535,6 +541,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -558,6 +565,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -581,6 +589,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -604,6 +613,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -627,6 +637,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: true,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -651,6 +662,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -674,6 +686,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -697,6 +710,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -720,6 +734,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -743,6 +758,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -766,6 +782,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -790,6 +807,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -813,6 +831,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -836,6 +855,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -859,6 +879,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -882,6 +903,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -906,6 +928,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -929,6 +952,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -952,6 +976,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -975,6 +1000,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -998,6 +1024,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -1022,6 +1049,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -1045,6 +1073,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: true,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -1068,6 +1097,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: false,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          },
 | 
			
		||||
@ -1091,6 +1121,7 @@ export default defineFakeRoute([
 | 
			
		||||
            frameLoading: true,
 | 
			
		||||
            keepAlive: false,
 | 
			
		||||
            hiddenTag: false,
 | 
			
		||||
            fixedTag: false,
 | 
			
		||||
            showLink: false,
 | 
			
		||||
            showParent: false
 | 
			
		||||
          }
 | 
			
		||||
@ -1472,6 +1503,7 @@ export default defineFakeRoute([
 | 
			
		||||
                frameLoading: true,
 | 
			
		||||
                keepAlive: false,
 | 
			
		||||
                hiddenTag: false,
 | 
			
		||||
                fixedTag: false,
 | 
			
		||||
                showLink: true,
 | 
			
		||||
                showParent: false
 | 
			
		||||
              },
 | 
			
		||||
@ -1495,6 +1527,7 @@ export default defineFakeRoute([
 | 
			
		||||
                frameLoading: true,
 | 
			
		||||
                keepAlive: false,
 | 
			
		||||
                hiddenTag: false,
 | 
			
		||||
                fixedTag: false,
 | 
			
		||||
                showLink: true,
 | 
			
		||||
                showParent: false
 | 
			
		||||
              },
 | 
			
		||||
@ -1518,6 +1551,7 @@ export default defineFakeRoute([
 | 
			
		||||
                frameLoading: true,
 | 
			
		||||
                keepAlive: false,
 | 
			
		||||
                hiddenTag: false,
 | 
			
		||||
                fixedTag: false,
 | 
			
		||||
                showLink: true,
 | 
			
		||||
                showParent: false
 | 
			
		||||
              },
 | 
			
		||||
@ -1541,6 +1575,7 @@ export default defineFakeRoute([
 | 
			
		||||
                frameLoading: true,
 | 
			
		||||
                keepAlive: false,
 | 
			
		||||
                hiddenTag: false,
 | 
			
		||||
                fixedTag: false,
 | 
			
		||||
                showLink: true,
 | 
			
		||||
                showParent: false
 | 
			
		||||
              },
 | 
			
		||||
@ -1564,6 +1599,7 @@ export default defineFakeRoute([
 | 
			
		||||
                frameLoading: true,
 | 
			
		||||
                keepAlive: false,
 | 
			
		||||
                hiddenTag: false,
 | 
			
		||||
                fixedTag: false,
 | 
			
		||||
                showLink: true,
 | 
			
		||||
                showParent: false
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								package.json
									
									
									
									
									
								
							@ -51,15 +51,15 @@
 | 
			
		||||
    "@amap/amap-jsapi-loader": "^1.0.1",
 | 
			
		||||
    "@howdyjs/mouse-menu": "^2.1.3",
 | 
			
		||||
    "@infectoone/vue-ganttastic": "^2.3.2",
 | 
			
		||||
    "@logicflow/core": "^1.2.23",
 | 
			
		||||
    "@logicflow/extension": "^1.2.23",
 | 
			
		||||
    "@logicflow/core": "^1.2.25",
 | 
			
		||||
    "@logicflow/extension": "^1.2.25",
 | 
			
		||||
    "@pureadmin/descriptions": "^1.2.1",
 | 
			
		||||
    "@pureadmin/table": "^3.1.2",
 | 
			
		||||
    "@pureadmin/utils": "^2.4.7",
 | 
			
		||||
    "@vue-flow/background": "^1.3.0",
 | 
			
		||||
    "@vue-flow/core": "^1.33.5",
 | 
			
		||||
    "@vue-office/docx": "^1.6.0",
 | 
			
		||||
    "@vue-office/excel": "^1.7.3",
 | 
			
		||||
    "@vue-office/excel": "^1.7.6",
 | 
			
		||||
    "@vueuse/core": "^10.9.0",
 | 
			
		||||
    "@vueuse/motion": "^2.1.0",
 | 
			
		||||
    "@wangeditor/editor": "^5.1.23",
 | 
			
		||||
@ -95,7 +95,7 @@
 | 
			
		||||
    "v3-infinite-loading": "^1.3.1",
 | 
			
		||||
    "version-rocket": "^1.7.1",
 | 
			
		||||
    "vue": "^3.4.21",
 | 
			
		||||
    "vue-i18n": "^9.10.2",
 | 
			
		||||
    "vue-i18n": "^9.11.0",
 | 
			
		||||
    "vue-json-pretty": "^2.4.0",
 | 
			
		||||
    "vue-pdf-embed": "^2.0.3",
 | 
			
		||||
    "vue-router": "^4.3.0",
 | 
			
		||||
@ -107,8 +107,8 @@
 | 
			
		||||
    "vue3-puzzle-vcode": "^1.1.7",
 | 
			
		||||
    "vuedraggable": "^4.1.0",
 | 
			
		||||
    "vxe-table": "^4.5.21",
 | 
			
		||||
    "wavesurfer.js": "^7.7.5",
 | 
			
		||||
    "xgplayer": "^3.0.15",
 | 
			
		||||
    "wavesurfer.js": "^7.7.8",
 | 
			
		||||
    "xgplayer": "^3.0.16",
 | 
			
		||||
    "xlsx": "^0.18.5"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
@ -123,16 +123,16 @@
 | 
			
		||||
    "@intlify/unplugin-vue-i18n": "^4.0.0",
 | 
			
		||||
    "@pureadmin/theme": "^3.2.0",
 | 
			
		||||
    "@types/dagre": "^0.7.52",
 | 
			
		||||
    "@types/gradient-string": "^1.1.5",
 | 
			
		||||
    "@types/gradient-string": "^1.1.6",
 | 
			
		||||
    "@types/intro.js": "^5.1.5",
 | 
			
		||||
    "@types/js-cookie": "^3.0.6",
 | 
			
		||||
    "@types/node": "^20.12.2",
 | 
			
		||||
    "@types/node": "^20.12.5",
 | 
			
		||||
    "@types/nprogress": "^0.2.3",
 | 
			
		||||
    "@types/qrcode": "^1.5.5",
 | 
			
		||||
    "@types/qs": "^6.9.14",
 | 
			
		||||
    "@types/sortablejs": "^1.15.8",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^7.4.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^7.4.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^7.5.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^7.5.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^5.0.4",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.1.0",
 | 
			
		||||
    "autoprefixer": "^10.4.19",
 | 
			
		||||
@ -155,16 +155,16 @@
 | 
			
		||||
    "prettier": "^3.2.5",
 | 
			
		||||
    "rimraf": "^5.0.5",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.12.0",
 | 
			
		||||
    "sass": "^1.72.0",
 | 
			
		||||
    "sass": "^1.74.1",
 | 
			
		||||
    "stylelint": "^16.3.1",
 | 
			
		||||
    "stylelint-config-recess-order": "^5.0.0",
 | 
			
		||||
    "stylelint-config-recommended-vue": "^1.5.0",
 | 
			
		||||
    "stylelint-config-standard-scss": "^13.0.0",
 | 
			
		||||
    "stylelint-config-standard-scss": "^13.1.0",
 | 
			
		||||
    "stylelint-prettier": "^5.0.0",
 | 
			
		||||
    "svgo": "^3.2.0",
 | 
			
		||||
    "tailwindcss": "^3.4.3",
 | 
			
		||||
    "typescript": "^5.4.3",
 | 
			
		||||
    "vite": "^5.2.7",
 | 
			
		||||
    "typescript": "^5.4.4",
 | 
			
		||||
    "vite": "^5.2.8",
 | 
			
		||||
    "vite-plugin-cdn-import": "^0.3.5",
 | 
			
		||||
    "vite-plugin-compression": "^0.5.1",
 | 
			
		||||
    "vite-plugin-fake-server": "^2.1.1",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										873
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										873
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -28,12 +28,56 @@ export type RefreshTokenResult = {
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UserInfo = {
 | 
			
		||||
  /** 头像 */
 | 
			
		||||
  avatar: string;
 | 
			
		||||
  /** 用户名 */
 | 
			
		||||
  username: string;
 | 
			
		||||
  /** 昵称 */
 | 
			
		||||
  nickname: string;
 | 
			
		||||
  /** 邮箱 */
 | 
			
		||||
  email: string;
 | 
			
		||||
  /** 联系电话 */
 | 
			
		||||
  phone: string;
 | 
			
		||||
  /** 简介 */
 | 
			
		||||
  description: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UserInfoResult = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data: UserInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type ResultTable = {
 | 
			
		||||
  success: boolean;
 | 
			
		||||
  data?: {
 | 
			
		||||
    /** 列表数据 */
 | 
			
		||||
    list: Array<any>;
 | 
			
		||||
    /** 总条目数 */
 | 
			
		||||
    total?: number;
 | 
			
		||||
    /** 每页显示条目个数 */
 | 
			
		||||
    pageSize?: number;
 | 
			
		||||
    /** 当前页数 */
 | 
			
		||||
    currentPage?: number;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 登录 */
 | 
			
		||||
export const getLogin = (data?: object) => {
 | 
			
		||||
  return http.request<UserResult>("post", "/login", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 刷新token */
 | 
			
		||||
/** 刷新`token` */
 | 
			
		||||
export const refreshTokenApi = (data?: object) => {
 | 
			
		||||
  return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 账户设置-个人信息 */
 | 
			
		||||
export const getMine = (data?: object) => {
 | 
			
		||||
  return http.request<UserInfoResult>("get", "/mine", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** 账户设置-个人安全日志 */
 | 
			
		||||
export const getMineLogs = (data?: object) => {
 | 
			
		||||
  return http.request<ResultTable>("get", "/mine-logs", { data });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										7
									
								
								src/components/ReCropperPreview/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/components/ReCropperPreview/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
import reCropperPreview from "./src/index.vue";
 | 
			
		||||
import { withInstall } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
/** 图片裁剪预览组件 */
 | 
			
		||||
export const ReCropperPreview = withInstall(reCropperPreview);
 | 
			
		||||
 | 
			
		||||
export default ReCropperPreview;
 | 
			
		||||
@ -3,6 +3,10 @@ import { ref } from "vue";
 | 
			
		||||
import ReCropper from "@/components/ReCropper";
 | 
			
		||||
import { formatBytes } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "ReCropperPreview"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  imgSrc: String
 | 
			
		||||
});
 | 
			
		||||
@ -11,6 +11,10 @@ import { isFunction } from "@pureadmin/utils";
 | 
			
		||||
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
 | 
			
		||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "ReDialog"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const fullscreen = ref(false);
 | 
			
		||||
 | 
			
		||||
const footerButtons = computed(() => {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,10 @@
 | 
			
		||||
import flippers from "./filpper";
 | 
			
		||||
import { ref, unref, nextTick, onUnmounted } from "vue";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "ReFlop"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const timer = ref(null);
 | 
			
		||||
const flipObjs = ref([]);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,10 @@
 | 
			
		||||
import { h, onMounted, ref, useSlots } from "vue";
 | 
			
		||||
import { type TippyOptions, useTippy } from "vue-tippy";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "ReText"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  // 行数
 | 
			
		||||
  lineClamp: {
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,11 @@ import Breadcrumb from "./sidebar/breadCrumb.vue";
 | 
			
		||||
import topCollapse from "./sidebar/topCollapse.vue";
 | 
			
		||||
import { useTranslationLang } from "../hooks/useTranslationLang";
 | 
			
		||||
import globalization from "@/assets/svg/globalization.svg?component";
 | 
			
		||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
 | 
			
		||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
 | 
			
		||||
import Setting from "@iconify-icons/ri/settings-3-line";
 | 
			
		||||
import Check from "@iconify-icons/ep/check";
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  layout,
 | 
			
		||||
  device,
 | 
			
		||||
@ -21,6 +23,7 @@ const {
 | 
			
		||||
  userAvatar,
 | 
			
		||||
  avatarsStyle,
 | 
			
		||||
  toggleSideBar,
 | 
			
		||||
  toAccountSettings,
 | 
			
		||||
  getDropdownItemStyle,
 | 
			
		||||
  getDropdownItemClass
 | 
			
		||||
} = useNav();
 | 
			
		||||
@ -91,6 +94,13 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
 | 
			
		||||
        </span>
 | 
			
		||||
        <template #dropdown>
 | 
			
		||||
          <el-dropdown-menu class="logout">
 | 
			
		||||
            <el-dropdown-item @click="toAccountSettings">
 | 
			
		||||
              <IconifyIconOffline
 | 
			
		||||
                :icon="AccountSettingsIcon"
 | 
			
		||||
                style="margin: 5px"
 | 
			
		||||
              />
 | 
			
		||||
              {{ t("buttons.pureAccountSettings") }}
 | 
			
		||||
            </el-dropdown-item>
 | 
			
		||||
            <el-dropdown-item @click="logout">
 | 
			
		||||
              <IconifyIconOffline
 | 
			
		||||
                :icon="LogoutCircleRLine"
 | 
			
		||||
@ -177,7 +187,7 @@ const { t, locale, translationCh, translationEn } = useTranslationLang();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logout {
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,7 @@ import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
import globalization from "@/assets/svg/globalization.svg?component";
 | 
			
		||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
 | 
			
		||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
 | 
			
		||||
import Setting from "@iconify-icons/ri/settings-3-line";
 | 
			
		||||
import Check from "@iconify-icons/ep/check";
 | 
			
		||||
@ -26,6 +27,7 @@ const {
 | 
			
		||||
  username,
 | 
			
		||||
  userAvatar,
 | 
			
		||||
  avatarsStyle,
 | 
			
		||||
  toAccountSettings,
 | 
			
		||||
  getDropdownItemStyle,
 | 
			
		||||
  getDropdownItemClass
 | 
			
		||||
} = useNav();
 | 
			
		||||
@ -107,6 +109,13 @@ nextTick(() => {
 | 
			
		||||
          <p v-if="username" class="dark:text-white">{{ username }}</p>
 | 
			
		||||
        </span>
 | 
			
		||||
        <template #dropdown>
 | 
			
		||||
          <el-dropdown-item @click="toAccountSettings">
 | 
			
		||||
            <IconifyIconOffline
 | 
			
		||||
              :icon="AccountSettingsIcon"
 | 
			
		||||
              style="margin: 5px"
 | 
			
		||||
            />
 | 
			
		||||
            {{ t("buttons.pureAccountSettings") }}
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
          <el-dropdown-menu class="logout">
 | 
			
		||||
            <el-dropdown-item @click="logout">
 | 
			
		||||
              <IconifyIconOffline
 | 
			
		||||
@ -151,7 +160,7 @@ nextTick(() => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logout {
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import { getParentPaths, findRouteByPath } from "@/router/utils";
 | 
			
		||||
import { useTranslationLang } from "../../hooks/useTranslationLang";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
import globalization from "@/assets/svg/globalization.svg?component";
 | 
			
		||||
import AccountSettingsIcon from "@iconify-icons/ri/user-settings-line";
 | 
			
		||||
import LogoutCircleRLine from "@iconify-icons/ri/logout-circle-r-line";
 | 
			
		||||
import Setting from "@iconify-icons/ri/settings-3-line";
 | 
			
		||||
import Check from "@iconify-icons/ep/check";
 | 
			
		||||
@ -30,6 +31,7 @@ const {
 | 
			
		||||
  userAvatar,
 | 
			
		||||
  getDivStyle,
 | 
			
		||||
  avatarsStyle,
 | 
			
		||||
  toAccountSettings,
 | 
			
		||||
  getDropdownItemStyle,
 | 
			
		||||
  getDropdownItemClass
 | 
			
		||||
} = useNav();
 | 
			
		||||
@ -140,6 +142,13 @@ watch(
 | 
			
		||||
          <p v-if="username" class="dark:text-white">{{ username }}</p>
 | 
			
		||||
        </span>
 | 
			
		||||
        <template #dropdown>
 | 
			
		||||
          <el-dropdown-item @click="toAccountSettings">
 | 
			
		||||
            <IconifyIconOffline
 | 
			
		||||
              :icon="AccountSettingsIcon"
 | 
			
		||||
              style="margin: 5px"
 | 
			
		||||
            />
 | 
			
		||||
            {{ t("buttons.pureAccountSettings") }}
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
          <el-dropdown-menu class="logout">
 | 
			
		||||
            <el-dropdown-item @click="logout">
 | 
			
		||||
              <IconifyIconOffline
 | 
			
		||||
@ -184,7 +193,7 @@ watch(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.logout {
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
  width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
 | 
			
		||||
@ -90,6 +90,10 @@
 | 
			
		||||
          padding: 0 12px;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .fixed-tag {
 | 
			
		||||
        padding: 0 12px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ import { onClickOutside } from "@vueuse/core";
 | 
			
		||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
 | 
			
		||||
import { useSettingStoreHook } from "@/store/modules/settings";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
import { ref, watch, unref, toRaw, nextTick, onBeforeUnmount } from "vue";
 | 
			
		||||
import {
 | 
			
		||||
  delay,
 | 
			
		||||
@ -59,6 +60,10 @@ const contextmenuRef = ref();
 | 
			
		||||
const isShowArrow = ref(false);
 | 
			
		||||
const topPath = getTopMenu()?.path;
 | 
			
		||||
const { VITE_HIDE_HOME } = import.meta.env;
 | 
			
		||||
const fixedTags = [
 | 
			
		||||
  ...routerArrays,
 | 
			
		||||
  ...usePermissionStoreHook().flatteningRoutes.filter(v => v?.meta?.fixedTag)
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const dynamicTagView = async () => {
 | 
			
		||||
  await nextTick();
 | 
			
		||||
@ -228,10 +233,13 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
    other?: boolean
 | 
			
		||||
  ): void => {
 | 
			
		||||
    if (other) {
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("equal", [
 | 
			
		||||
        VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()),
 | 
			
		||||
        obj
 | 
			
		||||
      ]);
 | 
			
		||||
      useMultiTagsStoreHook().handleTags(
 | 
			
		||||
        "equal",
 | 
			
		||||
        [
 | 
			
		||||
          VITE_HIDE_HOME === "false" ? fixedTags : toRaw(getTopMenu()),
 | 
			
		||||
          obj
 | 
			
		||||
        ].flat()
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("splice", "", {
 | 
			
		||||
        startIndex,
 | 
			
		||||
@ -244,7 +252,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
  if (tag === "other") {
 | 
			
		||||
    spliceRoute(1, 1, true);
 | 
			
		||||
  } else if (tag === "left") {
 | 
			
		||||
    spliceRoute(1, valueIndex - 1);
 | 
			
		||||
    spliceRoute(fixedTags.length, valueIndex - 1, true);
 | 
			
		||||
  } else if (tag === "right") {
 | 
			
		||||
    spliceRoute(valueIndex + 1, multiTags.value.length);
 | 
			
		||||
  } else {
 | 
			
		||||
@ -321,10 +329,11 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
 | 
			
		||||
    case 5:
 | 
			
		||||
      // 关闭全部标签页
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("splice", "", {
 | 
			
		||||
        startIndex: 1,
 | 
			
		||||
        startIndex: fixedTags.length,
 | 
			
		||||
        length: multiTags.value.length
 | 
			
		||||
      });
 | 
			
		||||
      router.push(topPath);
 | 
			
		||||
      // router.push(fixedTags[fixedTags.length - 1]?.path);
 | 
			
		||||
      handleAliveRoute(route as ToRouteType);
 | 
			
		||||
      break;
 | 
			
		||||
    case 6:
 | 
			
		||||
@ -363,10 +372,14 @@ function showMenus(value: boolean) {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function disabledMenus(value: boolean) {
 | 
			
		||||
function disabledMenus(value: boolean, fixedTag = false) {
 | 
			
		||||
  Array.of(1, 2, 3, 4, 5).forEach(v => {
 | 
			
		||||
    tagsViews[v].disabled = value;
 | 
			
		||||
  });
 | 
			
		||||
  if (fixedTag) {
 | 
			
		||||
    tagsViews[2].show = false;
 | 
			
		||||
    tagsViews[2].disabled = true;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
 | 
			
		||||
@ -383,6 +396,13 @@ function showMenuModel(
 | 
			
		||||
  } else {
 | 
			
		||||
    currentIndex = allRoute.findIndex(v => isEqual(v.query, query));
 | 
			
		||||
  }
 | 
			
		||||
  function fixedTagDisabled() {
 | 
			
		||||
    if (allRoute[currentIndex]?.meta?.fixedTag) {
 | 
			
		||||
      Array.of(1, 2, 3, 4, 5).forEach(v => {
 | 
			
		||||
        tagsViews[v].disabled = true;
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  showMenus(true);
 | 
			
		||||
 | 
			
		||||
@ -401,6 +421,7 @@ function showMenuModel(
 | 
			
		||||
      tagsViews[v].disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
    tagsViews[2].disabled = true;
 | 
			
		||||
    fixedTagDisabled();
 | 
			
		||||
  } else if (currentIndex === 1 && routeLength === 2) {
 | 
			
		||||
    disabledMenus(false);
 | 
			
		||||
    // 左侧的菜单是顶级菜单,右侧不存在别的菜单
 | 
			
		||||
@ -408,6 +429,7 @@ function showMenuModel(
 | 
			
		||||
      tagsViews[v].show = false;
 | 
			
		||||
      tagsViews[v].disabled = true;
 | 
			
		||||
    });
 | 
			
		||||
    fixedTagDisabled();
 | 
			
		||||
  } else if (routeLength - 1 === currentIndex && currentIndex !== 0) {
 | 
			
		||||
    // 当前路由是所有路由中的最后一个
 | 
			
		||||
    tagsViews[3].show = false;
 | 
			
		||||
@ -415,18 +437,24 @@ function showMenuModel(
 | 
			
		||||
      tagsViews[v].disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
    tagsViews[3].disabled = true;
 | 
			
		||||
    if (allRoute[currentIndex - 1]?.meta?.fixedTag) {
 | 
			
		||||
      tagsViews[2].show = false;
 | 
			
		||||
      tagsViews[2].disabled = true;
 | 
			
		||||
    }
 | 
			
		||||
    fixedTagDisabled();
 | 
			
		||||
  } else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
 | 
			
		||||
    // 当前路由为顶级菜单
 | 
			
		||||
    disabledMenus(true);
 | 
			
		||||
  } else {
 | 
			
		||||
    disabledMenus(false);
 | 
			
		||||
    disabledMenus(false, allRoute[currentIndex - 1]?.meta?.fixedTag);
 | 
			
		||||
    fixedTagDisabled();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function openMenu(tag, e) {
 | 
			
		||||
  closeMenu();
 | 
			
		||||
  if (tag.path === topPath) {
 | 
			
		||||
    // 右键菜单为顶级菜单,只显示刷新
 | 
			
		||||
  if (tag.path === topPath || tag?.meta?.fixedTag) {
 | 
			
		||||
    // 右键菜单为顶级菜单或拥有 fixedTag 属性,只显示刷新
 | 
			
		||||
    showMenus(false);
 | 
			
		||||
    tagsViews[0].show = true;
 | 
			
		||||
  } else if (route.path !== tag.path && route.name !== tag.name) {
 | 
			
		||||
@ -485,7 +513,6 @@ function tagOnClick(item) {
 | 
			
		||||
  } else {
 | 
			
		||||
    router.push({ path });
 | 
			
		||||
  }
 | 
			
		||||
  // showMenuModel(item?.path, item?.query);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onClickOutside(contextmenuRef, closeMenu, {
 | 
			
		||||
@ -549,7 +576,11 @@ onBeforeUnmount(() => {
 | 
			
		||||
          v-for="(item, index) in multiTags"
 | 
			
		||||
          :ref="'dynamic' + index"
 | 
			
		||||
          :key="index"
 | 
			
		||||
          :class="['scroll-item is-closable', linkIsActive(item)]"
 | 
			
		||||
          :class="[
 | 
			
		||||
            'scroll-item is-closable',
 | 
			
		||||
            linkIsActive(item),
 | 
			
		||||
            !isAllEmpty(item?.meta?.fixedTag) && 'fixed-tag'
 | 
			
		||||
          ]"
 | 
			
		||||
          @contextmenu.prevent="openMenu(item, $event)"
 | 
			
		||||
          @mouseenter.prevent="onMouseenter(index)"
 | 
			
		||||
          @mouseleave.prevent="onMouseleave(index)"
 | 
			
		||||
@ -562,8 +593,10 @@ onBeforeUnmount(() => {
 | 
			
		||||
          </span>
 | 
			
		||||
          <span
 | 
			
		||||
            v-if="
 | 
			
		||||
              iconIsActive(item, index) ||
 | 
			
		||||
              (index === activeIndex && index !== 0)
 | 
			
		||||
              isAllEmpty(item?.meta?.fixedTag)
 | 
			
		||||
                ? iconIsActive(item, index) ||
 | 
			
		||||
                  (index === activeIndex && index !== 0)
 | 
			
		||||
                : false
 | 
			
		||||
            "
 | 
			
		||||
            class="el-icon-close"
 | 
			
		||||
            @click.stop="deleteMenu(item)"
 | 
			
		||||
 | 
			
		||||
@ -2,16 +2,16 @@ import { storeToRefs } from "pinia";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { emitter } from "@/utils/mitt";
 | 
			
		||||
import userAvatar from "@/assets/user.jpg";
 | 
			
		||||
import Avatar from "@/assets/user.jpg";
 | 
			
		||||
import { getTopMenu } from "@/router/utils";
 | 
			
		||||
import { useFullscreen } from "@vueuse/core";
 | 
			
		||||
import { useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import type { routeMetaType } from "../types";
 | 
			
		||||
import { transformI18n } from "@/plugins/i18n";
 | 
			
		||||
import { router, remainingPaths } from "@/router";
 | 
			
		||||
import { computed, type CSSProperties } from "vue";
 | 
			
		||||
import { useAppStoreHook } from "@/store/modules/app";
 | 
			
		||||
import { useUserStoreHook } from "@/store/modules/user";
 | 
			
		||||
import { useGlobal, isAllEmpty } from "@pureadmin/utils";
 | 
			
		||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
 | 
			
		||||
@ -37,9 +37,18 @@ export function useNav() {
 | 
			
		||||
    };
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /** 用户名 */
 | 
			
		||||
  /** 头像(如果头像为空则使用 src/assets/user.jpg ) */
 | 
			
		||||
  const userAvatar = computed(() => {
 | 
			
		||||
    return isAllEmpty(useUserStoreHook()?.avatar)
 | 
			
		||||
      ? Avatar
 | 
			
		||||
      : useUserStoreHook()?.avatar;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /** 昵称(如果昵称为空则显示用户名) */
 | 
			
		||||
  const username = computed(() => {
 | 
			
		||||
    return useUserStoreHook()?.username;
 | 
			
		||||
    return isAllEmpty(useUserStoreHook()?.nickname)
 | 
			
		||||
      ? useUserStoreHook()?.username
 | 
			
		||||
      : useUserStoreHook()?.nickname;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  /** 设置国际化选中后的样式 */
 | 
			
		||||
@ -99,6 +108,10 @@ export function useNav() {
 | 
			
		||||
    emitter.emit("openPanel");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function toAccountSettings() {
 | 
			
		||||
    router.push({ name: "AccountSettings" });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function toggleSideBar() {
 | 
			
		||||
    pureApp.toggleSideBar();
 | 
			
		||||
  }
 | 
			
		||||
@ -159,6 +172,7 @@ export function useNav() {
 | 
			
		||||
    userAvatar,
 | 
			
		||||
    avatarsStyle,
 | 
			
		||||
    tooltipEffect,
 | 
			
		||||
    toAccountSettings,
 | 
			
		||||
    getDropdownItemStyle,
 | 
			
		||||
    getDropdownItemClass
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
@ -38,5 +38,15 @@ export default [
 | 
			
		||||
      showLink: false,
 | 
			
		||||
      rank: 103
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    path: "/account-settings",
 | 
			
		||||
    name: "AccountSettings",
 | 
			
		||||
    component: () => import("@/views/account-settings/index.vue"),
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: $t("buttons.pureAccountSettings"),
 | 
			
		||||
      showLink: false,
 | 
			
		||||
      rank: 104
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
] satisfies Array<RouteConfigsTable>;
 | 
			
		||||
 | 
			
		||||
@ -17,9 +17,9 @@ import {
 | 
			
		||||
  isIncludeAllChildren
 | 
			
		||||
} from "@pureadmin/utils";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import type { menuType } from "@/layout/types";
 | 
			
		||||
import { buildHierarchyTree } from "@/utils/tree";
 | 
			
		||||
import { userKey, type DataInfo } from "@/utils/auth";
 | 
			
		||||
import { type menuType, routerArrays } from "@/layout/types";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
const IFrame = () => import("@/layout/frameView.vue");
 | 
			
		||||
@ -178,6 +178,14 @@ function handleAsyncRoutes(routeList) {
 | 
			
		||||
    );
 | 
			
		||||
    usePermissionStoreHook().handleWholeMenus(routeList);
 | 
			
		||||
  }
 | 
			
		||||
  if (!useMultiTagsStoreHook().getMultiTagsCache) {
 | 
			
		||||
    useMultiTagsStoreHook().handleTags("equal", [
 | 
			
		||||
      ...routerArrays,
 | 
			
		||||
      ...usePermissionStoreHook().flatteningRoutes.filter(
 | 
			
		||||
        v => v?.meta?.fixedTag
 | 
			
		||||
      )
 | 
			
		||||
    ]);
 | 
			
		||||
  }
 | 
			
		||||
  addPathMatch();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { store } from "@/store";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
import { usePermissionStoreHook } from "./permission";
 | 
			
		||||
import { responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
import type { multiType, positionType } from "./types";
 | 
			
		||||
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
 | 
			
		||||
@ -15,7 +16,12 @@ export const useMultiTagsStore = defineStore({
 | 
			
		||||
      ? storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
          `${responsiveStorageNameSpace()}tags`
 | 
			
		||||
        )
 | 
			
		||||
      : [...routerArrays],
 | 
			
		||||
      : [
 | 
			
		||||
          ...routerArrays,
 | 
			
		||||
          ...usePermissionStoreHook().flatteningRoutes.filter(
 | 
			
		||||
            v => v?.meta?.fixedTag
 | 
			
		||||
          )
 | 
			
		||||
        ],
 | 
			
		||||
    multiTagsCache: storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
      `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
    )?.multiTagsCache
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,12 @@ import type { cacheType } from "./types";
 | 
			
		||||
import { constantMenus } from "@/router";
 | 
			
		||||
import { useMultiTagsStoreHook } from "./multiTags";
 | 
			
		||||
import { debounce, getKeyList } from "@pureadmin/utils";
 | 
			
		||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
 | 
			
		||||
import {
 | 
			
		||||
  ascending,
 | 
			
		||||
  filterTree,
 | 
			
		||||
  filterNoPermissionTree,
 | 
			
		||||
  formatFlatteningRoutes
 | 
			
		||||
} from "@/router/utils";
 | 
			
		||||
 | 
			
		||||
export const usePermissionStore = defineStore({
 | 
			
		||||
  id: "pure-permission",
 | 
			
		||||
@ -13,6 +18,8 @@ export const usePermissionStore = defineStore({
 | 
			
		||||
    constantMenus,
 | 
			
		||||
    // 整体路由生成的菜单(静态、动态)
 | 
			
		||||
    wholeMenus: [],
 | 
			
		||||
    // 整体路由(一维数组格式)
 | 
			
		||||
    flatteningRoutes: [],
 | 
			
		||||
    // 缓存页面keepAlive
 | 
			
		||||
    cachePageList: []
 | 
			
		||||
  }),
 | 
			
		||||
@ -22,6 +29,9 @@ export const usePermissionStore = defineStore({
 | 
			
		||||
      this.wholeMenus = filterNoPermissionTree(
 | 
			
		||||
        filterTree(ascending(this.constantMenus.concat(routes)))
 | 
			
		||||
      );
 | 
			
		||||
      this.flatteningRoutes = formatFlatteningRoutes(
 | 
			
		||||
        this.constantMenus.concat(routes)
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    cacheOperate({ mode, name }: cacheType) {
 | 
			
		||||
      const delIndex = this.cachePageList.findIndex(v => v === name);
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,9 @@ export type setType = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type userType = {
 | 
			
		||||
  avatar?: string;
 | 
			
		||||
  username?: string;
 | 
			
		||||
  nickname?: string;
 | 
			
		||||
  roles?: Array<string>;
 | 
			
		||||
  verifyCode?: string;
 | 
			
		||||
  currentPage?: number;
 | 
			
		||||
 | 
			
		||||
@ -12,8 +12,12 @@ import { type DataInfo, setToken, removeToken, userKey } from "@/utils/auth";
 | 
			
		||||
export const useUserStore = defineStore({
 | 
			
		||||
  id: "pure-user",
 | 
			
		||||
  state: (): userType => ({
 | 
			
		||||
    // 头像
 | 
			
		||||
    avatar: storageLocal().getItem<DataInfo<number>>(userKey)?.avatar ?? "",
 | 
			
		||||
    // 用户名
 | 
			
		||||
    username: storageLocal().getItem<DataInfo<number>>(userKey)?.username ?? "",
 | 
			
		||||
    // 昵称
 | 
			
		||||
    nickname: storageLocal().getItem<DataInfo<number>>(userKey)?.nickname ?? "",
 | 
			
		||||
    // 页面级别权限
 | 
			
		||||
    roles: storageLocal().getItem<DataInfo<number>>(userKey)?.roles ?? [],
 | 
			
		||||
    // 前端生成的验证码(按实际需求替换)
 | 
			
		||||
@ -26,10 +30,18 @@ export const useUserStore = defineStore({
 | 
			
		||||
    loginDay: 7
 | 
			
		||||
  }),
 | 
			
		||||
  actions: {
 | 
			
		||||
    /** 存储头像 */
 | 
			
		||||
    SET_AVATAR(avatar: string) {
 | 
			
		||||
      this.avatar = avatar;
 | 
			
		||||
    },
 | 
			
		||||
    /** 存储用户名 */
 | 
			
		||||
    SET_USERNAME(username: string) {
 | 
			
		||||
      this.username = username;
 | 
			
		||||
    },
 | 
			
		||||
    /** 存储昵称 */
 | 
			
		||||
    SET_NICKNAME(nickname: string) {
 | 
			
		||||
      this.nickname = nickname;
 | 
			
		||||
    },
 | 
			
		||||
    /** 存储角色 */
 | 
			
		||||
    SET_ROLES(roles: Array<string>) {
 | 
			
		||||
      this.roles = roles;
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,12 @@ export interface DataInfo<T> {
 | 
			
		||||
  expires: T;
 | 
			
		||||
  /** 用于调用刷新accessToken的接口时所需的token */
 | 
			
		||||
  refreshToken: string;
 | 
			
		||||
  /** 头像 */
 | 
			
		||||
  avatar?: string;
 | 
			
		||||
  /** 用户名 */
 | 
			
		||||
  username?: string;
 | 
			
		||||
  /** 昵称 */
 | 
			
		||||
  nickname?: string;
 | 
			
		||||
  /** 当前登陆用户的角色 */
 | 
			
		||||
  roles?: Array<string>;
 | 
			
		||||
}
 | 
			
		||||
@ -37,7 +41,7 @@ export function getToken(): DataInfo<number> {
 | 
			
		||||
 * @description 设置`token`以及一些必要信息并采用无感刷新`token`方案
 | 
			
		||||
 * 无感刷新:后端返回`accessToken`(访问接口使用的`token`)、`refreshToken`(用于调用刷新`accessToken`的接口时所需的`token`,`refreshToken`的过期时间(比如30天)应大于`accessToken`的过期时间(比如2小时))、`expires`(`accessToken`的过期时间)
 | 
			
		||||
 * 将`accessToken`、`expires`这两条信息放在key值为authorized-token的cookie里(过期自动销毁)
 | 
			
		||||
 * 将`username`、`roles`、`refreshToken`、`expires`这四条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
 | 
			
		||||
 * 将`avatar`、`username`、`nickname`、`roles`、`refreshToken`、`expires`这六条信息放在key值为`user-info`的localStorage里(利用`multipleTabsKey`当浏览器完全关闭后自动销毁)
 | 
			
		||||
 */
 | 
			
		||||
export function setToken(data: DataInfo<Date>) {
 | 
			
		||||
  let expires = 0;
 | 
			
		||||
@ -62,26 +66,44 @@ export function setToken(data: DataInfo<Date>) {
 | 
			
		||||
      : {}
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  function setUserKey(username: string, roles: Array<string>) {
 | 
			
		||||
  function setUserKey({ avatar, username, nickname, roles }) {
 | 
			
		||||
    useUserStoreHook().SET_AVATAR(avatar);
 | 
			
		||||
    useUserStoreHook().SET_USERNAME(username);
 | 
			
		||||
    useUserStoreHook().SET_NICKNAME(nickname);
 | 
			
		||||
    useUserStoreHook().SET_ROLES(roles);
 | 
			
		||||
    storageLocal().setItem(userKey, {
 | 
			
		||||
      refreshToken,
 | 
			
		||||
      expires,
 | 
			
		||||
      avatar,
 | 
			
		||||
      username,
 | 
			
		||||
      nickname,
 | 
			
		||||
      roles
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (data.username && data.roles) {
 | 
			
		||||
    const { username, roles } = data;
 | 
			
		||||
    setUserKey(username, roles);
 | 
			
		||||
    setUserKey({
 | 
			
		||||
      avatar: data?.avatar ?? "",
 | 
			
		||||
      username,
 | 
			
		||||
      nickname: data?.nickname ?? "",
 | 
			
		||||
      roles
 | 
			
		||||
    });
 | 
			
		||||
  } 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 ?? [];
 | 
			
		||||
    setUserKey(username, roles);
 | 
			
		||||
    setUserKey({
 | 
			
		||||
      avatar,
 | 
			
		||||
      username,
 | 
			
		||||
      nickname,
 | 
			
		||||
      roles
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										62
									
								
								src/views/account-settings/components/accountManagement.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/views/account-settings/components/accountManagement.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { message } from "@/utils/message";
 | 
			
		||||
import { deviceDetection } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
const list = ref([
 | 
			
		||||
  {
 | 
			
		||||
    title: "账户密码",
 | 
			
		||||
    illustrate: "当前密码强度:强",
 | 
			
		||||
    button: "修改"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "密保手机",
 | 
			
		||||
    illustrate: "已经绑定手机:158****6789",
 | 
			
		||||
    button: "修改"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "密保问题",
 | 
			
		||||
    illustrate: "未设置密保问题,密保问题可有效保护账户安全",
 | 
			
		||||
    button: "修改"
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "备用邮箱",
 | 
			
		||||
    illustrate: "已绑定邮箱:pure***@163.com",
 | 
			
		||||
    button: "修改"
 | 
			
		||||
  }
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
function onClick(item) {
 | 
			
		||||
  console.log("onClick", item.title);
 | 
			
		||||
  message("请根据具体业务自行实现", { type: "success" });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      'min-w-[180px]',
 | 
			
		||||
      deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <h3 class="my-8">账户管理</h3>
 | 
			
		||||
    <div v-for="(item, index) in list" :key="index">
 | 
			
		||||
      <div class="flex items-center">
 | 
			
		||||
        <div class="flex-1">
 | 
			
		||||
          <p>{{ item.title }}</p>
 | 
			
		||||
          <el-text class="mx-1" type="info">{{ item.illustrate }}</el-text>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-button type="primary" text @click="onClick(item)">
 | 
			
		||||
          {{ item.button }}
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-divider />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-divider--horizontal {
 | 
			
		||||
  border-top: 0.1px var(--el-border-color) var(--el-border-style);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										65
									
								
								src/views/account-settings/components/preferences.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/views/account-settings/components/preferences.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,65 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import { message } from "@/utils/message";
 | 
			
		||||
import { deviceDetection } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
const list = ref([
 | 
			
		||||
  {
 | 
			
		||||
    title: "账户密码",
 | 
			
		||||
    illustrate: "其他用户的消息将以站内信的形式通知",
 | 
			
		||||
    checked: true
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "系统消息",
 | 
			
		||||
    illustrate: "系统消息将以站内信的形式通知",
 | 
			
		||||
    checked: true
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    title: "待办任务",
 | 
			
		||||
    illustrate: "待办任务将以站内信的形式通知",
 | 
			
		||||
    checked: true
 | 
			
		||||
  }
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
function onChange(val, item) {
 | 
			
		||||
  console.log("onChange", val);
 | 
			
		||||
  message(`${item.title}设置成功`, { type: "success" });
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      'min-w-[180px]',
 | 
			
		||||
      deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <h3 class="my-8">偏好设置</h3>
 | 
			
		||||
    <div v-for="(item, index) in list" :key="index">
 | 
			
		||||
      <div class="flex items-center">
 | 
			
		||||
        <div class="flex-1">
 | 
			
		||||
          <p>{{ item.title }}</p>
 | 
			
		||||
          <p class="wp-4">
 | 
			
		||||
            <el-text class="mx-1" type="info">
 | 
			
		||||
              {{ item.illustrate }}
 | 
			
		||||
            </el-text>
 | 
			
		||||
          </p>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-switch
 | 
			
		||||
          v-model="item.checked"
 | 
			
		||||
          inline-prompt
 | 
			
		||||
          active-text="是"
 | 
			
		||||
          inactive-text="否"
 | 
			
		||||
          @change="val => onChange(val, item)"
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
      <el-divider />
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.el-divider--horizontal {
 | 
			
		||||
  border-top: 0.1px var(--el-border-color) var(--el-border-style);
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
							
								
								
									
										187
									
								
								src/views/account-settings/components/profile.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/views/account-settings/components/profile.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,187 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { reactive, ref } from "vue";
 | 
			
		||||
import { formUpload } from "@/api/mock";
 | 
			
		||||
import { message } from "@/utils/message";
 | 
			
		||||
import { type UserInfo, getMine } from "@/api/user";
 | 
			
		||||
import type { FormInstance, FormRules } from "element-plus";
 | 
			
		||||
import ReCropperPreview from "@/components/ReCropperPreview";
 | 
			
		||||
import { createFormData, deviceDetection } from "@pureadmin/utils";
 | 
			
		||||
import uploadLine from "@iconify-icons/ri/upload-line";
 | 
			
		||||
 | 
			
		||||
const imgSrc = ref("");
 | 
			
		||||
const cropperInfo = ref();
 | 
			
		||||
const cropRef = ref();
 | 
			
		||||
const uploadRef = ref();
 | 
			
		||||
const isShow = ref(false);
 | 
			
		||||
const userInfoFormRef = ref<FormInstance>();
 | 
			
		||||
 | 
			
		||||
const userInfos = reactive({
 | 
			
		||||
  avatar: "",
 | 
			
		||||
  nickname: "",
 | 
			
		||||
  email: "",
 | 
			
		||||
  phone: "",
 | 
			
		||||
  description: ""
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const rules = reactive<FormRules<UserInfo>>({
 | 
			
		||||
  nickname: [{ required: true, message: "昵称必填", trigger: "blur" }]
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function queryEmail(queryString, callback) {
 | 
			
		||||
  const emailList = [
 | 
			
		||||
    { value: "@qq.com" },
 | 
			
		||||
    { value: "@126.com" },
 | 
			
		||||
    { value: "@163.com" }
 | 
			
		||||
  ];
 | 
			
		||||
  let results = [];
 | 
			
		||||
  let queryList = [];
 | 
			
		||||
  emailList.map(item =>
 | 
			
		||||
    queryList.push({ value: queryString.split("@")[0] + item.value })
 | 
			
		||||
  );
 | 
			
		||||
  results = queryString
 | 
			
		||||
    ? queryList.filter(
 | 
			
		||||
        item =>
 | 
			
		||||
          item.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
 | 
			
		||||
      )
 | 
			
		||||
    : queryList;
 | 
			
		||||
  callback(results);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const onChange = uploadFile => {
 | 
			
		||||
  const reader = new FileReader();
 | 
			
		||||
  reader.onload = e => {
 | 
			
		||||
    imgSrc.value = e.target.result as string;
 | 
			
		||||
    isShow.value = true;
 | 
			
		||||
  };
 | 
			
		||||
  reader.readAsDataURL(uploadFile.raw);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const handleClose = () => {
 | 
			
		||||
  cropRef.value.hidePopover();
 | 
			
		||||
  uploadRef.value.clearFiles();
 | 
			
		||||
  isShow.value = false;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const onCropper = info => (cropperInfo.value = info);
 | 
			
		||||
 | 
			
		||||
const handleSubmitImage = () => {
 | 
			
		||||
  const formData = createFormData({
 | 
			
		||||
    files: new File([cropperInfo.value], "avatar")
 | 
			
		||||
  });
 | 
			
		||||
  formUpload(formData)
 | 
			
		||||
    .then(({ success, data }) => {
 | 
			
		||||
      if (success) {
 | 
			
		||||
        message("更新头像成功", { type: "success" });
 | 
			
		||||
        handleClose();
 | 
			
		||||
      } else {
 | 
			
		||||
        message("更新头像失败");
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    .catch(error => {
 | 
			
		||||
      message(`提交异常 ${error}`, { type: "error" });
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 更新信息
 | 
			
		||||
const onSubmit = async (formEl: FormInstance) => {
 | 
			
		||||
  await formEl.validate((valid, fields) => {
 | 
			
		||||
    if (valid) {
 | 
			
		||||
      console.log(userInfos);
 | 
			
		||||
      message("更新信息成功", { type: "success" });
 | 
			
		||||
    } else {
 | 
			
		||||
      console.log("error submit!", fields);
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
getMine().then(res => {
 | 
			
		||||
  Object.assign(userInfos, res.data);
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      'min-w-[180px]',
 | 
			
		||||
      deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <h3 class="my-8">个人信息</h3>
 | 
			
		||||
    <el-form
 | 
			
		||||
      ref="userInfoFormRef"
 | 
			
		||||
      label-position="top"
 | 
			
		||||
      :rules="rules"
 | 
			
		||||
      :model="userInfos"
 | 
			
		||||
    >
 | 
			
		||||
      <el-form-item label="头像">
 | 
			
		||||
        <el-avatar :size="80" :src="userInfos.avatar" />
 | 
			
		||||
        <el-upload
 | 
			
		||||
          ref="uploadRef"
 | 
			
		||||
          accept="image/*"
 | 
			
		||||
          action="#"
 | 
			
		||||
          :limit="1"
 | 
			
		||||
          :auto-upload="false"
 | 
			
		||||
          :show-file-list="false"
 | 
			
		||||
          :on-change="onChange"
 | 
			
		||||
        >
 | 
			
		||||
          <el-button plain class="ml-4">
 | 
			
		||||
            <IconifyIconOffline :icon="uploadLine" />
 | 
			
		||||
            <span class="ml-2">更新头像</span>
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </el-upload>
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="昵称" prop="nickname">
 | 
			
		||||
        <el-input v-model="userInfos.nickname" placeholder="请输入昵称" />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="邮箱" prop="email">
 | 
			
		||||
        <el-autocomplete
 | 
			
		||||
          v-model="userInfos.email"
 | 
			
		||||
          :fetch-suggestions="queryEmail"
 | 
			
		||||
          :trigger-on-focus="false"
 | 
			
		||||
          placeholder="请输入邮箱"
 | 
			
		||||
          clearable
 | 
			
		||||
          class="w-full"
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="联系电话">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="userInfos.phone"
 | 
			
		||||
          placeholder="请输入联系电话"
 | 
			
		||||
          clearable
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-form-item label="简介">
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model="userInfos.description"
 | 
			
		||||
          placeholder="请输入简介"
 | 
			
		||||
          type="textarea"
 | 
			
		||||
          :autosize="{ minRows: 6, maxRows: 8 }"
 | 
			
		||||
          maxlength="56"
 | 
			
		||||
          show-word-limit
 | 
			
		||||
        />
 | 
			
		||||
      </el-form-item>
 | 
			
		||||
      <el-button type="primary" @click="onSubmit(userInfoFormRef)">
 | 
			
		||||
        更新信息
 | 
			
		||||
      </el-button>
 | 
			
		||||
    </el-form>
 | 
			
		||||
    <el-dialog
 | 
			
		||||
      v-model="isShow"
 | 
			
		||||
      width="40%"
 | 
			
		||||
      title="编辑头像"
 | 
			
		||||
      destroy-on-close
 | 
			
		||||
      :closeOnClickModal="false"
 | 
			
		||||
      :before-close="handleClose"
 | 
			
		||||
      :fullscreen="deviceDetection()"
 | 
			
		||||
    >
 | 
			
		||||
      <ReCropperPreview ref="cropRef" :imgSrc="imgSrc" @cropper="onCropper" />
 | 
			
		||||
      <template #footer>
 | 
			
		||||
        <div class="dialog-footer">
 | 
			
		||||
          <el-button bg text @click="handleClose">取消</el-button>
 | 
			
		||||
          <el-button bg text type="primary" @click="handleSubmitImage">
 | 
			
		||||
            确定
 | 
			
		||||
          </el-button>
 | 
			
		||||
        </div>
 | 
			
		||||
      </template>
 | 
			
		||||
    </el-dialog>
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										87
									
								
								src/views/account-settings/components/securityLog.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/views/account-settings/components/securityLog.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,87 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import dayjs from "dayjs";
 | 
			
		||||
import { getMineLogs } from "@/api/user";
 | 
			
		||||
import { reactive, ref, onMounted } from "vue";
 | 
			
		||||
import { deviceDetection } from "@pureadmin/utils";
 | 
			
		||||
import type { PaginationProps } from "@pureadmin/table";
 | 
			
		||||
 | 
			
		||||
const loading = ref(true);
 | 
			
		||||
const dataList = ref([]);
 | 
			
		||||
const pagination = reactive<PaginationProps>({
 | 
			
		||||
  total: 0,
 | 
			
		||||
  pageSize: 10,
 | 
			
		||||
  currentPage: 1,
 | 
			
		||||
  background: true,
 | 
			
		||||
  layout: "prev, pager, next"
 | 
			
		||||
});
 | 
			
		||||
const columns: TableColumnList = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "详情",
 | 
			
		||||
    prop: "summary",
 | 
			
		||||
    minWidth: 140
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "IP 地址",
 | 
			
		||||
    prop: "ip",
 | 
			
		||||
    minWidth: 100
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "地点",
 | 
			
		||||
    prop: "address",
 | 
			
		||||
    minWidth: 140
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "操作系统",
 | 
			
		||||
    prop: "system",
 | 
			
		||||
    minWidth: 100
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "浏览器类型",
 | 
			
		||||
    prop: "browser",
 | 
			
		||||
    minWidth: 100
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "时间",
 | 
			
		||||
    prop: "operatingTime",
 | 
			
		||||
    minWidth: 180,
 | 
			
		||||
    formatter: ({ operatingTime }) =>
 | 
			
		||||
      dayjs(operatingTime).format("YYYY-MM-DD HH:mm:ss")
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
async function onSearch() {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  const { data } = await getMineLogs();
 | 
			
		||||
  dataList.value = data.list;
 | 
			
		||||
  pagination.total = data.total;
 | 
			
		||||
  pagination.pageSize = data.pageSize;
 | 
			
		||||
  pagination.currentPage = data.currentPage;
 | 
			
		||||
 | 
			
		||||
  setTimeout(() => {
 | 
			
		||||
    loading.value = false;
 | 
			
		||||
  }, 200);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
  onSearch();
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    :class="[
 | 
			
		||||
      'min-w-[180px]',
 | 
			
		||||
      deviceDetection() ? 'max-w-[100%]' : 'max-w-[70%]'
 | 
			
		||||
    ]"
 | 
			
		||||
  >
 | 
			
		||||
    <h3 class="my-8">安全日志</h3>
 | 
			
		||||
    <pure-table
 | 
			
		||||
      row-key="id"
 | 
			
		||||
      table-layout="auto"
 | 
			
		||||
      :loading="loading"
 | 
			
		||||
      :data="dataList"
 | 
			
		||||
      :columns="columns"
 | 
			
		||||
      :pagination="pagination"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										183
									
								
								src/views/account-settings/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/views/account-settings/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { getMine } from "@/api/user";
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import { ref, onBeforeMount } from "vue";
 | 
			
		||||
import { ReText } from "@/components/ReText";
 | 
			
		||||
import Profile from "./components/profile.vue";
 | 
			
		||||
import Preferences from "./components/preferences.vue";
 | 
			
		||||
import SecurityLog from "./components/securityLog.vue";
 | 
			
		||||
import { useGlobal, deviceDetection } from "@pureadmin/utils";
 | 
			
		||||
import AccountManagement from "./components/accountManagement.vue";
 | 
			
		||||
import TopCollapse from "@/layout/components/sidebar/topCollapse.vue";
 | 
			
		||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
 | 
			
		||||
 | 
			
		||||
import leftLine from "@iconify-icons/ri/arrow-left-s-line";
 | 
			
		||||
import ProfileIcon from "@iconify-icons/ri/user-3-line";
 | 
			
		||||
import PreferencesIcon from "@iconify-icons/ri/settings-3-line";
 | 
			
		||||
import SecurityLogIcon from "@iconify-icons/ri/window-line";
 | 
			
		||||
import AccountManagementIcon from "@iconify-icons/ri/profile-line";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "AccountSettings"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
const isOpen = ref(deviceDetection() ? false : true);
 | 
			
		||||
const { $storage } = useGlobal<GlobalPropertiesApi>();
 | 
			
		||||
onBeforeMount(() => {
 | 
			
		||||
  useDataThemeChange().dataThemeChange($storage.layout?.overallStyle);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const userInfo = ref({
 | 
			
		||||
  avatar: "",
 | 
			
		||||
  username: "",
 | 
			
		||||
  nickname: ""
 | 
			
		||||
});
 | 
			
		||||
const panes = [
 | 
			
		||||
  {
 | 
			
		||||
    key: "profile",
 | 
			
		||||
    label: "个人信息",
 | 
			
		||||
    icon: ProfileIcon,
 | 
			
		||||
    component: Profile
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: "preferences",
 | 
			
		||||
    label: "偏好设置",
 | 
			
		||||
    icon: PreferencesIcon,
 | 
			
		||||
    component: Preferences
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: "securityLog",
 | 
			
		||||
    label: "安全日志",
 | 
			
		||||
    icon: SecurityLogIcon,
 | 
			
		||||
    component: SecurityLog
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    key: "accountManagement",
 | 
			
		||||
    label: "账户管理",
 | 
			
		||||
    icon: AccountManagementIcon,
 | 
			
		||||
    component: AccountManagement
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
const witchPane = ref("profile");
 | 
			
		||||
 | 
			
		||||
getMine().then(res => {
 | 
			
		||||
  userInfo.value = res.data;
 | 
			
		||||
});
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-container class="h-full">
 | 
			
		||||
    <el-aside
 | 
			
		||||
      v-if="isOpen"
 | 
			
		||||
      class="settings-sidebar px-2 dark:!bg-[var(--el-bg-color)]"
 | 
			
		||||
      :width="deviceDetection() ? '180px' : '240px'"
 | 
			
		||||
    >
 | 
			
		||||
      <el-menu :default-active="witchPane" class="settings-menu">
 | 
			
		||||
        <el-menu-item
 | 
			
		||||
          class="hover:!transition-all hover:!duration-200 hover:!text-base !h-[50px]"
 | 
			
		||||
          @click="router.go(-1)"
 | 
			
		||||
        >
 | 
			
		||||
          <div class="flex items-center">
 | 
			
		||||
            <IconifyIconOffline :icon="leftLine" />
 | 
			
		||||
            <span class="ml-2">返回</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-menu-item>
 | 
			
		||||
        <div class="flex items-center ml-8 mt-4 mb-4">
 | 
			
		||||
          <el-avatar :size="48" :src="userInfo.avatar" />
 | 
			
		||||
          <div class="ml-4 flex flex-col max-w-[130px]">
 | 
			
		||||
            <ReText class="font-bold !self-baseline">
 | 
			
		||||
              {{ userInfo.nickname }}
 | 
			
		||||
            </ReText>
 | 
			
		||||
            <ReText class="!self-baseline" type="info">
 | 
			
		||||
              {{ userInfo.username }}
 | 
			
		||||
            </ReText>
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
        <el-menu-item
 | 
			
		||||
          v-for="item in panes"
 | 
			
		||||
          :key="item.key"
 | 
			
		||||
          :index="item.key"
 | 
			
		||||
          @click="
 | 
			
		||||
            () => {
 | 
			
		||||
              witchPane = item.key;
 | 
			
		||||
              if (deviceDetection()) {
 | 
			
		||||
                isOpen = !isOpen;
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          "
 | 
			
		||||
        >
 | 
			
		||||
          <div class="flex items-center z-10">
 | 
			
		||||
            <el-icon><IconifyIconOffline :icon="item.icon" /></el-icon>
 | 
			
		||||
            <span>{{ item.label }}</span>
 | 
			
		||||
          </div>
 | 
			
		||||
        </el-menu-item>
 | 
			
		||||
      </el-menu>
 | 
			
		||||
    </el-aside>
 | 
			
		||||
    <el-main>
 | 
			
		||||
      <TopCollapse
 | 
			
		||||
        v-if="deviceDetection()"
 | 
			
		||||
        class="px-0"
 | 
			
		||||
        :is-active="isOpen"
 | 
			
		||||
        @toggleClick="isOpen = !isOpen"
 | 
			
		||||
      />
 | 
			
		||||
      <component
 | 
			
		||||
        :is="panes.find(item => item.key === witchPane).component"
 | 
			
		||||
        :class="[!deviceDetection() && 'ml-[120px]']"
 | 
			
		||||
      />
 | 
			
		||||
    </el-main>
 | 
			
		||||
  </el-container>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.settings-sidebar {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  background: $menuBg;
 | 
			
		||||
  border-right: 1px solid var(--pure-border-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.settings-menu {
 | 
			
		||||
  background-color: transparent;
 | 
			
		||||
  border: none;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-menu-item) {
 | 
			
		||||
    height: 48px !important;
 | 
			
		||||
    color: $menuText !important;
 | 
			
		||||
    background-color: transparent !important;
 | 
			
		||||
    transition: color 0.2s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: $menuTitleHover !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.is-active {
 | 
			
		||||
      color: #fff !important;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: #fff !important;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      &::before {
 | 
			
		||||
        position: absolute;
 | 
			
		||||
        inset: 0 8px;
 | 
			
		||||
        margin: 4px 0;
 | 
			
		||||
        clear: both;
 | 
			
		||||
        content: "";
 | 
			
		||||
        background: var(--el-color-primary);
 | 
			
		||||
        border-radius: 3px;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body[layout] {
 | 
			
		||||
  .el-menu--vertical .is-active {
 | 
			
		||||
    color: #fff !important;
 | 
			
		||||
    transition: color 0.2s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: #fff !important;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
@ -20,6 +20,7 @@
 | 
			
		||||
| `frameLoading`    | 加载动画(内嵌的`iframe`页面是否开启首次加载动画)           |
 | 
			
		||||
| `keepAlive`       | 缓存页面(是否缓存该路由页面,开启后会保存该页面的整体状态,刷新后会清空状态) |
 | 
			
		||||
| `hiddenTag`       | 标签页(当前菜单名称或自定义信息禁止添加到标签页)           |
 | 
			
		||||
| `fixedTag`        | 固定标签页(当前菜单名称是否固定显示在标签页且不可关闭)           |
 | 
			
		||||
| `showLink`        | 菜单(是否显示该菜单)                                       |
 | 
			
		||||
| `showParent`      | 父级菜单(是否显示父级菜单 [点击查看更多](https://yiming_chang.gitee.io/pure-admin-doc/pages/routerMenu/#%E7%AC%AC%E4%B8%80%E7%A7%8D-%E8%AF%A5%E6%A8%A1%E5%BC%8F%E9%92%88%E5%AF%B9%E7%88%B6%E7%BA%A7%E8%8F%9C%E5%8D%95%E4%B8%8B%E5%8F%AA%E6%9C%89%E4%B8%80%E4%B8%AA%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84%E6%83%85%E5%86%B5-%E5%9C%A8%E5%AD%90%E8%8F%9C%E5%8D%95%E7%9A%84-meta-%E5%B1%9E%E6%80%A7%E4%B8%AD%E5%8A%A0%E4%B8%8A-showparent-true-%E5%8D%B3%E5%8F%AF)) |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import ReAnimateSelector from "@/components/ReAnimateSelector";
 | 
			
		||||
import {
 | 
			
		||||
  menuTypeOptions,
 | 
			
		||||
  showLinkOptions,
 | 
			
		||||
  fixedTagOptions,
 | 
			
		||||
  keepAliveOptions,
 | 
			
		||||
  hiddenTagOptions,
 | 
			
		||||
  showParentOptions,
 | 
			
		||||
@ -26,7 +27,7 @@ const props = withDefaults(defineProps<FormProps>(), {
 | 
			
		||||
    path: "",
 | 
			
		||||
    component: "",
 | 
			
		||||
    rank: 99,
 | 
			
		||||
    redirect: " ",
 | 
			
		||||
    redirect: "",
 | 
			
		||||
    icon: "",
 | 
			
		||||
    extraIcon: "",
 | 
			
		||||
    enterTransition: "",
 | 
			
		||||
@ -37,6 +38,7 @@ const props = withDefaults(defineProps<FormProps>(), {
 | 
			
		||||
    frameLoading: true,
 | 
			
		||||
    keepAlive: false,
 | 
			
		||||
    hiddenTag: false,
 | 
			
		||||
    fixedTag: false,
 | 
			
		||||
    showLink: true,
 | 
			
		||||
    showParent: false
 | 
			
		||||
  })
 | 
			
		||||
@ -258,33 +260,6 @@ defineExpose({ getRef });
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
 | 
			
		||||
      <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
 | 
			
		||||
        <el-form-item label="缓存页面">
 | 
			
		||||
          <Segmented
 | 
			
		||||
            :modelValue="newFormInline.keepAlive ? 0 : 1"
 | 
			
		||||
            :options="keepAliveOptions"
 | 
			
		||||
            @change="
 | 
			
		||||
              ({ option: { value } }) => {
 | 
			
		||||
                newFormInline.keepAlive = value;
 | 
			
		||||
              }
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
      <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
 | 
			
		||||
        <el-form-item label="标签页">
 | 
			
		||||
          <Segmented
 | 
			
		||||
            :modelValue="newFormInline.hiddenTag ? 1 : 0"
 | 
			
		||||
            :options="hiddenTagOptions"
 | 
			
		||||
            @change="
 | 
			
		||||
              ({ option: { value } }) => {
 | 
			
		||||
                newFormInline.hiddenTag = value;
 | 
			
		||||
              }
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
 | 
			
		||||
      <re-col
 | 
			
		||||
        v-show="newFormInline.menuType !== 3"
 | 
			
		||||
        :value="12"
 | 
			
		||||
@ -305,7 +280,7 @@ defineExpose({ getRef });
 | 
			
		||||
      </re-col>
 | 
			
		||||
      <re-col
 | 
			
		||||
        v-show="newFormInline.menuType !== 3"
 | 
			
		||||
        :value="8"
 | 
			
		||||
        :value="12"
 | 
			
		||||
        :xs="24"
 | 
			
		||||
        :sm="24"
 | 
			
		||||
      >
 | 
			
		||||
@ -321,6 +296,47 @@ defineExpose({ getRef });
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
 | 
			
		||||
      <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
 | 
			
		||||
        <el-form-item label="缓存页面">
 | 
			
		||||
          <Segmented
 | 
			
		||||
            :modelValue="newFormInline.keepAlive ? 0 : 1"
 | 
			
		||||
            :options="keepAliveOptions"
 | 
			
		||||
            @change="
 | 
			
		||||
              ({ option: { value } }) => {
 | 
			
		||||
                newFormInline.keepAlive = value;
 | 
			
		||||
              }
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
 | 
			
		||||
      <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
 | 
			
		||||
        <el-form-item label="标签页">
 | 
			
		||||
          <Segmented
 | 
			
		||||
            :modelValue="newFormInline.hiddenTag ? 1 : 0"
 | 
			
		||||
            :options="hiddenTagOptions"
 | 
			
		||||
            @change="
 | 
			
		||||
              ({ option: { value } }) => {
 | 
			
		||||
                newFormInline.hiddenTag = value;
 | 
			
		||||
              }
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
      <re-col v-show="newFormInline.menuType < 2" :value="12" :xs="24" :sm="24">
 | 
			
		||||
        <el-form-item label="固定标签页">
 | 
			
		||||
          <Segmented
 | 
			
		||||
            :modelValue="newFormInline.fixedTag ? 0 : 1"
 | 
			
		||||
            :options="fixedTagOptions"
 | 
			
		||||
            @change="
 | 
			
		||||
              ({ option: { value } }) => {
 | 
			
		||||
                newFormInline.fixedTag = value;
 | 
			
		||||
              }
 | 
			
		||||
            "
 | 
			
		||||
          />
 | 
			
		||||
        </el-form-item>
 | 
			
		||||
      </re-col>
 | 
			
		||||
    </el-row>
 | 
			
		||||
  </el-form>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -61,7 +61,7 @@ const {
 | 
			
		||||
    </el-form>
 | 
			
		||||
 | 
			
		||||
    <PureTableBar
 | 
			
		||||
      title="菜单管理(初版,持续完善中)"
 | 
			
		||||
      title="菜单管理(仅演示,操作后不生效)"
 | 
			
		||||
      :columns="columns"
 | 
			
		||||
      :isExpandAll="false"
 | 
			
		||||
      :tableRef="tableRef?.getTableRef()"
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,19 @@ const showLinkOptions: Array<OptionsType> = [
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const fixedTagOptions: Array<OptionsType> = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "固定",
 | 
			
		||||
    tip: "当前菜单名称固定显示在标签页且不可关闭",
 | 
			
		||||
    value: true
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    label: "不固定",
 | 
			
		||||
    tip: "当前菜单名称不固定显示在标签页且可关闭",
 | 
			
		||||
    value: false
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const keepAliveOptions: Array<OptionsType> = [
 | 
			
		||||
  {
 | 
			
		||||
    label: "缓存",
 | 
			
		||||
@ -87,6 +100,7 @@ const frameLoadingOptions: Array<OptionsType> = [
 | 
			
		||||
export {
 | 
			
		||||
  menuTypeOptions,
 | 
			
		||||
  showLinkOptions,
 | 
			
		||||
  fixedTagOptions,
 | 
			
		||||
  keepAliveOptions,
 | 
			
		||||
  hiddenTagOptions,
 | 
			
		||||
  showParentOptions,
 | 
			
		||||
 | 
			
		||||
@ -155,6 +155,7 @@ export function useMenu() {
 | 
			
		||||
          frameLoading: row?.frameLoading ?? true,
 | 
			
		||||
          keepAlive: row?.keepAlive ?? false,
 | 
			
		||||
          hiddenTag: row?.hiddenTag ?? false,
 | 
			
		||||
          fixedTag: row?.fixedTag ?? false,
 | 
			
		||||
          showLink: row?.showLink ?? true,
 | 
			
		||||
          showParent: row?.showParent ?? false
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -19,6 +19,7 @@ interface FormItemProps {
 | 
			
		||||
  frameLoading: boolean;
 | 
			
		||||
  keepAlive: boolean;
 | 
			
		||||
  hiddenTag: boolean;
 | 
			
		||||
  fixedTag: boolean;
 | 
			
		||||
  showLink: boolean;
 | 
			
		||||
  showParent: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,11 @@ import editForm from "../form/index.vue";
 | 
			
		||||
import { zxcvbn } from "@zxcvbn-ts/core";
 | 
			
		||||
import { handleTree } from "@/utils/tree";
 | 
			
		||||
import { message } from "@/utils/message";
 | 
			
		||||
import croppingUpload from "../upload.vue";
 | 
			
		||||
import userAvatar from "@/assets/user.jpg";
 | 
			
		||||
import { usePublicHooks } from "../../hooks";
 | 
			
		||||
import { addDialog } from "@/components/ReDialog";
 | 
			
		||||
import type { PaginationProps } from "@pureadmin/table";
 | 
			
		||||
import ReCropperPreview from "@/components/ReCropperPreview";
 | 
			
		||||
import type { FormItemProps, RoleFormItemProps } from "../utils/types";
 | 
			
		||||
import {
 | 
			
		||||
  getKeyList,
 | 
			
		||||
@ -365,11 +365,10 @@ export function useUser(tableRef: Ref, treeRef: Ref) {
 | 
			
		||||
    addDialog({
 | 
			
		||||
      title: "裁剪、上传头像",
 | 
			
		||||
      width: "40%",
 | 
			
		||||
      draggable: true,
 | 
			
		||||
      closeOnClickModal: false,
 | 
			
		||||
      fullscreen: deviceDetection(),
 | 
			
		||||
      contentRenderer: () =>
 | 
			
		||||
        h(croppingUpload, {
 | 
			
		||||
        h(ReCropperPreview, {
 | 
			
		||||
          ref: cropRef,
 | 
			
		||||
          imgSrc: row.avatar || userAvatar,
 | 
			
		||||
          onCropper: info => (avatarInfo.value = info)
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ import { message } from "@/utils/message";
 | 
			
		||||
import { tableData } from "../data";
 | 
			
		||||
import { ref, computed } from "vue";
 | 
			
		||||
 | 
			
		||||
// 如果您不习惯tsx写法,可以传slot,然后在template里写
 | 
			
		||||
// 需是hooks写法(函数中有return),避免失去响应性
 | 
			
		||||
export function useColumns() {
 | 
			
		||||
  const search = ref("");
 | 
			
		||||
@ -26,8 +25,9 @@ export function useColumns() {
 | 
			
		||||
 | 
			
		||||
  const columns: TableColumnList = [
 | 
			
		||||
    {
 | 
			
		||||
      label: "日期",
 | 
			
		||||
      prop: "date"
 | 
			
		||||
      prop: "date",
 | 
			
		||||
      // 自定义表头,slot用法  #nameHeader="{ column, $index }"
 | 
			
		||||
      headerSlot: "nameHeader"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      label: "姓名",
 | 
			
		||||
@ -39,7 +39,7 @@ export function useColumns() {
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      align: "right",
 | 
			
		||||
      // 自定义表头
 | 
			
		||||
      // 自定义表头,tsx用法
 | 
			
		||||
      headerRenderer: () => (
 | 
			
		||||
        <el-input
 | 
			
		||||
          v-model={search.value}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,17 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useColumns } from "./columns";
 | 
			
		||||
import Calendar from "@iconify-icons/ri/calendar-2-line";
 | 
			
		||||
 | 
			
		||||
const { columns, filterTableData } = useColumns();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <pure-table :data="filterTableData" :columns="columns" />
 | 
			
		||||
  <pure-table :data="filterTableData" :columns="columns">
 | 
			
		||||
    <template #nameHeader>
 | 
			
		||||
      <span class="flex items-center">
 | 
			
		||||
        <IconifyIconOffline :icon="Calendar" />
 | 
			
		||||
        日期
 | 
			
		||||
      </span>
 | 
			
		||||
    </template>
 | 
			
		||||
  </pure-table>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								types/router.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								types/router.d.ts
									
									
									
									
										vendored
									
									
								
							@ -45,8 +45,10 @@ declare global {
 | 
			
		||||
      /** 离场动画 */
 | 
			
		||||
      leaveTransition?: string;
 | 
			
		||||
    };
 | 
			
		||||
    // 是否不添加信息到标签页,(默认`false`)
 | 
			
		||||
    /** 当前菜单名称或自定义信息禁止添加到标签页(默认`false`) */
 | 
			
		||||
    hiddenTag?: boolean;
 | 
			
		||||
    /** 当前菜单名称是否固定显示在标签页且不可关闭(默认`false`) */
 | 
			
		||||
    fixedTag?: boolean;
 | 
			
		||||
    /** 动态路由可打开的最大数量 `可选` */
 | 
			
		||||
    dynamicLevel?: number;
 | 
			
		||||
    /** 将某个菜单激活
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user