From 0f7b59f55b5e86e0ce2f60c2d38f59dbc2454592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BB=B2=E4=B9=88=E4=BA=86?= Date: Thu, 21 Dec 2023 16:42:39 +0800 Subject: [PATCH] first commit --- .gitignore | 24 + .prettierrc | 11 + .vscode/extensions.json | 3 + README.md | 18 + index.html | 13 + package.json | 32 + pnpm-lock.yaml | 1081 +++++++++++++++++ public/vite.svg | 1 + src/App.vue | 4 + src/assets/vue.svg | 1 + src/components/baInput/components/array.vue | 85 ++ .../baInput/components/baUpload.vue | 430 +++++++ src/components/baInput/components/editor.vue | 39 + .../baInput/components/iconSelector.vue | 296 +++++ .../baInput/components/remoteSelect.vue | 310 +++++ .../baInput/components/selectFile.vue | 244 ++++ src/components/baInput/helper.ts | 200 +++ src/components/baInput/index.ts | 204 ++++ src/components/baInput/index.vue | 430 +++++++ src/components/contextmenu/index.vue | 108 ++ src/components/contextmenu/interface.ts | 22 + src/components/icon/index.vue | 43 + src/components/icon/svg/index.ts | 71 ++ src/components/icon/svg/index.vue | 49 + src/layouts/admin/components/aside.vue | 53 + .../admin/components/closeFullScreen.vue | 71 ++ src/layouts/admin/components/config.vue | 417 +++++++ src/layouts/admin/components/header.vue | 28 + src/layouts/admin/components/logo.vue | 77 ++ src/layouts/admin/components/menus/helper.ts | 18 + .../admin/components/menus/menuHorizontal.vue | 105 ++ .../admin/components/menus/menuTree.vue | 81 ++ .../admin/components/menus/menuVertical.vue | 80 ++ .../components/menus/menuVerticalChildren.vue | 100 ++ .../admin/components/navBar/classic.vue | 78 ++ .../admin/components/navBar/default.vue | 62 + .../admin/components/navBar/double.vue | 96 ++ src/layouts/admin/components/navBar/tabs.vue | 232 ++++ src/layouts/admin/components/navMenus.vue | 219 ++++ src/layouts/admin/container/classic.vue | 31 + src/layouts/admin/container/default.vue | 31 + src/layouts/admin/container/double.vue | 31 + src/layouts/admin/container/streamline.vue | 29 + src/layouts/admin/index.vue | 124 ++ src/layouts/admin/router-view/main.vue | 104 ++ src/layouts/common/components/darkSwitch.vue | 75 ++ src/layouts/common/components/loading.vue | 59 + src/layouts/common/router-view/iframe.vue | 37 + src/main.ts | 21 + src/router/index.ts | 75 ++ src/router/static.ts | 62 + src/stores/adminInfo.ts | 37 + src/stores/config.ts | 125 ++ src/stores/constant/cacheKey.ts | 12 + src/stores/index.ts | 7 + src/stores/interface/index.ts | 45 + src/stores/navTabs.ts | 108 ++ src/style.css | 79 ++ src/styles/app.scss | 232 ++++ src/styles/element.scss | 68 ++ src/styles/index.scss | 3 + src/styles/mixins.scss | 30 + src/styles/var.scss | 32 + src/utils/common.ts | 65 + src/utils/horizontalScroll.ts | 33 + src/utils/iconfont.ts | 21 + src/utils/layout.ts | 41 + src/utils/pageShade.ts | 22 + src/utils/random.ts | 57 + src/utils/router.ts | 295 +++++ src/utils/storage.ts | 45 + src/utils/useCurrentInstance.ts | 13 + src/views/common/error/404.vue | 3 + src/views/dashboard/index.vue | 78 ++ src/views/user/login.vue | 1 + tsconfig.json | 22 + types/global.d.ts | 28 + types/vite-env.d.ts | 7 + vite.config.ts | 14 + 79 files changed, 7638 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierrc create mode 100644 .vscode/extensions.json create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 public/vite.svg create mode 100644 src/App.vue create mode 100644 src/assets/vue.svg create mode 100644 src/components/baInput/components/array.vue create mode 100644 src/components/baInput/components/baUpload.vue create mode 100644 src/components/baInput/components/editor.vue create mode 100644 src/components/baInput/components/iconSelector.vue create mode 100644 src/components/baInput/components/remoteSelect.vue create mode 100644 src/components/baInput/components/selectFile.vue create mode 100644 src/components/baInput/helper.ts create mode 100644 src/components/baInput/index.ts create mode 100644 src/components/baInput/index.vue create mode 100644 src/components/contextmenu/index.vue create mode 100644 src/components/contextmenu/interface.ts create mode 100644 src/components/icon/index.vue create mode 100644 src/components/icon/svg/index.ts create mode 100644 src/components/icon/svg/index.vue create mode 100644 src/layouts/admin/components/aside.vue create mode 100644 src/layouts/admin/components/closeFullScreen.vue create mode 100644 src/layouts/admin/components/config.vue create mode 100644 src/layouts/admin/components/header.vue create mode 100644 src/layouts/admin/components/logo.vue create mode 100644 src/layouts/admin/components/menus/helper.ts create mode 100644 src/layouts/admin/components/menus/menuHorizontal.vue create mode 100644 src/layouts/admin/components/menus/menuTree.vue create mode 100644 src/layouts/admin/components/menus/menuVertical.vue create mode 100644 src/layouts/admin/components/menus/menuVerticalChildren.vue create mode 100644 src/layouts/admin/components/navBar/classic.vue create mode 100644 src/layouts/admin/components/navBar/default.vue create mode 100644 src/layouts/admin/components/navBar/double.vue create mode 100644 src/layouts/admin/components/navBar/tabs.vue create mode 100644 src/layouts/admin/components/navMenus.vue create mode 100644 src/layouts/admin/container/classic.vue create mode 100644 src/layouts/admin/container/default.vue create mode 100644 src/layouts/admin/container/double.vue create mode 100644 src/layouts/admin/container/streamline.vue create mode 100644 src/layouts/admin/index.vue create mode 100644 src/layouts/admin/router-view/main.vue create mode 100644 src/layouts/common/components/darkSwitch.vue create mode 100644 src/layouts/common/components/loading.vue create mode 100644 src/layouts/common/router-view/iframe.vue create mode 100644 src/main.ts create mode 100644 src/router/index.ts create mode 100644 src/router/static.ts create mode 100644 src/stores/adminInfo.ts create mode 100644 src/stores/config.ts create mode 100644 src/stores/constant/cacheKey.ts create mode 100644 src/stores/index.ts create mode 100644 src/stores/interface/index.ts create mode 100644 src/stores/navTabs.ts create mode 100644 src/style.css create mode 100644 src/styles/app.scss create mode 100644 src/styles/element.scss create mode 100644 src/styles/index.scss create mode 100644 src/styles/mixins.scss create mode 100644 src/styles/var.scss create mode 100644 src/utils/common.ts create mode 100644 src/utils/horizontalScroll.ts create mode 100644 src/utils/iconfont.ts create mode 100644 src/utils/layout.ts create mode 100644 src/utils/pageShade.ts create mode 100644 src/utils/random.ts create mode 100644 src/utils/router.ts create mode 100644 src/utils/storage.ts create mode 100644 src/utils/useCurrentInstance.ts create mode 100644 src/views/common/error/404.vue create mode 100644 src/views/dashboard/index.vue create mode 100644 src/views/user/login.vue create mode 100644 tsconfig.json create mode 100644 types/global.d.ts create mode 100644 types/vite-env.d.ts create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f51155b --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "singleQuote": true, + "trailingComma": "none", + "tabWidth": 4, + "printWidth": 120, + "useTabs": false, + "semi": false, + "arrowParens": "avoid", + "endOfLine": "lf", + "htmlWhitespaceSensitivity": "ignore" +} \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c0a6e5a --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..ef72fd5 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..2f2327d --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "canneng-admin", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "@vueuse/core": "^10.7.0", + "element-plus": "^2.4.4", + "lodash-es": "^4.17.21", + "mitt": "^3.0.1", + "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^3.2.1", + "screenfull": "^6.0.2", + "vue": "^3.3.11", + "vue-router": "4" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "@types/node": "^20.10.5", + "@vitejs/plugin-vue": "^4.5.2", + "sass": "^1.69.5", + "typescript": "^5.2.2", + "vite": "^5.0.8", + "vue-tsc": "^1.8.25" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..706dd80 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1081 @@ +lockfileVersion: 5.4 + +specifiers: + '@element-plus/icons-vue': ^2.3.1 + '@types/lodash-es': ^4.17.12 + '@types/node': ^20.10.5 + '@vitejs/plugin-vue': ^4.5.2 + '@vueuse/core': ^10.7.0 + element-plus: ^2.4.4 + lodash-es: ^4.17.21 + mitt: ^3.0.1 + pinia: ^2.1.7 + pinia-plugin-persistedstate: ^3.2.1 + sass: ^1.69.5 + screenfull: ^6.0.2 + typescript: ^5.2.2 + vite: ^5.0.8 + vue: ^3.3.11 + vue-router: '4' + vue-tsc: ^1.8.25 + +dependencies: + '@element-plus/icons-vue': 2.3.1_vue@3.3.13 + '@vueuse/core': 10.7.0_vue@3.3.13 + element-plus: 2.4.4_vue@3.3.13 + lodash-es: 4.17.21 + mitt: 3.0.1 + pinia: 2.1.7_dembj2eby4ermcojoe7jay3m6m + pinia-plugin-persistedstate: 3.2.1_pinia@2.1.7 + screenfull: 6.0.2 + vue: 3.3.13_typescript@5.3.3 + vue-router: 4.2.5_vue@3.3.13 + +devDependencies: + '@types/lodash-es': 4.17.12 + '@types/node': 20.10.5 + '@vitejs/plugin-vue': 4.5.2_vite@5.0.10+vue@3.3.13 + sass: 1.69.5 + typescript: 5.3.3 + vite: 5.0.10_ordawu77b2uoaf4b5eovt4e6tq + vue-tsc: 1.8.25_typescript@5.3.3 + +packages: + + /@babel/helper-string-parser/7.23.4: + resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} + engines: {node: '>=6.9.0'} + + /@babel/helper-validator-identifier/7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + + /@babel/parser/7.23.6: + resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.6 + + /@babel/types/7.23.6: + resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.23.4 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + + /@ctrl/tinycolor/3.6.1: + resolution: {integrity: sha512-SITSV6aIXsuVNV3f3O0f2n/cgyEDWoSqtZMYiAmcsYHydcKrOz3gUxB/iXd/Qf08+IZX4KpgNbvUdMBmWz+kcA==} + engines: {node: '>=10'} + dev: false + + /@element-plus/icons-vue/2.3.1_vue@3.3.13: + resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==} + peerDependencies: + vue: ^3.2.0 + dependencies: + vue: 3.3.13_typescript@5.3.3 + dev: false + + /@esbuild/aix-ppc64/0.19.10: + resolution: {integrity: sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm/0.19.10: + resolution: {integrity: sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.19.10: + resolution: {integrity: sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.19.10: + resolution: {integrity: sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.19.10: + resolution: {integrity: sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.19.10: + resolution: {integrity: sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.19.10: + resolution: {integrity: sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.19.10: + resolution: {integrity: sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.19.10: + resolution: {integrity: sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.19.10: + resolution: {integrity: sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.19.10: + resolution: {integrity: sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.19.10: + resolution: {integrity: sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.19.10: + resolution: {integrity: sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.19.10: + resolution: {integrity: sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.19.10: + resolution: {integrity: sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.19.10: + resolution: {integrity: sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.19.10: + resolution: {integrity: sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.19.10: + resolution: {integrity: sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.19.10: + resolution: {integrity: sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.19.10: + resolution: {integrity: sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.19.10: + resolution: {integrity: sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.19.10: + resolution: {integrity: sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.19.10: + resolution: {integrity: sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@floating-ui/core/1.5.2: + resolution: {integrity: sha512-Ii3MrfY/GAIN3OhXNzpCKaLxHQfJF9qvwq/kEJYdqDxeIHa01K8sldugal6TmeeXl+WMvhv9cnVzUTaFFJF09A==} + dependencies: + '@floating-ui/utils': 0.1.6 + dev: false + + /@floating-ui/dom/1.5.3: + resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} + dependencies: + '@floating-ui/core': 1.5.2 + '@floating-ui/utils': 0.1.6 + dev: false + + /@floating-ui/utils/0.1.6: + resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} + dev: false + + /@jridgewell/sourcemap-codec/1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + + /@rollup/rollup-android-arm-eabi/4.9.1: + resolution: {integrity: sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-android-arm64/4.9.1: + resolution: {integrity: sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-arm64/4.9.1: + resolution: {integrity: sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-darwin-x64/4.9.1: + resolution: {integrity: sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm-gnueabihf/4.9.1: + resolution: {integrity: sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-gnu/4.9.1: + resolution: {integrity: sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-arm64-musl/4.9.1: + resolution: {integrity: sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-riscv64-gnu/4.9.1: + resolution: {integrity: sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-gnu/4.9.1: + resolution: {integrity: sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-linux-x64-musl/4.9.1: + resolution: {integrity: sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-arm64-msvc/4.9.1: + resolution: {integrity: sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-ia32-msvc/4.9.1: + resolution: {integrity: sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@rollup/rollup-win32-x64-msvc/4.9.1: + resolution: {integrity: sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@sxzz/popperjs-es/2.11.7: + resolution: {integrity: sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==} + dev: false + + /@types/lodash-es/4.17.12: + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + dependencies: + '@types/lodash': 4.14.202 + + /@types/lodash/4.14.202: + resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} + + /@types/node/20.10.5: + resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==} + dependencies: + undici-types: 5.26.5 + dev: true + + /@types/web-bluetooth/0.0.16: + resolution: {integrity: sha512-oh8q2Zc32S6gd/j50GowEjKLoOVOwHP/bWVjKJInBwQqdOYMdPrf1oVlelTlyfFK3CKxL1uahMDAr+vy8T7yMQ==} + dev: false + + /@types/web-bluetooth/0.0.20: + resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + dev: false + + /@vitejs/plugin-vue/4.5.2_vite@5.0.10+vue@3.3.13: + resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 || ^5.0.0 + vue: ^3.2.25 + dependencies: + vite: 5.0.10_ordawu77b2uoaf4b5eovt4e6tq + vue: 3.3.13_typescript@5.3.3 + dev: true + + /@volar/language-core/1.11.1: + resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==} + dependencies: + '@volar/source-map': 1.11.1 + dev: true + + /@volar/source-map/1.11.1: + resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==} + dependencies: + muggle-string: 0.3.1 + dev: true + + /@volar/typescript/1.11.1: + resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==} + dependencies: + '@volar/language-core': 1.11.1 + path-browserify: 1.0.1 + dev: true + + /@vue/compiler-core/3.3.13: + resolution: {integrity: sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A==} + dependencies: + '@babel/parser': 7.23.6 + '@vue/shared': 3.3.13 + estree-walker: 2.0.2 + source-map-js: 1.0.2 + + /@vue/compiler-dom/3.3.13: + resolution: {integrity: sha512-EYRDpbLadGtNL0Gph+HoKiYqXLqZ0xSSpR5Dvnu/Ep7ggaCbjRDIus1MMxTS2Qm0koXED4xSlvTZaTnI8cYAsw==} + dependencies: + '@vue/compiler-core': 3.3.13 + '@vue/shared': 3.3.13 + + /@vue/compiler-sfc/3.3.13: + resolution: {integrity: sha512-DQVmHEy/EKIgggvnGRLx21hSqnr1smUS9Aq8tfxiiot8UR0/pXKHN9k78/qQ7etyQTFj5em5nruODON7dBeumw==} + dependencies: + '@babel/parser': 7.23.6 + '@vue/compiler-core': 3.3.13 + '@vue/compiler-dom': 3.3.13 + '@vue/compiler-ssr': 3.3.13 + '@vue/reactivity-transform': 3.3.13 + '@vue/shared': 3.3.13 + estree-walker: 2.0.2 + magic-string: 0.30.5 + postcss: 8.4.32 + source-map-js: 1.0.2 + + /@vue/compiler-ssr/3.3.13: + resolution: {integrity: sha512-d/P3bCeUGmkJNS1QUZSAvoCIW4fkOKK3l2deE7zrp0ypJEy+En2AcypIkqvcFQOcw3F0zt2VfMvNsA9JmExTaw==} + dependencies: + '@vue/compiler-dom': 3.3.13 + '@vue/shared': 3.3.13 + + /@vue/devtools-api/6.5.1: + resolution: {integrity: sha512-+KpckaAQyfbvshdDW5xQylLni1asvNSGme1JFs8I1+/H5pHEhqUKMEQD/qn3Nx5+/nycBq11qAEi8lk+LXI2dA==} + dev: false + + /@vue/language-core/1.8.25_typescript@5.3.3: + resolution: {integrity: sha512-NJk/5DnAZlpvXX8BdWmHI45bWGLViUaS3R/RMrmFSvFMSbJKuEODpM4kR0F0Ofv5SFzCWuNiMhxameWpVdQsnA==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.3.13 + '@vue/shared': 3.3.13 + computeds: 0.0.1 + minimatch: 9.0.3 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + typescript: 5.3.3 + vue-template-compiler: 2.7.15 + dev: true + + /@vue/reactivity-transform/3.3.13: + resolution: {integrity: sha512-oWnydGH0bBauhXvh5KXUy61xr9gKaMbtsMHk40IK9M4gMuKPJ342tKFarY0eQ6jef8906m35q37wwA8DMZOm5Q==} + dependencies: + '@babel/parser': 7.23.6 + '@vue/compiler-core': 3.3.13 + '@vue/shared': 3.3.13 + estree-walker: 2.0.2 + magic-string: 0.30.5 + + /@vue/reactivity/3.3.13: + resolution: {integrity: sha512-fjzCxceMahHhi4AxUBzQqqVhuA21RJ0COaWTbIBl1PruGW1CeY97louZzLi4smpYx+CHfFPPU/CS8NybbGvPKQ==} + dependencies: + '@vue/shared': 3.3.13 + + /@vue/runtime-core/3.3.13: + resolution: {integrity: sha512-1TzA5TvGuh2zUwMJgdfvrBABWZ7y8kBwBhm7BXk8rvdx2SsgcGfz2ruv2GzuGZNvL1aKnK8CQMV/jFOrxNQUMA==} + dependencies: + '@vue/reactivity': 3.3.13 + '@vue/shared': 3.3.13 + + /@vue/runtime-dom/3.3.13: + resolution: {integrity: sha512-JJkpE8R/hJKXqVTgUoODwS5wqKtOsmJPEqmp90PDVGygtJ4C0PtOkcEYXwhiVEmef6xeXcIlrT3Yo5aQ4qkHhQ==} + dependencies: + '@vue/runtime-core': 3.3.13 + '@vue/shared': 3.3.13 + csstype: 3.1.3 + + /@vue/server-renderer/3.3.13_vue@3.3.13: + resolution: {integrity: sha512-vSnN+nuf6iSqTL3Qgx/9A+BT+0Zf/VJOgF5uMZrKjYPs38GMYyAU1coDyBNHauehXDaP+zl73VhwWv0vBRBHcg==} + peerDependencies: + vue: 3.3.13 + dependencies: + '@vue/compiler-ssr': 3.3.13 + '@vue/shared': 3.3.13 + vue: 3.3.13_typescript@5.3.3 + + /@vue/shared/3.3.13: + resolution: {integrity: sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA==} + + /@vueuse/core/10.7.0_vue@3.3.13: + resolution: {integrity: sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w==} + dependencies: + '@types/web-bluetooth': 0.0.20 + '@vueuse/metadata': 10.7.0 + '@vueuse/shared': 10.7.0_vue@3.3.13 + vue-demi: 0.14.6_vue@3.3.13 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/core/9.13.0_vue@3.3.13: + resolution: {integrity: sha512-pujnclbeHWxxPRqXWmdkKV5OX4Wk4YeK7wusHqRwU0Q7EFusHoqNA/aPhB6KCh9hEqJkLAJo7bb0Lh9b+OIVzw==} + dependencies: + '@types/web-bluetooth': 0.0.16 + '@vueuse/metadata': 9.13.0 + '@vueuse/shared': 9.13.0_vue@3.3.13 + vue-demi: 0.14.6_vue@3.3.13 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/metadata/10.7.0: + resolution: {integrity: sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA==} + dev: false + + /@vueuse/metadata/9.13.0: + resolution: {integrity: sha512-gdU7TKNAUVlXXLbaF+ZCfte8BjRJQWPCa2J55+7/h+yDtzw3vOoGQDRXzI6pyKyo6bXFT5/QoPE4hAknExjRLQ==} + dev: false + + /@vueuse/shared/10.7.0_vue@3.3.13: + resolution: {integrity: sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==} + dependencies: + vue-demi: 0.14.6_vue@3.3.13 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /@vueuse/shared/9.13.0_vue@3.3.13: + resolution: {integrity: sha512-UrnhU+Cnufu4S6JLCPZnkWh0WwZGUp72ktOF2DFptMlOs3TOdVv8xJN53zhHGARmVOsz5KqOls09+J1NR6sBKw==} + dependencies: + vue-demi: 0.14.6_vue@3.3.13 + transitivePeerDependencies: + - '@vue/composition-api' + - vue + dev: false + + /anymatch/3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + dev: true + + /async-validator/4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + dev: false + + /balanced-match/1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + dev: true + + /binary-extensions/2.2.0: + resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} + engines: {node: '>=8'} + dev: true + + /brace-expansion/2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + + /braces/3.0.2: + resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.0.1 + dev: true + + /chokidar/3.5.3: + resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} + engines: {node: '>= 8.10.0'} + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /computeds/0.0.1: + resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} + dev: true + + /csstype/3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + /dayjs/1.11.10: + resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} + dev: false + + /de-indent/1.0.2: + resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} + dev: true + + /element-plus/2.4.4_vue@3.3.13: + resolution: {integrity: sha512-TlKubXJgxwhER0dw+8ULn9hr9kZjraV4R6Q/eidwWUwCKxwXYPBGmMKsZ/85tlxlhMYbcLZd/YZh6G3QkHX4fg==} + peerDependencies: + vue: ^3.2.0 + dependencies: + '@ctrl/tinycolor': 3.6.1 + '@element-plus/icons-vue': 2.3.1_vue@3.3.13 + '@floating-ui/dom': 1.5.3 + '@popperjs/core': /@sxzz/popperjs-es/2.11.7 + '@types/lodash': 4.14.202 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 9.13.0_vue@3.3.13 + async-validator: 4.2.5 + dayjs: 1.11.10 + escape-html: 1.0.3 + lodash: 4.17.21 + lodash-es: 4.17.21 + lodash-unified: 1.0.3_vpgwo5v3ie2bia5ss74pgoa5ly + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.3.13_typescript@5.3.3 + transitivePeerDependencies: + - '@vue/composition-api' + dev: false + + /esbuild/0.19.10: + resolution: {integrity: sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.10 + '@esbuild/android-arm': 0.19.10 + '@esbuild/android-arm64': 0.19.10 + '@esbuild/android-x64': 0.19.10 + '@esbuild/darwin-arm64': 0.19.10 + '@esbuild/darwin-x64': 0.19.10 + '@esbuild/freebsd-arm64': 0.19.10 + '@esbuild/freebsd-x64': 0.19.10 + '@esbuild/linux-arm': 0.19.10 + '@esbuild/linux-arm64': 0.19.10 + '@esbuild/linux-ia32': 0.19.10 + '@esbuild/linux-loong64': 0.19.10 + '@esbuild/linux-mips64el': 0.19.10 + '@esbuild/linux-ppc64': 0.19.10 + '@esbuild/linux-riscv64': 0.19.10 + '@esbuild/linux-s390x': 0.19.10 + '@esbuild/linux-x64': 0.19.10 + '@esbuild/netbsd-x64': 0.19.10 + '@esbuild/openbsd-x64': 0.19.10 + '@esbuild/sunos-x64': 0.19.10 + '@esbuild/win32-arm64': 0.19.10 + '@esbuild/win32-ia32': 0.19.10 + '@esbuild/win32-x64': 0.19.10 + dev: true + + /escape-html/1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /estree-walker/2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + /fill-range/7.0.1: + resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: true + + /fsevents/2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /glob-parent/5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: true + + /he/1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + dev: true + + /immutable/4.3.4: + resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} + dev: true + + /is-binary-path/2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + dependencies: + binary-extensions: 2.2.0 + dev: true + + /is-extglob/2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: true + + /is-glob/4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: true + + /is-number/7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: true + + /lodash-es/4.17.21: + resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + dev: false + + /lodash-unified/1.0.3_vpgwo5v3ie2bia5ss74pgoa5ly: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.17.21 + lodash-es: 4.17.21 + dev: false + + /lodash/4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + dev: false + + /lru-cache/6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + dependencies: + yallist: 4.0.0 + dev: true + + /magic-string/0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} + engines: {node: '>=12'} + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + + /memoize-one/6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + dev: false + + /minimatch/9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + + /mitt/3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + dev: false + + /muggle-string/0.3.1: + resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==} + dev: true + + /nanoid/3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + + /normalize-wheel-es/1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + dev: false + + /path-browserify/1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + dev: true + + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + + /picomatch/2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: true + + /pinia-plugin-persistedstate/3.2.1_pinia@2.1.7: + resolution: {integrity: sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==} + peerDependencies: + pinia: ^2.0.0 + dependencies: + pinia: 2.1.7_dembj2eby4ermcojoe7jay3m6m + dev: false + + /pinia/2.1.7_dembj2eby4ermcojoe7jay3m6m: + resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} + peerDependencies: + '@vue/composition-api': ^1.4.0 + typescript: '>=4.4.4' + vue: ^2.6.14 || ^3.3.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + typescript: + optional: true + dependencies: + '@vue/devtools-api': 6.5.1 + typescript: 5.3.3 + vue: 3.3.13_typescript@5.3.3 + vue-demi: 0.14.6_vue@3.3.13 + dev: false + + /postcss/8.4.32: + resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.7 + picocolors: 1.0.0 + source-map-js: 1.0.2 + + /readdirp/3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + dependencies: + picomatch: 2.3.1 + dev: true + + /rollup/4.9.1: + resolution: {integrity: sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.9.1 + '@rollup/rollup-android-arm64': 4.9.1 + '@rollup/rollup-darwin-arm64': 4.9.1 + '@rollup/rollup-darwin-x64': 4.9.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.9.1 + '@rollup/rollup-linux-arm64-gnu': 4.9.1 + '@rollup/rollup-linux-arm64-musl': 4.9.1 + '@rollup/rollup-linux-riscv64-gnu': 4.9.1 + '@rollup/rollup-linux-x64-gnu': 4.9.1 + '@rollup/rollup-linux-x64-musl': 4.9.1 + '@rollup/rollup-win32-arm64-msvc': 4.9.1 + '@rollup/rollup-win32-ia32-msvc': 4.9.1 + '@rollup/rollup-win32-x64-msvc': 4.9.1 + fsevents: 2.3.3 + dev: true + + /sass/1.69.5: + resolution: {integrity: sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + chokidar: 3.5.3 + immutable: 4.3.4 + source-map-js: 1.0.2 + dev: true + + /screenfull/6.0.2: + resolution: {integrity: sha512-AQdy8s4WhNvUZ6P8F6PB21tSPIYKniic+Ogx0AacBMjKP1GUHN2E9URxQHtCusiwxudnCKkdy4GrHXPPJSkCCw==} + engines: {node: ^14.13.1 || >=16.0.0} + dev: false + + /semver/7.5.4: + resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + + /to-fast-properties/2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + + /to-regex-range/5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: true + + /typescript/5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + engines: {node: '>=14.17'} + hasBin: true + + /undici-types/5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /vite/5.0.10_ordawu77b2uoaf4b5eovt4e6tq: + resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + '@types/node': 20.10.5 + esbuild: 0.19.10 + postcss: 8.4.32 + rollup: 4.9.1 + sass: 1.69.5 + optionalDependencies: + fsevents: 2.3.3 + dev: true + + /vue-demi/0.14.6_vue@3.3.13: + resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + peerDependencies: + '@vue/composition-api': ^1.0.0-rc.1 + vue: ^3.0.0-0 || ^2.6.0 + peerDependenciesMeta: + '@vue/composition-api': + optional: true + dependencies: + vue: 3.3.13_typescript@5.3.3 + dev: false + + /vue-router/4.2.5_vue@3.3.13: + resolution: {integrity: sha512-DIUpKcyg4+PTQKfFPX88UWhlagBEBEfJ5A8XDXRJLUnZOvcpMF8o/dnL90vpVkGaPbjvXazV/rC1qBKrZlFugw==} + peerDependencies: + vue: ^3.2.0 + dependencies: + '@vue/devtools-api': 6.5.1 + vue: 3.3.13_typescript@5.3.3 + dev: false + + /vue-template-compiler/2.7.15: + resolution: {integrity: sha512-yQxjxMptBL7UAog00O8sANud99C6wJF+7kgbcwqkvA38vCGF7HWE66w0ZFnS/kX5gSoJr/PQ4/oS3Ne2pW37Og==} + dependencies: + de-indent: 1.0.2 + he: 1.2.0 + dev: true + + /vue-tsc/1.8.25_typescript@5.3.3: + resolution: {integrity: sha512-lHsRhDc/Y7LINvYhZ3pv4elflFADoEOo67vfClAfF2heVHpHmVquLSjojgCSIwzA4F0Pc4vowT/psXCYcfk+iQ==} + hasBin: true + peerDependencies: + typescript: '*' + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.25_typescript@5.3.3 + semver: 7.5.4 + typescript: 5.3.3 + dev: true + + /vue/3.3.13_typescript@5.3.3: + resolution: {integrity: sha512-LDnUpQvDgsfc0u/YgtAgTMXJlJQqjkxW1PVcOnJA5cshPleULDjHi7U45pl2VJYazSSvLH8UKcid/kzH8I0a0Q==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@vue/compiler-dom': 3.3.13 + '@vue/compiler-sfc': 3.3.13 + '@vue/runtime-dom': 3.3.13 + '@vue/server-renderer': 3.3.13_vue@3.3.13 + '@vue/shared': 3.3.13 + typescript: 5.3.3 + + /yallist/4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + dev: true diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..689160a --- /dev/null +++ b/src/App.vue @@ -0,0 +1,4 @@ + + diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/baInput/components/array.vue b/src/components/baInput/components/array.vue new file mode 100644 index 0000000..c9f46de --- /dev/null +++ b/src/components/baInput/components/array.vue @@ -0,0 +1,85 @@ + + + + + diff --git a/src/components/baInput/components/baUpload.vue b/src/components/baInput/components/baUpload.vue new file mode 100644 index 0000000..0179a21 --- /dev/null +++ b/src/components/baInput/components/baUpload.vue @@ -0,0 +1,430 @@ + + + + + diff --git a/src/components/baInput/components/editor.vue b/src/components/baInput/components/editor.vue new file mode 100644 index 0000000..8c1e35b --- /dev/null +++ b/src/components/baInput/components/editor.vue @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/src/components/baInput/components/iconSelector.vue b/src/components/baInput/components/iconSelector.vue new file mode 100644 index 0000000..23d3770 --- /dev/null +++ b/src/components/baInput/components/iconSelector.vue @@ -0,0 +1,296 @@ + + + + + diff --git a/src/components/baInput/components/remoteSelect.vue b/src/components/baInput/components/remoteSelect.vue new file mode 100644 index 0000000..6c71a37 --- /dev/null +++ b/src/components/baInput/components/remoteSelect.vue @@ -0,0 +1,310 @@ + + + + + diff --git a/src/components/baInput/components/selectFile.vue b/src/components/baInput/components/selectFile.vue new file mode 100644 index 0000000..afd9818 --- /dev/null +++ b/src/components/baInput/components/selectFile.vue @@ -0,0 +1,244 @@ + + + + + diff --git a/src/components/baInput/helper.ts b/src/components/baInput/helper.ts new file mode 100644 index 0000000..660caf7 --- /dev/null +++ b/src/components/baInput/helper.ts @@ -0,0 +1,200 @@ +import type { FieldData } from './index' + +export const npuaFalse = () => { + return { + null: false, + primaryKey: false, + unsigned: false, + autoIncrement: false, + } +} + +/** + * 所有 Input 支持的类型对应的数据字段类型等数据(默认/示例设计) + */ +export const fieldData: FieldData = { + string: { + type: 'varchar', + length: 200, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + password: { + type: 'varchar', + length: 32, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + number: { + type: 'int', + length: 10, + precision: 0, + default: '0', + ...npuaFalse(), + }, + radio: { + type: 'enum', + length: 0, + precision: 0, + default: '', + ...npuaFalse(), + }, + checkbox: { + type: 'set', + length: 0, + precision: 0, + default: '', + ...npuaFalse(), + }, + switch: { + type: 'tinyint', + length: 1, + precision: 0, + default: '1', + ...npuaFalse(), + unsigned: true, + }, + textarea: { + type: 'varchar', + length: 255, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + array: { + type: 'varchar', + length: 255, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + datetime: { + type: 'bigint', + length: 16, + precision: 0, + default: 'null', + ...npuaFalse(), + null: true, + unsigned: true, + }, + year: { + type: 'year', + length: 4, + precision: 0, + default: 'null', + ...npuaFalse(), + null: true, + }, + date: { + type: 'date', + length: 0, + precision: 0, + default: 'null', + ...npuaFalse(), + null: true, + }, + time: { + type: 'time', + length: 0, + precision: 0, + default: 'null', + ...npuaFalse(), + null: true, + }, + select: { + type: 'enum', + length: 0, + precision: 0, + default: '', + ...npuaFalse(), + }, + selects: { + type: 'varchar', + length: 100, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + remoteSelect: { + type: 'int', + length: 10, + precision: 0, + default: '0', + ...npuaFalse(), + unsigned: true, + }, + remoteSelects: { + type: 'varchar', + length: 100, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + editor: { + type: 'text', + length: 0, + precision: 0, + default: 'empty string', + ...npuaFalse(), + null: true, + }, + city: { + type: 'varchar', + length: 100, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + image: { + type: 'varchar', + length: 200, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + images: { + type: 'varchar', + length: 255, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + file: { + type: 'varchar', + length: 200, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + files: { + type: 'varchar', + length: 255, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + icon: { + type: 'varchar', + length: 50, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, + color: { + type: 'varchar', + length: 30, + precision: 0, + default: 'empty string', + ...npuaFalse(), + }, +} + +export const stringToArray = (val: string | string[]) => { + if (typeof val === 'string') { + return val == '' ? [] : val.split(',') + } else { + return val as string[] + } +} diff --git a/src/components/baInput/index.ts b/src/components/baInput/index.ts new file mode 100644 index 0000000..ea98a97 --- /dev/null +++ b/src/components/baInput/index.ts @@ -0,0 +1,204 @@ +import type { Component, CSSProperties } from 'vue' + +/** + * 支持的输入框类型 + * 若您正在设计数据表,可以找到 ./helper.ts 文件来参考对应类型的:数据字段设计示例 + */ +export const inputTypes = [ + 'string', + 'password', + 'number', + 'radio', + 'checkbox', + 'switch', + 'textarea', + 'array', + 'datetime', + 'year', + 'date', + 'time', + 'select', + 'selects', + 'remoteSelect', + 'remoteSelects', + 'editor', + 'city', + 'image', + 'images', + 'file', + 'files', + 'icon', + 'color', +] +export type modelValueTypes = string | number | boolean | object + +export interface InputData { + // 标题 + title?: string + // 内容,比如radio的选项列表数据 content: { a: '选项1', b: '选项2' } + content?: any + // 提示信息 + tip?: string + // 需要生成子级元素时,子级元素属性(比如radio) + childrenAttr?: anyObj + // 城市选择器等级,1=省,2=市,3=区 + level?: number +} + +/** + * input可用属性,用于代码提示,渲染不同输入组件时,需要的属性是不一样的 + * https://element-plus.org/zh-CN/component/input.html#input-属性 + */ +export interface InputAttr { + id?: string + name?: string + type?: string + placeholder?: string + maxlength?: string | number + minlength?: string | number + showWordLimit?: boolean + clearable?: boolean + showPassword?: boolean + disabled?: boolean + size?: 'large' | 'default' | 'small' + prefixIcon?: string | Component + suffixIcon?: string | Component + rows?: number + border?: boolean + autosize?: boolean | anyObj + autocomplete?: string + readonly?: boolean + max?: string | number + min?: string | number + step?: string | number + resize?: 'none' | 'both' | 'horizontal' | 'vertical' + autofocus?: boolean + form?: string + label?: string + tabindex?: string | number + validateEvent?: boolean + inputStyle?: anyObj + // DateTimePicker属性 + editable?: boolean + startPlaceholder?: string + endPlaceholder?: string + timeArrowControl?: boolean + format?: string + popperClass?: string + rangeSeparator?: string + defaultValue?: Date + defaultTime?: Date | Date[] + valueFormat?: string + unlinkPanels?: boolean + clearIcon?: string | Component + shortcuts?: { text: string; value: Date | Function }[] + disabledDate?: Function + cellClassName?: Function + teleported?: boolean + // select属性 + multiple?: boolean + valueKey?: string + collapseTags?: string + collapseTagsTooltip?: boolean + multipleLimit?: number + effect?: 'dark' | 'light' + filterable?: boolean + allowCreate?: boolean + filterMethod?: Function + remote?: false // 禁止使用远程搜索,如需使用请使用单独封装好的 remoteSelect 组件 + remoteMethod?: false + labelFormatter?: (optionData: anyObj, optionKey: string) => string + noMatchText?: string + noDataText?: string + reserveKeyword?: boolean + defaultFirstOption?: boolean + popperAppendToBody?: boolean + persistent?: boolean + automaticDropdown?: boolean + fitInputWidth?: boolean + tagType?: 'success' | 'info' | 'warning' | 'danger' + params?: anyObj + // 远程select属性 + pk?: string + field?: string + remoteUrl?: string + tooltipParams?: anyObj + // 图标选择器属性 + showIconName?: boolean + placement?: string + title?: string + // 颜色选择器 + showAlpha?: boolean + colorFormat?: string + predefine?: string[] + // 图片文件上传属性 + action?: string + headers?: anyObj + method?: string + data?: anyObj + withCredentials?: boolean + showFileList?: boolean + drag?: boolean + accept?: string + listType?: string + autoUpload?: boolean + limit?: number + hideSelectFile?: boolean + returnFullUrl?: boolean + forceLocal?: boolean + // editor属性 + height?: string + mode?: string + editorStyle?: CSSProperties + style?: CSSProperties + toolbarConfig?: anyObj + editorConfig?: anyObj + editorType?: string + preview?: boolean + language?: string + theme?: 'light' | 'dark' + toolbarsExclude?: string[] + fileForceLocal?: boolean + // array组件属性 + keyTitle?: string + valueTitle?: string + // 返回数据类型 + dataType?: string + // 事件 + onPreview?: Function + onRemove?: Function + onSuccess?: Function + onError?: Function + onProgress?: Function + onExceed?: Function + onBeforeUpload?: Function + onBeforeRemove?: Function + onChange?: Function + onInput?: Function + onVisibleChange?: Function + onRemoveTag?: Function + onClear?: Function + onBlur?: Function + onFocus?: Function + onCalendarChange?: Function + onPanelChange?: Function + onActiveChange?: Function + onRow?: Function + [key: string]: any +} + +/** + * Input 支持的类型对应的数据字段设计数据 + */ +export interface FieldData { + [key: string]: { + type: string // 数据类型 + length: number // 长度 + precision: number // 小数点 + default: string // 默认值 + null: boolean // 允许 null + primaryKey: boolean // 主键 + unsigned: boolean // 无符号 + autoIncrement: boolean // 自动递增 + } +} diff --git a/src/components/baInput/index.vue b/src/components/baInput/index.vue new file mode 100644 index 0000000..e005447 --- /dev/null +++ b/src/components/baInput/index.vue @@ -0,0 +1,430 @@ + + + diff --git a/src/components/contextmenu/index.vue b/src/components/contextmenu/index.vue new file mode 100644 index 0000000..842d6d1 --- /dev/null +++ b/src/components/contextmenu/index.vue @@ -0,0 +1,108 @@ + + + + + diff --git a/src/components/contextmenu/interface.ts b/src/components/contextmenu/interface.ts new file mode 100644 index 0000000..eabeb74 --- /dev/null +++ b/src/components/contextmenu/interface.ts @@ -0,0 +1,22 @@ +import type { RouteLocationNormalized } from 'vue-router' + +export interface Axis { + x: number + y: number +} + +export interface ContextMenuItem { + name: string + label: string + icon?: string + disabled?: boolean +} + +export interface ContextmenuItemClickEmitArg extends ContextMenuItem { + menu?: RouteLocationNormalized +} + +export interface Props { + width?: number + items: ContextMenuItem[] +} diff --git a/src/components/icon/index.vue b/src/components/icon/index.vue new file mode 100644 index 0000000..b5ba528 --- /dev/null +++ b/src/components/icon/index.vue @@ -0,0 +1,43 @@ + diff --git a/src/components/icon/svg/index.ts b/src/components/icon/svg/index.ts new file mode 100644 index 0000000..9c5255f --- /dev/null +++ b/src/components/icon/svg/index.ts @@ -0,0 +1,71 @@ +import { readFileSync, readdirSync } from 'fs' + +let idPerfix = '' +const iconNames: string[] = [] +const svgTitle = /+].*?)>/ +const clearHeightWidth = /(width|height)="([^>+].*?)"/g +const hasViewBox = /(viewBox="[^>+].*?")/g +const clearReturn = /(\r)|(\n)/g +// 清理 svg 的 fill +const clearFill = /(fill="[^>+].*?")/g + +function findSvgFile(dir: string): string[] { + const svgRes = [] + const dirents = readdirSync(dir, { + withFileTypes: true, + }) + for (const dirent of dirents) { + iconNames.push(`${idPerfix}-${dirent.name.replace('.svg', '')}`) + if (dirent.isDirectory()) { + svgRes.push(...findSvgFile(dir + dirent.name + '/')) + } else { + const svg = readFileSync(dir + dirent.name) + .toString() + .replace(clearReturn, '') + .replace(clearFill, 'fill=""') + .replace(svgTitle, ($1, $2) => { + let width = 0 + let height = 0 + let content = $2.replace(clearHeightWidth, (s1: string, s2: string, s3: number) => { + if (s2 === 'width') { + width = s3 + } else if (s2 === 'height') { + height = s3 + } + return '' + }) + if (!hasViewBox.test($2)) { + content += `viewBox="0 0 ${width} ${height}"` + } + return `` + }) + .replace('', '') + svgRes.push(svg) + } + } + return svgRes +} + +export const svgBuilder = (path: string, perfix = 'local') => { + if (path === '') return + idPerfix = perfix + const res = findSvgFile(path) + return { + name: 'svg-transform', + transformIndexHtml(html: string) { + /* eslint-disable */ + return html.replace( + '', + ` + + + ${res.join('')} + + ` + ) + /* eslint-enable */ + }, + } +} diff --git a/src/components/icon/svg/index.vue b/src/components/icon/svg/index.vue new file mode 100644 index 0000000..675d62d --- /dev/null +++ b/src/components/icon/svg/index.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/src/layouts/admin/components/aside.vue b/src/layouts/admin/components/aside.vue new file mode 100644 index 0000000..caa5fc4 --- /dev/null +++ b/src/layouts/admin/components/aside.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/src/layouts/admin/components/closeFullScreen.vue b/src/layouts/admin/components/closeFullScreen.vue new file mode 100644 index 0000000..10a6aa7 --- /dev/null +++ b/src/layouts/admin/components/closeFullScreen.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/src/layouts/admin/components/config.vue b/src/layouts/admin/components/config.vue new file mode 100644 index 0000000..95d5da9 --- /dev/null +++ b/src/layouts/admin/components/config.vue @@ -0,0 +1,417 @@ + + + + + diff --git a/src/layouts/admin/components/header.vue b/src/layouts/admin/components/header.vue new file mode 100644 index 0000000..fa4cfb6 --- /dev/null +++ b/src/layouts/admin/components/header.vue @@ -0,0 +1,28 @@ + + + + diff --git a/src/layouts/admin/components/logo.vue b/src/layouts/admin/components/logo.vue new file mode 100644 index 0000000..547b2a3 --- /dev/null +++ b/src/layouts/admin/components/logo.vue @@ -0,0 +1,77 @@ + + + + + diff --git a/src/layouts/admin/components/menus/helper.ts b/src/layouts/admin/components/menus/helper.ts new file mode 100644 index 0000000..117aff3 --- /dev/null +++ b/src/layouts/admin/components/menus/helper.ts @@ -0,0 +1,18 @@ +import type { RouteRecordRaw } from 'vue-router' + +/** + * 寻找当前路由在顶栏菜单中的数据 + */ +export const currentRouteTopActivity = (path: string, menus: RouteRecordRaw[]): RouteRecordRaw | false => { + for (let i = 0; i < menus.length; i++) { + const item: RouteRecordRaw = menus[i] + // 找到目标 + if (item.path == path) return item + // 从子级继续寻找 + if (item.children && item.children.length > 0) { + const find = currentRouteTopActivity(path, item.children) + if (find) return item + } + } + return false +} diff --git a/src/layouts/admin/components/menus/menuHorizontal.vue b/src/layouts/admin/components/menus/menuHorizontal.vue new file mode 100644 index 0000000..e23c8de --- /dev/null +++ b/src/layouts/admin/components/menus/menuHorizontal.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/src/layouts/admin/components/menus/menuTree.vue b/src/layouts/admin/components/menus/menuTree.vue new file mode 100644 index 0000000..47c332b --- /dev/null +++ b/src/layouts/admin/components/menus/menuTree.vue @@ -0,0 +1,81 @@ + + + + diff --git a/src/layouts/admin/components/menus/menuVertical.vue b/src/layouts/admin/components/menus/menuVertical.vue new file mode 100644 index 0000000..6db2ed6 --- /dev/null +++ b/src/layouts/admin/components/menus/menuVertical.vue @@ -0,0 +1,80 @@ + + + + diff --git a/src/layouts/admin/components/menus/menuVerticalChildren.vue b/src/layouts/admin/components/menus/menuVerticalChildren.vue new file mode 100644 index 0000000..d9f7e11 --- /dev/null +++ b/src/layouts/admin/components/menus/menuVerticalChildren.vue @@ -0,0 +1,100 @@ + + + + diff --git a/src/layouts/admin/components/navBar/classic.vue b/src/layouts/admin/components/navBar/classic.vue new file mode 100644 index 0000000..c91ce3f --- /dev/null +++ b/src/layouts/admin/components/navBar/classic.vue @@ -0,0 +1,78 @@ + + + + + diff --git a/src/layouts/admin/components/navBar/default.vue b/src/layouts/admin/components/navBar/default.vue new file mode 100644 index 0000000..686389c --- /dev/null +++ b/src/layouts/admin/components/navBar/default.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/layouts/admin/components/navBar/double.vue b/src/layouts/admin/components/navBar/double.vue new file mode 100644 index 0000000..7875b9d --- /dev/null +++ b/src/layouts/admin/components/navBar/double.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/src/layouts/admin/components/navBar/tabs.vue b/src/layouts/admin/components/navBar/tabs.vue new file mode 100644 index 0000000..6e1d0f7 --- /dev/null +++ b/src/layouts/admin/components/navBar/tabs.vue @@ -0,0 +1,232 @@ + + + + + diff --git a/src/layouts/admin/components/navMenus.vue b/src/layouts/admin/components/navMenus.vue new file mode 100644 index 0000000..065c24e --- /dev/null +++ b/src/layouts/admin/components/navMenus.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/src/layouts/admin/container/classic.vue b/src/layouts/admin/container/classic.vue new file mode 100644 index 0000000..840df08 --- /dev/null +++ b/src/layouts/admin/container/classic.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/layouts/admin/container/default.vue b/src/layouts/admin/container/default.vue new file mode 100644 index 0000000..840df08 --- /dev/null +++ b/src/layouts/admin/container/default.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/layouts/admin/container/double.vue b/src/layouts/admin/container/double.vue new file mode 100644 index 0000000..840df08 --- /dev/null +++ b/src/layouts/admin/container/double.vue @@ -0,0 +1,31 @@ + + + + + diff --git a/src/layouts/admin/container/streamline.vue b/src/layouts/admin/container/streamline.vue new file mode 100644 index 0000000..3faf691 --- /dev/null +++ b/src/layouts/admin/container/streamline.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/layouts/admin/index.vue b/src/layouts/admin/index.vue new file mode 100644 index 0000000..a426303 --- /dev/null +++ b/src/layouts/admin/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/src/layouts/admin/router-view/main.vue b/src/layouts/admin/router-view/main.vue new file mode 100644 index 0000000..e5bdbc0 --- /dev/null +++ b/src/layouts/admin/router-view/main.vue @@ -0,0 +1,104 @@ + + + + + diff --git a/src/layouts/common/components/darkSwitch.vue b/src/layouts/common/components/darkSwitch.vue new file mode 100644 index 0000000..2d52b27 --- /dev/null +++ b/src/layouts/common/components/darkSwitch.vue @@ -0,0 +1,75 @@ + + + diff --git a/src/layouts/common/components/loading.vue b/src/layouts/common/components/loading.vue new file mode 100644 index 0000000..7feb37d --- /dev/null +++ b/src/layouts/common/components/loading.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/src/layouts/common/router-view/iframe.vue b/src/layouts/common/router-view/iframe.vue new file mode 100644 index 0000000..e8d3ed1 --- /dev/null +++ b/src/layouts/common/router-view/iframe.vue @@ -0,0 +1,37 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..e2372d2 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,21 @@ +import { createApp } from 'vue' +import App from './App.vue' +import router from './router' +import pinia from '@/stores/index' +import { registerIcons } from '@/utils/common' +import mitt from 'mitt' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/display.css' +// modules import mark, Please do not remove. +import '@/styles/index.scss' + +const app = createApp(App) + +app.use(router) +app.use(pinia) +app.use(ElementPlus) +registerIcons(app) // icons + +app.mount('#app') +app.config.globalProperties.eventBus = mitt() diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..2268115 --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,75 @@ +import { createRouter, createWebHashHistory } from 'vue-router' +import staticRoutes from '@/router/static' + +const router = createRouter({ + history: createWebHashHistory(), + routes: staticRoutes +}) + +// router.beforeEach((to, from, next) => { +// NProgress.configure({ showSpinner: false }) +// NProgress.start() +// if (!window.existLoading) { +// loading.show() +// window.existLoading = true +// } + +// // 按需动态加载页面的语言包-start +// let loadPath: string[] = [] +// const config = useConfig() +// if (to.path in langAutoLoadMap) { +// loadPath.push(...langAutoLoadMap[to.path as keyof typeof langAutoLoadMap]) +// } +// let prefix = '' +// if (isAdminApp(to.fullPath)) { +// prefix = './backend/' + config.lang.defaultLang + +// // 去除 path 中的 /admin +// const adminPath = to.path.slice(to.path.indexOf(adminBaseRoutePath) + adminBaseRoutePath.length) +// if (adminPath) loadPath.push(prefix + adminPath + '.ts') +// } else { +// prefix = './frontend/' + config.lang.defaultLang +// loadPath.push(prefix + to.path + '.ts') +// } + +// // 根据路由 name 加载的语言包 +// if (to.name) { +// loadPath.push(prefix + '/' + to.name.toString() + '.ts') +// } + +// if (!window.loadLangHandle.publicMessageLoaded) window.loadLangHandle.publicMessageLoaded = [] +// const publicMessagePath = prefix + '.ts' +// if (!window.loadLangHandle.publicMessageLoaded.includes(publicMessagePath)) { +// loadPath.push(publicMessagePath) +// window.loadLangHandle.publicMessageLoaded.push(publicMessagePath) +// } + +// // 去重 +// loadPath = uniq(loadPath) + +// for (const key in loadPath) { +// loadPath[key] = loadPath[key].replaceAll('${lang}', config.lang.defaultLang) +// if (loadPath[key] in window.loadLangHandle) { +// window.loadLangHandle[loadPath[key]]().then((res: { default: anyObj }) => { +// const pathName = loadPath[key].slice( +// loadPath[key].lastIndexOf(prefix) + (prefix.length + 1), +// loadPath[key].lastIndexOf('.') +// ) +// mergeMessage(res.default, pathName) +// }) +// } +// } +// // 动态加载语言包-end + +// next() +// }) + +// // 路由加载后 +// router.afterEach(() => { +// if (window.existLoading) { +// loading.hide() +// } +// NProgress.done() +// }) + +export default router diff --git a/src/router/static.ts b/src/router/static.ts new file mode 100644 index 0000000..1229702 --- /dev/null +++ b/src/router/static.ts @@ -0,0 +1,62 @@ +import type { RouteRecordRaw } from 'vue-router' + +const pageTitle = (name: string): string => { + return `pagesTitle.${name}` +} +/** + * 后台基础路由路径 + */ +export const adminBaseRoutePath = '/admin' +export const adminBaseRoute = { + path: adminBaseRoutePath, + name: 'admin', + component: () => import('@/layouts/admin/index.vue'), + // 直接重定向到 loading 路由 + redirect: adminBaseRoutePath + '/loading', + meta: { + title: `pagesTitle.admin` + }, + children: [ + { + path: 'loading/:to?', + name: 'adminMainLoading', + component: () => import('@/layouts/common/components/loading.vue'), + meta: { + title: `pagesTitle.loading` + } + } + ] +} + +/* + * 静态路由 + * 自动加载 ./static 目录的所有文件,并 push 到以下数组 + */ +const staticRoutes: Array = [ + adminBaseRoute, + { + // 管理员登录页 - 不放在 adminBaseRoute.children 因为登录页不需要使用后台的布局 + path: '/login', + name: 'login', + component: () => import('@/views/user/login.vue'), + meta: { + title: pageTitle('login') + } + }, + + { + path: '/:path(.*)*', + redirect: '/404' + }, + { + // 404 + path: '/404', + name: 'notFound', + component: () => import('@/views/common/error/404.vue'), + meta: { + title: pageTitle('notFound') // 页面不存在 + } + } +] + +export default staticRoutes diff --git a/src/stores/adminInfo.ts b/src/stores/adminInfo.ts new file mode 100644 index 0000000..37aebc6 --- /dev/null +++ b/src/stores/adminInfo.ts @@ -0,0 +1,37 @@ +import { defineStore } from 'pinia' +import { ADMIN_INFO } from '@/stores/constant/cacheKey' +import type { AdminInfo } from '@/stores/interface' + +export const useAdminInfo = defineStore('adminInfo', { + state: (): AdminInfo => { + return { + id: 0, + username: '', + nickname: '', + avatar: '', + last_login_time: '', + token: '', + refresh_token: '', + super: false + } + }, + actions: { + dataFill(state: AdminInfo) { + this.$state = { ...this.$state, ...state } + }, + removeToken() { + this.token = '' + this.refresh_token = '' + }, + setToken(token: string, type: 'auth' | 'refresh') { + const field = type == 'auth' ? 'token' : 'refresh_token' + this[field] = token + }, + getToken(type: 'auth' | 'refresh' = 'auth') { + return type === 'auth' ? this.token : this.refresh_token + } + }, + persist: { + key: ADMIN_INFO + } +}) diff --git a/src/stores/config.ts b/src/stores/config.ts new file mode 100644 index 0000000..efad570 --- /dev/null +++ b/src/stores/config.ts @@ -0,0 +1,125 @@ +import { reactive } from 'vue' +import { defineStore } from 'pinia' +import { STORE_CONFIG } from '@/stores/constant/cacheKey' +import type { Layout } from '@/stores/interface' + +export const useConfig = defineStore( + 'config', + () => { + const layout: Layout = reactive({ + /* 全局 */ + showDrawer: false, + // 是否收缩布局(小屏设备) + shrink: false, + // 后台布局方式,可选值 + layoutMode: 'Default', + // 后台主页面切换动画,可选值 + mainAnimation: 'slide-right', + // 是否暗黑模式 + isDark: false, + + /* 侧边菜单 */ + // 侧边菜单背景色 + menuBackground: ['#ffffff', '#1d1e1f'], + // 侧边菜单文字颜色 + menuColor: ['#303133', '#CFD3DC'], + // 侧边菜单激活项背景色 + menuActiveBackground: ['#ffffff', '#1d1e1f'], + // 侧边菜单激活项文字色 + menuActiveColor: ['#409eff', '#3375b9'], + // 侧边菜单顶栏背景色 + menuTopBarBackground: ['#fcfcfc', '#1d1e1f'], + // 侧边菜单宽度(展开时),单位px + menuWidth: 260, + // 侧边菜单项默认图标 + menuDefaultIcon: 'fa fa-circle-o', + // 是否水平折叠收起菜单 + menuCollapse: false, + // 是否只保持一个子菜单的展开(手风琴) + menuUniqueOpened: false, + // 显示菜单栏顶栏(LOGO) + menuShowTopBar: true, + + /* 顶栏 */ + // 顶栏文字色 + headerBarTabColor: ['#000000', '#CFD3DC'], + // 顶栏激活项背景色 + headerBarTabActiveBackground: ['#ffffff', '#1d1e1f'], + // 顶栏激活项文字色 + headerBarTabActiveColor: ['#000000', '#409EFF'], + // 顶栏背景色 + headerBarBackground: ['#ffffff', '#1d1e1f'], + // 顶栏悬停时背景色 + headerBarHoverBackground: ['#f5f5f5', '#18222c'] + }) + + const lang = reactive({ + // 默认语言,可选值 + defaultLang: 'zh-cn', + // 当在默认语言包找不到翻译时,继续在 fallbackLang 语言包内查找翻译 + fallbackLang: 'zh-cn', + // 支持的语言列表 + langArray: [ + { name: 'zh-cn', value: '中文简体' }, + { name: 'en', value: 'English' } + ] + }) + + function menuWidth() { + if (layout.shrink) { + return layout.menuCollapse ? '0px' : layout.menuWidth + 'px' + } + // 菜单是否折叠 + return layout.menuCollapse ? '64px' : layout.menuWidth + 'px' + } + + function setLang(val: string) { + lang.defaultLang = val + } + + function onSetLayoutColor(data = layout.layoutMode) { + // 切换布局时,如果是为默认配色方案,对菜单激活背景色重新赋值 + const tempValue = layout.isDark + ? { idx: 1, color: '#1d1e1f', newColor: '#141414' } + : { idx: 0, color: '#ffffff', newColor: '#f5f5f5' } + if ( + data == 'Classic' && + layout.headerBarBackground[tempValue.idx] == tempValue.color && + layout.headerBarTabActiveBackground[tempValue.idx] == tempValue.color + ) { + layout.headerBarTabActiveBackground[tempValue.idx] = tempValue.newColor + } else if ( + data == 'Default' && + layout.headerBarBackground[tempValue.idx] == tempValue.color && + layout.headerBarTabActiveBackground[tempValue.idx] == tempValue.newColor + ) { + layout.headerBarTabActiveBackground[tempValue.idx] = tempValue.color + } + } + + function setLayoutMode(data: string) { + layout.layoutMode = data + onSetLayoutColor(data) + } + + const setLayout = (name: keyof Layout, value: any) => { + layout[name] = value as never + } + + const getColorVal = function (name: keyof Layout): string { + const colors = layout[name] as string[] + if (layout.isDark) { + return colors[1] + } else { + return colors[0] + } + } + + return { layout, lang, menuWidth, setLang, setLayoutMode, setLayout, getColorVal, onSetLayoutColor } + }, + { + persist: { + key: STORE_CONFIG + } + } +) diff --git a/src/stores/constant/cacheKey.ts b/src/stores/constant/cacheKey.ts new file mode 100644 index 0000000..c3c13ad --- /dev/null +++ b/src/stores/constant/cacheKey.ts @@ -0,0 +1,12 @@ +/** + * 本地缓存Key + */ + +// 管理员资料 +export const ADMIN_INFO = 'adminInfo' + +// WEB端布局配置 +export const STORE_CONFIG = 'storeConfig' + +// 后台标签页 +export const STORE_TAB_VIEW_CONFIG = 'storeTabViewConfig' \ No newline at end of file diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..e952ed8 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,7 @@ +import { createPinia } from 'pinia' +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' + +const pinia = createPinia() +pinia.use(piniaPluginPersistedstate) + +export default pinia diff --git a/src/stores/interface/index.ts b/src/stores/interface/index.ts new file mode 100644 index 0000000..6360031 --- /dev/null +++ b/src/stores/interface/index.ts @@ -0,0 +1,45 @@ +// 变量名对应含义请在 /stores/* 里边找 +import type { RouteRecordRaw, RouteLocationNormalized } from 'vue-router' + +export interface Layout { + showDrawer: boolean + shrink: boolean + layoutMode: string + mainAnimation: string + isDark: boolean + menuWidth: number + menuDefaultIcon: string + menuCollapse: boolean + menuUniqueOpened: boolean + menuShowTopBar: boolean + menuBackground: string[] + menuColor: string[] + menuActiveBackground: string[] + menuActiveColor: string[] + menuTopBarBackground: string[] + headerBarTabColor: string[] + headerBarBackground: string[] + headerBarHoverBackground: string[] + headerBarTabActiveBackground: string[] + headerBarTabActiveColor: string[] +} + +export interface NavTabs { + activeIndex: number + activeRoute: RouteLocationNormalized | null + tabsView: RouteLocationNormalized[] + tabFullScreen: boolean + tabsViewRoutes: RouteRecordRaw[] + authNode: Map +} + +export interface AdminInfo { + id: number + username: string + nickname: string + avatar: string + last_login_time: string + token: string + refresh_token: string + super:boolean +} diff --git a/src/stores/navTabs.ts b/src/stores/navTabs.ts new file mode 100644 index 0000000..2306789 --- /dev/null +++ b/src/stores/navTabs.ts @@ -0,0 +1,108 @@ +import { reactive } from 'vue' +import { defineStore } from 'pinia' +import { STORE_TAB_VIEW_CONFIG } from '@/stores/constant/cacheKey' +import type { NavTabs } from '@/stores/interface/index' +import type { RouteLocationNormalized, RouteRecordRaw } from 'vue-router' +import { adminBaseRoutePath } from '@/router/static' + +export const useNavTabs = defineStore( + 'navTabs', + () => { + const state: NavTabs = reactive({ + // 激活tab的index + activeIndex: 0, + // 激活的tab + activeRoute: null, + // tab列表 + tabsView: [], + // 当前tab是否全屏 + tabFullScreen: false, + // 从后台加载到的菜单路由列表 + tabsViewRoutes: [], + // 按钮权限节点 + authNode: new Map(), + }) + + function addTab(route: RouteLocationNormalized) { + if (!route.meta.addtab) return + for (const key in state.tabsView) { + if (state.tabsView[key].path === route.path) { + state.tabsView[key].params = route.params ? route.params : state.tabsView[key].params + state.tabsView[key].query = route.query ? route.query : state.tabsView[key].query + return + } + } + state.tabsView.push(route) + } + + function closeTab(route: RouteLocationNormalized) { + state.tabsView.map((v, k) => { + if (v.path == route.path) { + state.tabsView.splice(k, 1) + return + } + }) + } + + /** + * 关闭多个标签 + * @param retainMenu 需要保留的标签,否则关闭全部标签 + */ + const closeTabs = (retainMenu: RouteLocationNormalized | false = false) => { + if (retainMenu) { + state.tabsView = [retainMenu] + } else { + state.tabsView = [] + } + } + + const setActiveRoute = (route: RouteLocationNormalized): void => { + const currentRouteIndex: number = state.tabsView.findIndex((item: RouteLocationNormalized) => { + return item.path === route.path + }) + if (currentRouteIndex === -1) return + state.activeRoute = route + state.activeIndex = currentRouteIndex + } + + const setTabsViewRoutes = (data: RouteRecordRaw[]): void => { + state.tabsViewRoutes = encodeRoutesURI(data) + } + + const setAuthNode = (key: string, data: string[]) => { + state.authNode.set(key, data) + } + + const fillAuthNode = (data: Map) => { + state.authNode = data + } + + const setFullScreen = (fullScreen: boolean): void => { + state.tabFullScreen = fullScreen + } + + return { state, addTab, closeTab, closeTabs, setActiveRoute, setTabsViewRoutes, setAuthNode, fillAuthNode, setFullScreen } + }, + { + persist: { + key: STORE_TAB_VIEW_CONFIG, + paths: ['state.tabFullScreen'], + }, + } +) + +/** + * 对iframe的url进行编码 + */ +function encodeRoutesURI(data: RouteRecordRaw[]) { + data.forEach((item) => { + if (item.meta?.menu_type == 'iframe') { + item.path = adminBaseRoutePath + '/iframe/' + encodeURIComponent(item.path) + } + + if (item.children && item.children.length) { + item.children = encodeRoutesURI(item.children) + } + }) + return data +} diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..bb131d6 --- /dev/null +++ b/src/style.css @@ -0,0 +1,79 @@ +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +.card { + padding: 2em; +} + +#app { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} diff --git a/src/styles/app.scss b/src/styles/app.scss new file mode 100644 index 0000000..d9802ec --- /dev/null +++ b/src/styles/app.scss @@ -0,0 +1,232 @@ +/* 基本样式 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; + outline: none !important; +} + +html, +body, +#app { + margin: 0; + padding: 0; + width: 100%; + height: 100%; + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif; + font-weight: 400; + -webkit-font-smoothing: antialiased; + -webkit-tap-highlight-color: transparent; + background-color: var(--ba-bg-color); + font-size: 14px; + overflow: hidden; + position: relative; +} + +// 阿里 iconfont Symbol引用css +.iconfont-icon { + width: 1em; + height: 1em; + vertical-align: -0.15em; + fill: currentColor; + overflow: hidden; +} + +.w100 { + width: 100% !important; +} +.h100 { + height: 100% !important; +} +.ba-center { + display: flex; + align-items: center; + justify-content: center; +} + +.default-main { + margin: var(--ba-main-space) var(--ba-main-space) 60px var(--ba-main-space); +} +.zoom-handle { + position: absolute; + width: 20px; + height: 20px; + bottom: -10px; + right: -10px; + cursor: se-resize; +} +.block-help { + display: block; + width: 100%; + color: #909399; + font-size: 13px; + line-height: 16px; + padding-top: 5px; +} + +/* 表格顶部菜单-s */ +.table-header { + .table-header-operate .icon { + font-size: 14px !important; + color: var(--el-color-white) !important; + } + .el-button.is-disabled .icon { + color: var(--el-button-disabled-text-color) !important; + } +} +/* 表格顶部菜单-e */ + +/* 鼠标置入浮动效果-s */ +.suspension { + transition: all 0.3s ease; +} +.suspension:hover { + -webkit-transform: translateY(-4px) scale(1.02); + -moz-transform: translateY(-4px) scale(1.02); + -ms-transform: translateY(-4px) scale(1.02); + -o-transform: translateY(-4px) scale(1.02); + transform: translateY(-4px) scale(1.02); + -webkit-box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2); + box-shadow: 0 14px 24px rgba(0, 0, 0, 0.2); + z-index: 999; + border-radius: 6px; +} +/* 鼠标置入浮动效果-e */ + +/* 表格-s */ +.ba-table-box { + border-radius: var(--el-border-radius-round); +} +.ba-table-alert { + background-color: var(--el-fill-color-darker) !important; + border: 1px solid var(--ba-boder-color); + border-bottom: 0; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; +} +/* 表格-e */ + +/* 新增/编辑表单-s */ +.ba-operate-dialog { + overflow: hidden; + border-radius: var(--el-border-radius-base); +} +.ba-operate-dialog .el-dialog__header { + padding-bottom: 16px; + border-bottom: 1px solid var(--ba-bg-color); +} +.ba-operate-dialog .el-dialog__body { + height: 60vh; + padding-top: 0; + padding-bottom: 52px; +} +.ba-operate-dialog .el-dialog__footer { + padding: 10px var(--el-dialog-padding-primary); + box-shadow: var(--el-box-shadow); + position: absolute; + width: 100%; + bottom: 0; +} +.ba-operate-form { + padding-top: 20px; +} +/* 新增/编辑表单-e */ + +/* 全局遮罩-s */ +.ba-layout-shade { + position: fixed; + top: 0; + left: 0; + height: 100vh; + width: 100vw; + background-color: rgba(0, 0, 0, 0.5); + z-index: 9999990; +} +/* 全局遮罩-e */ + +/* 图片上传预览-s */ +.img-preview-dialog .el-dialog__body { + display: flex; + align-items: center; + justify-content: center; + img { + max-width: 100%; + } +} +/* 图片上传预览-e */ + +/* 页面切换动画-s */ +.slide-right-enter-active, +.slide-right-leave-active, +.slide-left-enter-active, +.slide-left-leave-active { + will-change: transform; + transition: all 0.3s ease; +} +// slide-right +.slide-right-enter-from { + opacity: 0; + transform: translateX(-20px); +} +.slide-right-leave-to { + opacity: 0; + transform: translateX(20px); +} +// slide-left +.slide-left-enter-from { + @extend .slide-right-leave-to; +} +.slide-left-leave-to { + @extend .slide-right-enter-from; +} +/* 页面切换动画-e */ + +/* 布局相关-s */ +.frontend-footer-brother { + min-height: calc(100vh - 120px); +} +.user-views { + padding-left: 15px; + .user-views-card { + margin-bottom: 15px; + } +} +.ba-aside-drawer { + .el-drawer__body { + padding: 0; + } +} +/* 布局相关-e */ + +/* 暗黑模式公共样式-s */ +.ba-icon-dark { + color: var(--el-text-color-primary) !important; +} +/* 暗黑模式公共样式-e */ + +/* NProgress-s */ +#nprogress { + .bar, + .spinner { + z-index: 999999; + } +} +/* NProgress-e */ + +/* 自适应-s */ +@media screen and (max-width: 768px) { + .xs-hidden { + display: none; + } +} +@media screen and (max-width: 1024px) { + .ba-operate-dialog { + width: 96%; + } +} +@media screen and (max-width: 991px) { + .user-views { + padding: 0; + } +} +/* 自适应-e */ diff --git a/src/styles/element.scss b/src/styles/element.scss new file mode 100644 index 0000000..1ddad61 --- /dev/null +++ b/src/styles/element.scss @@ -0,0 +1,68 @@ +/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-s */ +.el-input .el-input__inner { + line-height: calc(var(--el-input-height, 40px) - 4px); +} +/* 修复 Chrome 浏览器输入框内选中字符行高异常的bug-e */ + +.datetime-picker { + height: 32px; + padding-top: 0; + padding-bottom: 0; +} +.el-divider__text.is-center { + transform: translateX(-50%) translateY(-62%); +} + +.el-menu { + user-select: none; + .el-sub-menu__title:hover { + background-color: var(--el-color-primary-light-9) !important; + } +} + +.el-table { + --el-table-border-color: var(--ba-border-color); +} + +.el-card { + border: none; +} +.el-card__header { + border-bottom: 1px solid var(--el-border-color-extra-light); +} +.el-textarea__inner { + padding: 5px 11px; +} + +/* dialog滚动条-s */ +.el-overlay-dialog, +.ba-scroll-style { + scrollbar-width: none; + &::-webkit-scrollbar { + width: 5px; + height: 5px; + } + &::-webkit-scrollbar-thumb { + background: #eaeaea; + border-radius: var(--el-border-radius-base); + box-shadow: none; + -webkit-box-shadow: none; + } + &:hover { + &::-webkit-scrollbar-thumb:hover { + background: #c8c9cc; + } + } +} +/* dialog滚动条-e */ + +/* 小屏设备 el-radio-group 样式优化-s */ +.ba-input-item-radio { + margin-bottom: 10px; + .el-radio-group { + .el-radio { + margin-bottom: 8px; + } + } +} +/* 小屏设备 el-radio-group 样式调整-e */ diff --git a/src/styles/index.scss b/src/styles/index.scss new file mode 100644 index 0000000..4f1726b --- /dev/null +++ b/src/styles/index.scss @@ -0,0 +1,3 @@ +@use '@/styles/app'; +@use '@/styles/element'; +@use '@/styles/var'; diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss new file mode 100644 index 0000000..ae0d086 --- /dev/null +++ b/src/styles/mixins.scss @@ -0,0 +1,30 @@ +@mixin set-css-var-value($name, $value) { + #{joinVarName($name)}: #{$value}; +} + +@function joinVarName($list) { + $name: '--ba'; + @each $item in $list { + @if $item != '' { + $name: $name + '-' + $item; + } + } + @return $name; +} + +@function getCssVarName($args...) { + @return joinVarName($args); +} + +/* + * 通过映射设置所有的CSS变量 + */ +@mixin set-component-css-var($name, $variables) { + @each $attribute, $value in $variables { + @if $attribute == 'default' { + #{getCssVarName($name)}: #{$value}; + } @else { + #{getCssVarName($name, $attribute)}: #{$value}; + } + } +} diff --git a/src/styles/var.scss b/src/styles/var.scss new file mode 100644 index 0000000..8559562 --- /dev/null +++ b/src/styles/var.scss @@ -0,0 +1,32 @@ +@use 'sass:map'; +@use 'mixins' as *; + +// 后台主体窗口左右间距 +$main-space: 16px; +$primary-light: #3f6ad8; + +// --ba-background +$bg-color: () !default; +$bg-color: map.merge( + ( + '': #f5f5f5, + 'overlay': #ffffff, + ), + $bg-color +); + +// --ba-border-color +$border-color: () !default; +$border-color: map.merge( + ( + '': #f6f6f6, + ), + $border-color +); + +:root { + @include set-css-var-value('main-space', $main-space); + @include set-css-var-value('color-primary-light', $primary-light); + @include set-component-css-var('bg-color', $bg-color); + @include set-component-css-var('border-color', $border-color); +} diff --git a/src/utils/common.ts b/src/utils/common.ts new file mode 100644 index 0000000..e20da9e --- /dev/null +++ b/src/utils/common.ts @@ -0,0 +1,65 @@ +import type { App } from 'vue' +import { adminBaseRoutePath } from '@/router/static' +import router from '@/router/index' +import { trimStart } from 'lodash-es' +import * as elIcons from '@element-plus/icons-vue' +import Icon from '@/components/icon/index.vue' + +export function registerIcons(app: App) { + /* + * 全局注册 Icon + * 使用方式: + * 详见<待完善> + */ + app.component('Icon', Icon) + + /* + * 全局注册element Plus的icon + */ + const icons = elIcons as any + for (const i in icons) { + app.component(`el-icon-${icons[i].name}`, icons[i]) + } +} + +/** + * 是否在后台应用内 + * @param path 不传递则通过当前路由 path 检查 + */ +export const isAdminApp = (path = '') => { + const regex = new RegExp(`^${adminBaseRoutePath}`) + if (path) { + return regex.test(path) + } + if (regex.test(getCurrentRoutePath())) { + return true + } + return false +} + +/** + * 获取路由 path + */ +export const getCurrentRoutePath = () => { + let path = router.currentRoute.value.path + if (path == '/') path = trimStart(window.location.hash, '#') + if (path.indexOf('?') !== -1) path = path.replace(/\?.*/, '') + return path +} + +/** + * 获取资源完整地址 + * @param relativeUrl 资源相对地址 + * @param domain 指定域名 + */ +export const fullUrl = (relativeUrl: string, domain = '') => { + return domain + relativeUrl +} + +/** + * 是否是外部链接 + * @param path + */ +export function isExternal(path: string): boolean { + return /^(https?|ftp|mailto|tel):/.test(path) +} diff --git a/src/utils/horizontalScroll.ts b/src/utils/horizontalScroll.ts new file mode 100644 index 0000000..47d6817 --- /dev/null +++ b/src/utils/horizontalScroll.ts @@ -0,0 +1,33 @@ +/** + * 横向滚动条 + */ +export default class horizontalScroll { + private el: HTMLElement + + constructor(nativeElement: HTMLElement) { + this.el = nativeElement + this.handleWheelEvent() + } + + handleWheelEvent() { + let wheel = '' + + if ('onmousewheel' in this.el) { + wheel = 'mousewheel' + } else if ('onwheel' in this.el) { + wheel = 'wheel' + } else if ('attachEvent' in window) { + wheel = 'onmousewheel' + } else { + wheel = 'DOMMouseScroll' + } + this.el['addEventListener'](wheel, this.scroll, { passive: true }) + } + + scroll = (event: any) => { + if (this.el.clientWidth >= this.el.scrollWidth) { + return + } + this.el.scrollLeft += event.deltaY ? event.deltaY : event.detail && event.detail !== 0 ? event.detail : -event.wheelDelta + } +} diff --git a/src/utils/iconfont.ts b/src/utils/iconfont.ts new file mode 100644 index 0000000..b49f38a --- /dev/null +++ b/src/utils/iconfont.ts @@ -0,0 +1,21 @@ +import { nextTick } from 'vue' +import * as elIcons from '@element-plus/icons-vue' +/* + * 获取element plus 自带的图标 + */ +export function getElementPlusIconfontNames() { + return new Promise((resolve, reject) => { + nextTick(() => { + const iconfonts = [] + const icons = elIcons as any + for (const i in icons) { + iconfonts.push(`el-icon-${icons[i].name}`) + } + if (iconfonts.length > 0) { + resolve(iconfonts) + } else { + reject('No ElementPlus Icons') + } + }) + }) +} diff --git a/src/utils/layout.ts b/src/utils/layout.ts new file mode 100644 index 0000000..8d4ba1d --- /dev/null +++ b/src/utils/layout.ts @@ -0,0 +1,41 @@ +import type { CSSProperties } from 'vue' +import { useNavTabs } from '@/stores/navTabs' +import { useConfig } from '@/stores/config' + +/** + * main高度 + * @param extra main高度额外减去的px数,可以实现隐藏原有的滚动条 + * @returns CSSProperties + */ +export function mainHeight(extra = 0): CSSProperties { + let height = extra + const adminLayoutMainExtraHeight: anyObj = { + Default: 70, + Classic: 50, + Streamline: 60 + } + const config = useConfig() + const navTabs = useNavTabs() + if (!navTabs.state.tabFullScreen) { + height += adminLayoutMainExtraHeight[config.layout.layoutMode] + } + + return { + height: 'calc(100vh - ' + height.toString() + 'px)' + } +} + +/** + * 设置导航栏宽度 + * @returns + */ +export function setNavTabsWidth() { + const navTabs = document.querySelector('.nav-tabs') as HTMLElement + if (!navTabs) { + return + } + const navBar = document.querySelector('.nav-bar') as HTMLElement + const navMenus = document.querySelector('.nav-menus') as HTMLElement + const minWidth = navBar.offsetWidth - (navMenus.offsetWidth + 20) + navTabs.style.width = minWidth.toString() + 'px' +} diff --git a/src/utils/pageShade.ts b/src/utils/pageShade.ts new file mode 100644 index 0000000..98cd5a9 --- /dev/null +++ b/src/utils/pageShade.ts @@ -0,0 +1,22 @@ +import { useEventListener } from '@vueuse/core' + +/* + * 显示页面遮罩 + */ +export const showShade = function (className = 'shade', closeCallBack: Function): void { + const containerEl = document.querySelector('.layout-container') as HTMLElement + const shadeDiv = document.createElement('div') + shadeDiv.setAttribute('class', 'ba-layout-shade ' + className) + containerEl.appendChild(shadeDiv) + useEventListener(shadeDiv, 'click', () => closeShade(closeCallBack)) +} + +/* + * 隐藏页面遮罩 + */ +export const closeShade = function (closeCallBack: Function = () => {}): void { + const shadeEl = document.querySelector('.ba-layout-shade') as HTMLElement + shadeEl && shadeEl.remove() + + closeCallBack() +} diff --git a/src/utils/random.ts b/src/utils/random.ts new file mode 100644 index 0000000..6b2a11d --- /dev/null +++ b/src/utils/random.ts @@ -0,0 +1,57 @@ +const hexList: string[] = [] +for (let i = 0; i <= 15; i++) { + hexList[i] = i.toString(16) +} + +/** + * 生成随机数 + * @param min 最小值 + * @param max 最大值 + * @returns 生成的随机数 + */ +export function randomNum(min: number, max: number) { + switch (arguments.length) { + case 1: + return parseInt((Math.random() * min + 1).toString(), 10) + break + case 2: + return parseInt((Math.random() * (max - min + 1) + min).toString(), 10) + break + default: + return 0 + break + } +} + +/** + * 生成全球唯一标识 + * @returns uuid + */ +export function uuid(): string { + let uuid = '' + for (let i = 1; i <= 36; i++) { + if (i === 9 || i === 14 || i === 19 || i === 24) { + uuid += '-' + } else if (i === 15) { + uuid += 4 + } else if (i === 20) { + uuid += hexList[(Math.random() * 4) | 8] + } else { + uuid += hexList[(Math.random() * 16) | 0] + } + } + return uuid +} + +/** + * 生成唯一标识 + * @param prefix 前缀 + * @returns 唯一标识 + */ +export function shortUuid(prefix = ''): string { + const time = Date.now() + const random = Math.floor(Math.random() * 1000000000) + if (!window.unique) window.unique = 0 + window.unique++ + return prefix + '_' + random + window.unique + String(time) +} diff --git a/src/utils/router.ts b/src/utils/router.ts new file mode 100644 index 0000000..6d31c8c --- /dev/null +++ b/src/utils/router.ts @@ -0,0 +1,295 @@ +import router from '@/router/index' +import { isNavigationFailure, NavigationFailureType } from 'vue-router' +import type { RouteRecordRaw, RouteLocationRaw } from 'vue-router' +import { ElNotification } from 'element-plus' +import { useConfig } from '@/stores/config' +import { useNavTabs } from '@/stores/navTabs' +import { closeShade } from '@/utils/pageShade' +import { adminBaseRoute } from '@/router/static' +import { compact, isEmpty, reverse } from 'lodash-es' +import { isAdminApp } from '@/utils/common' + +/** + * 导航失败有错误消息的路由push + * @param to — 导航位置,同 router.push + */ +export const routePush = async (to: RouteLocationRaw) => { + try { + const failure = await router.push(to) + if (isNavigationFailure(failure, NavigationFailureType.aborted)) { + ElNotification({ + message: 'utils.Navigation failed, navigation guard intercepted!', + type: 'error' + }) + } else if (isNavigationFailure(failure, NavigationFailureType.duplicated)) { + ElNotification({ + message: 'utils.Navigation failed, it is at the navigation target position!', + type: 'warning' + }) + } + } catch (error) { + ElNotification({ + message: 'utils.Navigation failed, invalid route!', + type: 'error' + }) + console.error(error) + } +} + +/** + * 获取第一个菜单 + */ +export const getFirstRoute = (routes: RouteRecordRaw[], menuType = 'tab'): false | RouteRecordRaw => { + const routerPaths: string[] = [] + const routers = router.getRoutes() + routers.forEach(item => { + if (item.path) routerPaths.push(item.path) + }) + let find: boolean | RouteRecordRaw = false + for (const key in routes) { + if ( + routes[key].meta?.type == 'menu' && + routes[key].meta?.menu_type == menuType && + routerPaths.indexOf(routes[key].path) !== -1 + ) { + return routes[key] + } else if (routes[key].children && routes[key].children?.length) { + find = getFirstRoute(routes[key].children!) + if (find) return find + } + } + return find +} + +/** + * 打开侧边菜单 + * @param menu 菜单数据 + */ +export const onClickMenu = (menu: RouteRecordRaw) => { + switch (menu.meta?.menu_type) { + case 'iframe': + case 'tab': + routePush({ path: menu.path }) + break + case 'link': + window.open(menu.path, '_blank') + break + + default: + ElNotification({ + message: 'utils.Navigation failed, the menu type is unrecognized!', + type: 'error' + }) + break + } + + const config = useConfig() + if (config.layout.shrink) { + closeShade(() => { + config.setLayout('menuCollapse', true) + }) + } +} + +/** + * 处理后台的路由 + */ +export const handleAdminRoute = (routes: any) => { + const viewsComponent = import.meta.glob('/src/views/**/*.vue') + addRouteAll(viewsComponent, routes, adminBaseRoute.name as string) + const menuAdminBaseRoute = (adminBaseRoute.path as string) + '/' + + // 更新stores中的路由菜单数据 + const navTabs = useNavTabs() + navTabs.setTabsViewRoutes(handleMenuRule(routes, menuAdminBaseRoute)) + navTabs.fillAuthNode(handleAuthNode(routes, menuAdminBaseRoute)) +} + +/** + * 获取菜单的paths + */ +export const getMenuPaths = (menus: RouteRecordRaw[]): string[] => { + let menuPaths: string[] = [] + menus.forEach(item => { + menuPaths.push(item.path) + if (item.children && item.children.length > 0) { + menuPaths = menuPaths.concat(getMenuPaths(item.children)) + } + }) + return menuPaths +} + +/** + * 后台的菜单处理 + */ +const handleMenuRule = (routes: any, pathPrefix = '/', type = ['menu', 'menu_dir']) => { + const menuRule: RouteRecordRaw[] = [] + for (const key in routes) { + if (routes[key].extend == 'add_rules_only') { + continue + } + if (!type.includes(routes[key].type)) { + continue + } + if (routes[key].type == 'menu_dir' && routes[key].children && !routes[key].children.length) { + continue + } + if ( + ['route', 'menu', 'nav_user_menu', 'nav'].includes(routes[key].type) && + ((routes[key].menu_type == 'tab' && !routes[key].component) || + (['link', 'iframe'].includes(routes[key].menu_type) && !routes[key].url)) + ) { + continue + } + const currentPath = ['link', 'iframe'].includes(routes[key].menu_type) + ? routes[key].url + : pathPrefix + routes[key].path + let children: RouteRecordRaw[] = [] + if (routes[key].children && routes[key].children.length > 0) { + children = handleMenuRule(routes[key].children, pathPrefix, type) + } + menuRule.push({ + path: currentPath, + name: routes[key].name, + component: routes[key].component, + meta: { + id: routes[key].id, + title: routes[key].title, + icon: routes[key].icon, + keepalive: routes[key].keepalive, + menu_type: routes[key].menu_type, + type: routes[key].type + }, + children: children + }) + } + return menuRule +} + +/** + * 处理权限节点 + * @param routes 路由数据 + * @param prefix 节点前缀 + * @returns 组装好的权限节点 + */ +const handleAuthNode = (routes: any, prefix = '/') => { + const authNode: Map = new Map([]) + assembleAuthNode(routes, authNode, prefix, prefix) + return authNode +} +const assembleAuthNode = (routes: any, authNode: Map, prefix = '/', parent = '/') => { + const authNodeTemp = [] + for (const key in routes) { + if (routes[key].type == 'button') authNodeTemp.push(prefix + routes[key].name) + if (routes[key].children && routes[key].children.length > 0) { + assembleAuthNode(routes[key].children, authNode, prefix, prefix + routes[key].name) + } + } + if (authNodeTemp && authNodeTemp.length > 0) { + authNode.set(parent, authNodeTemp) + } +} + +/** + * 动态添加路由-带子路由 + * @param viewsComponent + * @param routes + * @param parentName + * @param analyticRelation 根据 name 从已注册路由分析父级路由 + */ +export const addRouteAll = ( + viewsComponent: Record, + routes: any, + parentName: string, + analyticRelation = false +) => { + for (const idx in routes) { + if (routes[idx].extend == 'add_menu_only') { + continue + } + if ( + (routes[idx].menu_type == 'tab' && viewsComponent[routes[idx].component]) || + routes[idx].menu_type == 'iframe' + ) { + addRouteItem(viewsComponent, routes[idx], parentName, analyticRelation) + } + + if (routes[idx].children && routes[idx].children.length > 0) { + addRouteAll(viewsComponent, routes[idx].children, parentName, analyticRelation) + } + } +} + +/** + * 动态添加路由 + * @param viewsComponent + * @param route + * @param parentName + * @param analyticRelation 根据 name 从已注册路由分析父级路由 + */ +export const addRouteItem = ( + viewsComponent: Record, + route: any, + parentName: string, + analyticRelation: boolean +) => { + let path = '', + component + if (route.menu_type == 'iframe') { + path = (isAdminApp() ? adminBaseRoute.path : '') + '/iframe/' + encodeURIComponent(route.url) + component = () => import('@/layouts/common/router-view/iframe.vue') + } else { + path = parentName ? route.path : '/' + route.path + component = viewsComponent[route.component] + } + + if (route.menu_type == 'tab' && analyticRelation) { + const parentNames = getParentNames(route.name) + if (parentNames.length) { + for (const key in parentNames) { + if (router.hasRoute(parentNames[key])) { + parentName = parentNames[key] + break + } + } + } + } + + const routeBaseInfo: RouteRecordRaw = { + path: path, + name: route.name, + component: component, + meta: { + title: route.title, + extend: route.extend, + icon: route.icon, + keepalive: route.keepalive, + menu_type: route.menu_type, + type: route.type, + url: route.url, + addtab: true + } + } + if (parentName) { + router.addRoute(parentName, routeBaseInfo) + } else { + router.addRoute(routeBaseInfo) + } +} + +/** + * 根据name字符串,获取父级name组合的数组 + * @param name + */ +const getParentNames = (name: string) => { + const names = compact(name.split('/')) + const tempNames = [] + const parentNames = [] + for (const key in names) { + tempNames.push(names[key]) + if (parseInt(key) != names.length - 1) { + parentNames.push(tempNames.join('/')) + } + } + return reverse(parentNames) +} diff --git a/src/utils/storage.ts b/src/utils/storage.ts new file mode 100644 index 0000000..1b4d3e5 --- /dev/null +++ b/src/utils/storage.ts @@ -0,0 +1,45 @@ +/** + * window.localStorage + * @method set 设置 + * @method get 获取 + * @method remove 移除 + * @method clear 移除全部 + */ +export const Local = { + set(key: string, val: any) { + window.localStorage.setItem(key, JSON.stringify(val)) + }, + get(key: string) { + const json: any = window.localStorage.getItem(key) + return JSON.parse(json) + }, + remove(key: string) { + window.localStorage.removeItem(key) + }, + clear() { + window.localStorage.clear() + }, +} + +/** + * window.sessionStorage + * @method set 设置会话缓存 + * @method get 获取会话缓存 + * @method remove 移除会话缓存 + * @method clear 移除全部会话缓存 + */ +export const Session = { + set(key: string, val: any) { + window.sessionStorage.setItem(key, JSON.stringify(val)) + }, + get(key: string) { + const json: any = window.sessionStorage.getItem(key) + return JSON.parse(json) + }, + remove(key: string) { + window.sessionStorage.removeItem(key) + }, + clear() { + window.sessionStorage.clear() + }, +} diff --git a/src/utils/useCurrentInstance.ts b/src/utils/useCurrentInstance.ts new file mode 100644 index 0000000..c0fc26e --- /dev/null +++ b/src/utils/useCurrentInstance.ts @@ -0,0 +1,13 @@ +import { getCurrentInstance } from 'vue' +import type { ComponentInternalInstance } from 'vue' + +export default function useCurrentInstance() { + if (!getCurrentInstance()) { + throw new Error('useCurrentInstance() can only be used inside setup() or functional components!') + } + const { appContext } = getCurrentInstance() as ComponentInternalInstance + const proxy = appContext.config.globalProperties + return { + proxy, + } +} diff --git a/src/views/common/error/404.vue b/src/views/common/error/404.vue new file mode 100644 index 0000000..1af96e2 --- /dev/null +++ b/src/views/common/error/404.vue @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue new file mode 100644 index 0000000..d7eb11c --- /dev/null +++ b/src/views/dashboard/index.vue @@ -0,0 +1,78 @@ + + + + \ No newline at end of file diff --git a/src/views/user/login.vue b/src/views/user/login.vue new file mode 100644 index 0000000..00af92b --- /dev/null +++ b/src/views/user/login.vue @@ -0,0 +1 @@ + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..9f021f4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + "removeComments": false, + "strict": true, + "jsx": "preserve", + "sourceMap": false, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + "isolatedModules": true, + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + }, + "types": ["element-plus/global"] + }, + "exclude": ["node_modules"] +} diff --git a/types/global.d.ts b/types/global.d.ts new file mode 100644 index 0000000..7aabd62 --- /dev/null +++ b/types/global.d.ts @@ -0,0 +1,28 @@ +interface Window { + existLoading: boolean + lazy: number + unique: number + tokenRefreshing: boolean + requests: Function[] + eventSource: EventSource + loadLangHandle: Record +} + +interface anyObj { + [key: string]: any +} + +interface TableDefaultData { + list: T + remark: string + total: number +} + +interface ApiResponse { + code: number + data: T + msg: string + time: number +} + +type ApiPromise = Promise> diff --git a/types/vite-env.d.ts b/types/vite-env.d.ts new file mode 100644 index 0000000..e9cd753 --- /dev/null +++ b/types/vite-env.d.ts @@ -0,0 +1,7 @@ +/// +declare module '*.vue' { + import { DefineComponent } from 'vue' + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types + const component: DefineComponent<{}, {}, any> + export default component +} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..7d36834 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import path from 'path' +const nodeResolve = (dir: string) => path.resolve(__dirname, '.', dir) +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': nodeResolve('src'), + '~': nodeResolve('public'), + } + } +})