mirror of
				https://github.com/pure-admin/pure-admin-thin.git
				synced 2025-11-04 17:44:48 +08:00 
			
		
		
		
	Merge remote-tracking branch 'upstream/main' into merge_1
This commit is contained in:
		
						commit
						0117e581d9
					
				
							
								
								
									
										3
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								.env
									
									
									
									
									
								
							@ -1,2 +1,5 @@
 | 
			
		||||
# 平台本地运行端口号
 | 
			
		||||
VITE_PORT = 8848
 | 
			
		||||
 | 
			
		||||
# 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置)
 | 
			
		||||
VITE_HIDE_HOME = false
 | 
			
		||||
 | 
			
		||||
@ -13,4 +13,4 @@ VITE_CDN = true
 | 
			
		||||
# 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件)
 | 
			
		||||
# 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
 | 
			
		||||
# 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认)
 | 
			
		||||
VITE_COMPRESSION = "both-clear"
 | 
			
		||||
VITE_COMPRESSION = "none"
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
/dist/*
 | 
			
		||||
/public/*
 | 
			
		||||
public/*
 | 
			
		||||
public/*
 | 
			
		||||
src/style/reset.scss
 | 
			
		||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
MIT License
 | 
			
		||||
 | 
			
		||||
Copyright (c) 2023 pure-admin
 | 
			
		||||
Copyright (c) 2020-present, pure-admin
 | 
			
		||||
 | 
			
		||||
Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
			
		||||
of this software and associated documentation files (the "Software"), to deal
 | 
			
		||||
 | 
			
		||||
@ -49,3 +49,5 @@ bilibili: https://www.bilibili.com/video/BV1534y1S7HV/
 | 
			
		||||
## License
 | 
			
		||||
 | 
			
		||||
In principle, no fees and copyrights are charged, and you can use it with confidence, but if you need secondary open source, please contact the author for permission!
 | 
			
		||||
 | 
			
		||||
[MIT © 2020-present, pure-admin](./LICENSE)
 | 
			
		||||
 | 
			
		||||
@ -92,4 +92,4 @@ pnpm remove 包名
 | 
			
		||||
 | 
			
		||||
原则上不收取任何费用及版权,可以放心使用,不过如需二次开源(比如用此平台二次开发并开源)请联系作者获取许可!
 | 
			
		||||
 | 
			
		||||
[MIT © xiaoxian521-2023](./LICENSE)
 | 
			
		||||
[MIT © 2020-present, pure-admin](./LICENSE)
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ const warpperEnv = (envConf: Recordable): ViteEnv => {
 | 
			
		||||
    VITE_PUBLIC_PATH: "",
 | 
			
		||||
    VITE_ROUTER_HISTORY: "",
 | 
			
		||||
    VITE_CDN: false,
 | 
			
		||||
    VITE_HIDE_HOME: "false",
 | 
			
		||||
    VITE_COMPRESSION: "none"
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
import type { Plugin } from "vite";
 | 
			
		||||
import dayjs, { Dayjs } from "dayjs";
 | 
			
		||||
import utils from "@pureadmin/utils";
 | 
			
		||||
import duration from "dayjs/plugin/duration";
 | 
			
		||||
import { green, blue, bold } from "picocolors";
 | 
			
		||||
import { getPackageSize } from "@pureadmin/utils";
 | 
			
		||||
dayjs.extend(duration);
 | 
			
		||||
 | 
			
		||||
export function viteBuildInfo(): Plugin {
 | 
			
		||||
@ -33,7 +33,7 @@ export function viteBuildInfo(): Plugin {
 | 
			
		||||
    closeBundle() {
 | 
			
		||||
      if (config.command === "build") {
 | 
			
		||||
        endTime = dayjs(new Date());
 | 
			
		||||
        getPackageSize({
 | 
			
		||||
        utils.getPackageSize({
 | 
			
		||||
          folder: outDir,
 | 
			
		||||
          callback: (size: string) => {
 | 
			
		||||
            console.log(
 | 
			
		||||
 | 
			
		||||
@ -10,8 +10,8 @@ const include = [
 | 
			
		||||
  "dayjs",
 | 
			
		||||
  "axios",
 | 
			
		||||
  "pinia",
 | 
			
		||||
  "echarts",
 | 
			
		||||
  "js-cookie",
 | 
			
		||||
  "sortablejs",
 | 
			
		||||
  "@vueuse/core",
 | 
			
		||||
  "@pureadmin/utils",
 | 
			
		||||
  "responsive-storage",
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,6 @@ import { configCompressPlugin } from "./compress";
 | 
			
		||||
import { visualizer } from "rollup-plugin-visualizer";
 | 
			
		||||
import removeConsole from "vite-plugin-remove-console";
 | 
			
		||||
import themePreprocessorPlugin from "@pureadmin/theme";
 | 
			
		||||
import DefineOptions from "unplugin-vue-define-options/vite";
 | 
			
		||||
import { genScssMultipleScopeVars } from "../src/layout/theme";
 | 
			
		||||
 | 
			
		||||
export function getPluginsList(
 | 
			
		||||
@ -25,7 +24,6 @@ export function getPluginsList(
 | 
			
		||||
    vueJsx(),
 | 
			
		||||
    VITE_CDN ? cdn : null,
 | 
			
		||||
    configCompressPlugin(VITE_COMPRESSION),
 | 
			
		||||
    DefineOptions(),
 | 
			
		||||
    // 线上环境删除console
 | 
			
		||||
    removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
 | 
			
		||||
    viteBuildInfo(),
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								index.html
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								index.html
									
									
									
									
									
								
							@ -21,54 +21,54 @@
 | 
			
		||||
        html,
 | 
			
		||||
        body,
 | 
			
		||||
        #app {
 | 
			
		||||
          position: relative;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          justify-content: center;
 | 
			
		||||
          width: 100%;
 | 
			
		||||
          height: 100%;
 | 
			
		||||
          display: flex;
 | 
			
		||||
          position: relative;
 | 
			
		||||
          justify-content: center;
 | 
			
		||||
          align-items: center;
 | 
			
		||||
          overflow: hidden;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .loader,
 | 
			
		||||
        .loader:before,
 | 
			
		||||
        .loader:after {
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
        .loader::before,
 | 
			
		||||
        .loader::after {
 | 
			
		||||
          width: 2.5em;
 | 
			
		||||
          height: 2.5em;
 | 
			
		||||
          border-radius: 50%;
 | 
			
		||||
          animation: load-animation 1.8s infinite ease-in-out;
 | 
			
		||||
          animation-fill-mode: both;
 | 
			
		||||
          animation: loadAnimation 1.8s infinite ease-in-out;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .loader {
 | 
			
		||||
          color: #406eeb;
 | 
			
		||||
          font-size: 10px;
 | 
			
		||||
          margin: 80px auto;
 | 
			
		||||
          position: relative;
 | 
			
		||||
          top: 0;
 | 
			
		||||
          margin: 80px auto;
 | 
			
		||||
          font-size: 10px;
 | 
			
		||||
          color: #406eeb;
 | 
			
		||||
          text-indent: -9999em;
 | 
			
		||||
          transform: translateZ(0);
 | 
			
		||||
          animation-delay: -0.16s;
 | 
			
		||||
          top: 0;
 | 
			
		||||
          transform: translate(-50%, 0);
 | 
			
		||||
          animation-delay: -0.16s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .loader:before,
 | 
			
		||||
        .loader:after {
 | 
			
		||||
          content: "";
 | 
			
		||||
        .loader::before,
 | 
			
		||||
        .loader::after {
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          top: 0;
 | 
			
		||||
          content: "";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .loader:before {
 | 
			
		||||
        .loader::before {
 | 
			
		||||
          left: -3.5em;
 | 
			
		||||
          animation-delay: -0.32s;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        .loader:after {
 | 
			
		||||
        .loader::after {
 | 
			
		||||
          left: 3.5em;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        @keyframes loadAnimation {
 | 
			
		||||
        @keyframes load-animation {
 | 
			
		||||
          0%,
 | 
			
		||||
          80%,
 | 
			
		||||
          100% {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										119
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										119
									
								
								package.json
									
									
									
									
									
								
							@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "pure-admin-thin",
 | 
			
		||||
  "version": "3.9.7",
 | 
			
		||||
  "version": "4.1.0",
 | 
			
		||||
  "private": true,
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
 | 
			
		||||
@ -15,8 +15,8 @@
 | 
			
		||||
    "cloc": "NODE_OPTIONS=--max-old-space-size=4096 cloc . --exclude-dir=node_modules --exclude-lang=YAML",
 | 
			
		||||
    "clean:cache": "rm -rf node_modules && rm -rf .eslintcache && pnpm install",
 | 
			
		||||
    "lint:eslint": "eslint --cache --max-warnings 0  \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
 | 
			
		||||
    "lint:prettier": "prettier --write  \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
 | 
			
		||||
    "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,css,scss,postcss,less}\" --cache --cache-location node_modules/.cache/stylelint/",
 | 
			
		||||
    "lint:prettier": "prettier --write  \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
 | 
			
		||||
    "lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
 | 
			
		||||
    "lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
 | 
			
		||||
    "lint:pretty": "pretty-quick --staged",
 | 
			
		||||
    "lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
 | 
			
		||||
@ -29,93 +29,104 @@
 | 
			
		||||
    "not op_mini all"
 | 
			
		||||
  ],
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@pureadmin/descriptions": "^1.1.0",
 | 
			
		||||
    "@pureadmin/table": "^2.0.0",
 | 
			
		||||
    "@pureadmin/utils": "^1.8.5",
 | 
			
		||||
    "@vueuse/core": "^9.13.0",
 | 
			
		||||
    "@pureadmin/descriptions": "^1.1.1",
 | 
			
		||||
    "@pureadmin/table": "^2.1.0",
 | 
			
		||||
    "@pureadmin/utils": "^1.8.9",
 | 
			
		||||
    "@vueuse/core": "^10.1.2",
 | 
			
		||||
    "@vueuse/motion": "2.0.0-beta.12",
 | 
			
		||||
    "animate.css": "^4.1.1",
 | 
			
		||||
    "axios": "1.2.2",
 | 
			
		||||
    "axios": "^1.4.0",
 | 
			
		||||
    "dayjs": "^1.11.7",
 | 
			
		||||
    "echarts": "^5.4.1",
 | 
			
		||||
    "element-plus": "^2.2.32",
 | 
			
		||||
    "echarts": "^5.4.2",
 | 
			
		||||
    "element-plus": "^2.3.4",
 | 
			
		||||
    "element-resize-detector": "^1.2.4",
 | 
			
		||||
    "js-cookie": "^3.0.1",
 | 
			
		||||
    "js-cookie": "^3.0.5",
 | 
			
		||||
    "mitt": "^3.0.0",
 | 
			
		||||
    "mockjs": "^1.1.0",
 | 
			
		||||
    "nprogress": "^0.2.0",
 | 
			
		||||
    "path": "^0.12.7",
 | 
			
		||||
    "pinia": "^2.0.32",
 | 
			
		||||
    "qs": "^6.11.0",
 | 
			
		||||
    "pinia": "^2.0.36",
 | 
			
		||||
    "qs": "^6.11.1",
 | 
			
		||||
    "responsive-storage": "^2.2.0",
 | 
			
		||||
    "vue": "^3.2.47",
 | 
			
		||||
    "sortablejs": "^1.15.0",
 | 
			
		||||
    "vue": "^3.3.1",
 | 
			
		||||
    "vue-router": "^4.1.6",
 | 
			
		||||
    "vue-types": "^5.0.2"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@commitlint/cli": "13.1.0",
 | 
			
		||||
    "@commitlint/config-conventional": "13.1.0",
 | 
			
		||||
    "@iconify-icons/ep": "^1.2.10",
 | 
			
		||||
    "@iconify-icons/ri": "^1.2.4",
 | 
			
		||||
    "@iconify/vue": "^4.1.0",
 | 
			
		||||
    "@commitlint/cli": "^17.6.3",
 | 
			
		||||
    "@commitlint/config-conventional": "^17.6.3",
 | 
			
		||||
    "@iconify-icons/ep": "^1.2.11",
 | 
			
		||||
    "@iconify-icons/ri": "^1.2.7",
 | 
			
		||||
    "@iconify/vue": "^4.1.1",
 | 
			
		||||
    "@pureadmin/theme": "^3.0.0",
 | 
			
		||||
    "@types/element-resize-detector": "1.1.3",
 | 
			
		||||
    "@types/js-cookie": "^3.0.1",
 | 
			
		||||
    "@types/js-cookie": "^3.0.3",
 | 
			
		||||
    "@types/mockjs": "^1.0.7",
 | 
			
		||||
    "@types/node": "^18.11.9",
 | 
			
		||||
    "@types/node": "^18.15.12",
 | 
			
		||||
    "@types/nprogress": "0.2.0",
 | 
			
		||||
    "@types/qs": "^6.9.7",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.43.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.43.0",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.0.0",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.0",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^7.0.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^11.0.2",
 | 
			
		||||
    "autoprefixer": "^10.4.13",
 | 
			
		||||
    "@types/sortablejs": "^1.15.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.59.5",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.59.5",
 | 
			
		||||
    "@vitejs/plugin-vue": "^4.2.2",
 | 
			
		||||
    "@vitejs/plugin-vue-jsx": "^3.0.1",
 | 
			
		||||
    "@vue/eslint-config-prettier": "^7.1.0",
 | 
			
		||||
    "@vue/eslint-config-typescript": "^11.0.3",
 | 
			
		||||
    "autoprefixer": "^10.4.14",
 | 
			
		||||
    "cloc": "^2.11.0",
 | 
			
		||||
    "cssnano": "^5.1.14",
 | 
			
		||||
    "eslint": "^8.8.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.0.0",
 | 
			
		||||
    "eslint-plugin-vue": "^9.9.0",
 | 
			
		||||
    "husky": "^7.0.4",
 | 
			
		||||
    "lint-staged": "11.1.2",
 | 
			
		||||
    "cssnano": "^6.0.1",
 | 
			
		||||
    "eslint": "^8.40.0",
 | 
			
		||||
    "eslint-plugin-prettier": "^4.2.1",
 | 
			
		||||
    "eslint-plugin-vue": "^9.12.0",
 | 
			
		||||
    "husky": "^8.0.3",
 | 
			
		||||
    "lint-staged": "^13.2.2",
 | 
			
		||||
    "picocolors": "^1.0.0",
 | 
			
		||||
    "postcss": "^8.4.21",
 | 
			
		||||
    "postcss": "^8.4.23",
 | 
			
		||||
    "postcss-html": "^1.5.0",
 | 
			
		||||
    "postcss-import": "^15.1.0",
 | 
			
		||||
    "postcss-scss": "^4.0.6",
 | 
			
		||||
    "prettier": "^2.5.1",
 | 
			
		||||
    "prettier": "^2.8.7",
 | 
			
		||||
    "pretty-quick": "3.1.1",
 | 
			
		||||
    "rimraf": "3.0.2",
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.9.0",
 | 
			
		||||
    "sass": "^1.57.1",
 | 
			
		||||
    "sass-loader": "^13.2.0",
 | 
			
		||||
    "stylelint": "^14.3.0",
 | 
			
		||||
    "stylelint-config-html": "^1.0.0",
 | 
			
		||||
    "stylelint-config-prettier": "^9.0.3",
 | 
			
		||||
    "stylelint-config-recommended": "^9.0.0",
 | 
			
		||||
    "stylelint-config-standard": "^29.0.0",
 | 
			
		||||
    "stylelint-order": "^5.0.0",
 | 
			
		||||
    "sass": "^1.62.1",
 | 
			
		||||
    "sass-loader": "^13.2.2",
 | 
			
		||||
    "stylelint": "^15.6.1",
 | 
			
		||||
    "stylelint-config-html": "^1.1.0",
 | 
			
		||||
    "stylelint-config-recess-order": "^4.0.0",
 | 
			
		||||
    "stylelint-config-recommended": "^12.0.0",
 | 
			
		||||
    "stylelint-config-recommended-scss": "^11.0.0",
 | 
			
		||||
    "stylelint-config-recommended-vue": "^1.4.0",
 | 
			
		||||
    "stylelint-config-standard": "^33.0.0",
 | 
			
		||||
    "stylelint-config-standard-scss": "^9.0.0",
 | 
			
		||||
    "stylelint-order": "^6.0.3",
 | 
			
		||||
    "stylelint-prettier": "^3.0.0",
 | 
			
		||||
    "stylelint-scss": "^5.0.0",
 | 
			
		||||
    "svgo": "^3.0.2",
 | 
			
		||||
    "tailwindcss": "^3.2.7",
 | 
			
		||||
    "terser": "^5.16.1",
 | 
			
		||||
    "typescript": "^4.9.5",
 | 
			
		||||
    "unplugin-vue-define-options": "^1.0.0",
 | 
			
		||||
    "vite": "^4.1.4",
 | 
			
		||||
    "tailwindcss": "^3.3.2",
 | 
			
		||||
    "terser": "^5.17.1",
 | 
			
		||||
    "typescript": "^5.0.4",
 | 
			
		||||
    "vite": "^4.3.5",
 | 
			
		||||
    "vite-plugin-cdn-import": "^0.3.5",
 | 
			
		||||
    "vite-plugin-compression": "^0.5.1",
 | 
			
		||||
    "vite-plugin-mock": "^2.9.6",
 | 
			
		||||
    "vite-plugin-remove-console": "^2.1.0",
 | 
			
		||||
    "vite-plugin-remove-console": "^2.1.1",
 | 
			
		||||
    "vite-svg-loader": "^4.0.0",
 | 
			
		||||
    "vue-eslint-parser": "^9.1.0",
 | 
			
		||||
    "vue-tsc": "^1.2.0"
 | 
			
		||||
    "vue-eslint-parser": "^9.2.1",
 | 
			
		||||
    "vue-tsc": "^1.6.4"
 | 
			
		||||
  },
 | 
			
		||||
  "pnpm": {
 | 
			
		||||
    "peerDependencyRules": {
 | 
			
		||||
      "ignoreMissing": [
 | 
			
		||||
        "rollup",
 | 
			
		||||
        "webpack"
 | 
			
		||||
        "webpack",
 | 
			
		||||
        "core-js"
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
    "allowedDeprecatedVersions": {
 | 
			
		||||
      "sourcemap-codec": "*",
 | 
			
		||||
      "stable": "*"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "repository": "git@github.com:pure-admin/pure-admin-thin.git",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4087
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4087
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,6 +1,7 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: {
 | 
			
		||||
    "postcss-import": {},
 | 
			
		||||
    "tailwindcss/nesting": {},
 | 
			
		||||
    tailwindcss: {},
 | 
			
		||||
    autoprefixer: {},
 | 
			
		||||
    ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
{
 | 
			
		||||
  "Version": "3.9.7",
 | 
			
		||||
  "Version": "4.1.0",
 | 
			
		||||
  "Title": "PureAdmin",
 | 
			
		||||
  "FixedHeader": true,
 | 
			
		||||
  "HiddenSideBar": false,
 | 
			
		||||
@ -17,5 +17,6 @@
 | 
			
		||||
  "ShowModel": "smart",
 | 
			
		||||
  "MenuArrowIconNoTransition": true,
 | 
			
		||||
  "CachingAsyncRoutes": false,
 | 
			
		||||
  "TooltipEffect": "light"
 | 
			
		||||
  "TooltipEffect": "light",
 | 
			
		||||
  "ResponsiveStorageNameSpace": "responsive-"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
<template>
 | 
			
		||||
  <el-config-provider :locale="currentLocale">
 | 
			
		||||
    <router-view />
 | 
			
		||||
    <ReDialog />
 | 
			
		||||
  </el-config-provider>
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
@ -8,10 +9,12 @@
 | 
			
		||||
import { defineComponent } from "vue";
 | 
			
		||||
import { ElConfigProvider } from "element-plus";
 | 
			
		||||
import zhCn from "element-plus/lib/locale/lang/zh-cn";
 | 
			
		||||
import { ReDialog } from "@/components/ReDialog";
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "app",
 | 
			
		||||
  components: {
 | 
			
		||||
    [ElConfigProvider.name]: ElConfigProvider
 | 
			
		||||
    [ElConfigProvider.name]: ElConfigProvider,
 | 
			
		||||
    ReDialog
 | 
			
		||||
  },
 | 
			
		||||
  computed: {
 | 
			
		||||
    currentLocale() {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/components/ReCol/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/components/ReCol/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
import { ElCol } from "element-plus";
 | 
			
		||||
import { h, defineComponent } from "vue";
 | 
			
		||||
 | 
			
		||||
// 封装element-plus的el-col组件
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "ReCol",
 | 
			
		||||
  props: {
 | 
			
		||||
    value: {
 | 
			
		||||
      type: Number,
 | 
			
		||||
      default: 24
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  render() {
 | 
			
		||||
    const attrs = this.$attrs;
 | 
			
		||||
    const val = this.value;
 | 
			
		||||
    return h(
 | 
			
		||||
      ElCol,
 | 
			
		||||
      {
 | 
			
		||||
        xs: val,
 | 
			
		||||
        sm: val,
 | 
			
		||||
        md: val,
 | 
			
		||||
        lg: val,
 | 
			
		||||
        xl: val,
 | 
			
		||||
        ...attrs
 | 
			
		||||
      },
 | 
			
		||||
      { default: () => this.$slots.default() }
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										39
									
								
								src/components/ReDialog/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/components/ReDialog/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import { ref } from "vue";
 | 
			
		||||
import reDialog from "./index.vue";
 | 
			
		||||
import { useTimeoutFn } from "@vueuse/core";
 | 
			
		||||
import { withInstall } from "@pureadmin/utils";
 | 
			
		||||
import type {
 | 
			
		||||
  EventType,
 | 
			
		||||
  ArgsType,
 | 
			
		||||
  DialogProps,
 | 
			
		||||
  ButtonProps,
 | 
			
		||||
  DialogOptions
 | 
			
		||||
} from "./type";
 | 
			
		||||
 | 
			
		||||
const dialogStore = ref<Array<DialogOptions>>([]);
 | 
			
		||||
 | 
			
		||||
const addDialog = (options: DialogOptions) => {
 | 
			
		||||
  const open = () =>
 | 
			
		||||
    dialogStore.value.push(Object.assign(options, { visible: true }));
 | 
			
		||||
  if (options?.openDelay) {
 | 
			
		||||
    useTimeoutFn(() => {
 | 
			
		||||
      open();
 | 
			
		||||
    }, options.openDelay);
 | 
			
		||||
  } else {
 | 
			
		||||
    open();
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeDialog = (options: DialogOptions, index: number, args?: any) => {
 | 
			
		||||
  dialogStore.value.splice(index, 1);
 | 
			
		||||
  options.closeCallBack && options.closeCallBack({ options, index, args });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const closeAllDialog = () => {
 | 
			
		||||
  dialogStore.value = [];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const ReDialog = withInstall(reDialog);
 | 
			
		||||
 | 
			
		||||
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
 | 
			
		||||
export { ReDialog, dialogStore, addDialog, closeDialog, closeAllDialog };
 | 
			
		||||
							
								
								
									
										118
									
								
								src/components/ReDialog/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/components/ReDialog/index.vue
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,118 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { computed } from "vue";
 | 
			
		||||
import { isFunction } from "@pureadmin/utils";
 | 
			
		||||
import {
 | 
			
		||||
  type DialogOptions,
 | 
			
		||||
  type ButtonProps,
 | 
			
		||||
  type EventType,
 | 
			
		||||
  dialogStore,
 | 
			
		||||
  closeDialog
 | 
			
		||||
} from "./index";
 | 
			
		||||
 | 
			
		||||
const footerButtons = computed(() => {
 | 
			
		||||
  return (options: DialogOptions) => {
 | 
			
		||||
    return options?.footerButtons?.length > 0
 | 
			
		||||
      ? options.footerButtons
 | 
			
		||||
      : ([
 | 
			
		||||
          {
 | 
			
		||||
            label: "取消",
 | 
			
		||||
            text: true,
 | 
			
		||||
            bg: true,
 | 
			
		||||
            btnClick: ({ dialog: { options, index } }) => {
 | 
			
		||||
              const done = () =>
 | 
			
		||||
                closeDialog(options, index, { command: "cancel" });
 | 
			
		||||
              if (options?.beforeCancel && isFunction(options?.beforeCancel)) {
 | 
			
		||||
                options.beforeCancel(done, { options, index });
 | 
			
		||||
              } else {
 | 
			
		||||
                done();
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            label: "确定",
 | 
			
		||||
            type: "primary",
 | 
			
		||||
            text: true,
 | 
			
		||||
            bg: true,
 | 
			
		||||
            btnClick: ({ dialog: { options, index } }) => {
 | 
			
		||||
              const done = () =>
 | 
			
		||||
                closeDialog(options, index, { command: "sure" });
 | 
			
		||||
              if (options?.beforeSure && isFunction(options?.beforeSure)) {
 | 
			
		||||
                options.beforeSure(done, { options, index });
 | 
			
		||||
              } else {
 | 
			
		||||
                done();
 | 
			
		||||
              }
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ] as Array<ButtonProps>);
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function eventsCallBack(
 | 
			
		||||
  event: EventType,
 | 
			
		||||
  options: DialogOptions,
 | 
			
		||||
  index: number
 | 
			
		||||
) {
 | 
			
		||||
  if (options?.[event] && isFunction(options?.[event])) {
 | 
			
		||||
    return options?.[event]({ options, index });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function handleClose(
 | 
			
		||||
  options: DialogOptions,
 | 
			
		||||
  index: number,
 | 
			
		||||
  args = { command: "close" }
 | 
			
		||||
) {
 | 
			
		||||
  closeDialog(options, index, args);
 | 
			
		||||
  eventsCallBack("close", options, index);
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <el-dialog
 | 
			
		||||
    v-for="(options, index) in dialogStore"
 | 
			
		||||
    :key="index"
 | 
			
		||||
    v-bind="options"
 | 
			
		||||
    v-model="options.visible"
 | 
			
		||||
    @opened="eventsCallBack('open', options, index)"
 | 
			
		||||
    @close="handleClose(options, index)"
 | 
			
		||||
    @openAutoFocus="eventsCallBack('openAutoFocus', options, index)"
 | 
			
		||||
    @closeAutoFocus="eventsCallBack('closeAutoFocus', options, index)"
 | 
			
		||||
  >
 | 
			
		||||
    <!-- header -->
 | 
			
		||||
    <template
 | 
			
		||||
      v-if="options?.headerRenderer"
 | 
			
		||||
      #header="{ close, titleId, titleClass }"
 | 
			
		||||
    >
 | 
			
		||||
      <component
 | 
			
		||||
        :is="options?.headerRenderer({ close, titleId, titleClass })"
 | 
			
		||||
      />
 | 
			
		||||
    </template>
 | 
			
		||||
    <!-- default -->
 | 
			
		||||
    <component
 | 
			
		||||
      v-bind="options?.props"
 | 
			
		||||
      :is="options.contentRenderer({ options, index })"
 | 
			
		||||
      @close="args => handleClose(options, index, args)"
 | 
			
		||||
    />
 | 
			
		||||
    <!-- footer -->
 | 
			
		||||
    <template v-if="!options?.hideFooter" #footer>
 | 
			
		||||
      <template v-if="options?.footerRenderer">
 | 
			
		||||
        <component :is="options?.footerRenderer({ options, index })" />
 | 
			
		||||
      </template>
 | 
			
		||||
      <span v-else>
 | 
			
		||||
        <el-button
 | 
			
		||||
          v-for="(btn, key) in footerButtons(options)"
 | 
			
		||||
          :key="key"
 | 
			
		||||
          v-bind="btn"
 | 
			
		||||
          @click="
 | 
			
		||||
            btn.btnClick({
 | 
			
		||||
              dialog: { options, index },
 | 
			
		||||
              button: { btn, index: key }
 | 
			
		||||
            })
 | 
			
		||||
          "
 | 
			
		||||
        >
 | 
			
		||||
          {{ btn?.label }}
 | 
			
		||||
        </el-button>
 | 
			
		||||
      </span>
 | 
			
		||||
    </template>
 | 
			
		||||
  </el-dialog>
 | 
			
		||||
</template>
 | 
			
		||||
							
								
								
									
										216
									
								
								src/components/ReDialog/type.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/components/ReDialog/type.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,216 @@
 | 
			
		||||
import type { CSSProperties, VNode, Component } from "vue";
 | 
			
		||||
 | 
			
		||||
type DoneFn = (cancel?: boolean) => void;
 | 
			
		||||
type EventType = "open" | "close" | "openAutoFocus" | "closeAutoFocus";
 | 
			
		||||
type ArgsType = {
 | 
			
		||||
  /** `cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页 */
 | 
			
		||||
  command: "cancel" | "sure" | "close";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** https://element-plus.org/zh-CN/component/dialog.html#attributes */
 | 
			
		||||
type DialogProps = {
 | 
			
		||||
  /** `Dialog` 的显示与隐藏 */
 | 
			
		||||
  visible?: boolean;
 | 
			
		||||
  /** `Dialog` 的标题 */
 | 
			
		||||
  title?: string;
 | 
			
		||||
  /** `Dialog` 的宽度,默认 `50%` */
 | 
			
		||||
  width?: string | number;
 | 
			
		||||
  /** 是否为全屏 `Dialog`,默认 `false` */
 | 
			
		||||
  fullscreen?: boolean;
 | 
			
		||||
  /** `Dialog CSS` 中的 `margin-top` 值,默认 `15vh` */
 | 
			
		||||
  top?: string;
 | 
			
		||||
  /** 是否需要遮罩层,默认 `true` */
 | 
			
		||||
  modal?: boolean;
 | 
			
		||||
  /** `Dialog` 自身是否插入至 `body` 元素上。嵌套的 `Dialog` 必须指定该属性并赋值为 `true`,默认 `false` */
 | 
			
		||||
  appendToBody?: boolean;
 | 
			
		||||
  /** 是否在 `Dialog` 出现时将 `body` 滚动锁定,默认 `true` */
 | 
			
		||||
  lockScroll?: boolean;
 | 
			
		||||
  /** `Dialog` 的自定义类名 */
 | 
			
		||||
  class?: string;
 | 
			
		||||
  /** `Dialog` 的自定义样式 */
 | 
			
		||||
  style?: CSSProperties;
 | 
			
		||||
  /** `Dialog` 打开的延时时间,单位毫秒,默认 `0` */
 | 
			
		||||
  openDelay?: number;
 | 
			
		||||
  /** `Dialog` 关闭的延时时间,单位毫秒,默认 `0` */
 | 
			
		||||
  closeDelay?: number;
 | 
			
		||||
  /** 是否可以通过点击 `modal` 关闭 `Dialog`,默认 `true` */
 | 
			
		||||
  closeOnClickModal?: boolean;
 | 
			
		||||
  /** 是否可以通过按下 `ESC` 关闭 `Dialog`,默认 `true` */
 | 
			
		||||
  closeOnPressEscape?: boolean;
 | 
			
		||||
  /** 是否显示关闭按钮,默认 `true` */
 | 
			
		||||
  showClose?: boolean;
 | 
			
		||||
  /** 关闭前的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
 | 
			
		||||
  beforeClose?: (done: DoneFn) => void;
 | 
			
		||||
  /** 为 `Dialog` 启用可拖拽功能,默认 `false` */
 | 
			
		||||
  draggable?: boolean;
 | 
			
		||||
  /** 是否让 `Dialog` 的 `header` 和 `footer` 部分居中排列,默认 `false` */
 | 
			
		||||
  center?: boolean;
 | 
			
		||||
  /** 是否水平垂直对齐对话框,默认 `false` */
 | 
			
		||||
  alignCenter?: boolean;
 | 
			
		||||
  /** 当关闭 `Dialog` 时,销毁其中的元素,默认 `false` */
 | 
			
		||||
  destroyOnClose?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type BtnClickDialog = {
 | 
			
		||||
  options?: DialogOptions;
 | 
			
		||||
  index?: number;
 | 
			
		||||
};
 | 
			
		||||
type BtnClickButton = {
 | 
			
		||||
  btn?: ButtonProps;
 | 
			
		||||
  index?: number;
 | 
			
		||||
};
 | 
			
		||||
/** https://element-plus.org/zh-CN/component/button.html#button-attributes */
 | 
			
		||||
type ButtonProps = {
 | 
			
		||||
  /** 按钮文字 */
 | 
			
		||||
  label: string;
 | 
			
		||||
  /** 按钮尺寸 */
 | 
			
		||||
  size?: "large" | "default" | "small";
 | 
			
		||||
  /** 按钮类型 */
 | 
			
		||||
  type?: "primary" | "success" | "warning" | "danger" | "info";
 | 
			
		||||
  /** 是否为朴素按钮,默认 `false` */
 | 
			
		||||
  plain?: boolean;
 | 
			
		||||
  /** 是否为文字按钮,默认 `false` */
 | 
			
		||||
  text?: boolean;
 | 
			
		||||
  /** 是否显示文字按钮背景颜色,默认 `false` */
 | 
			
		||||
  bg?: boolean;
 | 
			
		||||
  /** 是否为链接按钮,默认 `false` */
 | 
			
		||||
  link?: boolean;
 | 
			
		||||
  /** 是否为圆角按钮,默认 `false` */
 | 
			
		||||
  round?: boolean;
 | 
			
		||||
  /** 是否为圆形按钮,默认 `false` */
 | 
			
		||||
  circle?: boolean;
 | 
			
		||||
  /** 是否为加载中状态,默认 `false` */
 | 
			
		||||
  loading?: boolean;
 | 
			
		||||
  /** 自定义加载中状态图标组件 */
 | 
			
		||||
  loadingIcon?: string | Component;
 | 
			
		||||
  /** 按钮是否为禁用状态,默认 `false` */
 | 
			
		||||
  disabled?: boolean;
 | 
			
		||||
  /** 图标组件 */
 | 
			
		||||
  icon?: string | Component;
 | 
			
		||||
  /** 是否开启原生 `autofocus` 属性,默认 `false` */
 | 
			
		||||
  autofocus?: boolean;
 | 
			
		||||
  /** 原生 `type` 属性,默认 `button` */
 | 
			
		||||
  nativeType?: "button" | "submit" | "reset";
 | 
			
		||||
  /** 自动在两个中文字符之间插入空格 */
 | 
			
		||||
  autoInsertSpace?: boolean;
 | 
			
		||||
  /** 自定义按钮颜色, 并自动计算 `hover` 和 `active` 触发后的颜色 */
 | 
			
		||||
  color?: string;
 | 
			
		||||
  /** `dark` 模式, 意味着自动设置 `color` 为 `dark` 模式的颜色,默认 `false` */
 | 
			
		||||
  dark?: boolean;
 | 
			
		||||
  /** 自定义元素标签 */
 | 
			
		||||
  tag?: string | Component;
 | 
			
		||||
  /** 点击按钮后触发的回调 */
 | 
			
		||||
  btnClick?: ({
 | 
			
		||||
    dialog,
 | 
			
		||||
    button
 | 
			
		||||
  }: {
 | 
			
		||||
    /** 当前 `Dialog` 信息 */
 | 
			
		||||
    dialog: BtnClickDialog;
 | 
			
		||||
    /** 当前 `button` 信息 */
 | 
			
		||||
    button: BtnClickButton;
 | 
			
		||||
  }) => void;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface DialogOptions extends DialogProps {
 | 
			
		||||
  /** 内容区组件的 `props`,可通过 `defineProps` 接收 */
 | 
			
		||||
  props?: any;
 | 
			
		||||
  /** 是否隐藏 `Dialog` 按钮操作区的内容 */
 | 
			
		||||
  hideFooter?: boolean;
 | 
			
		||||
  /**
 | 
			
		||||
   * @description 自定义对话框标题的内容渲染器
 | 
			
		||||
   * @see {@link https://element-plus.org/zh-CN/component/dialog.html#%E8%87%AA%E5%AE%9A%E4%B9%89%E5%A4%B4%E9%83%A8}
 | 
			
		||||
   */
 | 
			
		||||
  headerRenderer?: ({
 | 
			
		||||
    close,
 | 
			
		||||
    titleId,
 | 
			
		||||
    titleClass
 | 
			
		||||
  }: {
 | 
			
		||||
    close: Function;
 | 
			
		||||
    titleId: string;
 | 
			
		||||
    titleClass: string;
 | 
			
		||||
  }) => VNode | Component;
 | 
			
		||||
  /** 自定义内容渲染器 */
 | 
			
		||||
  contentRenderer?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => VNode | Component;
 | 
			
		||||
  /** 自定义按钮操作区的内容渲染器,会覆盖`footerButtons`以及默认的 `取消` 和 `确定` 按钮 */
 | 
			
		||||
  footerRenderer?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => VNode | Component;
 | 
			
		||||
  /** 自定义底部按钮操作 */
 | 
			
		||||
  footerButtons?: Array<ButtonProps>;
 | 
			
		||||
  /** `Dialog` 打开后的回调 */
 | 
			
		||||
  open?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => void;
 | 
			
		||||
  /** `Dialog` 关闭后的回调(只有点击右上角关闭按钮或者空白页关闭页面时才会触发) */
 | 
			
		||||
  close?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => void;
 | 
			
		||||
  /** `Dialog` 关闭后的回调。 `args` 返回的 `command` 值解析:`cancel` 点击取消按钮、`sure` 点击确定按钮、`close` 点击右上角关闭按钮或者空白页  */
 | 
			
		||||
  closeCallBack?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index,
 | 
			
		||||
    args
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
    args: any;
 | 
			
		||||
  }) => void;
 | 
			
		||||
  /** 输入焦点聚焦在 `Dialog` 内容时的回调 */
 | 
			
		||||
  openAutoFocus?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => void;
 | 
			
		||||
  /** 输入焦点从 `Dialog` 内容失焦时的回调 */
 | 
			
		||||
  closeAutoFocus?: ({
 | 
			
		||||
    options,
 | 
			
		||||
    index
 | 
			
		||||
  }: {
 | 
			
		||||
    options: DialogOptions;
 | 
			
		||||
    index: number;
 | 
			
		||||
  }) => void;
 | 
			
		||||
  /** 点击底部取消按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
 | 
			
		||||
  beforeCancel?: (
 | 
			
		||||
    done: Function,
 | 
			
		||||
    {
 | 
			
		||||
      options,
 | 
			
		||||
      index
 | 
			
		||||
    }: {
 | 
			
		||||
      options: DialogOptions;
 | 
			
		||||
      index: number;
 | 
			
		||||
    }
 | 
			
		||||
  ) => void;
 | 
			
		||||
  /** 点击底部确定按钮的回调,会暂停 `Dialog` 的关闭. 回调函数内执行 `done` 参数方法的时候才是真正关闭对话框的时候 */
 | 
			
		||||
  beforeSure?: (
 | 
			
		||||
    done: Function,
 | 
			
		||||
    {
 | 
			
		||||
      options,
 | 
			
		||||
      index
 | 
			
		||||
    }: {
 | 
			
		||||
      options: DialogOptions;
 | 
			
		||||
      index: number;
 | 
			
		||||
    }
 | 
			
		||||
  ) => void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions };
 | 
			
		||||
							
								
								
									
										5
									
								
								src/components/RePureTableBar/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/components/RePureTableBar/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
import pureTableBar from "./src/bar";
 | 
			
		||||
import { withInstall } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
/** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */
 | 
			
		||||
export const PureTableBar = withInstall(pureTableBar);
 | 
			
		||||
							
								
								
									
										339
									
								
								src/components/RePureTableBar/src/bar.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										339
									
								
								src/components/RePureTableBar/src/bar.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,339 @@
 | 
			
		||||
import { useEpThemeStoreHook } from "@/store/modules/epTheme";
 | 
			
		||||
import { delay, getKeyList, cloneDeep } from "@pureadmin/utils";
 | 
			
		||||
import { defineComponent, ref, computed, type PropType, nextTick } from "vue";
 | 
			
		||||
 | 
			
		||||
import Sortable from "sortablejs";
 | 
			
		||||
import DragIcon from "./svg/drag.svg?component";
 | 
			
		||||
import ExpandIcon from "./svg/expand.svg?component";
 | 
			
		||||
import RefreshIcon from "./svg/refresh.svg?component";
 | 
			
		||||
import SettingIcon from "./svg/settings.svg?component";
 | 
			
		||||
import CollapseIcon from "./svg/collapse.svg?component";
 | 
			
		||||
 | 
			
		||||
const props = {
 | 
			
		||||
  /** 头部最左边的标题 */
 | 
			
		||||
  title: {
 | 
			
		||||
    type: String,
 | 
			
		||||
    default: "列表"
 | 
			
		||||
  },
 | 
			
		||||
  /** 对于树形表格,如果想启用展开和折叠功能,传入当前表格的ref即可 */
 | 
			
		||||
  tableRef: {
 | 
			
		||||
    type: Object as PropType<any>
 | 
			
		||||
  },
 | 
			
		||||
  /** 需要展示的列 */
 | 
			
		||||
  columns: {
 | 
			
		||||
    type: Array as PropType<TableColumnList>,
 | 
			
		||||
    default: () => []
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
  name: "PureTableBar",
 | 
			
		||||
  props,
 | 
			
		||||
  emits: ["refresh"],
 | 
			
		||||
  setup(props, { emit, slots, attrs }) {
 | 
			
		||||
    const buttonRef = ref();
 | 
			
		||||
    const size = ref("default");
 | 
			
		||||
    const isExpandAll = ref(true);
 | 
			
		||||
    const loading = ref(false);
 | 
			
		||||
    const checkAll = ref(true);
 | 
			
		||||
    const isIndeterminate = ref(false);
 | 
			
		||||
    let checkColumnList = getKeyList(cloneDeep(props?.columns), "label");
 | 
			
		||||
    const checkedColumns = ref(checkColumnList);
 | 
			
		||||
    const dynamicColumns = ref(cloneDeep(props?.columns));
 | 
			
		||||
 | 
			
		||||
    const getDropdownItemStyle = computed(() => {
 | 
			
		||||
      return s => {
 | 
			
		||||
        return {
 | 
			
		||||
          background:
 | 
			
		||||
            s === size.value ? useEpThemeStoreHook().epThemeColor : "",
 | 
			
		||||
          color: s === size.value ? "#fff" : "var(--el-text-color-primary)"
 | 
			
		||||
        };
 | 
			
		||||
      };
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const iconClass = computed(() => {
 | 
			
		||||
      return [
 | 
			
		||||
        "text-black",
 | 
			
		||||
        "dark:text-white",
 | 
			
		||||
        "duration-100",
 | 
			
		||||
        "hover:!text-primary",
 | 
			
		||||
        "cursor-pointer",
 | 
			
		||||
        "outline-none"
 | 
			
		||||
      ];
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const topClass = computed(() => {
 | 
			
		||||
      return [
 | 
			
		||||
        "flex",
 | 
			
		||||
        "justify-between",
 | 
			
		||||
        "pt-[3px]",
 | 
			
		||||
        "px-[11px]",
 | 
			
		||||
        "border-b-[1px]",
 | 
			
		||||
        "border-solid",
 | 
			
		||||
        "border-[#dcdfe6]",
 | 
			
		||||
        "dark:border-[#303030]"
 | 
			
		||||
      ];
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    function onReFresh() {
 | 
			
		||||
      loading.value = true;
 | 
			
		||||
      emit("refresh");
 | 
			
		||||
      delay(500).then(() => (loading.value = false));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function onExpand() {
 | 
			
		||||
      isExpandAll.value = !isExpandAll.value;
 | 
			
		||||
      toggleRowExpansionAll(props.tableRef.data, isExpandAll.value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function toggleRowExpansionAll(data, isExpansion) {
 | 
			
		||||
      data.forEach(item => {
 | 
			
		||||
        props.tableRef.toggleRowExpansion(item, isExpansion);
 | 
			
		||||
        if (item.children !== undefined && item.children !== null) {
 | 
			
		||||
          toggleRowExpansionAll(item.children, isExpansion);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function handleCheckAllChange(val: boolean) {
 | 
			
		||||
      checkedColumns.value = val ? checkColumnList : [];
 | 
			
		||||
      isIndeterminate.value = false;
 | 
			
		||||
      dynamicColumns.value.map(column =>
 | 
			
		||||
        val ? (column.hide = false) : (column.hide = true)
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function handleCheckedColumnsChange(value: string[]) {
 | 
			
		||||
      const checkedCount = value.length;
 | 
			
		||||
      checkAll.value = checkedCount === checkColumnList.length;
 | 
			
		||||
      isIndeterminate.value =
 | 
			
		||||
        checkedCount > 0 && checkedCount < checkColumnList.length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function handleCheckColumnListChange(val: boolean, label: string) {
 | 
			
		||||
      dynamicColumns.value.filter(item => item.label === label)[0].hide = !val;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async function onReset() {
 | 
			
		||||
      checkAll.value = true;
 | 
			
		||||
      isIndeterminate.value = false;
 | 
			
		||||
      dynamicColumns.value = cloneDeep(props?.columns);
 | 
			
		||||
      checkColumnList = [];
 | 
			
		||||
      checkColumnList = await getKeyList(cloneDeep(props?.columns), "label");
 | 
			
		||||
      checkedColumns.value = checkColumnList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const dropdown = {
 | 
			
		||||
      dropdown: () => (
 | 
			
		||||
        <el-dropdown-menu class="translation">
 | 
			
		||||
          <el-dropdown-item
 | 
			
		||||
            style={getDropdownItemStyle.value("large")}
 | 
			
		||||
            onClick={() => (size.value = "large")}
 | 
			
		||||
          >
 | 
			
		||||
            宽松
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
          <el-dropdown-item
 | 
			
		||||
            style={getDropdownItemStyle.value("default")}
 | 
			
		||||
            onClick={() => (size.value = "default")}
 | 
			
		||||
          >
 | 
			
		||||
            默认
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
          <el-dropdown-item
 | 
			
		||||
            style={getDropdownItemStyle.value("small")}
 | 
			
		||||
            onClick={() => (size.value = "small")}
 | 
			
		||||
          >
 | 
			
		||||
            紧凑
 | 
			
		||||
          </el-dropdown-item>
 | 
			
		||||
        </el-dropdown-menu>
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /** 列展示拖拽排序 */
 | 
			
		||||
    const rowDrop = (event: { preventDefault: () => void }) => {
 | 
			
		||||
      event.preventDefault();
 | 
			
		||||
      nextTick(() => {
 | 
			
		||||
        const wrapper: HTMLElement = document.querySelector(
 | 
			
		||||
          ".el-checkbox-group>div"
 | 
			
		||||
        );
 | 
			
		||||
        Sortable.create(wrapper, {
 | 
			
		||||
          animation: 300,
 | 
			
		||||
          handle: ".drag-btn",
 | 
			
		||||
          onEnd: ({ newIndex, oldIndex, item }) => {
 | 
			
		||||
            const targetThElem = item;
 | 
			
		||||
            const wrapperElem = targetThElem.parentNode as HTMLElement;
 | 
			
		||||
            const oldColumn = dynamicColumns.value[oldIndex];
 | 
			
		||||
            const newColumn = dynamicColumns.value[newIndex];
 | 
			
		||||
            if (oldColumn?.fixed || newColumn?.fixed) {
 | 
			
		||||
              // 当前列存在fixed属性 则不可拖拽
 | 
			
		||||
              const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
 | 
			
		||||
              if (newIndex > oldIndex) {
 | 
			
		||||
                wrapperElem.insertBefore(targetThElem, oldThElem);
 | 
			
		||||
              } else {
 | 
			
		||||
                wrapperElem.insertBefore(
 | 
			
		||||
                  targetThElem,
 | 
			
		||||
                  oldThElem ? oldThElem.nextElementSibling : oldThElem
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
              return;
 | 
			
		||||
            }
 | 
			
		||||
            const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
 | 
			
		||||
            dynamicColumns.value.splice(newIndex, 0, currentRow);
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const isFixedColumn = (label: string) => {
 | 
			
		||||
      return dynamicColumns.value.filter(item => item.label === label)[0].fixed
 | 
			
		||||
        ? true
 | 
			
		||||
        : false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const reference = {
 | 
			
		||||
      reference: () => (
 | 
			
		||||
        <SettingIcon
 | 
			
		||||
          class={["w-[16px]", iconClass.value]}
 | 
			
		||||
          onMouseover={e => (buttonRef.value = e.currentTarget)}
 | 
			
		||||
        />
 | 
			
		||||
      )
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return () => (
 | 
			
		||||
      <>
 | 
			
		||||
        <div {...attrs} class="w-[99/100] mt-6 p-2 bg-bg_color">
 | 
			
		||||
          <div class="flex justify-between w-full h-[60px] p-4">
 | 
			
		||||
            <p class="font-bold truncate">{props.title}</p>
 | 
			
		||||
            <div class="flex items-center justify-around">
 | 
			
		||||
              {slots?.buttons ? (
 | 
			
		||||
                <div class="flex mr-4">{slots.buttons()}</div>
 | 
			
		||||
              ) : null}
 | 
			
		||||
              {props.tableRef?.size ? (
 | 
			
		||||
                <>
 | 
			
		||||
                  <el-tooltip
 | 
			
		||||
                    effect="dark"
 | 
			
		||||
                    content={isExpandAll.value ? "折叠" : "展开"}
 | 
			
		||||
                    placement="top"
 | 
			
		||||
                  >
 | 
			
		||||
                    <ExpandIcon
 | 
			
		||||
                      class={["w-[16px]", iconClass.value]}
 | 
			
		||||
                      style={{
 | 
			
		||||
                        transform: isExpandAll.value ? "none" : "rotate(-90deg)"
 | 
			
		||||
                      }}
 | 
			
		||||
                      onClick={() => onExpand()}
 | 
			
		||||
                    />
 | 
			
		||||
                  </el-tooltip>
 | 
			
		||||
                  <el-divider direction="vertical" />
 | 
			
		||||
                </>
 | 
			
		||||
              ) : null}
 | 
			
		||||
              <el-tooltip effect="dark" content="刷新" placement="top">
 | 
			
		||||
                <RefreshIcon
 | 
			
		||||
                  class={[
 | 
			
		||||
                    "w-[16px]",
 | 
			
		||||
                    iconClass.value,
 | 
			
		||||
                    loading.value ? "animate-spin" : ""
 | 
			
		||||
                  ]}
 | 
			
		||||
                  onClick={() => onReFresh()}
 | 
			
		||||
                />
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
              <el-divider direction="vertical" />
 | 
			
		||||
              <el-tooltip effect="dark" content="密度" placement="top">
 | 
			
		||||
                <el-dropdown v-slots={dropdown} trigger="click">
 | 
			
		||||
                  <CollapseIcon class={["w-[16px]", iconClass.value]} />
 | 
			
		||||
                </el-dropdown>
 | 
			
		||||
              </el-tooltip>
 | 
			
		||||
              <el-divider direction="vertical" />
 | 
			
		||||
 | 
			
		||||
              <el-popover
 | 
			
		||||
                v-slots={reference}
 | 
			
		||||
                popper-style={{ padding: 0 }}
 | 
			
		||||
                width="160"
 | 
			
		||||
                trigger="click"
 | 
			
		||||
              >
 | 
			
		||||
                <div class={[topClass.value]}>
 | 
			
		||||
                  <el-checkbox
 | 
			
		||||
                    class="!-mr-1"
 | 
			
		||||
                    label="列展示"
 | 
			
		||||
                    v-model={checkAll.value}
 | 
			
		||||
                    indeterminate={isIndeterminate.value}
 | 
			
		||||
                    onChange={value => handleCheckAllChange(value)}
 | 
			
		||||
                  />
 | 
			
		||||
                  <el-button type="primary" link onClick={() => onReset()}>
 | 
			
		||||
                    重置
 | 
			
		||||
                  </el-button>
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="pt-[6px] pl-[11px]">
 | 
			
		||||
                  <el-checkbox-group
 | 
			
		||||
                    v-model={checkedColumns.value}
 | 
			
		||||
                    onChange={value => handleCheckedColumnsChange(value)}
 | 
			
		||||
                  >
 | 
			
		||||
                    <el-space
 | 
			
		||||
                      direction="vertical"
 | 
			
		||||
                      alignment="flex-start"
 | 
			
		||||
                      size={0}
 | 
			
		||||
                    >
 | 
			
		||||
                      {checkColumnList.map(item => {
 | 
			
		||||
                        return (
 | 
			
		||||
                          <div class="flex items-center">
 | 
			
		||||
                            <DragIcon
 | 
			
		||||
                              class={[
 | 
			
		||||
                                "drag-btn w-[16px] mr-2",
 | 
			
		||||
                                isFixedColumn(item)
 | 
			
		||||
                                  ? "!cursor-no-drop"
 | 
			
		||||
                                  : "!cursor-grab"
 | 
			
		||||
                              ]}
 | 
			
		||||
                              onMouseenter={(event: {
 | 
			
		||||
                                preventDefault: () => void;
 | 
			
		||||
                              }) => rowDrop(event)}
 | 
			
		||||
                            />
 | 
			
		||||
                            <el-checkbox
 | 
			
		||||
                              key={item}
 | 
			
		||||
                              label={item}
 | 
			
		||||
                              onChange={value =>
 | 
			
		||||
                                handleCheckColumnListChange(value, item)
 | 
			
		||||
                              }
 | 
			
		||||
                            >
 | 
			
		||||
                              <span
 | 
			
		||||
                                title={item}
 | 
			
		||||
                                class="inline-block w-[120px] truncate hover:text-text_color_primary"
 | 
			
		||||
                              >
 | 
			
		||||
                                {item}
 | 
			
		||||
                              </span>
 | 
			
		||||
                            </el-checkbox>
 | 
			
		||||
                          </div>
 | 
			
		||||
                        );
 | 
			
		||||
                      })}
 | 
			
		||||
                    </el-space>
 | 
			
		||||
                  </el-checkbox-group>
 | 
			
		||||
                </div>
 | 
			
		||||
              </el-popover>
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <el-tooltip
 | 
			
		||||
              popper-options={{
 | 
			
		||||
                modifiers: [
 | 
			
		||||
                  {
 | 
			
		||||
                    name: "computeStyles",
 | 
			
		||||
                    options: {
 | 
			
		||||
                      adaptive: false,
 | 
			
		||||
                      enabled: false
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                ]
 | 
			
		||||
              }}
 | 
			
		||||
              placement="top"
 | 
			
		||||
              virtual-ref={buttonRef.value}
 | 
			
		||||
              virtual-triggering
 | 
			
		||||
              trigger="hover"
 | 
			
		||||
              content="列设置"
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          {slots.default({
 | 
			
		||||
            size: size.value,
 | 
			
		||||
            dynamicColumns: dynamicColumns.value
 | 
			
		||||
          })}
 | 
			
		||||
        </div>
 | 
			
		||||
      </>
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										1
									
								
								src/components/RePureTableBar/src/svg/collapse.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/RePureTableBar/src/svg/collapse.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M13.79 10.21a1 1 0 0 0 1.42 0 1 1 0 0 0 0-1.42l-2.5-2.5a1 1 0 0 0-.33-.21 1 1 0 0 0-.76 0 1 1 0 0 0-.33.21l-2.5 2.5a1 1 0 0 0 1.42 1.42l.79-.8v5.18l-.79-.8a1 1 0 0 0-1.42 1.42l2.5 2.5a1 1 0 0 0 .33.21.94.94 0 0 0 .76 0 1 1 0 0 0 .33-.21l2.5-2.5a1 1 0 0 0-1.42-1.42l-.79.8V9.41ZM7 4h10a1 1 0 0 0 0-2H7a1 1 0 0 0 0 2Zm10 16H7a1 1 0 0 0 0 2h10a1 1 0 0 0 0-2Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 441 B  | 
							
								
								
									
										1
									
								
								src/components/RePureTableBar/src/svg/drag.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/RePureTableBar/src/svg/drag.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg width="32" height="32" fill="currentColor" aria-hidden="true" data-icon="holder" viewBox="64 64 896 896"><path d="M300 276.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zm0 284a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 228a56 56 0 1 0 112 0 56 56 0 0 0-112 0zm0 284a56 56 0 1 0 112 0 56 56 0 0 0-112 0zM300 844.5a56 56 0 1 0 56-97 56 56 0 0 0-56 97zM640 796a56 56 0 1 0 112 0 56 56 0 0 0-112 0z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 398 B  | 
							
								
								
									
										1
									
								
								src/components/RePureTableBar/src/svg/expand.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/RePureTableBar/src/svg/expand.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M22 4V2H2v2h9v14.17l-5.5-5.5-1.42 1.41L12 22l7.92-7.92-1.42-1.41-5.5 5.5V4h9Z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 163 B  | 
							
								
								
									
										1
									
								
								src/components/RePureTableBar/src/svg/refresh.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/RePureTableBar/src/svg/refresh.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 11A8.1 8.1 0 0 0 4.5 9M4 5v4h4m-4 4a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 235 B  | 
							
								
								
									
										1
									
								
								src/components/RePureTableBar/src/svg/settings.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/components/RePureTableBar/src/svg/settings.svg
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
<svg width="32" height="32" viewBox="0 0 24 24"><path fill="currentColor" d="M3.34 17a10.018 10.018 0 0 1-.978-2.326 3 3 0 0 0 .002-5.347A9.99 9.99 0 0 1 4.865 4.99a3 3 0 0 0 4.631-2.674 9.99 9.99 0 0 1 5.007.002 3 3 0 0 0 4.632 2.672A9.99 9.99 0 0 1 20.66 7c.433.749.757 1.53.978 2.326a3 3 0 0 0-.002 5.347 9.99 9.99 0 0 1-2.501 4.337 3 3 0 0 0-4.631 2.674 9.99 9.99 0 0 1-5.007-.002 3 3 0 0 0-4.632-2.672A10.018 10.018 0 0 1 3.34 17zm5.66.196a4.993 4.993 0 0 1 2.25 2.77c.499.047 1 .048 1.499.001A4.993 4.993 0 0 1 15 17.197a4.993 4.993 0 0 1 3.525-.565c.29-.408.54-.843.748-1.298A4.993 4.993 0 0 1 18 12c0-1.26.47-2.437 1.273-3.334a8.126 8.126 0 0 0-.75-1.298A4.993 4.993 0 0 1 15 6.804a4.993 4.993 0 0 1-2.25-2.77c-.499-.047-1-.048-1.499-.001A4.993 4.993 0 0 1 9 6.803a4.993 4.993 0 0 1-3.525.565 7.99 7.99 0 0 0-.748 1.298A4.993 4.993 0 0 1 6 12a4.99 4.99 0 0 1-1.273 3.334 8.126 8.126 0 0 0 .75 1.298A4.993 4.993 0 0 1 9 17.196zM12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1011 B  | 
@ -49,4 +49,7 @@ export const getServerConfig = async (app: App): Promise<undefined> => {
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export { getConfig, setConfig };
 | 
			
		||||
/** 本地响应式存储的命名空间 */
 | 
			
		||||
const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace;
 | 
			
		||||
 | 
			
		||||
export { getConfig, setConfig, responsiveStorageNameSpace };
 | 
			
		||||
 | 
			
		||||
@ -130,16 +130,16 @@ const transitionMain = defineComponent({
 | 
			
		||||
 | 
			
		||||
<style scoped>
 | 
			
		||||
.app-main {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  overflow-x: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.app-main-nofixed-header {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  min-height: 100vh;
 | 
			
		||||
  position: relative;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main-content {
 | 
			
		||||
 | 
			
		||||
@ -82,28 +82,28 @@ const {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
 | 
			
		||||
  .hamburger-container {
 | 
			
		||||
    line-height: 48px;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    float: left;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    line-height: 48px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .vertical-header-right {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
    min-width: 280px;
 | 
			
		||||
    height: 48px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    color: #000000d9;
 | 
			
		||||
    justify-content: flex-end;
 | 
			
		||||
 | 
			
		||||
    .el-dropdown-link {
 | 
			
		||||
      height: 48px;
 | 
			
		||||
      padding: 10px;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      justify-content: space-around;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      height: 48px;
 | 
			
		||||
      padding: 10px;
 | 
			
		||||
      color: #000000d9;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
 | 
			
		||||
      p {
 | 
			
		||||
        font-size: 14px;
 | 
			
		||||
@ -127,9 +127,9 @@ const {
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -46,8 +46,8 @@ notices.value.map(v => (noticesNum.value += v.list.length));
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
  width: 60px;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
 | 
			
		||||
  .header-notice-icon {
 | 
			
		||||
@ -59,7 +59,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
 | 
			
		||||
  width: 330px;
 | 
			
		||||
 | 
			
		||||
  .noticeList-container {
 | 
			
		||||
    padding: 15px 24px 0 24px;
 | 
			
		||||
    padding: 15px 24px 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :deep(.el-tabs__header) {
 | 
			
		||||
@ -71,7 +71,7 @@ notices.value.map(v => (noticesNum.value += v.list.length));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  :deep(.el-tabs__nav-wrap) {
 | 
			
		||||
    padding: 0 36px 0 36px;
 | 
			
		||||
    padding: 0 36px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -118,6 +118,7 @@ function hoverDescription(event, description) {
 | 
			
		||||
  align-items: flex-start;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  padding: 12px 0;
 | 
			
		||||
 | 
			
		||||
  // border-bottom: 1px solid #f0f0f0;
 | 
			
		||||
 | 
			
		||||
  .notice-container-avatar {
 | 
			
		||||
@ -127,15 +128,15 @@ function hoverDescription(event, description) {
 | 
			
		||||
 | 
			
		||||
  .notice-container-text {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
 | 
			
		||||
    .notice-text-title {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      margin-bottom: 8px;
 | 
			
		||||
      font-weight: 400;
 | 
			
		||||
      font-size: 14px;
 | 
			
		||||
      font-weight: 400;
 | 
			
		||||
      line-height: 1.5715;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
 | 
			
		||||
@ -143,8 +144,8 @@ function hoverDescription(event, description) {
 | 
			
		||||
        flex: 1;
 | 
			
		||||
        width: 200px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .notice-title-extra {
 | 
			
		||||
@ -162,8 +163,8 @@ function hoverDescription(event, description) {
 | 
			
		||||
 | 
			
		||||
    .notice-text-description {
 | 
			
		||||
      display: -webkit-box;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      -webkit-line-clamp: 2;
 | 
			
		||||
      -webkit-box-orient: vertical;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -60,9 +60,9 @@ emitter.on("openPanel", () => {
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.showright-panel {
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: calc(100% - 15px);
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -71,23 +71,23 @@ emitter.on("openPanel", () => {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  z-index: -1;
 | 
			
		||||
  background: rgb(0 0 0 / 20%);
 | 
			
		||||
  opacity: 0;
 | 
			
		||||
  transition: opacity 0.3s cubic-bezier(0.7, 0.3, 0.1, 1);
 | 
			
		||||
  background: rgba(0, 0, 0, 0.2);
 | 
			
		||||
  z-index: -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right-panel {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 315px;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  right: 0;
 | 
			
		||||
  box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.05);
 | 
			
		||||
  z-index: 40000;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  max-width: 315px;
 | 
			
		||||
  height: 100vh;
 | 
			
		||||
  box-shadow: 0 0 15px 0 rgb(0 0 0 / 5%);
 | 
			
		||||
  transition: all 0.25s cubic-bezier(0.7, 0.3, 0.1, 1);
 | 
			
		||||
  transform: translate(100%);
 | 
			
		||||
  z-index: 40000;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.show {
 | 
			
		||||
@ -95,9 +95,9 @@ emitter.on("openPanel", () => {
 | 
			
		||||
 | 
			
		||||
  .right-panel-background {
 | 
			
		||||
    z-index: 20000;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    opacity: 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .right-panel {
 | 
			
		||||
@ -106,20 +106,20 @@ emitter.on("openPanel", () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.handle-button {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 45%;
 | 
			
		||||
  left: -48px;
 | 
			
		||||
  z-index: 0;
 | 
			
		||||
  width: 48px;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: -48px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
  border-radius: 6px 0 0 6px !important;
 | 
			
		||||
  z-index: 0;
 | 
			
		||||
  line-height: 48px;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  pointer-events: auto;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
  line-height: 48px;
 | 
			
		||||
  top: 45%;
 | 
			
		||||
  background: rgb(24, 144, 255);
 | 
			
		||||
  background: rgb(24 144 255);
 | 
			
		||||
  border-radius: 6px 0 0 6px !important;
 | 
			
		||||
 | 
			
		||||
  i {
 | 
			
		||||
    font-size: 24px;
 | 
			
		||||
@ -128,24 +128,24 @@ emitter.on("openPanel", () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.right-panel-items {
 | 
			
		||||
  margin-top: 60px;
 | 
			
		||||
  height: calc(100vh - 60px);
 | 
			
		||||
  margin-top: 60px;
 | 
			
		||||
  overflow-y: auto;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.project-configuration {
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  top: 15px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 30px;
 | 
			
		||||
  position: fixed;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  top: 15px;
 | 
			
		||||
  margin-left: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
:deep(.el-divider--horizontal) {
 | 
			
		||||
  width: 90%;
 | 
			
		||||
  margin: 20px auto 0 auto;
 | 
			
		||||
  margin: 20px auto 0;
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -81,11 +81,11 @@ function handleTo() {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    height: 56px;
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    padding: 14px;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    margin-top: 8px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border: 0.1px solid #ccc;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    transition: all 0.3s;
 | 
			
		||||
 | 
			
		||||
    &-title {
 | 
			
		||||
 | 
			
		||||
@ -418,35 +418,35 @@ onBeforeMount(() => {
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-between;
 | 
			
		||||
    margin: 25px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-datatheme {
 | 
			
		||||
  display: block;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
  display: block;
 | 
			
		||||
  padding-top: 25px;
 | 
			
		||||
  text-align: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.pure-theme {
 | 
			
		||||
  margin-top: 25px;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
  justify-content: space-around;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 50px;
 | 
			
		||||
  margin-top: 25px;
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    width: 18%;
 | 
			
		||||
    height: 45px;
 | 
			
		||||
    background: #f0f2f5;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    background: #f0f2f5;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    box-shadow: 0 1px 2.5px 0 rgb(0 0 0 / 18%);
 | 
			
		||||
 | 
			
		||||
@ -459,13 +459,13 @@ onBeforeMount(() => {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:nth-child(2) {
 | 
			
		||||
          width: 70%;
 | 
			
		||||
          height: 30%;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          top: 0;
 | 
			
		||||
          right: 0;
 | 
			
		||||
          width: 70%;
 | 
			
		||||
          height: 30%;
 | 
			
		||||
          background: #fff;
 | 
			
		||||
          box-shadow: 0 0 1px #888;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -491,13 +491,13 @@ onBeforeMount(() => {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        &:nth-child(2) {
 | 
			
		||||
          width: 30%;
 | 
			
		||||
          height: 70%;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
          bottom: 0;
 | 
			
		||||
          left: 0;
 | 
			
		||||
          width: 30%;
 | 
			
		||||
          height: 70%;
 | 
			
		||||
          background: #fff;
 | 
			
		||||
          box-shadow: 0 0 1px #888;
 | 
			
		||||
          position: absolute;
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@ -505,11 +505,11 @@ onBeforeMount(() => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.theme-color {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 40px;
 | 
			
		||||
  margin-top: 20px;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
 | 
			
		||||
  li {
 | 
			
		||||
    float: left;
 | 
			
		||||
@ -519,8 +519,8 @@ onBeforeMount(() => {
 | 
			
		||||
    margin-right: 8px;
 | 
			
		||||
    font-weight: 700;
 | 
			
		||||
    text-align: center;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border-radius: 2px;
 | 
			
		||||
 | 
			
		||||
    &:nth-child(2) {
 | 
			
		||||
      border: 1px solid #ddd;
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,6 @@ const router = useRouter();
 | 
			
		||||
const routes: any = router.options.routes;
 | 
			
		||||
const multiTags: any = useMultiTagsStoreHook().multiTags;
 | 
			
		||||
 | 
			
		||||
const isDashboard = (route: RouteLocationMatched): boolean | string => {
 | 
			
		||||
  const name = route && (route.name as string);
 | 
			
		||||
  if (!name) return false;
 | 
			
		||||
  return name.trim().toLocaleLowerCase() === "Welcome".toLocaleLowerCase();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getBreadcrumb = (): void => {
 | 
			
		||||
  // 当前路由信息
 | 
			
		||||
  let currentRoute;
 | 
			
		||||
@ -34,28 +28,24 @@ const getBreadcrumb = (): void => {
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  } else {
 | 
			
		||||
    currentRoute = findRouteByPath(router.currentRoute.value.path, multiTags);
 | 
			
		||||
    currentRoute = findRouteByPath(router.currentRoute.value.path, routes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // 当前路由的父级路径组成的数组
 | 
			
		||||
  const parentRoutes = getParentPaths(router.currentRoute.value.path, routes);
 | 
			
		||||
  const parentRoutes = getParentPaths(
 | 
			
		||||
    router.currentRoute.value.name as string,
 | 
			
		||||
    routes,
 | 
			
		||||
    "name"
 | 
			
		||||
  );
 | 
			
		||||
  // 存放组成面包屑的数组
 | 
			
		||||
  let matched = [];
 | 
			
		||||
  const matched = [];
 | 
			
		||||
 | 
			
		||||
  // 获取每个父级路径对应的路由信息
 | 
			
		||||
  parentRoutes.forEach(path => {
 | 
			
		||||
    if (path !== "/") matched.push(findRouteByPath(path, routes));
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (currentRoute?.path !== "/welcome") matched.push(currentRoute);
 | 
			
		||||
 | 
			
		||||
  if (!isDashboard(matched[0])) {
 | 
			
		||||
    matched = [
 | 
			
		||||
      {
 | 
			
		||||
        path: "/welcome",
 | 
			
		||||
        parentPath: "/",
 | 
			
		||||
        meta: { title: "首页" }
 | 
			
		||||
      } as unknown as RouteLocationMatched
 | 
			
		||||
    ].concat(matched);
 | 
			
		||||
  }
 | 
			
		||||
  matched.push(currentRoute);
 | 
			
		||||
 | 
			
		||||
  matched.forEach((item, index) => {
 | 
			
		||||
    if (currentRoute?.query || currentRoute?.params) return;
 | 
			
		||||
@ -90,6 +80,9 @@ watch(
 | 
			
		||||
  () => route.path,
 | 
			
		||||
  () => {
 | 
			
		||||
    getBreadcrumb();
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    deep: true
 | 
			
		||||
  }
 | 
			
		||||
);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ const {
 | 
			
		||||
  title,
 | 
			
		||||
  routers,
 | 
			
		||||
  logout,
 | 
			
		||||
  backHome,
 | 
			
		||||
  backTopMenu,
 | 
			
		||||
  onPanel,
 | 
			
		||||
  menuSelect,
 | 
			
		||||
  username,
 | 
			
		||||
@ -39,7 +39,7 @@ watch(
 | 
			
		||||
    v-loading="usePermissionStoreHook().wholeMenus.length === 0"
 | 
			
		||||
    class="horizontal-header"
 | 
			
		||||
  >
 | 
			
		||||
    <div class="horizontal-header-left" @click="backHome">
 | 
			
		||||
    <div class="horizontal-header-left" @click="backTopMenu">
 | 
			
		||||
      <img src="/logo.svg" alt="logo" />
 | 
			
		||||
      <span>{{ title }}</span>
 | 
			
		||||
    </div>
 | 
			
		||||
@ -104,9 +104,9 @@ watch(
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { getTopMenu } from "@/router/utils";
 | 
			
		||||
import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
@ -6,6 +7,7 @@ const props = defineProps({
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const { title } = useNav();
 | 
			
		||||
const topPath = getTopMenu().path;
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -16,7 +18,7 @@ const { title } = useNav();
 | 
			
		||||
        key="props.collapse"
 | 
			
		||||
        :title="title"
 | 
			
		||||
        class="sidebar-logo-link"
 | 
			
		||||
        to="/"
 | 
			
		||||
        :to="topPath"
 | 
			
		||||
      >
 | 
			
		||||
        <img src="/logo.svg" alt="logo" />
 | 
			
		||||
        <span class="sidebar-title">{{ title }}</span>
 | 
			
		||||
@ -26,7 +28,7 @@ const { title } = useNav();
 | 
			
		||||
        key="expand"
 | 
			
		||||
        :title="title"
 | 
			
		||||
        class="sidebar-logo-link"
 | 
			
		||||
        to="/"
 | 
			
		||||
        :to="topPath"
 | 
			
		||||
      >
 | 
			
		||||
        <img src="/logo.svg" alt="logo" />
 | 
			
		||||
        <span class="sidebar-title">{{ title }}</span>
 | 
			
		||||
@ -37,33 +39,33 @@ const { title } = useNav();
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.sidebar-logo-container {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 48px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  .sidebar-logo-link {
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    flex-wrap: nowrap;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
 | 
			
		||||
    img {
 | 
			
		||||
      height: 32px;
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      height: 32px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sidebar-title {
 | 
			
		||||
      height: 32px;
 | 
			
		||||
      line-height: 32px;
 | 
			
		||||
      margin: 2px 0 0 12px;
 | 
			
		||||
      color: $subMenuActiveText;
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      height: 32px;
 | 
			
		||||
      margin: 2px 0 0 12px;
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      font-size: 18px;
 | 
			
		||||
      font-weight: 600;
 | 
			
		||||
      line-height: 32px;
 | 
			
		||||
      color: $subMenuActiveText;
 | 
			
		||||
      text-overflow: ellipsis;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -135,9 +135,9 @@ watch(
 | 
			
		||||
  max-width: 120px;
 | 
			
		||||
 | 
			
		||||
  ::v-deep(.el-dropdown-menu__item) {
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
    display: inline-flex;
 | 
			
		||||
    flex-wrap: wrap;
 | 
			
		||||
    min-width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,8 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import path from "path";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { menuType } from "../../types";
 | 
			
		||||
import extraIcon from "./extraIcon.vue";
 | 
			
		||||
import { childrenType } from "../../types";
 | 
			
		||||
import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 | 
			
		||||
import { ref, toRaw, PropType, nextTick, computed, CSSProperties } from "vue";
 | 
			
		||||
@ -16,7 +16,7 @@ const { layout, isCollapse, tooltipEffect, getDivStyle } = useNav();
 | 
			
		||||
 | 
			
		||||
const props = defineProps({
 | 
			
		||||
  item: {
 | 
			
		||||
    type: Object as PropType<childrenType>
 | 
			
		||||
    type: Object as PropType<menuType>
 | 
			
		||||
  },
 | 
			
		||||
  isNest: {
 | 
			
		||||
    type: Boolean,
 | 
			
		||||
@ -111,7 +111,7 @@ const expandCloseIcon = computed(() => {
 | 
			
		||||
  };
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const onlyOneChild: childrenType = ref(null);
 | 
			
		||||
const onlyOneChild: menuType = ref(null);
 | 
			
		||||
// 存放菜单是否存在showTooltip属性标识
 | 
			
		||||
const hoverMenuMap = new WeakMap();
 | 
			
		||||
// 存储菜单文本dom元素
 | 
			
		||||
@ -148,10 +148,7 @@ function overflowSlice(text, item?: any) {
 | 
			
		||||
  return newText;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function hasOneShowingChild(
 | 
			
		||||
  children: childrenType[] = [],
 | 
			
		||||
  parent: childrenType
 | 
			
		||||
) {
 | 
			
		||||
function hasOneShowingChild(children: menuType[] = [], parent: menuType) {
 | 
			
		||||
  const showingChildren = children.filter((item: any) => {
 | 
			
		||||
    onlyOneChild.value = item;
 | 
			
		||||
    return true;
 | 
			
		||||
 | 
			
		||||
@ -6,14 +6,16 @@ import SidebarItem from "./sidebarItem.vue";
 | 
			
		||||
import leftCollapse from "./leftCollapse.vue";
 | 
			
		||||
import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
import { storageLocal } from "@pureadmin/utils";
 | 
			
		||||
import { responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
import { ref, computed, watch, onBeforeMount } from "vue";
 | 
			
		||||
import { findRouteByPath, getParentPaths } from "@/router/utils";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
 | 
			
		||||
const route = useRoute();
 | 
			
		||||
const showLogo = ref(
 | 
			
		||||
  storageLocal().getItem<StorageConfigs>("responsive-configure")?.showLogo ??
 | 
			
		||||
    true
 | 
			
		||||
  storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
    `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
  )?.showLogo ?? true
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const { routers, device, pureApp, isCollapse, menuSelect, toggleSideBar } =
 | 
			
		||||
@ -27,7 +29,12 @@ const menuData = computed(() => {
 | 
			
		||||
    : usePermissionStoreHook().wholeMenus;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const loading = computed(() =>
 | 
			
		||||
  pureApp.layout === "mix" ? false : menuData.value.length === 0 ? true : false
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
function getSubMenuData(path: string) {
 | 
			
		||||
  subMenuData.value = [];
 | 
			
		||||
  // path的上级路由组成的数组
 | 
			
		||||
  const parentPathArr = getParentPaths(
 | 
			
		||||
    path,
 | 
			
		||||
@ -53,6 +60,7 @@ onBeforeMount(() => {
 | 
			
		||||
watch(
 | 
			
		||||
  () => [route.path, usePermissionStoreHook().wholeMenus],
 | 
			
		||||
  () => {
 | 
			
		||||
    if (route.path.includes("/redirect")) return;
 | 
			
		||||
    getSubMenuData(route.path);
 | 
			
		||||
    menuSelect(route.path, routers);
 | 
			
		||||
  }
 | 
			
		||||
@ -61,7 +69,7 @@ watch(
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
  <div
 | 
			
		||||
    v-loading="menuData.length === 0"
 | 
			
		||||
    v-loading="loading"
 | 
			
		||||
    :class="['sidebar-container', showLogo ? 'has-logo' : '']"
 | 
			
		||||
  >
 | 
			
		||||
    <Logo v-if="showLogo" :collapse="isCollapse" />
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
@keyframes scheduleInWidth {
 | 
			
		||||
@keyframes schedule-in-width {
 | 
			
		||||
  from {
 | 
			
		||||
    width: 0;
 | 
			
		||||
  }
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes scheduleOutWidth {
 | 
			
		||||
@keyframes schedule-out-width {
 | 
			
		||||
  from {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
  }
 | 
			
		||||
@ -39,41 +39,41 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.tags-view {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  color: var(--el-text-color-primary);
 | 
			
		||||
  background: #fff;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  box-shadow: 0 0 1px #888;
 | 
			
		||||
 | 
			
		||||
  .scroll-item {
 | 
			
		||||
    border-radius: 3px 3px 0 0;
 | 
			
		||||
    padding: 0 6px;
 | 
			
		||||
    box-shadow: 0 0 1px #888;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
    height: 28px;
 | 
			
		||||
    padding: 0 6px;
 | 
			
		||||
    margin-right: 4px;
 | 
			
		||||
    line-height: 28px;
 | 
			
		||||
    transition: all 0.4s;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    border-radius: 3px 3px 0 0;
 | 
			
		||||
    box-shadow: 0 0 1px #888;
 | 
			
		||||
    transition: all 0.4s;
 | 
			
		||||
 | 
			
		||||
    .el-icon-close {
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      font-size: 10px;
 | 
			
		||||
      color: var(--el-color-primary);
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      transform: translate(-50%, -50%);
 | 
			
		||||
      transition: font-size 0.2s;
 | 
			
		||||
      transform: translate(-50%, -50%);
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        border-radius: 50%;
 | 
			
		||||
        font-size: 13px;
 | 
			
		||||
        color: #fff;
 | 
			
		||||
        background: #b4bccc;
 | 
			
		||||
        font-size: 13px;
 | 
			
		||||
        border-radius: 50%;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -91,24 +91,24 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  a {
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
    color: var(--el-text-color-primary);
 | 
			
		||||
    padding: 0 4px;
 | 
			
		||||
    color: var(--el-text-color-primary);
 | 
			
		||||
    text-decoration: none;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .scroll-container {
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    flex: 1;
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
 | 
			
		||||
    .tab {
 | 
			
		||||
      position: relative;
 | 
			
		||||
      float: left;
 | 
			
		||||
      list-style: none;
 | 
			
		||||
      overflow: visible;
 | 
			
		||||
      white-space: nowrap;
 | 
			
		||||
      list-style: none;
 | 
			
		||||
      transition: transform 0.5s ease-in-out;
 | 
			
		||||
 | 
			
		||||
      .scroll-item {
 | 
			
		||||
@ -123,29 +123,28 @@
 | 
			
		||||
 | 
			
		||||
  /* 右键菜单 */
 | 
			
		||||
  .contextmenu {
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
    padding: 5px 0;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    color: var(--el-text-color-primary);
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    font-size: 13px;
 | 
			
		||||
    font-weight: normal;
 | 
			
		||||
    color: var(--el-text-color-primary);
 | 
			
		||||
    white-space: nowrap;
 | 
			
		||||
    list-style-type: none;
 | 
			
		||||
    background: #fff;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    outline: 0;
 | 
			
		||||
    box-shadow: 0 2px 8px rgb(0 0 0 / 15%);
 | 
			
		||||
 | 
			
		||||
    li {
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      padding: 7px 12px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      padding: 7px 12px;
 | 
			
		||||
      margin: 0;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
        // background: var(--el-color-primary-light-9);
 | 
			
		||||
        color: var(--el-color-primary);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -159,11 +158,11 @@
 | 
			
		||||
 | 
			
		||||
.el-dropdown-menu {
 | 
			
		||||
  li {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    margin: 0;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
 | 
			
		||||
    svg {
 | 
			
		||||
      display: block;
 | 
			
		||||
@ -184,6 +183,7 @@
 | 
			
		||||
:deep(.el-dropdown-menu__item--divided) {
 | 
			
		||||
  margin: 1px 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-dropdown-menu__item--divided::before {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
@ -193,7 +193,6 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.scroll-item.is-active {
 | 
			
		||||
  // background-color: var(--el-color-primary-light-9);
 | 
			
		||||
  position: relative;
 | 
			
		||||
  color: #fff;
 | 
			
		||||
 | 
			
		||||
@ -213,16 +212,16 @@
 | 
			
		||||
.arrow-left,
 | 
			
		||||
.arrow-right,
 | 
			
		||||
.arrow-down {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 40px;
 | 
			
		||||
  height: 38px;
 | 
			
		||||
  color: var(--el-text-color-primary);
 | 
			
		||||
  position: relative;
 | 
			
		||||
 | 
			
		||||
  svg {
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    position: absolute;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    width: 20px;
 | 
			
		||||
    height: 20px;
 | 
			
		||||
    transform: translate(-50%, 50%);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -236,8 +235,8 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.arrow-right {
 | 
			
		||||
  box-shadow: -5px 0 5px -6px #ccc;
 | 
			
		||||
  border-right: 0.5px solid #ccc;
 | 
			
		||||
  box-shadow: -5px 0 5px -6px #ccc;
 | 
			
		||||
 | 
			
		||||
  &:hover {
 | 
			
		||||
    cursor: e-resize;
 | 
			
		||||
@ -255,8 +254,8 @@
 | 
			
		||||
 | 
			
		||||
/* 卡片模式下鼠标移出隐藏蓝色边框 */
 | 
			
		||||
.card-out {
 | 
			
		||||
  border: none;
 | 
			
		||||
  color: #666;
 | 
			
		||||
  border: none;
 | 
			
		||||
 | 
			
		||||
  a {
 | 
			
		||||
    color: #666;
 | 
			
		||||
@ -265,32 +264,32 @@
 | 
			
		||||
 | 
			
		||||
/* 灵动模式 */
 | 
			
		||||
.schedule-active {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 2px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background: var(--el-color-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 灵动模式下鼠标移入显示蓝色进度条 */
 | 
			
		||||
.schedule-in {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 2px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background: var(--el-color-primary);
 | 
			
		||||
  animation: scheduleInWidth 200ms ease-in;
 | 
			
		||||
  animation: schedule-in-width 200ms ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 灵动模式下鼠标移出隐藏蓝色进度条 */
 | 
			
		||||
.schedule-out {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  width: 0;
 | 
			
		||||
  height: 2px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  left: 0;
 | 
			
		||||
  bottom: 0;
 | 
			
		||||
  background: var(--el-color-primary);
 | 
			
		||||
  animation: scheduleOutWidth 200ms ease-in;
 | 
			
		||||
  animation: schedule-out-width 200ms ease-in;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,10 +4,10 @@ import { RouteConfigs } from "../../types";
 | 
			
		||||
import { useTags } from "../../hooks/useTag";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
import { isEqual, isAllEmpty } from "@pureadmin/utils";
 | 
			
		||||
import { handleAliveRoute, getTopMenu } from "@/router/utils";
 | 
			
		||||
import { useSettingStoreHook } from "@/store/modules/settings";
 | 
			
		||||
import { ref, watch, unref, nextTick, onBeforeMount } from "vue";
 | 
			
		||||
import { handleAliveRoute, delAliveRoutes } from "@/router/utils";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import { ref, watch, unref, toRaw, nextTick, onBeforeMount } from "vue";
 | 
			
		||||
import { useResizeObserver, useDebounceFn, useFullscreen } from "@vueuse/core";
 | 
			
		||||
 | 
			
		||||
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
 | 
			
		||||
@ -48,6 +48,8 @@ const tabDom = ref();
 | 
			
		||||
const containerDom = ref();
 | 
			
		||||
const scrollbarDom = ref();
 | 
			
		||||
const isShowArrow = ref(false);
 | 
			
		||||
const topPath = getTopMenu().path;
 | 
			
		||||
const { VITE_HIDE_HOME } = import.meta.env;
 | 
			
		||||
const { isFullscreen, toggle } = useFullscreen();
 | 
			
		||||
 | 
			
		||||
const dynamicTagView = () => {
 | 
			
		||||
@ -163,13 +165,12 @@ function onFresh() {
 | 
			
		||||
  const { fullPath, query } = unref(route);
 | 
			
		||||
  router.replace({
 | 
			
		||||
    path: "/redirect" + fullPath,
 | 
			
		||||
    query: query
 | 
			
		||||
    query
 | 
			
		||||
  });
 | 
			
		||||
  handleAliveRoute(route as toRouteType, "refresh");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
  // 存放被删除的缓存路由
 | 
			
		||||
  let delAliveRouteList = [];
 | 
			
		||||
  const valueIndex: number = multiTags.value.findIndex((item: any) => {
 | 
			
		||||
    if (item.query) {
 | 
			
		||||
      if (item.path === obj.path) {
 | 
			
		||||
@ -190,9 +191,12 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
    other?: boolean
 | 
			
		||||
  ): void => {
 | 
			
		||||
    if (other) {
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("equal", [routerArrays[0], obj]);
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("equal", [
 | 
			
		||||
        VITE_HIDE_HOME === "false" ? routerArrays[0] : toRaw(getTopMenu()),
 | 
			
		||||
        obj
 | 
			
		||||
      ]);
 | 
			
		||||
    } else {
 | 
			
		||||
      delAliveRouteList = useMultiTagsStoreHook().handleTags("splice", "", {
 | 
			
		||||
      useMultiTagsStoreHook().handleTags("splice", "", {
 | 
			
		||||
        startIndex,
 | 
			
		||||
        length
 | 
			
		||||
      }) as any;
 | 
			
		||||
@ -212,10 +216,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
  }
 | 
			
		||||
  const newRoute = useMultiTagsStoreHook().handleTags("slice");
 | 
			
		||||
  if (current === route.path) {
 | 
			
		||||
    // 删除缓存路由
 | 
			
		||||
    tag
 | 
			
		||||
      ? delAliveRoutes(delAliveRouteList)
 | 
			
		||||
      : handleAliveRoute(route.matched, "delete");
 | 
			
		||||
    // 如果删除当前激活tag就自动切换到最后一个tag
 | 
			
		||||
    if (tag === "left") return;
 | 
			
		||||
    if (newRoute[0]?.query) {
 | 
			
		||||
@ -226,8 +226,6 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
      router.push({ path: newRoute[0].path });
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    // 删除缓存路由
 | 
			
		||||
    tag ? delAliveRoutes(delAliveRouteList) : delAliveRoutes([obj]);
 | 
			
		||||
    if (!multiTags.value.length) return;
 | 
			
		||||
    if (multiTags.value.some(item => item.path === route.path)) return;
 | 
			
		||||
    if (newRoute[0]?.query) {
 | 
			
		||||
@ -242,6 +240,7 @@ function deleteDynamicTag(obj: any, current: any, tag?: string) {
 | 
			
		||||
 | 
			
		||||
function deleteMenu(item, tag?: string) {
 | 
			
		||||
  deleteDynamicTag(item, item.path, tag);
 | 
			
		||||
  handleAliveRoute(route as toRouteType);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function onClickDrop(key, item, selectRoute?: RouteConfigs) {
 | 
			
		||||
@ -288,7 +287,8 @@ function onClickDrop(key, item, selectRoute?: RouteConfigs) {
 | 
			
		||||
        startIndex: 1,
 | 
			
		||||
        length: multiTags.value.length
 | 
			
		||||
      });
 | 
			
		||||
      router.push("/welcome");
 | 
			
		||||
      router.push(topPath);
 | 
			
		||||
      handleAliveRoute(route as toRouteType);
 | 
			
		||||
      break;
 | 
			
		||||
    case 6:
 | 
			
		||||
      // 整体页面全屏
 | 
			
		||||
@ -344,7 +344,7 @@ function disabledMenus(value: boolean) {
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是首页,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
 | 
			
		||||
/** 检查当前右键的菜单两边是否存在别的菜单,如果左侧的菜单是顶级菜单,则不显示关闭左侧标签页,如果右侧没有菜单,则不显示关闭右侧标签页 */
 | 
			
		||||
function showMenuModel(
 | 
			
		||||
  currentPath: string,
 | 
			
		||||
  query: object = {},
 | 
			
		||||
@ -366,11 +366,11 @@ function showMenuModel(
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * currentIndex为1时,左侧的菜单是首页,则不显示关闭左侧标签页
 | 
			
		||||
   * currentIndex为1时,左侧的菜单顶级菜单,则不显示关闭左侧标签页
 | 
			
		||||
   * 如果currentIndex等于routeLength-1,右侧没有菜单,则不显示关闭右侧标签页
 | 
			
		||||
   */
 | 
			
		||||
  if (currentIndex === 1 && routeLength !== 2) {
 | 
			
		||||
    // 左侧的菜单是首页,右侧存在别的菜单
 | 
			
		||||
    // 左侧的菜单是顶级菜单,右侧存在别的菜单
 | 
			
		||||
    tagsViews[2].show = false;
 | 
			
		||||
    Array.of(1, 3, 4, 5).forEach(v => {
 | 
			
		||||
      tagsViews[v].disabled = false;
 | 
			
		||||
@ -378,7 +378,7 @@ function showMenuModel(
 | 
			
		||||
    tagsViews[2].disabled = true;
 | 
			
		||||
  } else if (currentIndex === 1 && routeLength === 2) {
 | 
			
		||||
    disabledMenus(false);
 | 
			
		||||
    // 左侧的菜单是首页,右侧不存在别的菜单
 | 
			
		||||
    // 左侧的菜单是顶级菜单,右侧不存在别的菜单
 | 
			
		||||
    Array.of(2, 3, 4).forEach(v => {
 | 
			
		||||
      tagsViews[v].show = false;
 | 
			
		||||
      tagsViews[v].disabled = true;
 | 
			
		||||
@ -390,8 +390,8 @@ function showMenuModel(
 | 
			
		||||
      tagsViews[v].disabled = false;
 | 
			
		||||
    });
 | 
			
		||||
    tagsViews[3].disabled = true;
 | 
			
		||||
  } else if (currentIndex === 0 || currentPath === "/redirect/welcome") {
 | 
			
		||||
    // 当前路由为首页
 | 
			
		||||
  } else if (currentIndex === 0 || currentPath === `/redirect${topPath}`) {
 | 
			
		||||
    // 当前路由为顶级菜单
 | 
			
		||||
    disabledMenus(true);
 | 
			
		||||
  } else {
 | 
			
		||||
    disabledMenus(false);
 | 
			
		||||
@ -400,8 +400,8 @@ function showMenuModel(
 | 
			
		||||
 | 
			
		||||
function openMenu(tag, e) {
 | 
			
		||||
  closeMenu();
 | 
			
		||||
  if (tag.path === "/welcome") {
 | 
			
		||||
    // 右键菜单为首页,只显示刷新
 | 
			
		||||
  if (tag.path === topPath) {
 | 
			
		||||
    // 右键菜单为顶级菜单,只显示刷新
 | 
			
		||||
    showMenus(false);
 | 
			
		||||
    tagsViews[0].show = true;
 | 
			
		||||
  } else if (route.path !== tag.path && route.name !== tag.name) {
 | 
			
		||||
@ -523,7 +523,7 @@ onMounted(() => {
 | 
			
		||||
          :class="[
 | 
			
		||||
            'scroll-item is-closable',
 | 
			
		||||
            linkIsActive(item),
 | 
			
		||||
            $route.path === item.path && showModel === 'card'
 | 
			
		||||
            route.path === item.path && showModel === 'card'
 | 
			
		||||
              ? 'card-active'
 | 
			
		||||
              : ''
 | 
			
		||||
          ]"
 | 
			
		||||
@ -607,5 +607,5 @@ onMounted(() => {
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@import "./index.scss";
 | 
			
		||||
@import url("./index.scss");
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
@ -50,15 +50,15 @@ onMounted(() => {
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
.frame {
 | 
			
		||||
  height: calc(100vh - 88px);
 | 
			
		||||
  z-index: 998;
 | 
			
		||||
  height: calc(100vh - 88px);
 | 
			
		||||
 | 
			
		||||
  .frame-iframe {
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    border: 0;
 | 
			
		||||
    box-sizing: border-box;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ import { storeToRefs } from "pinia";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { emitter } from "@/utils/mitt";
 | 
			
		||||
import { routeMetaType } from "../types";
 | 
			
		||||
import { getTopMenu } from "@/router/utils";
 | 
			
		||||
import { useGlobal } from "@pureadmin/utils";
 | 
			
		||||
import { computed, CSSProperties } from "vue";
 | 
			
		||||
import { useRouter, useRoute } from "vue-router";
 | 
			
		||||
@ -68,8 +69,8 @@ export function useNav() {
 | 
			
		||||
    useUserStoreHook().logOut();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function backHome() {
 | 
			
		||||
    router.push("/welcome");
 | 
			
		||||
  function backTopMenu() {
 | 
			
		||||
    router.push(getTopMenu().path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  function onPanel() {
 | 
			
		||||
@ -138,7 +139,7 @@ export function useNav() {
 | 
			
		||||
    logout,
 | 
			
		||||
    routers,
 | 
			
		||||
    $storage,
 | 
			
		||||
    backHome,
 | 
			
		||||
    backTopMenu,
 | 
			
		||||
    onPanel,
 | 
			
		||||
    getDivStyle,
 | 
			
		||||
    changeTitle,
 | 
			
		||||
 | 
			
		||||
@ -11,6 +11,7 @@ import {
 | 
			
		||||
import { tagsViewsType } from "../types";
 | 
			
		||||
import { useEventListener } from "@vueuse/core";
 | 
			
		||||
import { useRoute, useRouter } from "vue-router";
 | 
			
		||||
import { responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
import { useSettingStoreHook } from "@/store/modules/settings";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import {
 | 
			
		||||
@ -45,13 +46,16 @@ export function useTags() {
 | 
			
		||||
 | 
			
		||||
  /** 显示模式,默认灵动模式 */
 | 
			
		||||
  const showModel = ref(
 | 
			
		||||
    storageLocal().getItem<StorageConfigs>("responsive-configure")?.showModel ||
 | 
			
		||||
      "smart"
 | 
			
		||||
    storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
      `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
    )?.showModel || "smart"
 | 
			
		||||
  );
 | 
			
		||||
  /** 是否隐藏标签页,默认显示 */
 | 
			
		||||
  const showTags =
 | 
			
		||||
    ref(
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>("responsive-configure").hideTabs
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
      ).hideTabs
 | 
			
		||||
    ) ?? ref("false");
 | 
			
		||||
  const multiTags: any = computed(() => {
 | 
			
		||||
    return useMultiTagsStoreHook().multiTags;
 | 
			
		||||
@ -200,10 +204,13 @@ export function useTags() {
 | 
			
		||||
  onMounted(() => {
 | 
			
		||||
    if (!showModel.value) {
 | 
			
		||||
      const configure = storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        "responsive-configure"
 | 
			
		||||
        `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
      );
 | 
			
		||||
      configure.showModel = "card";
 | 
			
		||||
      storageLocal().setItem("responsive-configure", configure);
 | 
			
		||||
      storageLocal().setItem(
 | 
			
		||||
        `${responsiveStorageNameSpace()}configure`,
 | 
			
		||||
        configure
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -179,20 +179,16 @@ const layoutHeader = defineComponent({
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<style lang="scss" scoped>
 | 
			
		||||
@mixin clearfix {
 | 
			
		||||
.app-wrapper {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
 | 
			
		||||
  &::after {
 | 
			
		||||
    content: "";
 | 
			
		||||
    display: table;
 | 
			
		||||
    clear: both;
 | 
			
		||||
    content: "";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.app-wrapper {
 | 
			
		||||
  @include clearfix;
 | 
			
		||||
 | 
			
		||||
  position: relative;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
 | 
			
		||||
  &.mobile.openSidebar {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
@ -201,13 +197,13 @@ const layoutHeader = defineComponent({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.app-mask {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  background: #000;
 | 
			
		||||
  opacity: 0.3;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  top: 0;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.re-screen {
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,19 @@
 | 
			
		||||
import type { IconifyIcon } from "@iconify/vue";
 | 
			
		||||
const { VITE_HIDE_HOME } = import.meta.env;
 | 
			
		||||
 | 
			
		||||
export const routerArrays: Array<RouteConfigs> = [
 | 
			
		||||
  {
 | 
			
		||||
    path: "/welcome",
 | 
			
		||||
    parentPath: "/",
 | 
			
		||||
    meta: {
 | 
			
		||||
      title: "首页",
 | 
			
		||||
      icon: "homeFilled"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
];
 | 
			
		||||
export const routerArrays: Array<RouteConfigs> =
 | 
			
		||||
  VITE_HIDE_HOME === "false"
 | 
			
		||||
    ? [
 | 
			
		||||
        {
 | 
			
		||||
          path: "/welcome",
 | 
			
		||||
          parentPath: "/",
 | 
			
		||||
          meta: {
 | 
			
		||||
            title: "首页",
 | 
			
		||||
            icon: "homeFilled"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      ]
 | 
			
		||||
    : [];
 | 
			
		||||
 | 
			
		||||
export type routeMetaType = {
 | 
			
		||||
  title?: string;
 | 
			
		||||
@ -58,20 +62,23 @@ export interface setType {
 | 
			
		||||
  hideTabs: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type childrenType = {
 | 
			
		||||
export type menuType = {
 | 
			
		||||
  id?: number;
 | 
			
		||||
  path?: string;
 | 
			
		||||
  noShowingChildren?: boolean;
 | 
			
		||||
  children?: childrenType[];
 | 
			
		||||
  children?: menuType[];
 | 
			
		||||
  value: unknown;
 | 
			
		||||
  meta?: {
 | 
			
		||||
    icon?: string;
 | 
			
		||||
    title?: string;
 | 
			
		||||
    rank?: number;
 | 
			
		||||
    showParent?: boolean;
 | 
			
		||||
    extraIcon?: string;
 | 
			
		||||
  };
 | 
			
		||||
  showTooltip?: boolean;
 | 
			
		||||
  parentId?: number;
 | 
			
		||||
  pathList?: number[];
 | 
			
		||||
  redirect?: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type themeColorsType = {
 | 
			
		||||
 | 
			
		||||
@ -12,6 +12,7 @@ import {
 | 
			
		||||
} from "vue-router";
 | 
			
		||||
import {
 | 
			
		||||
  ascending,
 | 
			
		||||
  getTopMenu,
 | 
			
		||||
  initRouter,
 | 
			
		||||
  isOneOfArray,
 | 
			
		||||
  getHistoryMode,
 | 
			
		||||
@ -95,13 +96,14 @@ export function resetRouter() {
 | 
			
		||||
/** 路由白名单 */
 | 
			
		||||
const whiteList = ["/login"];
 | 
			
		||||
 | 
			
		||||
const { VITE_HIDE_HOME } = import.meta.env;
 | 
			
		||||
 | 
			
		||||
router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
  if (to.meta?.keepAlive) {
 | 
			
		||||
    const newMatched = to.matched;
 | 
			
		||||
    handleAliveRoute(newMatched, "add");
 | 
			
		||||
    handleAliveRoute(to, "add");
 | 
			
		||||
    // 页面整体刷新和点击标签页刷新
 | 
			
		||||
    if (_from.name === undefined || _from.name === "Redirect") {
 | 
			
		||||
      handleAliveRoute(newMatched);
 | 
			
		||||
      handleAliveRoute(to);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const userInfo = storageSession().getItem<DataInfo<number>>(sessionKey);
 | 
			
		||||
@ -124,6 +126,10 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
    if (to.meta?.roles && !isOneOfArray(to.meta?.roles, userInfo?.roles)) {
 | 
			
		||||
      next({ path: "/error/403" });
 | 
			
		||||
    }
 | 
			
		||||
    // 开启隐藏首页后在浏览器地址栏手动输入首页welcome路由则跳转到404页面
 | 
			
		||||
    if (VITE_HIDE_HOME === "true" && to.fullPath === "/welcome") {
 | 
			
		||||
      next({ path: "/error/404" });
 | 
			
		||||
    }
 | 
			
		||||
    if (_from?.name) {
 | 
			
		||||
      // name为超链接
 | 
			
		||||
      if (externalLink) {
 | 
			
		||||
@ -145,6 +151,7 @@ router.beforeEach((to: toRouteType, _from, next) => {
 | 
			
		||||
              path,
 | 
			
		||||
              router.options.routes[0].children
 | 
			
		||||
            );
 | 
			
		||||
            getTopMenu(true);
 | 
			
		||||
            // query、params模式路由传参数的标签页不在此处处理
 | 
			
		||||
            if (route && route.meta?.title) {
 | 
			
		||||
              useMultiTagsStoreHook().handleTags("push", {
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
const { VITE_HIDE_HOME } = import.meta.env;
 | 
			
		||||
const Layout = () => import("@/layout/index.vue");
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
@ -16,7 +17,8 @@ export default {
 | 
			
		||||
      name: "Welcome",
 | 
			
		||||
      component: () => import("@/views/welcome/index.vue"),
 | 
			
		||||
      meta: {
 | 
			
		||||
        title: "首页"
 | 
			
		||||
        title: "首页",
 | 
			
		||||
        showLink: VITE_HIDE_HOME === "true" ? false : true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
@ -15,10 +15,9 @@ export default [
 | 
			
		||||
    path: "/redirect",
 | 
			
		||||
    component: Layout,
 | 
			
		||||
    meta: {
 | 
			
		||||
      icon: "homeFilled",
 | 
			
		||||
      title: "首页",
 | 
			
		||||
      title: "加载中...",
 | 
			
		||||
      showLink: false,
 | 
			
		||||
      rank: 104
 | 
			
		||||
      rank: 102
 | 
			
		||||
    },
 | 
			
		||||
    children: [
 | 
			
		||||
      {
 | 
			
		||||
 | 
			
		||||
@ -3,13 +3,11 @@ import {
 | 
			
		||||
  RouteRecordRaw,
 | 
			
		||||
  RouteComponent,
 | 
			
		||||
  createWebHistory,
 | 
			
		||||
  createWebHashHistory,
 | 
			
		||||
  RouteRecordNormalized
 | 
			
		||||
  createWebHashHistory
 | 
			
		||||
} from "vue-router";
 | 
			
		||||
import { router } from "./index";
 | 
			
		||||
import { isProxy, toRaw } from "vue";
 | 
			
		||||
import { useTimeoutFn } from "@vueuse/core";
 | 
			
		||||
import { RouteConfigs } from "@/layout/types";
 | 
			
		||||
import {
 | 
			
		||||
  isString,
 | 
			
		||||
  cloneDeep,
 | 
			
		||||
@ -19,8 +17,10 @@ import {
 | 
			
		||||
  isIncludeAllChildren
 | 
			
		||||
} from "@pureadmin/utils";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { menuType } from "@/layout/types";
 | 
			
		||||
import { buildHierarchyTree } from "@/utils/tree";
 | 
			
		||||
import { sessionKey, type DataInfo } from "@/utils/auth";
 | 
			
		||||
import { useMultiTagsStoreHook } from "@/store/modules/multiTags";
 | 
			
		||||
import { usePermissionStoreHook } from "@/store/modules/permission";
 | 
			
		||||
const IFrame = () => import("@/layout/frameView.vue");
 | 
			
		||||
// https://cn.vitejs.dev/guide/features.html#glob-import
 | 
			
		||||
@ -94,30 +94,20 @@ function filterNoPermissionTree(data: RouteComponent[]) {
 | 
			
		||||
  return filterChildrenTree(newTree);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 批量删除缓存路由(keepalive) */
 | 
			
		||||
function delAliveRoutes(delAliveRouteList: Array<RouteConfigs>) {
 | 
			
		||||
  delAliveRouteList.forEach(route => {
 | 
			
		||||
    usePermissionStoreHook().cacheOperate({
 | 
			
		||||
      mode: "delete",
 | 
			
		||||
      name: route?.name
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 通过path获取父级路径 */
 | 
			
		||||
function getParentPaths(path: string, routes: RouteRecordRaw[]) {
 | 
			
		||||
/** 通过指定 `key` 获取父级路径集合,默认 `key` 为 `path` */
 | 
			
		||||
function getParentPaths(value: string, routes: RouteRecordRaw[], key = "path") {
 | 
			
		||||
  // 深度遍历查找
 | 
			
		||||
  function dfs(routes: RouteRecordRaw[], path: string, parents: string[]) {
 | 
			
		||||
  function dfs(routes: RouteRecordRaw[], value: string, parents: string[]) {
 | 
			
		||||
    for (let i = 0; i < routes.length; i++) {
 | 
			
		||||
      const item = routes[i];
 | 
			
		||||
      // 找到path则返回父级path
 | 
			
		||||
      if (item.path === path) return parents;
 | 
			
		||||
      // 返回父级path
 | 
			
		||||
      if (item[key] === value) return parents;
 | 
			
		||||
      // children不存在或为空则不递归
 | 
			
		||||
      if (!item.children || !item.children.length) continue;
 | 
			
		||||
      // 往下查找时将当前path入栈
 | 
			
		||||
      parents.push(item.path);
 | 
			
		||||
 | 
			
		||||
      if (dfs(item.children, path, parents).length) return parents;
 | 
			
		||||
      if (dfs(item.children, value, parents).length) return parents;
 | 
			
		||||
      // 深度遍历查找未找到时当前path 出栈
 | 
			
		||||
      parents.pop();
 | 
			
		||||
    }
 | 
			
		||||
@ -125,10 +115,10 @@ function getParentPaths(path: string, routes: RouteRecordRaw[]) {
 | 
			
		||||
    return [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return dfs(routes, path, []);
 | 
			
		||||
  return dfs(routes, value, []);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 查找对应path的路由信息 */
 | 
			
		||||
/** 查找对应 `path` 的路由信息 */
 | 
			
		||||
function findRouteByPath(path: string, routes: RouteRecordRaw[]) {
 | 
			
		||||
  let res = routes.find((item: { path: string }) => item.path == path);
 | 
			
		||||
  if (res) {
 | 
			
		||||
@ -266,27 +256,35 @@ function formatTwoStageRoutes(routesList: RouteRecordRaw[]) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 处理缓存路由(添加、删除、刷新) */
 | 
			
		||||
function handleAliveRoute(matched: RouteRecordNormalized[], mode?: string) {
 | 
			
		||||
function handleAliveRoute({ name }: toRouteType, mode?: string) {
 | 
			
		||||
  switch (mode) {
 | 
			
		||||
    case "add":
 | 
			
		||||
      matched.forEach(v => {
 | 
			
		||||
        usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
 | 
			
		||||
      usePermissionStoreHook().cacheOperate({
 | 
			
		||||
        mode: "add",
 | 
			
		||||
        name
 | 
			
		||||
      });
 | 
			
		||||
      break;
 | 
			
		||||
    case "delete":
 | 
			
		||||
      usePermissionStoreHook().cacheOperate({
 | 
			
		||||
        mode: "delete",
 | 
			
		||||
        name: matched[matched.length - 1].name
 | 
			
		||||
        name
 | 
			
		||||
      });
 | 
			
		||||
      break;
 | 
			
		||||
    case "refresh":
 | 
			
		||||
      usePermissionStoreHook().cacheOperate({
 | 
			
		||||
        mode: "refresh",
 | 
			
		||||
        name
 | 
			
		||||
      });
 | 
			
		||||
      break;
 | 
			
		||||
    default:
 | 
			
		||||
      usePermissionStoreHook().cacheOperate({
 | 
			
		||||
        mode: "delete",
 | 
			
		||||
        name: matched[matched.length - 1].name
 | 
			
		||||
        name
 | 
			
		||||
      });
 | 
			
		||||
      useTimeoutFn(() => {
 | 
			
		||||
        matched.forEach(v => {
 | 
			
		||||
          usePermissionStoreHook().cacheOperate({ mode: "add", name: v.name });
 | 
			
		||||
        usePermissionStoreHook().cacheOperate({
 | 
			
		||||
          mode: "add",
 | 
			
		||||
          name
 | 
			
		||||
        });
 | 
			
		||||
      }, 100);
 | 
			
		||||
  }
 | 
			
		||||
@ -361,17 +359,24 @@ function hasAuth(value: string | Array<string>): boolean {
 | 
			
		||||
  return isAuths ? true : false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** 获取所有菜单中的第一个菜单(顶级菜单)*/
 | 
			
		||||
function getTopMenu(tag = false): menuType {
 | 
			
		||||
  const topMenu = usePermissionStoreHook().wholeMenus[0]?.children[0];
 | 
			
		||||
  tag && useMultiTagsStoreHook().handleTags("push", topMenu);
 | 
			
		||||
  return topMenu;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
  hasAuth,
 | 
			
		||||
  getAuths,
 | 
			
		||||
  ascending,
 | 
			
		||||
  filterTree,
 | 
			
		||||
  initRouter,
 | 
			
		||||
  getTopMenu,
 | 
			
		||||
  addPathMatch,
 | 
			
		||||
  isOneOfArray,
 | 
			
		||||
  getHistoryMode,
 | 
			
		||||
  addAsyncRoutes,
 | 
			
		||||
  delAliveRoutes,
 | 
			
		||||
  getParentPaths,
 | 
			
		||||
  findRouteByPath,
 | 
			
		||||
  handleAliveRoute,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { store } from "@/store";
 | 
			
		||||
import { appType } from "./types";
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { getConfig, responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
import { deviceDetection, storageLocal } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
export const useAppStore = defineStore({
 | 
			
		||||
@ -9,29 +9,32 @@ export const useAppStore = defineStore({
 | 
			
		||||
  state: (): appType => ({
 | 
			
		||||
    sidebar: {
 | 
			
		||||
      opened:
 | 
			
		||||
        storageLocal().getItem<StorageConfigs>("responsive-layout")
 | 
			
		||||
          ?.sidebarStatus ?? getConfig().SidebarStatus,
 | 
			
		||||
        storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
          `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
        )?.sidebarStatus ?? getConfig().SidebarStatus,
 | 
			
		||||
      withoutAnimation: false,
 | 
			
		||||
      isClickCollapse: false
 | 
			
		||||
    },
 | 
			
		||||
    // 这里的layout用于监听容器拖拉后恢复对应的导航模式
 | 
			
		||||
    layout:
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>("responsive-layout")?.layout ??
 | 
			
		||||
      getConfig().Layout,
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
      )?.layout ?? getConfig().Layout,
 | 
			
		||||
    device: deviceDetection() ? "mobile" : "desktop"
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getSidebarStatus() {
 | 
			
		||||
      return this.sidebar.opened;
 | 
			
		||||
    getSidebarStatus(state) {
 | 
			
		||||
      return state.sidebar.opened;
 | 
			
		||||
    },
 | 
			
		||||
    getDevice() {
 | 
			
		||||
      return this.device;
 | 
			
		||||
    getDevice(state) {
 | 
			
		||||
      return state.device;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    TOGGLE_SIDEBAR(opened?: boolean, resize?: string) {
 | 
			
		||||
      const layout =
 | 
			
		||||
        storageLocal().getItem<StorageConfigs>("responsive-layout");
 | 
			
		||||
      const layout = storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
      );
 | 
			
		||||
      if (opened && resize) {
 | 
			
		||||
        this.sidebar.withoutAnimation = true;
 | 
			
		||||
        this.sidebar.opened = true;
 | 
			
		||||
@ -46,7 +49,7 @@ export const useAppStore = defineStore({
 | 
			
		||||
        this.sidebar.isClickCollapse = !this.sidebar.opened;
 | 
			
		||||
        layout.sidebarStatus = this.sidebar.opened;
 | 
			
		||||
      }
 | 
			
		||||
      storageLocal().setItem("responsive-layout", layout);
 | 
			
		||||
      storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
 | 
			
		||||
    },
 | 
			
		||||
    async toggleSideBar(opened?: boolean, resize?: string) {
 | 
			
		||||
      await this.TOGGLE_SIDEBAR(opened, resize);
 | 
			
		||||
 | 
			
		||||
@ -1,27 +1,29 @@
 | 
			
		||||
import { store } from "@/store";
 | 
			
		||||
import { defineStore } from "pinia";
 | 
			
		||||
import { getConfig } from "@/config";
 | 
			
		||||
import { storageLocal } from "@pureadmin/utils";
 | 
			
		||||
import { getConfig, responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
 | 
			
		||||
export const useEpThemeStore = defineStore({
 | 
			
		||||
  id: "pure-epTheme",
 | 
			
		||||
  state: () => ({
 | 
			
		||||
    epThemeColor:
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>("responsive-layout")
 | 
			
		||||
        ?.epThemeColor ?? getConfig().EpThemeColor,
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
      )?.epThemeColor ?? getConfig().EpThemeColor,
 | 
			
		||||
    epTheme:
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>("responsive-layout")?.theme ??
 | 
			
		||||
      getConfig().Theme
 | 
			
		||||
      storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
      )?.theme ?? getConfig().Theme
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getEpThemeColor() {
 | 
			
		||||
      return this.epThemeColor;
 | 
			
		||||
    getEpThemeColor(state) {
 | 
			
		||||
      return state.epThemeColor;
 | 
			
		||||
    },
 | 
			
		||||
    /** 用于mix导航模式下hamburger-svg的fill属性 */
 | 
			
		||||
    fill() {
 | 
			
		||||
      if (this.epTheme === "light") {
 | 
			
		||||
    fill(state) {
 | 
			
		||||
      if (state.epTheme === "light") {
 | 
			
		||||
        return "#409eff";
 | 
			
		||||
      } else if (this.epTheme === "yellow") {
 | 
			
		||||
      } else if (state.epTheme === "yellow") {
 | 
			
		||||
        return "#d25f00";
 | 
			
		||||
      } else {
 | 
			
		||||
        return "#fff";
 | 
			
		||||
@ -30,13 +32,14 @@ export const useEpThemeStore = defineStore({
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    setEpThemeColor(newColor: string): void {
 | 
			
		||||
      const layout =
 | 
			
		||||
        storageLocal().getItem<StorageConfigs>("responsive-layout");
 | 
			
		||||
      const layout = storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
        `${responsiveStorageNameSpace()}layout`
 | 
			
		||||
      );
 | 
			
		||||
      this.epTheme = layout?.theme;
 | 
			
		||||
      this.epThemeColor = newColor;
 | 
			
		||||
      if (!layout) return;
 | 
			
		||||
      layout.epThemeColor = newColor;
 | 
			
		||||
      storageLocal().setItem("responsive-layout", layout);
 | 
			
		||||
      storageLocal().setItem(`${responsiveStorageNameSpace()}layout`, layout);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
@ -2,37 +2,47 @@ import { defineStore } from "pinia";
 | 
			
		||||
import { store } from "@/store";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
import { multiType, positionType } from "./types";
 | 
			
		||||
import { responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
import { isEqual, isBoolean, isUrl, storageLocal } from "@pureadmin/utils";
 | 
			
		||||
 | 
			
		||||
export const useMultiTagsStore = defineStore({
 | 
			
		||||
  id: "pure-multiTags",
 | 
			
		||||
  state: () => ({
 | 
			
		||||
    // 存储标签页信息(路由信息)
 | 
			
		||||
    multiTags: storageLocal().getItem<StorageConfigs>("responsive-configure")
 | 
			
		||||
      ?.multiTagsCache
 | 
			
		||||
      ? storageLocal().getItem<StorageConfigs>("responsive-tags")
 | 
			
		||||
    multiTags: storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
      `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
    )?.multiTagsCache
 | 
			
		||||
      ? storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
          `${responsiveStorageNameSpace()}tags`
 | 
			
		||||
        )
 | 
			
		||||
      : [...routerArrays],
 | 
			
		||||
    multiTagsCache: storageLocal().getItem<StorageConfigs>(
 | 
			
		||||
      "responsive-configure"
 | 
			
		||||
      `${responsiveStorageNameSpace()}configure`
 | 
			
		||||
    )?.multiTagsCache
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getMultiTagsCache() {
 | 
			
		||||
      return this.multiTagsCache;
 | 
			
		||||
    getMultiTagsCache(state) {
 | 
			
		||||
      return state.multiTagsCache;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    multiTagsCacheChange(multiTagsCache: boolean) {
 | 
			
		||||
      this.multiTagsCache = multiTagsCache;
 | 
			
		||||
      if (multiTagsCache) {
 | 
			
		||||
        storageLocal().setItem("responsive-tags", this.multiTags);
 | 
			
		||||
        storageLocal().setItem(
 | 
			
		||||
          `${responsiveStorageNameSpace()}tags`,
 | 
			
		||||
          this.multiTags
 | 
			
		||||
        );
 | 
			
		||||
      } else {
 | 
			
		||||
        storageLocal().removeItem("responsive-tags");
 | 
			
		||||
        storageLocal().removeItem(`${responsiveStorageNameSpace()}tags`);
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    tagsCache(multiTags) {
 | 
			
		||||
      this.getMultiTagsCache &&
 | 
			
		||||
        storageLocal().setItem("responsive-tags", multiTags);
 | 
			
		||||
        storageLocal().setItem(
 | 
			
		||||
          `${responsiveStorageNameSpace()}tags`,
 | 
			
		||||
          multiTags
 | 
			
		||||
        );
 | 
			
		||||
    },
 | 
			
		||||
    handleTags<T>(
 | 
			
		||||
      mode: string,
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,8 @@ import { defineStore } from "pinia";
 | 
			
		||||
import { store } from "@/store";
 | 
			
		||||
import { cacheType } from "./types";
 | 
			
		||||
import { constantMenus } from "@/router";
 | 
			
		||||
import { getKeyList } from "@pureadmin/utils";
 | 
			
		||||
import { useMultiTagsStoreHook } from "./multiTags";
 | 
			
		||||
import { ascending, filterTree, filterNoPermissionTree } from "@/router/utils";
 | 
			
		||||
 | 
			
		||||
export const usePermissionStore = defineStore({
 | 
			
		||||
@ -22,17 +24,32 @@ export const usePermissionStore = defineStore({
 | 
			
		||||
      );
 | 
			
		||||
    },
 | 
			
		||||
    cacheOperate({ mode, name }: cacheType) {
 | 
			
		||||
      const delIndex = this.cachePageList.findIndex(v => v === name);
 | 
			
		||||
      switch (mode) {
 | 
			
		||||
        case "refresh":
 | 
			
		||||
          this.cachePageList = this.cachePageList.filter(v => v !== name);
 | 
			
		||||
          break;
 | 
			
		||||
        case "add":
 | 
			
		||||
          this.cachePageList.push(name);
 | 
			
		||||
          this.cachePageList = [...new Set(this.cachePageList)];
 | 
			
		||||
          break;
 | 
			
		||||
        case "delete":
 | 
			
		||||
          // eslint-disable-next-line no-case-declarations
 | 
			
		||||
          const delIndex = this.cachePageList.findIndex(v => v === name);
 | 
			
		||||
          delIndex !== -1 && this.cachePageList.splice(delIndex, 1);
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      /** 监听缓存页面是否存在于标签页,不存在则删除 */
 | 
			
		||||
      (() => {
 | 
			
		||||
        let cacheLength = this.cachePageList.length;
 | 
			
		||||
        const nameList = getKeyList(useMultiTagsStoreHook().multiTags, "name");
 | 
			
		||||
        while (cacheLength > 0) {
 | 
			
		||||
          nameList.findIndex(v => v === this.cachePageList[cacheLength - 1]) ===
 | 
			
		||||
            -1 &&
 | 
			
		||||
            this.cachePageList.splice(
 | 
			
		||||
              this.cachePageList.indexOf(this.cachePageList[cacheLength - 1]),
 | 
			
		||||
              1
 | 
			
		||||
            );
 | 
			
		||||
          cacheLength--;
 | 
			
		||||
        }
 | 
			
		||||
      })();
 | 
			
		||||
    },
 | 
			
		||||
    /** 清空缓存页面 */
 | 
			
		||||
    clearAllCachePage() {
 | 
			
		||||
 | 
			
		||||
@ -11,20 +11,19 @@ export const useSettingStore = defineStore({
 | 
			
		||||
    hiddenSideBar: getConfig().HiddenSideBar
 | 
			
		||||
  }),
 | 
			
		||||
  getters: {
 | 
			
		||||
    getTitle() {
 | 
			
		||||
      return this.title;
 | 
			
		||||
    getTitle(state) {
 | 
			
		||||
      return state.title;
 | 
			
		||||
    },
 | 
			
		||||
    getFixedHeader() {
 | 
			
		||||
      return this.fixedHeader;
 | 
			
		||||
    getFixedHeader(state) {
 | 
			
		||||
      return state.fixedHeader;
 | 
			
		||||
    },
 | 
			
		||||
    getHiddenSideBar() {
 | 
			
		||||
      return this.HiddenSideBar;
 | 
			
		||||
    getHiddenSideBar(state) {
 | 
			
		||||
      return state.hiddenSideBar;
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    CHANGE_SETTING({ key, value }) {
 | 
			
		||||
      // eslint-disable-next-line no-prototype-builtins
 | 
			
		||||
      if (this.hasOwnProperty(key)) {
 | 
			
		||||
      if (Reflect.has(this, key)) {
 | 
			
		||||
        this[key] = value;
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
@ -30,8 +30,8 @@ html.dark {
 | 
			
		||||
  .tags-view {
 | 
			
		||||
    .arrow-left,
 | 
			
		||||
    .arrow-right {
 | 
			
		||||
      box-shadow: none;
 | 
			
		||||
      border-right: 1px solid $border-style;
 | 
			
		||||
      box-shadow: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .arrow-right {
 | 
			
		||||
@ -44,6 +44,7 @@ html.dark {
 | 
			
		||||
    .el-divider__text {
 | 
			
		||||
      --el-bg-color: var(--el-bg-color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-divider--horizontal {
 | 
			
		||||
      border-top: none;
 | 
			
		||||
    }
 | 
			
		||||
@ -53,14 +54,18 @@ html.dark {
 | 
			
		||||
  .el-table__cell {
 | 
			
		||||
    background: var(--el-bg-color);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-card {
 | 
			
		||||
    --el-card-bg-color: var(--el-bg-color);
 | 
			
		||||
 | 
			
		||||
    // border: none !important;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-backtop {
 | 
			
		||||
    --el-backtop-bg-color: var(--el-color-primary-light-9);
 | 
			
		||||
    --el-backtop-hover-bg-color: var(--el-color-primary);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .el-dropdown-menu__item:not(.is-disabled):hover {
 | 
			
		||||
    background: transparent;
 | 
			
		||||
  }
 | 
			
		||||
@ -72,18 +77,18 @@ html.dark {
 | 
			
		||||
    &.el-message-box__close,
 | 
			
		||||
    &.el-notification__closeBtn {
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: rgba(255, 255, 255, 0.85) !important;
 | 
			
		||||
        background-color: rgba(255, 255, 255, 0.12);
 | 
			
		||||
        color: rgb(255 255 255 / 85%) !important;
 | 
			
		||||
        background-color: rgb(255 255 255 / 12%);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,非暗黑模式在 src/style/element-plus.scss 文件进行了适配 */
 | 
			
		||||
  .pure-message {
 | 
			
		||||
    background-color: rgb(36 37 37) !important;
 | 
			
		||||
    background-image: initial !important;
 | 
			
		||||
    background-color: rgb(36, 37, 37) !important;
 | 
			
		||||
    box-shadow: rgb(13 13 13 / 12%) 0px 3px 6px -4px,
 | 
			
		||||
      rgb(13 13 13 / 8%) 0px 6px 16px 0px, rgb(13 13 13 / 5%) 0px 9px 28px 8px !important;
 | 
			
		||||
    box-shadow: rgb(13 13 13 / 12%) 0 3px 6px -4px,
 | 
			
		||||
      rgb(13 13 13 / 8%) 0 6px 16px 0, rgb(13 13 13 / 5%) 0 9px 28px 8px !important;
 | 
			
		||||
 | 
			
		||||
    & .el-message__content {
 | 
			
		||||
      color: $color-white !important;
 | 
			
		||||
@ -93,8 +98,8 @@ html.dark {
 | 
			
		||||
 | 
			
		||||
    & .el-message__closeBtn {
 | 
			
		||||
      &:hover {
 | 
			
		||||
        color: rgba(255, 255, 255, 0.85);
 | 
			
		||||
        background-color: rgba(255, 255, 255, 0.12);
 | 
			
		||||
        color: rgb(255 255 255 / 85%);
 | 
			
		||||
        background-color: rgb(255 255 255 / 12%);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -78,6 +78,7 @@
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.el-icon {
 | 
			
		||||
  &.el-dialog__close,
 | 
			
		||||
  &.el-drawer__close,
 | 
			
		||||
@ -85,22 +86,23 @@
 | 
			
		||||
  &.el-notification__closeBtn {
 | 
			
		||||
    width: 24px;
 | 
			
		||||
    height: 24px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    transition: background-color 0.2s, color 0.2s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      color: rgba(0, 0, 0, 0.88) !important;
 | 
			
		||||
      background-color: rgba(0, 0, 0, 0.06);
 | 
			
		||||
      color: rgb(0 0 0 / 88%) !important;
 | 
			
		||||
      text-decoration: none;
 | 
			
		||||
      background-color: rgb(0 0 0 / 6%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* 克隆并自定义 ElMessage 样式,不会影响 ElMessage 原本样式,在 src/utils/message.ts 中调用自定义样式 ElMessage 方法即可,暗黑模式在 src/style/dark.scss 文件进行了适配 */
 | 
			
		||||
.pure-message {
 | 
			
		||||
  border-width: 0 !important;
 | 
			
		||||
  background: #fff !important;
 | 
			
		||||
  padding: 10px 13px !important;
 | 
			
		||||
  background: #fff !important;
 | 
			
		||||
  border-width: 0 !important;
 | 
			
		||||
  box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014,
 | 
			
		||||
    0 9px 28px 8px #0000000d !important;
 | 
			
		||||
 | 
			
		||||
@ -119,13 +121,13 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  & .el-message__closeBtn {
 | 
			
		||||
    outline: none;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    right: 9px !important;
 | 
			
		||||
    border-radius: 4px;
 | 
			
		||||
    outline: none;
 | 
			
		||||
    transition: background-color 0.2s, color 0.2s;
 | 
			
		||||
 | 
			
		||||
    &:hover {
 | 
			
		||||
      background-color: rgba(0, 0, 0, 0.06);
 | 
			
		||||
      background-color: rgb(0 0 0 / 6%);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,7 @@
 | 
			
		||||
@import "./mixin.scss";
 | 
			
		||||
@import "./transition.scss";
 | 
			
		||||
@import "./element-plus.scss";
 | 
			
		||||
@import "./sidebar.scss";
 | 
			
		||||
@import "./dark.scss";
 | 
			
		||||
@import "./transition";
 | 
			
		||||
@import "./element-plus";
 | 
			
		||||
@import "./sidebar";
 | 
			
		||||
@import "./dark";
 | 
			
		||||
 | 
			
		||||
/* 自定义全局 CssVar */
 | 
			
		||||
:root {
 | 
			
		||||
 | 
			
		||||
@ -1,28 +0,0 @@
 | 
			
		||||
@mixin clearfix {
 | 
			
		||||
  &::after {
 | 
			
		||||
    content: "";
 | 
			
		||||
    display: table;
 | 
			
		||||
    clear: both;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin relative {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin scrollBar {
 | 
			
		||||
  &::-webkit-scrollbar-track-piece {
 | 
			
		||||
    background: #d3dce6;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &::-webkit-scrollbar {
 | 
			
		||||
    width: 6px;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  &::-webkit-scrollbar-thumb {
 | 
			
		||||
    background: #99a9bf;
 | 
			
		||||
    border-radius: 20px;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -2,9 +2,9 @@
 | 
			
		||||
::before,
 | 
			
		||||
::after {
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  border-width: 0;
 | 
			
		||||
  border-style: solid;
 | 
			
		||||
  border-color: currentColor;
 | 
			
		||||
  border-style: solid;
 | 
			
		||||
  border-width: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#app {
 | 
			
		||||
@ -13,25 +13,24 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
html {
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  -webkit-text-size-adjust: 100%;
 | 
			
		||||
  -moz-tab-size: 4;
 | 
			
		||||
  tab-size: 4;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
  line-height: 1.5;
 | 
			
		||||
  tab-size: 4;
 | 
			
		||||
  text-size-adjust: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
body {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  line-height: inherit;
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
 | 
			
		||||
    "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
 | 
			
		||||
  line-height: inherit;
 | 
			
		||||
  -moz-osx-font-smoothing: grayscale;
 | 
			
		||||
  -webkit-font-smoothing: antialiased;
 | 
			
		||||
  text-rendering: optimizelegibility;
 | 
			
		||||
  font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB",
 | 
			
		||||
    "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
hr {
 | 
			
		||||
@ -69,9 +68,9 @@ small {
 | 
			
		||||
 | 
			
		||||
sub,
 | 
			
		||||
sup {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  font-size: 75%;
 | 
			
		||||
  line-height: 0;
 | 
			
		||||
  position: relative;
 | 
			
		||||
  vertical-align: baseline;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -85,8 +84,8 @@ sup {
 | 
			
		||||
 | 
			
		||||
table {
 | 
			
		||||
  text-indent: 0;
 | 
			
		||||
  border-color: inherit;
 | 
			
		||||
  border-collapse: collapse;
 | 
			
		||||
  border-color: inherit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
@ -94,12 +93,12 @@ input,
 | 
			
		||||
optgroup,
 | 
			
		||||
select,
 | 
			
		||||
textarea {
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  font-family: inherit;
 | 
			
		||||
  font-size: 100%;
 | 
			
		||||
  line-height: inherit;
 | 
			
		||||
  color: inherit;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
@ -160,8 +159,8 @@ pre {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fieldset {
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
legend {
 | 
			
		||||
@ -171,9 +170,9 @@ legend {
 | 
			
		||||
ol,
 | 
			
		||||
ul,
 | 
			
		||||
menu {
 | 
			
		||||
  list-style: none;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
  list-style: none;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
textarea {
 | 
			
		||||
@ -182,8 +181,8 @@ textarea {
 | 
			
		||||
 | 
			
		||||
input::placeholder,
 | 
			
		||||
textarea::placeholder {
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
  color: #9ca3af;
 | 
			
		||||
  opacity: 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
button,
 | 
			
		||||
@ -238,9 +237,9 @@ a:active {
 | 
			
		||||
a,
 | 
			
		||||
a:focus,
 | 
			
		||||
a:hover {
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
  color: inherit;
 | 
			
		||||
  text-decoration: none;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
div:focus {
 | 
			
		||||
@ -249,11 +248,11 @@ div:focus {
 | 
			
		||||
 | 
			
		||||
.clearfix {
 | 
			
		||||
  &::after {
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    display: block;
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
    content: " ";
 | 
			
		||||
    clear: both;
 | 
			
		||||
    height: 0;
 | 
			
		||||
    clear: both;
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
    visibility: hidden;
 | 
			
		||||
    content: " ";
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,20 +2,21 @@
 | 
			
		||||
@mixin merge-style($sideBarWidth) {
 | 
			
		||||
  $menuActiveText: #7a80b4;
 | 
			
		||||
 | 
			
		||||
  @media screen and (min-width: 150px) and (max-width: 420px) {
 | 
			
		||||
  @media screen and (width >= 150px) and (width <= 420px) {
 | 
			
		||||
    .app-main-nofixed-header {
 | 
			
		||||
      overflow-y: hidden;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  @media screen and (min-width: 420px) {
 | 
			
		||||
 | 
			
		||||
  @media screen and (width >= 420px) {
 | 
			
		||||
    .app-main-nofixed-header {
 | 
			
		||||
      overflow: hidden;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .sub-menu-icon {
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    margin-right: 5px;
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
 | 
			
		||||
    svg {
 | 
			
		||||
      width: 18px;
 | 
			
		||||
@ -24,26 +25,27 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .set-icon {
 | 
			
		||||
    height: 48px;
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    display: flex;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    width: 40px;
 | 
			
		||||
    height: 48px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .main-container {
 | 
			
		||||
    position: relative;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
    min-height: 100%;
 | 
			
		||||
    /* main-content 属性动画 */
 | 
			
		||||
    transition: margin-left var(--pure-transition-duration);
 | 
			
		||||
    margin-left: $sideBarWidth;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    background: #f0f2f5;
 | 
			
		||||
 | 
			
		||||
    /* main-content 属性动画 */
 | 
			
		||||
    transition: margin-left var(--pure-transition-duration);
 | 
			
		||||
 | 
			
		||||
    .el-scrollbar__wrap {
 | 
			
		||||
      overflow: auto;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      overflow: auto;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -53,6 +55,7 @@
 | 
			
		||||
    right: 0;
 | 
			
		||||
    z-index: 998;
 | 
			
		||||
    width: calc(100% - 210px);
 | 
			
		||||
 | 
			
		||||
    /* fixed-header 属性左上角动画 */
 | 
			
		||||
    transition: width var(--pure-transition-duration);
 | 
			
		||||
  }
 | 
			
		||||
@ -70,20 +73,21 @@
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .sidebar-container {
 | 
			
		||||
    /* 展开动画 */
 | 
			
		||||
    transition: width var(--pure-transition-duration);
 | 
			
		||||
    width: $sideBarWidth !important;
 | 
			
		||||
    background: $menuBg;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
    top: 0;
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
    left: 0;
 | 
			
		||||
    z-index: 1001;
 | 
			
		||||
    width: $sideBarWidth !important;
 | 
			
		||||
    height: 100%;
 | 
			
		||||
    overflow: hidden;
 | 
			
		||||
    font-size: 0;
 | 
			
		||||
    background: $menuBg;
 | 
			
		||||
    box-shadow: 0 0 1px #888;
 | 
			
		||||
 | 
			
		||||
    /* 展开动画 */
 | 
			
		||||
    transition: width var(--pure-transition-duration);
 | 
			
		||||
 | 
			
		||||
    .scrollbar-wrapper {
 | 
			
		||||
      overflow-x: hidden !important;
 | 
			
		||||
    }
 | 
			
		||||
@ -101,6 +105,7 @@
 | 
			
		||||
        /* logo: 48px、leftCollapse: 40px、leftCollapse-shadow: 4px */
 | 
			
		||||
        height: calc(100% - 92px);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .el-scrollbar.mobile {
 | 
			
		||||
        height: 100%;
 | 
			
		||||
      }
 | 
			
		||||
@ -113,15 +118,15 @@
 | 
			
		||||
    a {
 | 
			
		||||
      display: inline-block;
 | 
			
		||||
      display: flex;
 | 
			
		||||
      padding-left: 10px;
 | 
			
		||||
      flex-wrap: wrap;
 | 
			
		||||
      width: 100%;
 | 
			
		||||
      padding-left: 10px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu {
 | 
			
		||||
      border: none;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      background-color: transparent !important;
 | 
			
		||||
      border: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item,
 | 
			
		||||
@ -158,8 +163,8 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-active {
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
      color: $subMenuActiveText !important;
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu > * {
 | 
			
		||||
@ -168,22 +173,19 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu::before {
 | 
			
		||||
      content: "";
 | 
			
		||||
      clear: both;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: 8px;
 | 
			
		||||
      right: 8px;
 | 
			
		||||
      inset: 0 8px;
 | 
			
		||||
      margin: 4px 0;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
      clear: both;
 | 
			
		||||
      content: "";
 | 
			
		||||
      background: var(--el-color-primary) !important;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu .el-menu--inline .el-sub-menu__title,
 | 
			
		||||
    & .el-sub-menu .el-menu-item {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      min-width: $sideBarWidth !important;
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      background-color: $subMenuBg !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -196,21 +198,21 @@
 | 
			
		||||
      left: 0;
 | 
			
		||||
      width: 2px;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      background-color: $menuActiveBefore;
 | 
			
		||||
      content: "";
 | 
			
		||||
      clear: both;
 | 
			
		||||
      content: "";
 | 
			
		||||
      background-color: $menuActiveBefore;
 | 
			
		||||
      transition: all var(--pure-transition-duration) ease-in-out;
 | 
			
		||||
      transform: translateY(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu--collapse .outer-most.el-sub-menu > .el-sub-menu__title::before {
 | 
			
		||||
      content: "";
 | 
			
		||||
      display: block;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      height: 0;
 | 
			
		||||
      width: 3px;
 | 
			
		||||
      transform: translateY(-50%);
 | 
			
		||||
      top: 50%;
 | 
			
		||||
      display: block;
 | 
			
		||||
      width: 3px;
 | 
			
		||||
      height: 0;
 | 
			
		||||
      content: "";
 | 
			
		||||
      transform: translateY(-50%);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* 无子集的激活菜单背景 */
 | 
			
		||||
@ -218,17 +220,15 @@
 | 
			
		||||
      z-index: 1;
 | 
			
		||||
      color: #fff;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-active.submenu-title-noDropdown.outer-most::before {
 | 
			
		||||
      content: "";
 | 
			
		||||
      clear: both;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: 8px;
 | 
			
		||||
      right: 8px;
 | 
			
		||||
      inset: 0 8px;
 | 
			
		||||
      margin: 4px 0;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
      clear: both;
 | 
			
		||||
      content: "";
 | 
			
		||||
      background: var(--el-color-primary) !important;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -261,8 +261,8 @@
 | 
			
		||||
 | 
			
		||||
    /* 子菜单中还有子菜单 */
 | 
			
		||||
    .el-menu .el-sub-menu__title {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      min-width: $sideBarWidth !important;
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      background-color: $subMenuBg !important;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -279,8 +279,8 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-active {
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
      color: $subMenuActiveText !important;
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu > * {
 | 
			
		||||
@ -289,15 +289,12 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu::before {
 | 
			
		||||
      content: "";
 | 
			
		||||
      clear: both;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: 8px;
 | 
			
		||||
      right: 8px;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
      inset: 0 8px;
 | 
			
		||||
      clear: both;
 | 
			
		||||
      content: "";
 | 
			
		||||
      background: var(--el-color-primary) !important;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item,
 | 
			
		||||
@ -345,8 +342,8 @@
 | 
			
		||||
 | 
			
		||||
    /* 子菜单中还有子菜单 */
 | 
			
		||||
    .el-menu .el-sub-menu__title {
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      min-width: $sideBarWidth !important;
 | 
			
		||||
      font-size: 12px;
 | 
			
		||||
      background-color: $subMenuBg !important;
 | 
			
		||||
 | 
			
		||||
      &:hover {
 | 
			
		||||
@ -371,8 +368,8 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active {
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
      color: $subMenuActiveText !important;
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu > * {
 | 
			
		||||
@ -381,68 +378,65 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item.is-active.nest-menu::before {
 | 
			
		||||
      content: "";
 | 
			
		||||
      clear: both;
 | 
			
		||||
      position: absolute;
 | 
			
		||||
      left: 5px;
 | 
			
		||||
      right: 5px;
 | 
			
		||||
      top: 0;
 | 
			
		||||
      bottom: 0;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
      inset: 0 5px;
 | 
			
		||||
      clear: both;
 | 
			
		||||
      content: "";
 | 
			
		||||
      background: var(--el-color-primary) !important;
 | 
			
		||||
      border-radius: 3px;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .horizontal-header {
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: space-around;
 | 
			
		||||
    background: $menuBg;
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    height: 48px;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    background: $menuBg;
 | 
			
		||||
 | 
			
		||||
    .horizontal-header-left {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      width: auto;
 | 
			
		||||
      min-width: 200px;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      padding-left: 10px;
 | 
			
		||||
      cursor: pointer;
 | 
			
		||||
      transition: all var(--pure-transition-duration) ease;
 | 
			
		||||
 | 
			
		||||
      img {
 | 
			
		||||
        height: 32px;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        height: 32px;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      span {
 | 
			
		||||
        height: 32px;
 | 
			
		||||
        line-height: 32px;
 | 
			
		||||
        margin: 2px 0 0 12px;
 | 
			
		||||
        color: $subMenuActiveText;
 | 
			
		||||
        display: inline-block;
 | 
			
		||||
        height: 32px;
 | 
			
		||||
        margin: 2px 0 0 12px;
 | 
			
		||||
        overflow: hidden;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        font-size: 18px;
 | 
			
		||||
        font-weight: 600;
 | 
			
		||||
        line-height: 32px;
 | 
			
		||||
        color: $subMenuActiveText;
 | 
			
		||||
        text-overflow: ellipsis;
 | 
			
		||||
        white-space: nowrap;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .horizontal-header-menu {
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      min-width: 0;
 | 
			
		||||
      flex: 1;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      min-width: 0;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .horizontal-header-right {
 | 
			
		||||
      display: flex;
 | 
			
		||||
      min-width: 340px;
 | 
			
		||||
      align-items: center;
 | 
			
		||||
      color: $subMenuActiveText;
 | 
			
		||||
      justify-content: flex-end;
 | 
			
		||||
      min-width: 340px;
 | 
			
		||||
      color: $subMenuActiveText;
 | 
			
		||||
 | 
			
		||||
      /* 搜索 */
 | 
			
		||||
      .search-container,
 | 
			
		||||
@ -463,13 +457,13 @@
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      .el-dropdown-link {
 | 
			
		||||
        height: 48px;
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
        display: flex;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
        align-items: center;
 | 
			
		||||
        justify-content: space-around;
 | 
			
		||||
        height: 48px;
 | 
			
		||||
        padding: 10px;
 | 
			
		||||
        color: $subMenuActiveText;
 | 
			
		||||
        cursor: pointer;
 | 
			
		||||
 | 
			
		||||
        p {
 | 
			
		||||
          font-size: 14px;
 | 
			
		||||
@ -484,10 +478,10 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu {
 | 
			
		||||
      border: none;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      width: 100% !important;
 | 
			
		||||
      height: 100%;
 | 
			
		||||
      background-color: transparent;
 | 
			
		||||
      border: none;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .el-menu-item,
 | 
			
		||||
@ -521,8 +515,8 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .is-active {
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
      color: $subMenuActiveText !important;
 | 
			
		||||
      transition: color 0.3s;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -542,8 +536,8 @@
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sidebar-container {
 | 
			
		||||
      transition: transform var(--pure-transition-duration);
 | 
			
		||||
      width: $sideBarWidth;
 | 
			
		||||
      transition: transform var(--pure-transition-duration);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    &.hideSidebar {
 | 
			
		||||
@ -558,6 +552,7 @@
 | 
			
		||||
 | 
			
		||||
body[layout="vertical"] {
 | 
			
		||||
  $sideBarWidth: 210px;
 | 
			
		||||
 | 
			
		||||
  @include merge-style($sideBarWidth);
 | 
			
		||||
 | 
			
		||||
  .el-menu--collapse {
 | 
			
		||||
@ -575,8 +570,8 @@ body[layout="vertical"] {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sidebar-container {
 | 
			
		||||
      transition: width var(--pure-transition-duration);
 | 
			
		||||
      width: 54px !important;
 | 
			
		||||
      transition: width var(--pure-transition-duration);
 | 
			
		||||
 | 
			
		||||
      .is-active.submenu-title-noDropdown.outer-most {
 | 
			
		||||
        background: transparent !important;
 | 
			
		||||
@ -592,8 +587,8 @@ body[layout="vertical"] {
 | 
			
		||||
      .el-sub-menu {
 | 
			
		||||
        & > .el-sub-menu__title {
 | 
			
		||||
          & > span {
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            visibility: visible;
 | 
			
		||||
          }
 | 
			
		||||
@ -630,6 +625,7 @@ body[layout="vertical"] {
 | 
			
		||||
 | 
			
		||||
body[layout="horizontal"] {
 | 
			
		||||
  $sideBarWidth: 0;
 | 
			
		||||
 | 
			
		||||
  @include merge-style($sideBarWidth);
 | 
			
		||||
 | 
			
		||||
  .fixed-header,
 | 
			
		||||
@ -644,6 +640,7 @@ body[layout="horizontal"] {
 | 
			
		||||
 | 
			
		||||
body[layout="mix"] {
 | 
			
		||||
  $sideBarWidth: 210px;
 | 
			
		||||
 | 
			
		||||
  @include merge-style($sideBarWidth);
 | 
			
		||||
 | 
			
		||||
  .el-menu--collapse {
 | 
			
		||||
@ -661,8 +658,8 @@ body[layout="mix"] {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .sidebar-container {
 | 
			
		||||
      transition: width var(--pure-transition-duration);
 | 
			
		||||
      width: 54px !important;
 | 
			
		||||
      transition: width var(--pure-transition-duration);
 | 
			
		||||
 | 
			
		||||
      .is-active.submenu-title-noDropdown.outer-most {
 | 
			
		||||
        background: transparent !important;
 | 
			
		||||
@ -678,9 +675,10 @@ body[layout="mix"] {
 | 
			
		||||
      .el-sub-menu {
 | 
			
		||||
        & > .el-sub-menu__title {
 | 
			
		||||
          padding: 0;
 | 
			
		||||
 | 
			
		||||
          & > span {
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            width: 100%;
 | 
			
		||||
            height: 100%;
 | 
			
		||||
            text-align: center;
 | 
			
		||||
            visibility: visible;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,7 @@
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-leave-active {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  transition: all 0.3s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -40,10 +41,6 @@
 | 
			
		||||
  transform: translateX(20px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.breadcrumb-leave-active {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @description 重置el-menu的展开收起动画时长
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -60,7 +60,7 @@ class PureHttp {
 | 
			
		||||
  /** 请求拦截 */
 | 
			
		||||
  private httpInterceptorsRequest(): void {
 | 
			
		||||
    PureHttp.axiosInstance.interceptors.request.use(
 | 
			
		||||
      async (config: PureHttpRequestConfig) => {
 | 
			
		||||
      async (config: PureHttpRequestConfig): Promise<any> => {
 | 
			
		||||
        // 开启进度条动画
 | 
			
		||||
        NProgress.start();
 | 
			
		||||
        // 优先判断post/get等方法是否传入回掉,否则执行初始化设置等回掉
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,10 @@
 | 
			
		||||
import { App } from "vue";
 | 
			
		||||
import Storage from "responsive-storage";
 | 
			
		||||
import { routerArrays } from "@/layout/types";
 | 
			
		||||
 | 
			
		||||
const nameSpace = "responsive-";
 | 
			
		||||
import { responsiveStorageNameSpace } from "@/config";
 | 
			
		||||
 | 
			
		||||
export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
 | 
			
		||||
  const nameSpace = responsiveStorageNameSpace();
 | 
			
		||||
  const configObj = Object.assign(
 | 
			
		||||
    {
 | 
			
		||||
      // layout模式以及主题
 | 
			
		||||
@ -27,7 +27,7 @@ export const injectResponsiveStorage = (app: App, config: ServerConfigs) => {
 | 
			
		||||
    },
 | 
			
		||||
    config.MultiTagsCache
 | 
			
		||||
      ? {
 | 
			
		||||
          // 默认显示首页tag
 | 
			
		||||
          // 默认显示顶级菜单tag
 | 
			
		||||
          tags: Storage.getData("tags", nameSpace) ?? routerArrays
 | 
			
		||||
        }
 | 
			
		||||
      : {}
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noAccess from "@/assets/status/403.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "403"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noExist from "@/assets/status/404.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "404"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,12 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { useRouter } from "vue-router";
 | 
			
		||||
import noServer from "@/assets/status/500.svg?component";
 | 
			
		||||
 | 
			
		||||
defineOptions({
 | 
			
		||||
  name: "500"
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const router = useRouter();
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
@ -46,7 +49,7 @@ defineOptions({
 | 
			
		||||
      </p>
 | 
			
		||||
      <el-button
 | 
			
		||||
        type="primary"
 | 
			
		||||
        @click="$router.push('/')"
 | 
			
		||||
        @click="router.push('/')"
 | 
			
		||||
        v-motion
 | 
			
		||||
        :initial="{
 | 
			
		||||
          opacity: 0,
 | 
			
		||||
 | 
			
		||||
@ -7,11 +7,11 @@ import { useNav } from "@/layout/hooks/useNav";
 | 
			
		||||
import type { FormInstance } from "element-plus";
 | 
			
		||||
import { useLayout } from "@/layout/hooks/useLayout";
 | 
			
		||||
import { useUserStoreHook } from "@/store/modules/user";
 | 
			
		||||
import { initRouter, getTopMenu } from "@/router/utils";
 | 
			
		||||
import { bg, avatar, illustration } from "./utils/static";
 | 
			
		||||
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
 | 
			
		||||
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
 | 
			
		||||
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
 | 
			
		||||
import { initRouter } from "@/router/utils";
 | 
			
		||||
 | 
			
		||||
import dayIcon from "@/assets/svg/day.svg?component";
 | 
			
		||||
import darkIcon from "@/assets/svg/dark.svg?component";
 | 
			
		||||
@ -48,7 +48,7 @@ const onLogin = async (formEl: FormInstance | undefined) => {
 | 
			
		||||
          if (res.success) {
 | 
			
		||||
            // 获取后端路由
 | 
			
		||||
            initRouter().then(() => {
 | 
			
		||||
              router.push("/");
 | 
			
		||||
              router.push(getTopMenu(true).path);
 | 
			
		||||
              message("登录成功", { type: "success" });
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
@ -1,20 +1,39 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  root: true,
 | 
			
		||||
  plugins: ["stylelint-order"],
 | 
			
		||||
  customSyntax: "postcss-html",
 | 
			
		||||
  extends: ["stylelint-config-standard", "stylelint-config-prettier"],
 | 
			
		||||
  extends: [
 | 
			
		||||
    "stylelint-config-standard",
 | 
			
		||||
    "stylelint-config-html/vue",
 | 
			
		||||
    "stylelint-config-recess-order"
 | 
			
		||||
  ],
 | 
			
		||||
  plugins: ["stylelint-order", "stylelint-prettier", "stylelint-scss"],
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: ["**/*.(css|html|vue)"],
 | 
			
		||||
      customSyntax: "postcss-html"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      files: ["*.scss", "**/*.scss"],
 | 
			
		||||
      customSyntax: "postcss-scss",
 | 
			
		||||
      extends: [
 | 
			
		||||
        "stylelint-config-standard-scss",
 | 
			
		||||
        "stylelint-config-recommended-vue/scss"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  rules: {
 | 
			
		||||
    "selector-class-pattern": null,
 | 
			
		||||
    "no-descending-specificity": null,
 | 
			
		||||
    "scss/dollar-variable-pattern": null,
 | 
			
		||||
    "selector-pseudo-class-no-unknown": [
 | 
			
		||||
      true,
 | 
			
		||||
      {
 | 
			
		||||
        ignorePseudoClasses: ["global"]
 | 
			
		||||
        ignorePseudoClasses: ["deep", "global"]
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "selector-pseudo-element-no-unknown": [
 | 
			
		||||
      true,
 | 
			
		||||
      {
 | 
			
		||||
        ignorePseudoElements: ["v-deep"]
 | 
			
		||||
        ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "at-rule-no-unknown": [
 | 
			
		||||
@ -30,17 +49,11 @@ module.exports = {
 | 
			
		||||
          "if",
 | 
			
		||||
          "each",
 | 
			
		||||
          "include",
 | 
			
		||||
          "mixin"
 | 
			
		||||
          "mixin",
 | 
			
		||||
          "use"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "no-empty-source": null,
 | 
			
		||||
    "named-grid-areas-no-invalid": null,
 | 
			
		||||
    "unicode-bom": "never",
 | 
			
		||||
    "no-descending-specificity": null,
 | 
			
		||||
    "font-family-no-missing-generic-family-keyword": null,
 | 
			
		||||
    "declaration-colon-space-after": "always-single-line",
 | 
			
		||||
    "declaration-colon-space-before": "never",
 | 
			
		||||
    "rule-empty-line-before": [
 | 
			
		||||
      "always",
 | 
			
		||||
      {
 | 
			
		||||
@ -67,26 +80,5 @@ module.exports = {
 | 
			
		||||
      { severity: "warning" }
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  ignoreFiles: ["**/*.js", "**/*.jsx", "**/*.tsx", "**/*.ts", "**/*.json"],
 | 
			
		||||
  overrides: [
 | 
			
		||||
    {
 | 
			
		||||
      files: ["*.vue", "**/*.vue", "*.html", "**/*.html"],
 | 
			
		||||
      extends: ["stylelint-config-recommended", "stylelint-config-html"],
 | 
			
		||||
      rules: {
 | 
			
		||||
        "keyframes-name-pattern": null,
 | 
			
		||||
        "selector-pseudo-class-no-unknown": [
 | 
			
		||||
          true,
 | 
			
		||||
          {
 | 
			
		||||
            ignorePseudoClasses: ["deep", "global"]
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "selector-pseudo-element-no-unknown": [
 | 
			
		||||
          true,
 | 
			
		||||
          {
 | 
			
		||||
            ignorePseudoElements: ["v-deep", "v-global", "v-slotted"]
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
  ignoreFiles: ["**/*.js", "**/*.ts", "**/*.jsx", "**/*.tsx"]
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -10,10 +10,8 @@ module.exports = {
 | 
			
		||||
      colors: {
 | 
			
		||||
        bg_color: "var(--el-bg-color)",
 | 
			
		||||
        primary: "var(--el-color-primary)",
 | 
			
		||||
        primary_light_9: "var(--el-color-primary-light-9)",
 | 
			
		||||
        text_color_primary: "var(--el-text-color-primary)",
 | 
			
		||||
        text_color_regular: "var(--el-text-color-regular)",
 | 
			
		||||
        text_color_disabled: "var(--el-text-color-disabled)"
 | 
			
		||||
        text_color_regular: "var(--el-text-color-regular)"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,6 @@
 | 
			
		||||
    "allowJs": false,
 | 
			
		||||
    "resolveJsonModule": true,
 | 
			
		||||
    "lib": ["dom", "esnext"],
 | 
			
		||||
    "incremental": true,
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@/*": ["src/*"],
 | 
			
		||||
      "@build/*": ["build/*"]
 | 
			
		||||
@ -28,10 +27,9 @@
 | 
			
		||||
      "vite/client",
 | 
			
		||||
      "element-plus/global",
 | 
			
		||||
      "@pureadmin/table/volar",
 | 
			
		||||
      "@pureadmin/descriptions/volar",
 | 
			
		||||
      "unplugin-vue-define-options/macros-global"
 | 
			
		||||
      "@pureadmin/descriptions/volar"
 | 
			
		||||
    ],
 | 
			
		||||
    "typeRoots": ["./node_modules/@types/", "./types"]
 | 
			
		||||
    "typeRoots": ["./types", "./node_modules/@types/"]
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "mock/*.ts",
 | 
			
		||||
@ -41,5 +39,5 @@
 | 
			
		||||
    "types/*.d.ts",
 | 
			
		||||
    "vite.config.ts"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": ["node_modules", "dist", "**/*.js"]
 | 
			
		||||
  "exclude": ["dist", "**/*.js", "node_modules"]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								types/global-components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								types/global-components.d.ts
									
									
									
									
										vendored
									
									
								
							@ -11,7 +11,7 @@ declare module "vue" {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * todo:https://github.com/element-plus/element-plus/blob/dev/global.d.ts#L2
 | 
			
		||||
 * TODO https://github.com/element-plus/element-plus/blob/dev/global.d.ts#L2
 | 
			
		||||
 * No need to install @vue/runtime-core
 | 
			
		||||
 */
 | 
			
		||||
declare module "vue" {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								types/global.d.ts
									
									
									
									
										vendored
									
									
								
							@ -63,6 +63,7 @@ declare global {
 | 
			
		||||
    VITE_PUBLIC_PATH: string;
 | 
			
		||||
    VITE_ROUTER_HISTORY: string;
 | 
			
		||||
    VITE_CDN: boolean;
 | 
			
		||||
    VITE_HIDE_HOME: string;
 | 
			
		||||
    VITE_COMPRESSION: ViteCompression;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -96,6 +97,7 @@ declare global {
 | 
			
		||||
    MenuArrowIconNoTransition?: boolean;
 | 
			
		||||
    CachingAsyncRoutes?: boolean;
 | 
			
		||||
    TooltipEffect?: Effect;
 | 
			
		||||
    ResponsiveStorageNameSpace?: string;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user