├── .env ├── .env.development ├── .env.production ├── .eslintrc-auto-import.json ├── .eslintrc.cjs ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.cjs ├── .vscode └── extensions.json ├── LICENSE ├── README.md ├── commitlint.config.cjs ├── index.html ├── mock ├── constant.ts ├── controller │ └── user.ts ├── mockProdServer.ts └── utils.ts ├── package.json ├── pnpm-lock.yaml ├── public └── vite.svg ├── src ├── App.vue ├── api │ └── user.ts ├── assets │ └── images │ │ ├── login │ │ ├── background.png │ │ ├── login-box.png │ │ └── login-content.png │ │ └── user │ │ ├── avatar01.png │ │ ├── avatar15.png │ │ └── headImg.gif ├── components │ ├── base-box │ │ └── index.vue │ ├── base-button │ │ └── index.vue │ ├── base-charts │ │ ├── index.ts │ │ └── src │ │ │ ├── base-charts.vue │ │ │ ├── config │ │ │ ├── bar.ts │ │ │ ├── graph.ts │ │ │ ├── heatmap.ts │ │ │ ├── line.ts │ │ │ ├── liquidfill.ts │ │ │ ├── map.ts │ │ │ ├── pie.ts │ │ │ ├── radar.ts │ │ │ ├── scatter.ts │ │ │ └── wordcloud.ts │ │ │ ├── hooks │ │ │ ├── useCharts.ts │ │ │ └── useChartsConfig.ts │ │ │ └── types.ts │ ├── base-count-to │ │ └── index.vue │ ├── base-dialog │ │ ├── index.ts │ │ └── src │ │ │ ├── base-dialog.scss │ │ │ ├── base-dialog.vue │ │ │ └── props.ts │ ├── base-icon │ │ ├── base-icon-picker.vue │ │ ├── base-icon.vue │ │ └── icon.ts │ ├── base-loading │ │ ├── base-loading.vue │ │ └── spin │ │ │ ├── chaseSpin.vue │ │ │ ├── cubeSpin.vue │ │ │ ├── dotSpin.vue │ │ │ ├── planeSpin.vue │ │ │ ├── preloaderSpin.vue │ │ │ ├── pulseSpin.vue │ │ │ └── rectSpin.vue │ ├── base-result │ │ └── base-result.vue │ ├── base-seamscroll │ │ ├── index.ts │ │ └── src │ │ │ ├── base-seam-scroll.vue │ │ │ ├── props.ts │ │ │ └── types.ts │ └── base-skeleton │ │ └── base-skeleton.vue ├── directive │ ├── index.ts │ ├── loading.ts │ └── permission.ts ├── enums │ ├── DropMenuEnum.ts │ ├── httpEnum.ts │ ├── loadingEnum.ts │ └── storageEnum.ts ├── hooks │ ├── index.ts │ ├── useDark.ts │ ├── useEnv.ts │ ├── useLoading.ts │ ├── useMessage.ts │ ├── usePageSetting.ts │ ├── useStorage.ts │ └── useWatermark.ts ├── icons │ ├── 403.svg │ ├── 404.svg │ ├── 500.svg │ ├── about.svg │ ├── approval.svg │ ├── clear.svg │ ├── click.svg │ ├── close.svg │ ├── column.svg │ ├── comp.svg │ ├── confirm.svg │ ├── dark.svg │ ├── dashboard.svg │ ├── deliver.svg │ ├── down.svg │ ├── editPassword.svg │ ├── empty.svg │ ├── esc.svg │ ├── exit.svg │ ├── export.svg │ ├── feedback.svg │ ├── filter.svg │ ├── fold.svg │ ├── fullOutScreen.svg │ ├── fullScreen.svg │ ├── func.svg │ ├── gitee.svg │ ├── github.svg │ ├── good.svg │ ├── help.svg │ ├── icon.svg │ ├── light.svg │ ├── location.svg │ ├── log.svg │ ├── logo.svg │ ├── money.svg │ ├── more.svg │ ├── nested.svg │ ├── other.svg │ ├── out.svg │ ├── page.svg │ ├── permission.svg │ ├── qq.svg │ ├── read.svg │ ├── remind.svg │ ├── search.svg │ ├── setting.svg │ ├── size.svg │ ├── sizeMini.svg │ ├── sizePlus.svg │ ├── textSize.svg │ ├── unfold.svg │ ├── up.svg │ ├── view.svg │ ├── warning.svg │ ├── weixin.svg │ └── zhifubao.svg ├── layouts │ ├── hooks │ │ └── useTagEvent.ts │ ├── index.vue │ ├── nav-bar │ │ ├── breadcrumb.vue │ │ ├── index.vue │ │ ├── nav-fullscreen.vue │ │ ├── nav-search.vue │ │ ├── nav-switch.vue │ │ └── nav-user.vue │ ├── side-bar │ │ ├── index.scss │ │ ├── index.vue │ │ ├── logo.vue │ │ └── side-bar-item.vue │ └── tag-view │ │ ├── index.scss │ │ ├── index.vue │ │ ├── tag-action.vue │ │ ├── tag-fullscreen.vue │ │ ├── tag-item.vue │ │ └── tag-scroll.vue ├── main.ts ├── mockProdServer.ts ├── plugins │ ├── echarts.ts │ ├── globalUtils.ts │ ├── icon.ts │ └── index.ts ├── router │ ├── basic.ts │ ├── guard │ │ ├── index.ts │ │ ├── pageTitle.ts │ │ ├── permission.ts │ │ └── progress.ts │ ├── index.ts │ ├── modules │ │ ├── dashboard.ts │ │ ├── nested.ts │ │ ├── out-link.ts │ │ └── page.ts │ └── types.ts ├── stores │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── permission.ts │ │ ├── tagView.ts │ │ └── user.ts ├── styles │ ├── common.scss │ ├── index.scss │ ├── scrollbar.scss │ └── theme │ │ ├── base.scss │ │ ├── dark.scss │ │ └── index.scss ├── utils │ ├── common.ts │ ├── dom.ts │ ├── index.ts │ ├── is.ts │ ├── message.ts │ ├── request │ │ ├── cancel.ts │ │ ├── index.ts │ │ ├── loading.ts │ │ └── retry.ts │ ├── requestAnimationFrame.ts │ └── storage.ts └── views │ ├── dashboard │ ├── analysis │ │ ├── column.ts │ │ ├── components │ │ │ ├── CardList.vue │ │ │ ├── DetailAnalysis.vue │ │ │ ├── TrueDynamic.vue │ │ │ └── TurnoverAnalysis.vue │ │ └── index.vue │ └── workbench │ │ ├── column.ts │ │ ├── components │ │ ├── Calendar.vue │ │ ├── DynamicInfo.vue │ │ ├── NoticeList.vue │ │ ├── QuickNav.vue │ │ └── TodoList.vue │ │ └── index.vue │ ├── login │ └── index.vue │ ├── nested │ ├── menu1 │ │ ├── menu1-1 │ │ │ └── menu1-1-1 │ │ │ │ └── index.vue │ │ └── menu1-2 │ │ │ └── index.vue │ └── menu2 │ │ └── index.vue │ ├── page │ ├── 403.vue │ ├── 404.vue │ ├── 500.vue │ ├── error.vue │ └── success.vue │ └── redirect │ └── index.vue ├── tsconfig.json ├── tsconfig.node.json ├── types └── global.d.ts └── vite.config.ts /.env: -------------------------------------------------------------------------------- 1 | # app name 2 | VITE_APP_NAME=Vue3 Basic Admin 3 | 4 | # Whether to open mock 5 | VITE_USE_MOCK = true -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = / 3 | 4 | # api 5 | VITE_BASE_API= /api 6 | 7 | # upload api 8 | VITE_BASE_UPLOAD_API= /upload 9 | 10 | # Whether to open mock 11 | VITE_USE_MOCK = true 12 | 13 | # Delete console 14 | VITE_DROP_CONSOLE = false -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # public path 2 | VITE_PUBLIC_PATH = ./ 3 | 4 | # api 5 | VITE_BASE_API= /api 6 | 7 | # upload api 8 | VITE_BASE_UPLOAD_API= /upload 9 | 10 | # Whether to open mock 11 | VITE_USE_MOCK = true 12 | 13 | # Delete console 14 | VITE_DROP_CONSOLE = true -------------------------------------------------------------------------------- /.eslintrc-auto-import.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": {} 3 | } -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true 6 | }, 7 | extends: ["standard", "plugin:vue/vue3-essential", "eslint-config-prettier"], 8 | parserOptions: { 9 | ecmaVersion: "latest", 10 | parser: "@typescript-eslint/parser", 11 | sourceType: "module" 12 | }, 13 | rules: { 14 | "no-undef": "off", 15 | semi: ["error", "always"], 16 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 17 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 18 | "space-before-function-paren": 0, 19 | "vue/array-bracket-spacing": "error", 20 | "vue/arrow-spacing": "error", 21 | "vue/block-spacing": "error", 22 | "vue/brace-style": "error", 23 | "vue/camelcase": "off", 24 | "vue/comma-dangle": "error", 25 | "vue/component-name-in-template-casing": "error", 26 | "vue/eqeqeq": "error", 27 | "vue/key-spacing": "error", 28 | "vue/match-component-file-name": "error", 29 | "vue/object-curly-spacing": "off", 30 | "no-useless-escape": "off", 31 | "no-unused-vars": "off", 32 | "vue/attribute-hyphenation": "off", 33 | "vue/custom-event-name-casing": "off", 34 | "vue/multi-word-component-names": "off", 35 | "vue/comment-directive": "off" 36 | }, 37 | plugins: ["vue", "@typescript-eslint"] 38 | }; 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .history 15 | report.html 16 | components.d.ts 17 | auto-imports.d.ts 18 | 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | .DS_Store 25 | *.suo 26 | *.ntvs* 27 | *.njsproj 28 | *.sln 29 | *.sw? 30 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no -- commitlint --edit 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | *.svg 5 | /node_modules/** 6 | 7 | **/*.svg 8 | **/*.sh 9 | 10 | /public/* -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // 一行最多 100 字符 3 | printWidth: 150, 4 | // 使用 4 个空格缩进 5 | tabWidth: 4, 6 | // 不使用缩进符,而使用空格 7 | useTabs: false, 8 | // 行尾分号 9 | semi: true, 10 | // 使用单引号 11 | singleQuote: false, 12 | // 对象的 key 仅在必要时用引号 13 | quoteProps: "as-needed", 14 | // jsx 不使用单引号,而使用双引号 15 | jsxSingleQuote: false, 16 | // 尾随逗号 17 | trailingComma: "none", 18 | // 大括号内的首尾需要空格 19 | bracketSpacing: true, 20 | // jsx 标签的反尖括号需要换行 21 | jsxBracketSameLine: false, 22 | // 箭头函数,只有一个参数的时候,也需要括号 23 | arrowParens: "always", 24 | // 每个文件格式化的范围是文件的全部内容 25 | rangeStart: 0, 26 | rangeEnd: Infinity, 27 | // 不需要写文件开头的 @prettier 28 | requirePragma: false, 29 | // 不需要自动在文件开头插入 @prettier 30 | insertPragma: false, 31 | // 使用默认的折行标准 32 | proseWrap: "preserve", 33 | // 根据显示样式决定 html 要不要折行 34 | htmlWhitespaceSensitivity: "css", 35 | // 换行符使用 lf 36 | endOfLine: "lf", 37 | }; 38 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] 3 | } 4 | -------------------------------------------------------------------------------- /commitlint.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["@commitlint/config-conventional"], 3 | rules: { 4 | "type-enum": [ 5 | 2, 6 | "always", 7 | [ 8 | "build", // 编译相关修改(新版本发布) 9 | "feat", // 新功能 10 | "fix", // 修复bug 11 | "update", // 更新某功能 12 | "refactor", // 重构 13 | "docs", // 文档 14 | "chore", // 增加依赖或库 15 | "style", // 格式(不影响代码变动) 16 | "revert", // 撤销commit 回滚上一版本 17 | "perf", // 性能优化 18 | "test" // 测试单元 19 | ] 20 | ], 21 | "type-case": [0], 22 | "type-empty": [0], 23 | "scope-empty": [0], 24 | "scope-case": [0], 25 | "subject-full-stop": [0, "never"], 26 | "subject-case": [0, "never"], 27 | "header-max-length": [0, "always", 72] 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue3 Basic Admin 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mock/constant.ts: -------------------------------------------------------------------------------- 1 | export const userData = [ 2 | { 3 | id: 1, 4 | username: "admin", 5 | deptId: "6", 6 | deptName: "研发部", 7 | role: "admin", 8 | createTime: "2023-01-05 12:32:14", 9 | status: 0, 10 | remark: "拥有admin所有权限" 11 | } 12 | ]; 13 | 14 | export const roleData = [ 15 | { 16 | id: 11, 17 | role: "admin", 18 | roleName: "admin", 19 | createTime: "2023-01-12 12:32:45", 20 | status: 1, 21 | remark: "系统管理员,拥有所有权限", 22 | menuIds: [ 23 | "admin_dashboard", 24 | "admin_dashboard_analysis", 25 | "admin_dashboard_workbench", 26 | "admin_page", 27 | "admin_page_403", 28 | "admin_page_404", 29 | "admin_page_500", 30 | "admin_page_success", 31 | "admin_page_error", 32 | "admin_permission", 33 | "admin_permission_page", 34 | "admin_permission_button", 35 | "admin_permission_button:admin" 36 | ] 37 | } 38 | ]; 39 | -------------------------------------------------------------------------------- /mock/controller/user.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from "vite-plugin-mock"; 2 | import { RequestParams, resultSuccess, resultError } from "../utils"; 3 | import { userData, roleData } from "../constant"; 4 | import { Random } from "mockjs"; 5 | 6 | export default [ 7 | { 8 | url: "/api/login", 9 | method: "post", 10 | response: (request: RequestParams) => { 11 | const { username, password } = request.body; 12 | const userItem = userData.find((item) => item.username === username); 13 | if (!userItem) { 14 | return resultError("该用户不存在"); 15 | } 16 | return resultSuccess({ 17 | token: userItem.role + Random.string("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-.123456789", 180) 18 | }); 19 | } 20 | }, 21 | { 22 | url: "/api/getUserInfo", 23 | method: "get", 24 | response: (request: RequestParams) => { 25 | const { authorization } = request.headers as any; 26 | if (!authorization) { 27 | return resultError("获取用户信息失败"); 28 | } 29 | const userItem = userData.find((item) => authorization.includes(item.role)); 30 | if (!userItem) { 31 | return resultError("获取用户信息失败:未找到该用户"); 32 | } 33 | const roleIds = roleData.find((item) => item.role === userItem.role)?.menuIds; 34 | return resultSuccess({ 35 | ...userItem, 36 | roleIds 37 | }); 38 | } 39 | } 40 | ] as MockMethod[]; 41 | -------------------------------------------------------------------------------- /mock/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer"; 2 | import user from "./controller/user"; 3 | 4 | export function setupProdMockServer() { 5 | createProdMockServer([...user]); 6 | } 7 | -------------------------------------------------------------------------------- /mock/utils.ts: -------------------------------------------------------------------------------- 1 | import { ResultEnum } from "@/enums/httpEnum"; 2 | 3 | export interface RequestParams { 4 | methods: string; 5 | body: any; 6 | headers?: { authorization?: string }; 7 | query: any; 8 | } 9 | 10 | export const resultSuccess = (data: any = null, message = "请求成功") => { 11 | return { 12 | code: ResultEnum.SUCCESS, 13 | data, 14 | message 15 | }; 16 | }; 17 | 18 | export const resultError = (message = "请求失败") => { 19 | return { 20 | code: ResultEnum.ERROR, 21 | data: null, 22 | message 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue3-basic-template", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "start": "vite dev", 9 | "build": "vue-tsc --noEmit && vite build", 10 | "preview": "vite preview", 11 | "lint": "eslint --ext=vue,js src", 12 | "lint:fix": "eslint --ext=vue,js --fix src", 13 | "prepare": "husky install" 14 | }, 15 | "dependencies": { 16 | "@element-plus/icons-vue": "^2.1.0", 17 | "axios": "^1.3.4", 18 | "dayjs": "^1.11.7", 19 | "echarts": "^5.4.2", 20 | "echarts-liquidfill": "3", 21 | "echarts-wordcloud": "2", 22 | "element-plus": "^2.3.1", 23 | "mockjs": "^1.1.0", 24 | "nprogress": "^0.2.0", 25 | "path-browserify": "^1.0.1", 26 | "pinia": "^2.0.33", 27 | "pinia-plugin-persistedstate": "^3.1.0", 28 | "vue": "^3.2.47", 29 | "vue-router": "^4.1.6" 30 | }, 31 | "devDependencies": { 32 | "@commitlint/cli": "^17.5.0", 33 | "@commitlint/config-conventional": "^17.4.4", 34 | "@types/mockjs": "^1.0.7", 35 | "@types/node": "^18.15.10", 36 | "@types/nprogress": "^0.2.0", 37 | "@types/path-browserify": "^1.0.0", 38 | "@typescript-eslint/eslint-plugin": "^5.57.0", 39 | "@typescript-eslint/parser": "^5.57.0", 40 | "@vitejs/plugin-vue": "^4.1.0", 41 | "@vitejs/plugin-vue-jsx": "^3.0.1", 42 | "@vueuse/core": "^9.13.0", 43 | "commitlint": "^17.5.0", 44 | "eslint": "^8.36.0", 45 | "eslint-config-airbnb-base": "^15.0.0", 46 | "eslint-config-prettier": "^8.8.0", 47 | "eslint-config-standard": "^17.0.0", 48 | "eslint-plugin-import": "^2.25.2", 49 | "eslint-plugin-n": "^15.6.1", 50 | "eslint-plugin-prettier": "^4.2.1", 51 | "eslint-plugin-promise": "^6.1.1", 52 | "eslint-plugin-vue": "^9.10.0", 53 | "husky": "^8.0.3", 54 | "lint-staged": "^13.2.0", 55 | "prettier": "^2.8.7", 56 | "sass": "^1.60.0", 57 | "typescript": "^4.9.3", 58 | "unplugin-auto-import": "^0.15.2", 59 | "unplugin-vue-components": "^0.24.1", 60 | "vite": "^4.2.0", 61 | "vite-plugin-mock": "^2.9.6", 62 | "vite-plugin-svg-icons": "^2.0.1", 63 | "vue-tsc": "^1.2.0" 64 | }, 65 | "lint-staged": { 66 | "*.{js,ts,tsx,jsx,vue}": [ 67 | "eslint --fix", 68 | "prettier --write", 69 | "git add" 70 | ], 71 | "{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [ 72 | "prettier --write--parser json" 73 | ], 74 | "package.json": [ 75 | "prettier --write" 76 | ], 77 | "*.md": [ 78 | "prettier --write" 79 | ] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/api/user.ts: -------------------------------------------------------------------------------- 1 | import request from "@/utils/request"; 2 | 3 | export enum Api { 4 | LOGIN = "/login", 5 | GET_USER_INFO = "/getUserInfo" 6 | } 7 | 8 | export const login = (data?: any) => request.post(Api.LOGIN, data); 9 | 10 | export const getUserInfo = (data?: any) => request.get(Api.GET_USER_INFO, data); 11 | -------------------------------------------------------------------------------- /src/assets/images/login/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/login/background.png -------------------------------------------------------------------------------- /src/assets/images/login/login-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/login/login-box.png -------------------------------------------------------------------------------- /src/assets/images/login/login-content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/login/login-content.png -------------------------------------------------------------------------------- /src/assets/images/user/avatar01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/user/avatar01.png -------------------------------------------------------------------------------- /src/assets/images/user/avatar15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/user/avatar15.png -------------------------------------------------------------------------------- /src/assets/images/user/headImg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/biubiubiu01/vue3-basic-template/74e8d43db89fd27112e3659170dbea6c91d107a5/src/assets/images/user/headImg.gif -------------------------------------------------------------------------------- /src/components/base-button/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /src/components/base-charts/index.ts: -------------------------------------------------------------------------------- 1 | import BaseCharts from "./src/base-charts.vue"; 2 | import BaseMap from "./src/base-map.vue"; 3 | 4 | export * from "./src/types"; 5 | export { BaseCharts, BaseMap }; 6 | -------------------------------------------------------------------------------- /src/components/base-charts/src/base-charts.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/bar.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const barConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "axis", 6 | axisPointer: { 7 | type: "shadow" 8 | }, 9 | padding: [5, 10] 10 | }, 11 | legend: {}, 12 | grid: { 13 | left: "4%", 14 | right: "2%", 15 | bottom: "7%", 16 | top: "2%" 17 | }, 18 | xAxis: [ 19 | { 20 | type: "category", 21 | data: [], 22 | 23 | axisTick: { 24 | show: true 25 | } 26 | } 27 | ], 28 | yAxis: [ 29 | { 30 | type: "value", 31 | axisTick: { 32 | show: false 33 | } 34 | } 35 | ], 36 | series: [ 37 | { 38 | type: "bar", 39 | name: "", 40 | data: [], 41 | barWidth: "auto", 42 | barGap: "80%" 43 | } 44 | ] 45 | }; 46 | 47 | export default barConfig; 48 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/graph.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const graphConfig: EChartsOption = { 4 | tooltip: {}, 5 | series: [ 6 | { 7 | type: "graph", 8 | layout: "force", 9 | force: { 10 | repulsion: 120, 11 | edgeLength: [20, 70] 12 | }, 13 | roam: true, 14 | draggable: true, 15 | symbolSize: (params) => { 16 | return params; 17 | }, 18 | itemStyle: { 19 | shadowColor: "rgba(133,203,247,0.75)", 20 | shadowBlur: 15 21 | }, 22 | label: { 23 | show: true 24 | }, 25 | data: [] 26 | } 27 | ], 28 | animationDurationUpdate: (index) => { 29 | return index * 100; 30 | }, 31 | animationEasingUpdate: "bounceIn" 32 | }; 33 | 34 | export default graphConfig; 35 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/heatmap.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const heatmapConfig: EChartsOption = { 4 | visualMap: { 5 | left: "3%", 6 | bottom: "2%", 7 | color: ["#ff4601", "#fffc00", "#87cffa"], 8 | calculable: true, 9 | textStyle: { 10 | color: "#fff", 11 | fontSize: 12 12 | } 13 | }, 14 | geo: { 15 | map: "map", 16 | roam: true, 17 | itemStyle: { 18 | areaColor: "#17439a", 19 | borderColor: "#53D9FF", 20 | borderWidth: 1.3, 21 | shadowBlur: 15, 22 | shadowColor: "rgb(58,115,192)", 23 | shadowOffsetX: 7, 24 | shadowOffsetY: 6 25 | }, 26 | label: { 27 | show: true, 28 | color: "#fff" 29 | }, 30 | emphasis: { 31 | itemStyle: { 32 | areaColor: "#17439a" 33 | } 34 | }, 35 | zoom: 1.22 36 | }, 37 | series: [ 38 | { 39 | name: "hotMap", 40 | type: "heatmap", 41 | data: [], 42 | coordinateSystem: "geo", 43 | pointSize: 13, 44 | blurSize: 40 45 | } 46 | ] 47 | }; 48 | 49 | export default heatmapConfig; 50 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/line.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const lineConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "axis", 6 | axisPointer: { 7 | type: "cross" 8 | } 9 | }, 10 | grid: { 11 | left: "4%", 12 | right: "2%", 13 | bottom: "7%", 14 | top: "2%" 15 | }, 16 | xAxis: [ 17 | { 18 | type: "category", 19 | boundaryGap: false, 20 | data: [], 21 | 22 | axisPointer: { 23 | snap: true 24 | } 25 | } 26 | ], 27 | yAxis: [ 28 | { 29 | type: "value", 30 | axisTick: { 31 | show: false 32 | }, 33 | axisPointer: { 34 | snap: true 35 | } 36 | } 37 | ], 38 | series: [ 39 | { 40 | name: "", 41 | type: "line", 42 | data: [], 43 | smooth: true, 44 | showSymbol: false 45 | } 46 | ] 47 | }; 48 | 49 | export default lineConfig; 50 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/liquidfill.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const liquidfillConfig: EChartsOption = { 4 | series: [ 5 | { 6 | // @ts-ignore 7 | type: "liquidFill", 8 | radius: "75%", 9 | center: ["50%", "45%"], 10 | data: [], 11 | backgroundStyle: { 12 | color: { 13 | type: "linear", 14 | x: 1, 15 | y: 0, 16 | x2: 0.5, 17 | y2: 1, 18 | colorStops: [ 19 | { 20 | offset: 1, 21 | color: "rgba(168, 218, 247, 0.4)" 22 | }, 23 | { 24 | offset: 0.5, 25 | color: "rgba(168, 218, 247, 0.5)" 26 | }, 27 | { 28 | offset: 0, 29 | color: "rgba(168, 218, 247, 0.8)" 30 | } 31 | ], 32 | globalCoord: false 33 | } 34 | }, 35 | outline: { 36 | borderDistance: 0, 37 | itemStyle: { 38 | borderWidth: 5, 39 | borderColor: { 40 | type: "linear", 41 | x: 0, 42 | y: 0, 43 | x2: 0, 44 | y2: 1, 45 | colorStops: [ 46 | { 47 | offset: 0, 48 | color: "rgba(81,142,215, 0)" 49 | }, 50 | { 51 | offset: 0.5, 52 | color: "rgba(53,142,215, 0.45)" 53 | }, 54 | { 55 | offset: 1, 56 | color: "rgba(53,142,215, 0.6)" 57 | } 58 | ], 59 | globalCoord: false 60 | }, 61 | shadowColor: "rgba(66,102,247, 0.55)", 62 | shadowBlur: 10 63 | } 64 | }, 65 | label: { 66 | fontSize: 25 67 | } 68 | } 69 | ] 70 | }; 71 | 72 | export default liquidfillConfig; 73 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/map.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const mapConfig: EChartsOption = { 4 | tooltip: {}, 5 | visualMap: { 6 | left: "3%", 7 | bottom: "2%", 8 | calculable: true, 9 | inRange: { 10 | color: ["#24CFF4", "#2E98CA", "#1E62AC"] 11 | }, 12 | textStyle: { 13 | color: "#24CFF4" 14 | } 15 | }, 16 | series: [ 17 | { 18 | name: "地图", 19 | type: "map", 20 | map: "map", 21 | roam: true, 22 | zoom: 1.22, 23 | data: [], 24 | label: { 25 | show: true, 26 | color: "rgb(249, 249, 249)" 27 | }, 28 | itemStyle: { 29 | areaColor: "#24CFF4", 30 | borderColor: "#53D9FF", 31 | borderWidth: 1.3, 32 | shadowBlur: 15, 33 | shadowColor: "rgb(58,115,192)", 34 | shadowOffsetX: 7, 35 | shadowOffsetY: 6 36 | }, 37 | emphasis: { 38 | label: { 39 | show: true, 40 | color: "#f75a00" 41 | }, 42 | itemStyle: { 43 | areaColor: "#8dd7fc", 44 | borderWidth: 1.6, 45 | shadowBlur: 25 46 | } 47 | } 48 | } 49 | ] 50 | }; 51 | 52 | export default mapConfig; 53 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/pie.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const pieConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | legend: { 8 | show: true, 9 | left: "center", 10 | top: "3%", 11 | type: "scroll", 12 | itemWidth: 18, 13 | itemHeight: 11 14 | }, 15 | series: [ 16 | { 17 | name: "", 18 | type: "pie", 19 | radius: ["35%", "55%"], 20 | center: ["48%", "55%"], 21 | itemStyle: {}, 22 | data: [] 23 | } 24 | ] 25 | }; 26 | 27 | export default pieConfig; 28 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/radar.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const radarConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | legend: { 8 | show: true, 9 | left: "center", 10 | top: "0%", 11 | type: "scroll", 12 | itemWidth: 18, 13 | itemHeight: 11 14 | }, 15 | radar: { 16 | radius: "65%", 17 | splitNumber: 4, 18 | center: ["48%", "55%"], 19 | startAngle: 90, 20 | indicator: [] 21 | }, 22 | series: [ 23 | { 24 | type: "radar", 25 | symbolSize: 0, 26 | areaStyle: { 27 | shadowBlur: 13, 28 | shadowColor: "rgba(0,0,0,.2)", 29 | shadowOffsetX: 0, 30 | shadowOffsetY: 10, 31 | opacity: 1 32 | }, 33 | data: [] 34 | } 35 | ] 36 | }; 37 | 38 | export default radarConfig; 39 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/scatter.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const scatterConfig: EChartsOption = { 4 | tooltip: {}, 5 | visualMap: { 6 | left: "3%", 7 | bottom: "2%", 8 | calculable: true, 9 | inRange: { 10 | color: ["#24CFF4", "#2E98CA", "#1E62AC"] 11 | }, 12 | textStyle: { 13 | color: "#24CFF4" 14 | } 15 | }, 16 | geo: { 17 | map: "map", 18 | roam: true, 19 | zoom: 1.22, 20 | itemStyle: { 21 | areaColor: "#1E62AC", 22 | borderColor: "#53D9FF", 23 | borderWidth: 1.3, 24 | shadowBlur: 15, 25 | shadowColor: "rgb(58,115,192)", 26 | shadowOffsetX: 7, 27 | shadowOffsetY: 6 28 | }, 29 | label: { 30 | show: true, 31 | color: "rgb(249, 249, 249)" // 省份标签字体颜色 32 | }, 33 | emphasis: { 34 | itemStyle: { 35 | areaColor: "#8dd7fc", 36 | borderWidth: 1.6 37 | }, 38 | label: { 39 | show: true, 40 | color: "rgb(249, 249, 249)" // 省份标签字体颜色 41 | } 42 | } 43 | }, 44 | series: [ 45 | { 46 | name: "散点", 47 | type: "scatter", 48 | data: [], 49 | symbol: "pin", 50 | coordinateSystem: "geo", 51 | symbolSize: 45, 52 | label: { 53 | show: true, 54 | color: "#fff", 55 | formatter: (params: any): any => { 56 | return parseInt(params.value[2]); 57 | } 58 | }, 59 | itemStyle: { 60 | color: "#f99" 61 | } 62 | } 63 | ] 64 | }; 65 | 66 | export default scatterConfig; 67 | -------------------------------------------------------------------------------- /src/components/base-charts/src/config/wordcloud.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsOption } from "echarts"; 2 | 3 | const pieConfig: EChartsOption = { 4 | tooltip: { 5 | trigger: "item" 6 | }, 7 | series: [ 8 | { 9 | type: "wordCloud", 10 | sizeRange: [14, 28], 11 | rotationRange: [0, 0], 12 | width: "100%", 13 | height: "100%", 14 | shape: "pentagon ", 15 | gridSize: 25, 16 | top: 0, 17 | data: [] 18 | } 19 | ] 20 | }; 21 | 22 | export default pieConfig; 23 | -------------------------------------------------------------------------------- /src/components/base-charts/src/hooks/useCharts.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from "vue"; 2 | import type { EChartsOption } from "echarts"; 3 | import echarts from "@/plugins/echarts"; 4 | import { useResizeObserver, useDebounceFn, useTimeoutFn, tryOnUnmounted } from "@vueuse/core"; 5 | import { useDark } from "@/hooks/useDark"; 6 | import { deepClone } from "@/utils"; 7 | 8 | export const useCharts = (elRef: Ref) => { 9 | const { isDark } = useDark(); 10 | 11 | let chartInstance: echarts.ECharts | null = null; 12 | 13 | const cacheOption = ref({}); 14 | 15 | const getOption = computed((): EChartsOption => { 16 | return unref(isDark) 17 | ? (Object.assign({}, unref(cacheOption), { backgroundColor: "transparent" }) as EChartsOption) 18 | : (cacheOption.value as EChartsOption); 19 | }); 20 | 21 | const initCharts = () => { 22 | if (!unref(elRef)) return; 23 | chartInstance = echarts.init(unref(elRef), unref(isDark) ? "dark" : "default"); 24 | useResizeObserver( 25 | unref(elRef), 26 | useDebounceFn((e) => { 27 | const contentRect = e[0].contentRect; 28 | if (Math.round(contentRect.width) !== chartInstance?.getWidth() || Math.round(contentRect.height) !== chartInstance?.getHeight()) { 29 | resize(); 30 | } 31 | }, 100) 32 | ); 33 | }; 34 | 35 | const setOption = async (options: EChartsOption, clear = true) => { 36 | cacheOption.value = deepClone(options); 37 | await nextTick(); 38 | 39 | useTimeoutFn(() => { 40 | if (!chartInstance) { 41 | initCharts(); 42 | if (!chartInstance) return; 43 | } 44 | clear && chartInstance.clear(); 45 | chartInstance.setOption(unref(getOption)); 46 | }, 30); 47 | }; 48 | 49 | const resize = () => { 50 | chartInstance?.resize(); 51 | }; 52 | 53 | const clear = () => { 54 | chartInstance?.clear(); 55 | chartInstance?.dispose(); 56 | chartInstance = null; 57 | }; 58 | 59 | const getInstance = (): echarts.ECharts | null => { 60 | if (!chartInstance) { 61 | initCharts(); 62 | return chartInstance; 63 | } 64 | return chartInstance; 65 | }; 66 | 67 | tryOnUnmounted(() => { 68 | clear(); 69 | }); 70 | 71 | onActivated(() => { 72 | clear(); 73 | }); 74 | 75 | onDeactivated(() => { 76 | clear(); 77 | }); 78 | 79 | watch( 80 | () => isDark.value, 81 | () => { 82 | if (chartInstance) { 83 | clear(); 84 | setOption(cacheOption.value as EChartsOption); 85 | } 86 | } 87 | ); 88 | 89 | return { 90 | initCharts, 91 | getInstance, 92 | setOption, 93 | resize, 94 | clear 95 | }; 96 | }; 97 | -------------------------------------------------------------------------------- /src/components/base-charts/src/hooks/useChartsConfig.ts: -------------------------------------------------------------------------------- 1 | import type { EChartsType, EChartsOption } from "../types"; 2 | import { isObject, isArray, isUndefined, deepClone } from "@/utils"; 3 | 4 | export const useChartConfig = (type: EChartsType, options?: EChartsOption) => { 5 | const modulesList = getModules(); 6 | 7 | function getModules() { 8 | const configModules = import.meta.glob("../config/**.ts", { eager: true, import: "default" }); 9 | const re = "([^/]*)(\\.\\w+)$"; 10 | const modulesObj: EChartsOption = {}; 11 | Object.keys(configModules).forEach((item) => { 12 | modulesObj[item.match(re)?.[1]] = configModules[item]; 13 | }); 14 | return modulesObj; 15 | } 16 | 17 | const mergeConfig = (src: any = {}, target: any = {}): T => { 18 | let key: string; 19 | for (key in target) { 20 | if (isObject(target[key])) { 21 | src[key] = mergeConfig(src[key] || {}, target[key]); 22 | } else if (isArray(target[key])) { 23 | if (isUndefined(src[key])) { 24 | src[key] = []; 25 | } 26 | target[key].forEach((_: any, index: number) => { 27 | if (isUndefined(src[key][index])) { 28 | src[key][index] = {}; 29 | } 30 | Object.assign(src[key][index], target[key][index]); 31 | }); 32 | } else { 33 | Object.assign(src, target); 34 | } 35 | } 36 | return src; 37 | }; 38 | 39 | const getConfig = computed(() => { 40 | return mergeConfig(deepClone(modulesList[type]), options); 41 | }); 42 | 43 | return { 44 | getConfig 45 | }; 46 | }; 47 | -------------------------------------------------------------------------------- /src/components/base-charts/src/types.ts: -------------------------------------------------------------------------------- 1 | export type EChartsType = "line" | "bar" | "pie" | "wordcloud" | "radar" | "liquidfill" | "graph" | "map" | "scatter" | "heatmap"; 2 | 3 | export type { EChartsOption } from "echarts"; 4 | -------------------------------------------------------------------------------- /src/components/base-dialog/index.ts: -------------------------------------------------------------------------------- 1 | import BaseDialog from "./src/base-dialog.vue"; 2 | 3 | export { BaseDialog }; 4 | -------------------------------------------------------------------------------- /src/components/base-dialog/src/base-dialog.scss: -------------------------------------------------------------------------------- 1 | .dialog-fade-enter-active .el-dialog.base-dialog { 2 | animation: cocoFadeIn 0.3s; 3 | } 4 | .dialog-fade-leave-active .el-dialog.base-dialog { 5 | animation: cocoFadeOut 0.3s; 6 | } 7 | 8 | .el-overlay.blur { 9 | backdrop-filter: blur(5px); 10 | } 11 | 12 | .base-dialog { 13 | text-align: left; 14 | .el-dialog__header { 15 | padding-top: 6px; 16 | padding-bottom: 0; 17 | margin-right: 0; 18 | .base-dialog-header { 19 | height: 52px; 20 | line-height: 50px; 21 | .base-header-icon { 22 | position: absolute; 23 | right: 10px; 24 | top: 22px; 25 | } 26 | } 27 | } 28 | 29 | .el-dialog__footer { 30 | padding-bottom: 10px; 31 | } 32 | .el-dialog__body { 33 | padding: 14px !important; 34 | } 35 | .el-scrollbar__view { 36 | height: 100%; 37 | } 38 | &.el-dialog.is-fullscreen { 39 | .el-dialog__body { 40 | height: calc(100% - 112px); 41 | } 42 | } 43 | } 44 | 45 | @keyframes cocoFadeIn { 46 | 0% { 47 | transform: scale(0); 48 | opacity: 0; 49 | } 50 | to { 51 | transform: scale(1); 52 | opacity: 1; 53 | } 54 | } 55 | @keyframes cocoFadeOut { 56 | 0% { 57 | transform: scale3d(1, 1, 1); 58 | opacity: 1; 59 | } 60 | to { 61 | transform: scale3d(0.1, 0.1, 0.1); 62 | opacity: 0; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/base-dialog/src/props.ts: -------------------------------------------------------------------------------- 1 | import { PropType } from "vue"; 2 | 3 | export const defaultProps = { 4 | visible: { 5 | type: Boolean 6 | }, 7 | title: { 8 | type: String, 9 | default: "提示" 10 | }, 11 | width: { 12 | type: [String, Number], 13 | default: "50%" 14 | }, 15 | top: { 16 | type: String, 17 | default: "15vh" 18 | }, 19 | modal: { 20 | type: Boolean, 21 | default: true 22 | }, 23 | appendToBody: { 24 | type: Boolean, 25 | default: false 26 | }, 27 | lockScroll: { 28 | type: Boolean, 29 | default: true 30 | }, 31 | closeOnClickModal: { 32 | type: Boolean, 33 | default: false 34 | }, 35 | closeOnPressEscape: { 36 | type: Boolean, 37 | default: false 38 | }, 39 | showClose: { 40 | type: Boolean, 41 | default: false 42 | }, 43 | draggable: { 44 | type: Boolean, 45 | default: false 46 | }, 47 | center: { 48 | type: Boolean, 49 | default: false 50 | }, 51 | alignCenter: { 52 | type: Boolean, 53 | default: false 54 | }, 55 | destroyOnClose: { 56 | type: Boolean, 57 | default: false 58 | }, 59 | fullscreen: { 60 | type: Boolean, 61 | default: false 62 | } 63 | }; 64 | 65 | export const extraProps = { 66 | showFooter: { 67 | type: Boolean, 68 | default: true 69 | }, 70 | showHeader: { 71 | type: Boolean, 72 | default: true 73 | }, 74 | btnPosition: { 75 | type: String as PropType<"left" | "right" | "center">, 76 | default: "center" 77 | }, 78 | showCancelBtn: { 79 | type: Boolean, 80 | default: true 81 | }, 82 | cancelText: { 83 | type: String, 84 | default: "取 消" 85 | }, 86 | showSaveBtn: { 87 | type: Boolean, 88 | default: true 89 | }, 90 | saveText: { 91 | type: String, 92 | default: "确 认" 93 | }, 94 | showFullscreen: { 95 | type: Boolean, 96 | default: true 97 | }, 98 | minHeight: { 99 | type: String, 100 | default: "auto" 101 | }, 102 | height: { 103 | type: String, 104 | default: "auto" 105 | }, 106 | modalType: { 107 | type: String as PropType<"" | "blur">, 108 | default: "" 109 | }, 110 | closeConfirm: { 111 | type: Boolean, 112 | default: false 113 | }, 114 | closeFun: { 115 | type: Function, 116 | default: null 117 | } 118 | }; 119 | 120 | export default Object.assign({}, defaultProps, extraProps); 121 | -------------------------------------------------------------------------------- /src/components/base-icon/icon.ts: -------------------------------------------------------------------------------- 1 | import { Icons } from "@/plugins/icon"; 2 | 3 | const getSvgIconList = (): string[] => { 4 | const modules = import.meta.glob("@/icons/**/*.svg", { eager: true, import: "default" }); 5 | const re = "([^/]*)(\\.\\w+)$"; 6 | return Object.keys(modules).map((item) => item.match(re)?.[1]); 7 | }; 8 | 9 | const getElIconList = (): string[] => { 10 | return Object.keys(Icons); 11 | }; 12 | 13 | export const elIconList = getElIconList(); 14 | 15 | export const svgIconList = getSvgIconList(); 16 | 17 | export const allIconList = [...elIconList, ...svgIconList.map((item) => `svg-${item}`)]; 18 | -------------------------------------------------------------------------------- /src/components/base-loading/base-loading.vue: -------------------------------------------------------------------------------- 1 | 58 | 93 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/chaseSpin.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 20 | 93 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/cubeSpin.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 23 | 24 | 74 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/dotSpin.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 77 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/planeSpin.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | 14 | 35 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/preloaderSpin.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 63 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/pulseSpin.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 47 | -------------------------------------------------------------------------------- /src/components/base-loading/spin/rectSpin.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 19 | 20 | 53 | -------------------------------------------------------------------------------- /src/components/base-result/base-result.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/index.ts: -------------------------------------------------------------------------------- 1 | import BaseSeamScroll from "./src/base-seam-scroll.vue"; 2 | 3 | export * from "./src/types"; 4 | export { BaseSeamScroll }; 5 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/src/props.ts: -------------------------------------------------------------------------------- 1 | import type { PropType } from "vue"; 2 | import { SeamScrollOptionType } from "./types"; 3 | 4 | export const baseProps = { 5 | data: { 6 | type: Array as PropType, 7 | default: () => [] 8 | }, 9 | options: { 10 | type: Object as PropType, 11 | default: () => {} 12 | }, 13 | width: { 14 | type: [Number, String], 15 | default: "100%" 16 | }, 17 | height: { 18 | type: [Number, String], 19 | default: "300px" 20 | } 21 | }; 22 | 23 | export const defaultOptions: SeamScrollOptionType = { 24 | autoPlay: true, 25 | switchDelay: 400, 26 | hoverStop: true, 27 | step: 1, 28 | singleHeight: 0, 29 | waitTime: 1000 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/base-seamscroll/src/types.ts: -------------------------------------------------------------------------------- 1 | export type SeamScrollOptionType = { 2 | autoPlay?: boolean; 3 | switchDelay?: number; 4 | hoverStop?: boolean; 5 | step?: number; 6 | singleHeight?: number; 7 | waitTime?: number; 8 | }; 9 | 10 | export interface StateSeam { 11 | top: number; 12 | autoPlay: boolean; 13 | delay: number; 14 | copyHtml: string; 15 | isHover: boolean; 16 | reqFrame: any; 17 | singleWaitTime: any; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/base-skeleton/base-skeleton.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 68 | 69 | 81 | -------------------------------------------------------------------------------- /src/directive/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./loading"; 2 | export * from "./permission"; 3 | -------------------------------------------------------------------------------- /src/directive/loading.ts: -------------------------------------------------------------------------------- 1 | import type { Directive, App } from "vue"; 2 | import { useLoading } from "@/hooks"; 3 | import BaseLoading from "@/components/base-loading/base-loading.vue"; 4 | 5 | const loadingDirective: Directive = { 6 | mounted(el, bind) { 7 | const { props } = BaseLoading; 8 | 9 | const full = el.getAttribute("loading-full"); 10 | const text = el.getAttribute("loading-text") || props.text.default; 11 | const textColor = el.getAttribute("loading-text-color") || props.textColor.default; 12 | const background = el.getAttribute("loading-background") || props.background.default; 13 | const spin = el.getAttribute("loading-spin") || props.spin.default; 14 | 15 | const instance = useLoading({ 16 | text, 17 | textColor, 18 | background, 19 | spin 20 | }); 21 | el.instance = instance; 22 | if (bind.value) { 23 | instance.open(full ? document.body : el); 24 | } 25 | }, 26 | updated(el, bind) { 27 | const instance = el.instance; 28 | if (!instance) return; 29 | if (bind.value) { 30 | instance.open(el.getAttribute("loading-full") === "true" ? document.body : el); 31 | } else { 32 | instance.close(); 33 | } 34 | }, 35 | unmounted(el) { 36 | el?.instance?.close(); 37 | } 38 | }; 39 | 40 | export const setupLoadingDirective = (app: App) => { 41 | app.directive("custom-loading", loadingDirective); 42 | }; 43 | 44 | export default loadingDirective; 45 | -------------------------------------------------------------------------------- /src/directive/permission.ts: -------------------------------------------------------------------------------- 1 | import type { Directive, App, DirectiveBinding } from "vue"; 2 | import { useUserStoreWithOut } from "@/stores/modules/user"; 3 | 4 | function isAuth(el: Element, binding: any) { 5 | const userStore = useUserStoreWithOut(); 6 | const value = binding.value; 7 | if (!value) return; 8 | if (!userStore.roleIds.includes(value)) { 9 | el.parentNode?.removeChild(el); 10 | } 11 | } 12 | 13 | const permissionDirective: Directive = { 14 | mounted(el: Element, binding: DirectiveBinding) { 15 | isAuth(el, binding); 16 | }, 17 | updated(el: Element, binding: DirectiveBinding) { 18 | isAuth(el, binding); 19 | } 20 | }; 21 | 22 | export const setupPermissionDirective = (app: App) => { 23 | app.directive("permission", permissionDirective); 24 | }; 25 | 26 | export default permissionDirective; 27 | -------------------------------------------------------------------------------- /src/enums/DropMenuEnum.ts: -------------------------------------------------------------------------------- 1 | export enum DropMenuEnum { 2 | REFRESH_PAGE = "REFRESH_PAGE", 3 | CLOSE_CURRENT = "CLOSE_CURRENT", 4 | CLOSE_LEFT = "CLOSE_LEFT", 5 | CLOSE_RIGHT = "CLOSE_RIGHT", 6 | CLOSE_OTHER = "CLOSE_OTHER", 7 | CLOSE_ALL = "CLOSE_ALL" 8 | } 9 | 10 | export type DropMenuType = { 11 | text: string; 12 | icon?: string; 13 | command?: string | number; 14 | disabled?: boolean; 15 | divided?: boolean; 16 | }; 17 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description: Request result set 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = 500 7 | } 8 | 9 | /** 10 | * @description: contentType 11 | */ 12 | export enum ContentTypeEnum { 13 | // json 14 | JSON = "application/json;charset=UTF-8", 15 | // form-data qs 16 | FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8", 17 | // form-data upload 18 | FORM_DATA = "multipart/form-data;charset=UTF-8" 19 | } 20 | 21 | /** 22 | * @description: contentType 23 | */ 24 | export enum ErrorMsgEnum { 25 | ERROR_400 = "请求失败,参数类型不匹配", 26 | ERROR_401 = "请求失败,登录状态已过期", 27 | ERROR_403 = "请求失败,您无权访问", 28 | ERROR_404 = "请求失败,未找到该资源", 29 | ERROR_500 = "请求失败,服务器错误,请联系管理员", 30 | ERROR_503 = "请求失败,服务器异常", 31 | ERROR_504 = "请求失败,请求超时" 32 | } 33 | 34 | export enum ErrorTypeEnum { 35 | VUE = "vue", 36 | SCRIPT = "script", 37 | AJAX = "ajax" 38 | } 39 | -------------------------------------------------------------------------------- /src/enums/loadingEnum.ts: -------------------------------------------------------------------------------- 1 | import type { Component } from "vue"; 2 | 3 | import pulseSpin from "@/components/base-loading/spin/pulseSpin.vue"; 4 | import rectSpin from "@/components/base-loading/spin/rectSpin.vue"; 5 | import planeSpin from "@/components/base-loading/spin/planeSpin.vue"; 6 | import cubeSpin from "@/components/base-loading/spin/cubeSpin.vue"; 7 | import preloaderSpin from "@/components/base-loading/spin/preloaderSpin.vue"; 8 | import chaseSpin from "@/components/base-loading/spin/chaseSpin.vue"; 9 | import dotSpin from "@/components/base-loading/spin/dotSpin.vue"; 10 | 11 | export enum LoadingEnum { 12 | PULSE = "pulse", 13 | RECT = "rect", 14 | PLANE = "plane", 15 | CUBE = "cube", 16 | PRELOADER = "preloader", 17 | CHASE = "chase", 18 | DOT = "dot", 19 | LOADING = "loading" 20 | } 21 | 22 | const loadingMap = new Map(); 23 | 24 | loadingMap.set(LoadingEnum.PULSE, pulseSpin); 25 | loadingMap.set(LoadingEnum.RECT, rectSpin); 26 | loadingMap.set(LoadingEnum.PLANE, planeSpin); 27 | loadingMap.set(LoadingEnum.CUBE, cubeSpin); 28 | loadingMap.set(LoadingEnum.PRELOADER, preloaderSpin); 29 | loadingMap.set(LoadingEnum.CHASE, chaseSpin); 30 | loadingMap.set(LoadingEnum.DOT, dotSpin); 31 | 32 | export { loadingMap }; 33 | -------------------------------------------------------------------------------- /src/enums/storageEnum.ts: -------------------------------------------------------------------------------- 1 | export enum StorageEnum { 2 | THEME_MODE = "THEME_MODE" 3 | } 4 | -------------------------------------------------------------------------------- /src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { useMessage } from "./useMessage"; 2 | export { useEnv } from "./useEnv"; 3 | export { useLoading } from "./useLoading"; 4 | export { useDark } from "./useDark"; 5 | export { useStorage } from "./useStorage"; 6 | export { useWatermark } from "./useWatermark"; 7 | export { usePageSetting } from "./usePageSetting"; 8 | -------------------------------------------------------------------------------- /src/hooks/useDark.ts: -------------------------------------------------------------------------------- 1 | import { StorageEnum } from "@/enums/storageEnum"; 2 | import { useStorage } from "./useStorage"; 3 | import { addClass, removeClass } from "@/utils"; 4 | 5 | const { getItem, setItem } = useStorage("local"); 6 | 7 | const isDark = ref(getItem(StorageEnum.THEME_MODE) === "dark"); 8 | 9 | export const useDark = () => { 10 | const htmlEle = document.documentElement; 11 | 12 | const toggle = (dark: boolean = !isDark.value) => { 13 | if (dark) { 14 | addClass(htmlEle, "dark"); 15 | } else { 16 | removeClass(htmlEle, "dark"); 17 | } 18 | isDark.value = dark; 19 | setItem(StorageEnum.THEME_MODE, dark ? "dark" : "light"); 20 | }; 21 | 22 | toggle(isDark.value); 23 | 24 | return { isDark, toggle }; 25 | }; 26 | -------------------------------------------------------------------------------- /src/hooks/useEnv.ts: -------------------------------------------------------------------------------- 1 | export function useEnv() { 2 | const { VITE_APP_NAME, VITE_BASE_API, VITE_PUBLIC_PATH, VITE_BASE_UPLOAD_API, MODE } = import.meta.env; 3 | 4 | return { 5 | MODE, 6 | VITE_APP_NAME, 7 | VITE_BASE_API, 8 | VITE_PUBLIC_PATH, 9 | VITE_BASE_UPLOAD_API 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /src/hooks/useLoading.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import { LoadingEnum } from "@/enums/loadingEnum"; 3 | import baseLoading from "@/components/base-loading/base-loading.vue"; 4 | 5 | type LoadingType = { 6 | text?: string; 7 | textColor?: string; 8 | background?: string; 9 | spin?: LoadingEnum; 10 | minTime?: number; 11 | modal?: boolean; 12 | }; 13 | 14 | export function useLoading(config: LoadingType = {}) { 15 | const loadingConstructor = createApp(baseLoading, { ...config }); 16 | let instance: any = null; 17 | let startTime: number = 0; 18 | let endTime: number = 0; 19 | const minTime = config.minTime || 0; 20 | 21 | const open = (target: HTMLElement = document.body) => { 22 | if (!instance) { 23 | instance = loadingConstructor.mount(document.createElement("div")); 24 | } 25 | if (!instance || !instance.$el) return; 26 | target?.appendChild?.(instance.$el); 27 | startTime = performance.now(); 28 | }; 29 | 30 | const close = () => { 31 | if (!instance || !instance.$el) return; 32 | endTime = performance.now(); 33 | if (endTime - startTime < minTime) { 34 | setTimeout(() => { 35 | instance.$el.parentNode?.removeChild(instance.$el); 36 | }, Math.floor(minTime - (endTime - startTime))); 37 | } else { 38 | instance.$el.parentNode?.removeChild(instance.$el); 39 | } 40 | }; 41 | 42 | return { 43 | open, 44 | close 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/hooks/useMessage.ts: -------------------------------------------------------------------------------- 1 | import { messageSuccess, messageWarning, messageInfo, messageError, MessageConfig } from "@/utils"; 2 | 3 | export function useMessage(config?: MessageConfig) { 4 | const success = (text: string, close?: () => void) => { 5 | messageSuccess(text, close!, config!); 6 | }; 7 | const warning = (text: string, close?: () => void) => { 8 | messageWarning(text, close!, config!); 9 | }; 10 | const info = (text: string, close?: () => void) => { 11 | messageInfo(text, close!, config!); 12 | }; 13 | const error = (text: string, close?: () => void) => { 14 | messageError(text, close!, config!); 15 | }; 16 | return { success, warning, info, error }; 17 | } 18 | -------------------------------------------------------------------------------- /src/hooks/usePageSetting.ts: -------------------------------------------------------------------------------- 1 | export const usePageSetting = () => { 2 | const getSystemTitle = "Vue3 Basic Template"; 3 | 4 | const setPageTitle = (title: string) => { 5 | document.title = title ? `${title}-${getSystemTitle}` : getSystemTitle; 6 | }; 7 | return { 8 | setPageTitle 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/hooks/useStorage.ts: -------------------------------------------------------------------------------- 1 | import { local, session } from "@/utils"; 2 | 3 | export const useStorage = (type: "session" | "local" = "session") => { 4 | const storageMode = type === "session" ? session : local; 5 | 6 | const getItem = (key: string): any => { 7 | return storageMode.getItem(key); 8 | }; 9 | 10 | const setItem = (key: string, value: string) => { 11 | storageMode.setItem(key, value); 12 | }; 13 | 14 | const removeItem = (key: string) => { 15 | storageMode.removeItem(key); 16 | }; 17 | 18 | const clear = () => { 19 | storageMode.clear(); 20 | }; 21 | 22 | return { getItem, setItem, removeItem, clear }; 23 | }; 24 | -------------------------------------------------------------------------------- /src/icons/about.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/approval.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/clear.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/click.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/close.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/column.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/comp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/confirm.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/dashboard.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/editPassword.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/empty.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/esc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/exit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/export.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/feedback.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/filter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/fold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/fullOutScreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/fullScreen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/func.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/gitee.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/good.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/help.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/icon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/light.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/location.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/log.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 23 | -------------------------------------------------------------------------------- /src/icons/more.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/nested.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/other.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/out.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/page.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/permission.svg: -------------------------------------------------------------------------------- 1 | 3 | 6 | -------------------------------------------------------------------------------- /src/icons/qq.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/read.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/remind.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/setting.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/sizeMini.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/sizePlus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/textSize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/unfold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/weixin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/icons/zhifubao.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/index.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 74 | 75 | 80 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | 42 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 55 | 56 | 82 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-fullscreen.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-switch.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 47 | -------------------------------------------------------------------------------- /src/layouts/nav-bar/nav-user.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/layouts/side-bar/index.scss: -------------------------------------------------------------------------------- 1 | $menuItemHeight: 48px; 2 | 3 | .el-aside { 4 | transition: all 0.2s; 5 | .base-side-container { 6 | background-color: var(--el-menu-bg-color); 7 | 8 | &.collapse { 9 | .side-logo { 10 | padding-left: 0; 11 | } 12 | .side-logo-title { 13 | display: none; 14 | } 15 | 16 | .el-menu-item { 17 | .base-menu-title { 18 | display: none; 19 | } 20 | } 21 | } 22 | .side-logo { 23 | height: 54px; 24 | line-height: 54px; 25 | transition: all 0.2s ease; 26 | box-sizing: border-box; 27 | padding-left: 24px; 28 | .side-logo-title { 29 | color: var(--el-menu-active-color); 30 | font-weight: 600; 31 | transition: all 0.5s; 32 | margin-left: 12px; 33 | font-size: var(--base-menu-logo); 34 | } 35 | } 36 | .el-menu { 37 | background-color: transparent !important; 38 | border-right: 0; 39 | .el-sub-menu__title:hover, 40 | .el-menu-item:hover { 41 | color: var(--el-menu-active-color); 42 | } 43 | .el-menu-item.is-active { 44 | background-color: var(--el-color-primary); 45 | } 46 | .el-menu-item { 47 | transition: all 0.2s; 48 | height: $menuItemHeight; 49 | } 50 | .el-sub-menu__title { 51 | height: $menuItemHeight; 52 | } 53 | .base-icon { 54 | margin-right: 5px; 55 | width: var(--el-menu-icon-width); 56 | text-align: center; 57 | vertical-align: middle; 58 | } 59 | .el-sub-menu { 60 | .el-sub-menu .el-sub-menu__title, 61 | .el-menu-item { 62 | background: var(--el-submenu-bg-color); 63 | &.is-active { 64 | background-color: var(--el-color-primary); 65 | } 66 | } 67 | 68 | &.is-active { 69 | .el-sub-menu__title { 70 | color: var(--el-menu-active-color); 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/layouts/side-bar/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/layouts/side-bar/logo.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/layouts/side-bar/side-bar-item.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/layouts/tag-view/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 106 | 107 | 110 | -------------------------------------------------------------------------------- /src/layouts/tag-view/tag-action.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /src/layouts/tag-view/tag-fullscreen.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 23 | -------------------------------------------------------------------------------- /src/layouts/tag-view/tag-item.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | 4 | import { setupPinia } from "./stores"; 5 | import { setupIcon, setupGlobalUtils } from "@/plugins"; 6 | import { setupLoadingDirective, setupPermissionDirective } from "./directive"; 7 | import router from "./router/index"; 8 | import { setupRouterGuard } from "@/router/guard"; 9 | 10 | import "./styles/index.scss"; 11 | 12 | const app = createApp(App); 13 | 14 | const setupPlugins = () => { 15 | // 注册pinia 16 | setupPinia(app); 17 | // 注册el-icon图标 18 | setupIcon(app); 19 | // 注册全局方法 20 | setupGlobalUtils(app); 21 | }; 22 | 23 | const setupDirective = () => { 24 | // 注册loading自定义指令 25 | setupLoadingDirective(app); 26 | // 注册permission自定义指令 27 | setupPermissionDirective(app); 28 | }; 29 | 30 | setupPlugins(); 31 | setupDirective(); 32 | // 注册路由守卫 33 | setupRouterGuard(router); 34 | 35 | app.use(router).mount("#app"); 36 | -------------------------------------------------------------------------------- /src/mockProdServer.ts: -------------------------------------------------------------------------------- 1 | import { createProdMockServer } from "vite-plugin-mock/es/createProdMockServer"; 2 | 3 | import user from "../mock/controller/user"; 4 | 5 | export function setupProdMockServer() { 6 | createProdMockServer([...user]); 7 | } 8 | -------------------------------------------------------------------------------- /src/plugins/echarts.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts/core"; 2 | import "echarts-wordcloud"; 3 | import "echarts-liquidfill"; 4 | 5 | import { BarChart, LineChart, PieChart, MapChart, PictorialBarChart, RadarChart, ScatterChart, GraphChart, HeatmapChart } from "echarts/charts"; 6 | 7 | import { 8 | TitleComponent, 9 | TooltipComponent, 10 | GridComponent, 11 | PolarComponent, 12 | AriaComponent, 13 | ParallelComponent, 14 | LegendComponent, 15 | RadarComponent, 16 | ToolboxComponent, 17 | DataZoomComponent, 18 | VisualMapComponent, 19 | TimelineComponent, 20 | CalendarComponent, 21 | GraphicComponent 22 | } from "echarts/components"; 23 | 24 | import { CanvasRenderer } from "echarts/renderers"; 25 | 26 | echarts.use([ 27 | LegendComponent, 28 | TitleComponent, 29 | TooltipComponent, 30 | GridComponent, 31 | PolarComponent, 32 | AriaComponent, 33 | ParallelComponent, 34 | BarChart, 35 | LineChart, 36 | PieChart, 37 | MapChart, 38 | RadarChart, 39 | CanvasRenderer, 40 | PictorialBarChart, 41 | RadarComponent, 42 | ToolboxComponent, 43 | DataZoomComponent, 44 | VisualMapComponent, 45 | TimelineComponent, 46 | CalendarComponent, 47 | GraphicComponent, 48 | ScatterChart, 49 | GraphChart, 50 | HeatmapChart 51 | ]); 52 | 53 | export default echarts; 54 | -------------------------------------------------------------------------------- /src/plugins/globalUtils.ts: -------------------------------------------------------------------------------- 1 | import { messageSuccess, messageWarning, messageInfo, messageError, messageBox, deepClone, session, local } from "@/utils"; 2 | import type { App } from "vue"; 3 | 4 | export default (app: App) => { 5 | app.config.globalProperties.$messageBox = messageBox; 6 | app.config.globalProperties.$messageSuccess = messageSuccess; 7 | app.config.globalProperties.$messageWarning = messageWarning; 8 | app.config.globalProperties.$messageInfo = messageInfo; 9 | app.config.globalProperties.$messageError = messageError; 10 | app.config.globalProperties.$deepClone = deepClone; 11 | app.config.globalProperties.$session = session; 12 | app.config.globalProperties.$local = local; 13 | }; 14 | -------------------------------------------------------------------------------- /src/plugins/icon.ts: -------------------------------------------------------------------------------- 1 | import * as Icons from "@element-plus/icons-vue"; 2 | import "virtual:svg-icons-register"; 3 | import type { App } from "vue"; 4 | 5 | export { Icons }; 6 | 7 | export default (app: App) => { 8 | for (const [key, component] of Object.entries(Icons)) { 9 | app.component(key, component); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/plugins/index.ts: -------------------------------------------------------------------------------- 1 | export { default as setupIcon } from "./icon"; 2 | export { default as setupGlobalUtils } from "./globalUtils"; 3 | -------------------------------------------------------------------------------- /src/router/basic.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | import { isArray } from "@/utils"; 3 | 4 | const LoginRoute: AppRouteType = { 5 | path: "/login", 6 | name: "Login", 7 | component: () => import("@/views/login/index.vue") 8 | }; 9 | 10 | const RootRoute: AppRouteType = { 11 | path: "/", 12 | name: "Root", 13 | redirect: "/dashboard" 14 | }; 15 | 16 | const RedirectRoute: AppRouteType = { 17 | path: "/redirect", 18 | name: "Redirect", 19 | component: () => import("../layouts/index.vue"), 20 | redirect: "/redirect/index", 21 | children: [ 22 | { 23 | path: "/index", 24 | name: "RedirectTo", 25 | component: () => import("@/views/redirect/index.vue") 26 | } 27 | ] 28 | }; 29 | 30 | const PageError = { 31 | path: "/:path(.*)*", 32 | name: "", 33 | component: () => import("@/views/page/403.vue") 34 | }; 35 | 36 | const getAsyncRoute = (): AppRouteType[] => { 37 | const modules = import.meta.glob("./modules/**/*.ts", { eager: true, import: "default" }); 38 | const asyncRoute: AppRouteType[] = []; 39 | Object.values(modules).forEach((value) => { 40 | const moduleList = isArray(value) ? [...(value as AppRouteType[])] : [value as AppRouteType]; 41 | asyncRoute.push(...moduleList); 42 | }); 43 | return asyncRoute; 44 | }; 45 | 46 | const getRouteName = (routeList: AppRouteType[]) => { 47 | const routeArr: string[] = []; 48 | routeList.forEach((item) => { 49 | routeArr.push(item.name as string); 50 | if (item?.children) { 51 | routeArr.push(...getRouteName(item?.children)); 52 | } 53 | }); 54 | return routeArr; 55 | }; 56 | 57 | // 基础路由 58 | export const basicRoutes = [LoginRoute, RedirectRoute, RootRoute, PageError]; 59 | 60 | // 异步路由 61 | export const asyncRoutes = getAsyncRoute(); 62 | 63 | // 路由白名单 64 | export const WHITE_LIST = getRouteName(basicRoutes); 65 | -------------------------------------------------------------------------------- /src/router/guard/index.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import { createPermission } from "./permission"; 3 | import { createProgress } from "./progress"; 4 | import { createPageTitle } from "./pageTitle"; 5 | 6 | export function setupRouterGuard(router: Router) { 7 | createProgress(router); 8 | createPermission(router); 9 | createPageTitle(router); 10 | } 11 | -------------------------------------------------------------------------------- /src/router/guard/pageTitle.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import { usePageSetting } from "@/hooks"; 3 | 4 | export const createPageTitle = (router: Router) => { 5 | const { setPageTitle } = usePageSetting(); 6 | 7 | router.beforeEach((to) => { 8 | setPageTitle(to.meta.title as string); 9 | return true; 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /src/router/guard/permission.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import { useUserStoreWithOut } from "@/stores/modules/user"; 3 | import { usePermissionStoreWithOut } from "@/stores/modules/permission"; 4 | import { WHITE_LIST } from "../basic"; 5 | 6 | export const createPermission = (router: Router) => { 7 | router.beforeEach(async (to, from, next) => { 8 | const useStore = useUserStoreWithOut(); 9 | const { getToken: token } = storeToRefs(useStore); 10 | const usePermissionStore = usePermissionStoreWithOut(); 11 | const { getRoute } = usePermissionStore; 12 | 13 | // 白名单 14 | if (to.name && WHITE_LIST.includes(to.name)) { 15 | next(); 16 | return; 17 | } 18 | 19 | // 没有token 20 | if (!unref(token)) { 21 | if (!to?.meta?.permission && getRoute.length > 0) { 22 | next(); 23 | return; 24 | } 25 | const redirectData: { path: string; replace: boolean; query?: any } = { 26 | path: "/login", 27 | replace: true 28 | }; 29 | if (to.path) { 30 | redirectData.query = { 31 | redirect: to.path 32 | }; 33 | } 34 | next(redirectData); 35 | return; 36 | } 37 | 38 | // 是否已经挂载过路由 39 | if (getRoute.length > 0) { 40 | if (router.hasRoute(to.name)) { 41 | next(); 42 | } else { 43 | next(getRoute[0]); 44 | } 45 | return; 46 | } 47 | 48 | const routeList = await usePermissionStore.initRoute(); 49 | routeList.forEach((route) => { 50 | router.addRoute(route); 51 | }); 52 | 53 | let redirectPath = (from.query.redirect || to.path) as string; 54 | if (redirectPath === "/dashboard") { 55 | redirectPath = routeList[0]?.path; 56 | } 57 | const redirect = decodeURIComponent(redirectPath); 58 | const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect }; 59 | next(nextData); 60 | }); 61 | }; 62 | -------------------------------------------------------------------------------- /src/router/guard/progress.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "vue-router"; 2 | import NProgress from "nprogress"; 3 | 4 | export const createProgress = (router: Router) => { 5 | router.beforeEach(() => { 6 | NProgress.start(); 7 | return true; 8 | }); 9 | 10 | router.afterEach(() => { 11 | return true; 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createWebHashHistory, createRouter } from "vue-router"; 2 | import { AppRouteType } from "./types"; 3 | import { basicRoutes, WHITE_LIST } from "./basic"; 4 | 5 | const router = createRouter({ 6 | history: createWebHashHistory(import.meta.env.BASE_URL), 7 | routes: [...basicRoutes] as AppRouteType[], 8 | // only history 9 | scrollBehavior: () => ({ left: 0, top: 0 }) 10 | }); 11 | 12 | export function resetRouter() { 13 | router.getRoutes().forEach((route) => { 14 | const { name } = route; 15 | if (name && !WHITE_LIST.includes(name as string)) { 16 | router.hasRoute(name) && router.removeRoute(name); 17 | } 18 | }); 19 | } 20 | 21 | export default router; 22 | -------------------------------------------------------------------------------- /src/router/modules/dashboard.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const dashboard: AppRouteType[] = [ 4 | { 5 | path: "/dashboard", 6 | name: "Dashboard", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/dashboard/analysis", 9 | meta: { 10 | title: "首页", 11 | icon: "svg-dashboard", 12 | sort: 1, 13 | permission: "admin_dashboard" 14 | }, 15 | children: [ 16 | { 17 | path: "analysis", 18 | name: "Analysis", 19 | component: () => import("@/views/dashboard/analysis/index.vue"), 20 | meta: { 21 | title: "分析页", 22 | sort: 1, 23 | permission: "admin_dashboard_analysis", 24 | icon: "", 25 | affix: true 26 | } 27 | }, 28 | { 29 | path: "workbench", 30 | name: "Workbench", 31 | component: () => import("@/views/dashboard/workbench/index.vue"), 32 | meta: { 33 | title: "工作台", 34 | sort: 2, 35 | permission: "admin_dashboard_workbench", 36 | icon: "" 37 | } 38 | } 39 | ] 40 | } 41 | ]; 42 | 43 | export default dashboard; 44 | -------------------------------------------------------------------------------- /src/router/modules/nested.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const nested: AppRouteType[] = [ 4 | { 5 | path: "/nested", 6 | name: "Nested", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/nested/menu1", 9 | meta: { 10 | title: "嵌套路由", 11 | icon: "svg-nested", 12 | sort: 8 13 | }, 14 | children: [ 15 | { 16 | path: "menu1", 17 | name: "Menu1", 18 | redirect: "/nested/menu1/menu1-1", 19 | meta: { 20 | title: "路由菜单1", 21 | sort: 1, 22 | icon: "" 23 | }, 24 | children: [ 25 | { 26 | path: "menu1-1", 27 | name: "Menu1-1", 28 | meta: { 29 | title: "路由菜单1-1", 30 | sort: 1, 31 | icon: "" 32 | }, 33 | redirect: "/nested/menu1/menu1-1/menu1-1-1", 34 | children: [ 35 | { 36 | path: "menu1-1-1", 37 | name: "Menu1-1-1", 38 | component: () => import("@/views/nested/menu1/menu1-1/menu1-1-1/index.vue"), 39 | meta: { 40 | title: "路由菜单1-1-1", 41 | sort: 1, 42 | icon: "" 43 | } 44 | } 45 | ] 46 | }, 47 | { 48 | path: "menu1-2", 49 | name: "Menu1-2", 50 | component: () => import("@/views/nested/menu1/menu1-2/index.vue"), 51 | meta: { 52 | title: "路由菜单1-2", 53 | sort: 2, 54 | icon: "" 55 | } 56 | } 57 | ] 58 | }, 59 | { 60 | path: "menu2", 61 | name: "Menu2", 62 | component: () => import("@/views/nested/menu2/index.vue"), 63 | meta: { 64 | title: "路由菜单2", 65 | sort: 2, 66 | icon: "" 67 | } 68 | } 69 | ] 70 | } 71 | ]; 72 | 73 | export default nested; 74 | -------------------------------------------------------------------------------- /src/router/modules/out-link.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const outLink: AppRouteType[] = [ 4 | { 5 | path: "/out-link", 6 | name: "OutLink", 7 | component: () => import("@/layouts/index.vue"), 8 | meta: { 9 | title: "其他项目", 10 | icon: "svg-out", 11 | sort: 4 12 | }, 13 | children: [ 14 | { 15 | path: "http://gist006.gitee.io/vue-visual-drag/#/", 16 | name: "OutDrag", 17 | meta: { 18 | title: "可视化拖拽", 19 | sort: 1, 20 | icon: "" 21 | } 22 | }, 23 | { 24 | path: "https://gist006.gitee.io/vue3-bigdata/#/homepage", 25 | name: "OutBigData", 26 | meta: { 27 | title: "vue3大屏", 28 | sort: 2, 29 | icon: "" 30 | } 31 | }, 32 | { 33 | path: "http://gist006.gitee.io/vue-antd-admin/#/", 34 | name: "OutAdmin", 35 | meta: { 36 | title: "vue-antd-admin", 37 | sort: 3, 38 | icon: "" 39 | } 40 | } 41 | ] 42 | } 43 | ]; 44 | 45 | export default outLink; 46 | -------------------------------------------------------------------------------- /src/router/modules/page.ts: -------------------------------------------------------------------------------- 1 | import type { AppRouteType } from "@/router/types"; 2 | 3 | const page: AppRouteType[] = [ 4 | { 5 | path: "/page", 6 | name: "Page", 7 | component: () => import("@/layouts/index.vue"), 8 | redirect: "/page/403", 9 | meta: { 10 | title: "页面", 11 | icon: "svg-page", 12 | sort: 7, 13 | permission: "admin_page" 14 | }, 15 | children: [ 16 | { 17 | path: "403", 18 | name: "Error403", 19 | component: () => import("@/views/page/403.vue"), 20 | meta: { 21 | title: "403", 22 | sort: 1, 23 | permission: "admin_page_403", 24 | icon: "" 25 | } 26 | }, 27 | { 28 | path: "404", 29 | name: "Error404", 30 | component: () => import("@/views/page/404.vue"), 31 | meta: { 32 | title: "404", 33 | sort: 2, 34 | permission: "admin_page_404", 35 | icon: "" 36 | } 37 | }, 38 | { 39 | path: "500", 40 | name: "Error500", 41 | component: () => import("@/views/page/500.vue"), 42 | meta: { 43 | title: "500", 44 | sort: 3, 45 | permission: "admin_page_500", 46 | icon: "" 47 | } 48 | }, 49 | { 50 | path: "success", 51 | name: "Success", 52 | component: () => import("@/views/page/success.vue"), 53 | meta: { 54 | title: "成功页", 55 | sort: 4, 56 | permission: "admin_page_success", 57 | icon: "" 58 | } 59 | }, 60 | { 61 | path: "error", 62 | name: "Error", 63 | component: () => import("@/views/page/error.vue"), 64 | meta: { 65 | title: "失败页", 66 | sort: 5, 67 | permission: "admin_page_error", 68 | icon: "" 69 | } 70 | } 71 | ] 72 | } 73 | ]; 74 | 75 | export default page; 76 | -------------------------------------------------------------------------------- /src/router/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw, RouteMeta } from "vue-router"; 2 | 3 | export type Component = ReturnType | (() => Promise) | (() => Promise); 4 | 5 | export interface MetaType extends RouteMeta { 6 | title: string; 7 | keepAlive?: boolean; 8 | hidden?: boolean; 9 | permission?: string; 10 | affix?: boolean; 11 | icon?: string; 12 | hideChildren?: boolean; 13 | sort?: number; 14 | } 15 | 16 | export interface AppRouteType extends Omit { 17 | name: string; 18 | component?: Component | string; 19 | components?: Component; 20 | children?: AppRouteType[]; 21 | fullPath?: string; 22 | meta?: MetaType; 23 | redirect?: string; 24 | } 25 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from "pinia"; 2 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 3 | import type { App } from "vue"; 4 | import { usePermissionStoreWithOut } from "@/stores/modules/permission"; 5 | import { useTagStoreWithOut } from "@/stores/modules/tagView"; 6 | import { useUserStoreWithOut } from "@/stores/modules/user"; 7 | 8 | const store = createPinia(); 9 | 10 | export function setupPinia(app: App) { 11 | store.use(piniaPluginPersistedstate); 12 | app.use(store); 13 | } 14 | 15 | export function storeReset() { 16 | const permissionStore = usePermissionStoreWithOut(); 17 | const tagStore = useTagStoreWithOut(); 18 | const userStore = useUserStoreWithOut(); 19 | permissionStore.$reset(); 20 | tagStore.$reset(); 21 | userStore.$reset(); 22 | } 23 | 24 | export { store }; 25 | -------------------------------------------------------------------------------- /src/stores/modules/app.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { store } from "../index"; 3 | 4 | export interface MenuSetting { 5 | collapse: boolean; 6 | } 7 | 8 | export const useAppStore = defineStore({ 9 | id: "app", 10 | persist: true, 11 | state: (): MenuSetting => ({ 12 | collapse: false 13 | }), 14 | getters: {}, 15 | actions: { 16 | setCollapse(collapse: boolean): void { 17 | this.collapse = collapse; 18 | } 19 | } 20 | }); 21 | 22 | // 便于外部使用 23 | export const useAppStoreWithOut = () => { 24 | return useAppStore(store); 25 | }; 26 | -------------------------------------------------------------------------------- /src/stores/modules/permission.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { store } from "../index"; 3 | import { useUserStore } from "./user"; 4 | import { AppRouteType } from "@/router/types"; 5 | import { asyncRoutes } from "@/router/basic"; 6 | import router from "@/router/index"; 7 | 8 | interface PermissionState { 9 | route: AppRouteType[]; 10 | } 11 | 12 | export const usePermissionStore = defineStore({ 13 | id: "permission", 14 | state: (): PermissionState => ({ 15 | route: [] 16 | }), 17 | getters: { 18 | getRoute(): AppRouteType[] { 19 | return this.route; 20 | } 21 | }, 22 | actions: { 23 | setRoute(routeList: AppRouteType[]) { 24 | this.route = routeList; 25 | }, 26 | 27 | async initRoute() { 28 | const useStore = useUserStore(); 29 | const routeList = filterAsyncRoute(asyncRoutes, useStore.getRoleIds); 30 | sortRoute(routeList); 31 | this.setRoute(routeList); 32 | return routeList; 33 | }, 34 | 35 | async changeRole() { 36 | const routeList = await this.initRoute(); 37 | routeList.forEach((route) => { 38 | router.addRoute(route); 39 | }); 40 | } 41 | } 42 | }); 43 | 44 | function filterAsyncRoute(routes: AppRouteType[], role: string[]): AppRouteType[] { 45 | const arr: AppRouteType[] = []; 46 | routes.forEach((item) => { 47 | const temp = { ...item }; 48 | if (hasRole(temp, role)) { 49 | if (temp.children) { 50 | temp.children = filterAsyncRoute(temp.children, role); 51 | } 52 | arr.push(temp); 53 | } 54 | }); 55 | return arr; 56 | } 57 | 58 | function hasRole(route: AppRouteType, role: string[]) { 59 | if (route.meta && route.meta.permission) { 60 | return role.includes(route?.meta?.permission); 61 | } 62 | return true; 63 | } 64 | 65 | function sortRoute(route: AppRouteType[]) { 66 | route.sort((a, b) => { 67 | return a.meta!.sort! - b.meta!.sort!; 68 | }); 69 | route.forEach((item) => { 70 | if (item.children) { 71 | sortRoute(item.children); 72 | } 73 | }); 74 | } 75 | 76 | // 便于外部使用 77 | export const usePermissionStoreWithOut = () => { 78 | return usePermissionStore(store); 79 | }; 80 | -------------------------------------------------------------------------------- /src/stores/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { store, storeReset } from "../index"; 3 | import { login, getUserInfo } from "@/api/user"; 4 | import { usePermissionStore } from "./permission"; 5 | import { resetRouter } from "@/router"; 6 | import type { Router } from "vue-router"; 7 | 8 | interface UserState { 9 | token: string; 10 | roleIds: string[]; 11 | userInfo: any; 12 | } 13 | 14 | interface LoginParams { 15 | username: string; 16 | password: string; 17 | } 18 | 19 | export const useUserStore = defineStore({ 20 | id: "user", 21 | persist: true, 22 | state: (): UserState => ({ 23 | token: "", 24 | roleIds: [], 25 | userInfo: {} 26 | }), 27 | getters: { 28 | getToken(): string { 29 | return this.token; 30 | }, 31 | getRoleIds(): string[] { 32 | return this.roleIds; 33 | } 34 | }, 35 | actions: { 36 | setToken(token: string) { 37 | this.token = token; 38 | }, 39 | setRoleId(ids: string[]) { 40 | this.roleIds = ids; 41 | }, 42 | setUserInfo(userInfo: any) { 43 | this.userInfo = userInfo; 44 | }, 45 | async login(params: LoginParams) { 46 | const res = await login(params); 47 | this.setToken(res.data.token); 48 | await this.getUserInfo(); 49 | }, 50 | 51 | logout(router: Router) { 52 | storeReset(); 53 | resetRouter(); 54 | const { currentRoute } = router; 55 | router.replace({ 56 | path: "/login", 57 | query: { 58 | redirect: currentRoute.value.path 59 | } 60 | }); 61 | }, 62 | 63 | async getUserInfo() { 64 | if (!this.token) return; 65 | const res = await getUserInfo(); 66 | this.setRoleId(res.data.roleIds); 67 | this.setUserInfo(res.data); 68 | }, 69 | 70 | async changeRole(role: string) { 71 | storeReset(); 72 | resetRouter(); 73 | let params: any = {}; 74 | if (role === "admin") { 75 | params = { username: "admin", password: 123456 }; 76 | } else { 77 | params = { username: "test", password: 123456 }; 78 | } 79 | await this.login(params); 80 | const usePermission = usePermissionStore(); 81 | usePermission.changeRole(); 82 | } 83 | } 84 | }); 85 | 86 | // 便于外部使用 87 | export const useUserStoreWithOut = () => { 88 | return useUserStore(store); 89 | }; 90 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | @use "./common.scss"; 2 | @use "./scrollbar.scss"; 3 | @use "element-plus/theme-chalk/src/dark/css-vars.scss" as *; 4 | -------------------------------------------------------------------------------- /src/styles/scrollbar.scss: -------------------------------------------------------------------------------- 1 | //自定义滚动条样式 2 | $scrollbar-track-color: #f8f8f8; 3 | $scrollbar-thumb: rgba(144, 147, 153, 0.5); 4 | $scrollbar-thumb-hover: rgba(144, 147, 153, 0.8); 5 | 6 | .el-scrollbar__bar { 7 | z-index: 4; 8 | } 9 | .el-scrollbar__wrap { 10 | max-height: 100%; 11 | } 12 | 13 | ::-webkit-scrollbar { 14 | width: 7px; 15 | height: 7px; 16 | } 17 | ::-webkit-scrollbar-track-piece { 18 | background-color: $scrollbar-track-color; 19 | } 20 | // 滚动条的设置 21 | ::-webkit-scrollbar-thumb { 22 | background-color: $scrollbar-thumb; 23 | background-clip: padding-box; 24 | min-height: 28px; 25 | border-radius: 5px; 26 | transition: 0.3s background-color; 27 | } 28 | 29 | ::-webkit-scrollbar-thumb:hover { 30 | background-color: $scrollbar-thumb-hover; 31 | } 32 | -------------------------------------------------------------------------------- /src/styles/theme/base.scss: -------------------------------------------------------------------------------- 1 | html { 2 | // base-column-setting 头部颜色 3 | --base-column-header-color: #f4f6fa; 4 | 5 | //navbar 图标hover颜色 6 | --base-nav-bar-box-hover: rgba(0, 0, 0, 0.025); 7 | 8 | //tag border 9 | --base-tag-border: rgba(175, 175, 175, 0.3); 10 | //tag shadow 11 | --base-tag-shadow: rgb(0 21 41 / 8%); 12 | 13 | //menu背景颜色 14 | --el-menu-bg-color: rgb(41, 51, 72) !important; 15 | //submenu 背景颜色 16 | --el-submenu-bg-color: rgb(30, 36, 48) !important; 17 | // menu 文字颜色 18 | --el-menu-text-color: hsla(0, 0%, 100%, 0.65) !important; 19 | 20 | // menu hover 背景颜色 21 | --el-menu-hover-bg-color: transparent !important; 22 | 23 | //menu active文字颜色 24 | --el-menu-active-color: #fff !important; 25 | 26 | //系统主体背景颜色 27 | --base-main-bg-color: #f0f2f5; 28 | //系统主体 内容颜色 29 | --base-main-content-bg-color: #fff; 30 | } 31 | -------------------------------------------------------------------------------- /src/styles/theme/dark.scss: -------------------------------------------------------------------------------- 1 | //配置dark下的样式 2 | 3 | $--colors: (); 4 | 5 | @forward "element-plus/theme-chalk/src/dark/var.scss" with ( 6 | $colors: $--colors 7 | ); 8 | 9 | html.dark { 10 | //navbar 图标hover颜色 11 | --base-nav-bar-box-hover: rgba(255, 255, 255, 0.06); 12 | 13 | //tag border 14 | --base-tag-border: #4f4f4f; 15 | //tag shadow 16 | --base-tag-shadow: rgb(0 21 41 / 8%); 17 | 18 | //menu背景颜色 19 | --el-menu-bg-color: #181717 !important; 20 | //submenu 背景颜色 21 | --el-submenu-bg-color: #101010 !important; 22 | // menu 文字颜色 23 | --el-menu-text-color: hsla(0, 0%, 100%, 0.65) !important; 24 | 25 | // menu hover 背景颜色 26 | --el-menu-hover-bg-color: transparent !important; 27 | 28 | //menu active文字颜色 29 | --el-menu-active-color: #fff !important; 30 | 31 | //系统主体背景颜色 32 | --base-main-bg-color: #000; 33 | //系统主体 内容颜色 34 | --base-main-content-bg-color: #1d1e1f; 35 | } 36 | -------------------------------------------------------------------------------- /src/styles/theme/index.scss: -------------------------------------------------------------------------------- 1 | @use "./base.scss"; 2 | @use "./dark.scss"; 3 | 4 | .base-app-bg { 5 | background-color: var(--base-main-bg-color); 6 | } 7 | 8 | .base-box-bg { 9 | background-color: var(--base-main-content-bg-color); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/common.ts: -------------------------------------------------------------------------------- 1 | import { isObject, isUndefined, isString } from "./is"; 2 | 3 | /** 4 | * 排除掉obj里面的key值 5 | * @param {object} obj 6 | * @param {Array|string} args 7 | * @returns {object} 8 | */ 9 | export function omit, P extends keyof T>(obj: T, args: string | string[]) { 10 | if (!args) return obj; 11 | const newObj = {} as Omit; 12 | const isString = typeof args === "string"; 13 | const keys = Object.keys(obj).filter((item) => { 14 | if (isString) { 15 | return item !== args; 16 | } 17 | return !(args).includes(item as P); 18 | }) as Exclude[]; 19 | 20 | keys.forEach((key) => { 21 | if (obj[key] !== undefined) newObj[key] = obj[key]; 22 | }); 23 | return newObj; 24 | } 25 | 26 | /** 27 | * 深拷贝 28 | * @param {*} source 29 | * @returns {*} 30 | */ 31 | export function deepClone(source: any) { 32 | if (!source && typeof source !== "object") { 33 | return source; 34 | } 35 | const targetObj = source.constructor === Array ? [] : {}; 36 | Object.keys(source).forEach((keys) => { 37 | if (source[keys] && typeof source[keys] === "object") { 38 | (targetObj as any)[keys] = deepClone(source[keys]); 39 | } else { 40 | (targetObj as any)[keys] = source[keys]; 41 | } 42 | }); 43 | return targetObj; 44 | } 45 | 46 | /** 47 | * 深度合并 48 | * @param {*} src 49 | * @param {*} target 50 | * @returns {*} 51 | */ 52 | export function deepMerge(src: any = {}, target: any = {}): T { 53 | let key: string; 54 | for (key in target) { 55 | src[key] = isObject(target[key]) ? deepMerge(src[key], target[key]) : (src[key] = target[key]); 56 | } 57 | return src; 58 | } 59 | -------------------------------------------------------------------------------- /src/utils/dom.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from "./is"; 2 | 3 | export const hasClass = (ele: HTMLElement, cls: string | string[]): boolean => { 4 | const classArr = isArray(cls) ? cls : [cls]; 5 | return classArr.every((item: string) => !!ele.className.match(new RegExp("(\\s|^)" + item + "(\\s|$)"))); 6 | }; 7 | 8 | export const addClass = (ele: HTMLElement, cls: string | string[]) => { 9 | const classArr = isArray(cls) ? cls : [cls]; 10 | classArr.forEach((item: string) => { 11 | if (!hasClass(ele, item)) { 12 | ele.className += " " + item; 13 | } 14 | }); 15 | }; 16 | 17 | export const removeClass = (ele: HTMLElement, cls: string | string[]) => { 18 | const classArr = isArray(cls) ? cls : [cls]; 19 | classArr.forEach((item: string) => { 20 | if (hasClass(ele, item)) { 21 | const reg = new RegExp("(\\s|^)" + item + "(\\s|$)"); 22 | ele.className = ele.className.replace(reg, " ").trim(); 23 | } 24 | }); 25 | }; 26 | 27 | export const toggleClass = (ele: HTMLElement, cls: string | string[]) => { 28 | const classArr = isArray(cls) ? cls : [cls]; 29 | classArr.forEach((item: string) => { 30 | if (hasClass(ele, item)) { 31 | removeClass(ele, item); 32 | } else { 33 | addClass(ele, item); 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./is"; 3 | export * from "./message"; 4 | export * from "./storage"; 5 | export * from "./dom"; 6 | -------------------------------------------------------------------------------- /src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const toString = Object.prototype.toString; 2 | export const isBoolean = (val: any): val is boolean => typeof val === "boolean"; 3 | export const isFunction = (val: any): val is T => typeof val === "function"; 4 | export const isNumber = (val: any): val is number => typeof val === "number"; 5 | export const isUndefined = (val: any): val is undefined => typeof val === "undefined"; 6 | export const isString = (val: unknown): val is string => typeof val === "string"; 7 | export const isObject = (val: any): val is object => toString.call(val) === "[object Object]"; 8 | export const isArray = (val: any): val is object => toString.call(val) === "[object Array]"; 9 | export const isEmpty = (val: any): val is boolean => !val && val !== 0; 10 | -------------------------------------------------------------------------------- /src/utils/message.ts: -------------------------------------------------------------------------------- 1 | import { ElMessageBox, ElMessage } from "element-plus"; 2 | 3 | interface MessageBoxConfig { 4 | title?: string; 5 | icon?: string; 6 | type?: "success" | "info" | "warning" | "error"; 7 | customClass?: string; 8 | showClose?: boolean; 9 | showCancelButton?: boolean; 10 | showConfirmButton?: boolean; 11 | cancelButtonText?: string; 12 | confirmButtonText?: string; 13 | closeOnClickModal?: boolean; 14 | closeOnPressEscape?: boolean; 15 | center?: boolean; 16 | draggable?: boolean; 17 | confirmBack?: () => void; 18 | cancelBack?: () => void; 19 | } 20 | 21 | type MessageType = "success" | "warning" | "info" | "error"; 22 | 23 | export type MessageConfig = { 24 | icon?: string; 25 | dangerouslyUseHTMLString?: boolean; 26 | customClass?: string; 27 | duration?: number; 28 | showClose?: boolean; 29 | center?: boolean; 30 | offset?: number; 31 | grouping?: boolean; 32 | }; 33 | 34 | const defaultMessageConfig: MessageConfig = { 35 | center: true, 36 | duration: 2500, 37 | grouping: true 38 | }; 39 | 40 | const defaultConfig: MessageBoxConfig = { 41 | title: "提示", 42 | type: "warning", 43 | showClose: true, 44 | showCancelButton: true, 45 | showConfirmButton: true, 46 | cancelButtonText: "取消", 47 | confirmButtonText: "确定", 48 | closeOnClickModal: false, 49 | closeOnPressEscape: false, 50 | center: false, 51 | draggable: false 52 | }; 53 | 54 | export const messageBox = async (message: string, config?: MessageBoxConfig) => { 55 | const options = Object.assign({}, defaultConfig, config); 56 | const { title, confirmBack, cancelBack, ...rest } = options; 57 | return new Promise((resolve, reject) => { 58 | ElMessageBox.confirm(message, title, { 59 | ...rest 60 | }) 61 | .then(() => { 62 | confirmBack && confirmBack(); 63 | resolve(true); 64 | }) 65 | .catch(() => { 66 | cancelBack && cancelBack(); 67 | reject(new Error("action:cancel")); 68 | }); 69 | }); 70 | }; 71 | 72 | const message = (type: MessageType, text: string, close?: () => void, config?: MessageConfig) => { 73 | const options = Object.assign({}, defaultMessageConfig, config); 74 | return ElMessage({ 75 | message: text, 76 | type, 77 | onClose: close, 78 | ...options 79 | }); 80 | }; 81 | 82 | export const messageSuccess = (text: string, close?: () => void, config?: MessageConfig) => message("success", text, close, config); 83 | export const messageWarning = (text: string, close?: () => void, config?: MessageConfig) => message("warning", text, close, config); 84 | export const messageInfo = (text: string, close?: () => void, config?: MessageConfig) => message("info", text, close, config); 85 | export const messageError = (text: string, close?: () => void, config?: MessageConfig) => message("error", text, close, config); 86 | -------------------------------------------------------------------------------- /src/utils/request/cancel.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosRequestConfig } from "axios"; 2 | 3 | export class AxiosCancel { 4 | pendingMap: Map; 5 | constructor() { 6 | this.pendingMap = new Map(); 7 | } 8 | 9 | generateReqKey(config: AxiosRequestConfig): string { 10 | const { method, url } = config; 11 | return [url || "", method || "", JSON.stringify(config.params), JSON.stringify(config.data)].join("&"); 12 | } 13 | 14 | addPending(config: AxiosRequestConfig) { 15 | this.removePending(config); 16 | const requestKey: string = this.generateReqKey(config); 17 | if (!this.pendingMap.has(requestKey)) { 18 | const controller = new AbortController(); 19 | config.signal = controller.signal; 20 | this.pendingMap.set(requestKey, controller); 21 | } else { 22 | config.signal = (this.pendingMap.get(requestKey) as AbortController).signal; 23 | } 24 | } 25 | 26 | removePending(config: AxiosRequestConfig) { 27 | const requestKey: string = this.generateReqKey(config); 28 | if (this.pendingMap.has(requestKey)) { 29 | (this.pendingMap.get(requestKey) as AbortController).abort(); 30 | this.pendingMap.delete(requestKey); 31 | } 32 | } 33 | 34 | removeAllPending() { 35 | this.pendingMap.forEach((cancel: AbortController) => { 36 | cancel.abort(); 37 | }); 38 | this.reset(); 39 | } 40 | 41 | reset() { 42 | this.pendingMap = new Map(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/utils/request/loading.ts: -------------------------------------------------------------------------------- 1 | import { useLoading } from "@/hooks"; 2 | 3 | const loading = useLoading({ 4 | minTime: 500 5 | }); 6 | 7 | export class AxiosLoading { 8 | loadingCount: number; 9 | constructor() { 10 | this.loadingCount = 0; 11 | } 12 | 13 | addLoading() { 14 | if (this.loadingCount === 0) { 15 | loading.open(); 16 | } 17 | this.loadingCount++; 18 | } 19 | 20 | closeLoading() { 21 | if (this.loadingCount > 0) { 22 | if (this.loadingCount === 1) { 23 | loading.close(); 24 | } 25 | this.loadingCount--; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/request/retry.ts: -------------------------------------------------------------------------------- 1 | import type { AxiosError, AxiosInstance } from "axios"; 2 | 3 | export class AxiosRetry { 4 | retry(service: AxiosInstance, err: AxiosError) { 5 | const config = err?.config as any; 6 | config._retryCount = config._retryCount || 0; 7 | config._retryCount += 1; 8 | delete config.headers; 9 | setTimeout(() => { 10 | service(config); 11 | }, 100); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/requestAnimationFrame.ts: -------------------------------------------------------------------------------- 1 | let lastTime = 0; 2 | const prefixes = "webkit moz ms o".split(" "); // 各浏览器前缀 3 | 4 | let requestAnimationFrame: any; 5 | let cancelAnimationFrame: any; 6 | 7 | const isServer: boolean = typeof window === "undefined"; 8 | if (isServer) { 9 | requestAnimationFrame = function () {}; 10 | cancelAnimationFrame = function () {}; 11 | } else { 12 | requestAnimationFrame = window.requestAnimationFrame; 13 | cancelAnimationFrame = window.cancelAnimationFrame; 14 | let prefix: string; 15 | // 通过遍历各浏览器前缀,来得到requestAnimationFrame和cancelAnimationFrame在当前浏览器的实现形式 16 | for (let i = 0; i < prefixes.length; i++) { 17 | if (!!requestAnimationFrame && !!cancelAnimationFrame) { 18 | break; 19 | } 20 | prefix = prefixes[i]; 21 | requestAnimationFrame = requestAnimationFrame || (window as any)[prefix + "RequestAnimationFrame"]; 22 | cancelAnimationFrame = 23 | cancelAnimationFrame || (window as any)[prefix + "CancelAnimationFrame"] || (window as any)[prefix + "CancelRequestAnimationFrame"]; 24 | } 25 | 26 | // 如果当前浏览器不支持requestAnimationFrame和cancelAnimationFrame,则会退到setTimeout 27 | if (!requestAnimationFrame || !cancelAnimationFrame) { 28 | requestAnimationFrame = function (callback: (params?: any) => void) { 29 | const currTime = new Date().getTime(); 30 | // 为了使setTimeout的尽可能的接近每秒60帧的效果 31 | const timeToCall = Math.max(0, 16 - (currTime - lastTime)); 32 | const id = window.setTimeout(() => { 33 | // eslint-disable-next-line 34 | callback(currTime + timeToCall); 35 | }, timeToCall); 36 | lastTime = currTime + timeToCall; 37 | return id; 38 | }; 39 | 40 | cancelAnimationFrame = function (id: number) { 41 | window.clearTimeout(id); 42 | }; 43 | } 44 | } 45 | 46 | export { requestAnimationFrame, cancelAnimationFrame }; 47 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | interface ProxyStorage { 2 | getItem(key: string): any; 3 | setItem(key: string, value: string): void; 4 | removeItem(key: string): void; 5 | clear(): void; 6 | } 7 | 8 | class VStorage implements ProxyStorage { 9 | protected storage: ProxyStorage; 10 | 11 | constructor(storageModel: ProxyStorage) { 12 | this.storage = storageModel; 13 | } 14 | 15 | public getItem(key: string): any { 16 | return this.storage.getItem(key); 17 | } 18 | 19 | public setItem(key: string, value: string): void { 20 | this.storage.setItem(key, value); 21 | } 22 | 23 | public removeItem(key: string): void { 24 | this.storage.removeItem(key); 25 | } 26 | 27 | public clear(): void { 28 | this.storage.clear(); 29 | } 30 | } 31 | 32 | export const local = new VStorage(localStorage); 33 | 34 | export const session = new VStorage(sessionStorage); 35 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/column.ts: -------------------------------------------------------------------------------- 1 | type CardDataType = { 2 | title: string; 3 | extraValue: number | string; 4 | extraTitle: string; 5 | value: number; 6 | icon: string; 7 | prefix: string; 8 | suffix: string; 9 | }; 10 | 11 | export const cardListData: CardDataType[] = [ 12 | { 13 | title: "今日收益", 14 | value: 10500, 15 | extraTitle: "总收益", 16 | extraValue: 17500, 17 | prefix: "¥", 18 | suffix: "", 19 | icon: "svg-money" 20 | }, 21 | { 22 | title: "今日访问量", 23 | value: 16800, 24 | extraTitle: "总访问量", 25 | extraValue: 130845, 26 | prefix: "", 27 | suffix: "次", 28 | icon: "svg-view" 29 | }, 30 | { 31 | title: "待发货", 32 | value: 626, 33 | extraTitle: "订单量", 34 | extraValue: 1679, 35 | prefix: "", 36 | suffix: "个", 37 | icon: "svg-deliver" 38 | }, 39 | { 40 | title: "好评率", 41 | value: 98, 42 | extraTitle: "同期对比", 43 | extraValue: "+3.25%", 44 | prefix: "", 45 | suffix: "%", 46 | icon: "svg-good" 47 | } 48 | ]; 49 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/CardList.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | 34 | 62 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/TrueDynamic.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 58 | 59 | 81 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/components/TurnoverAnalysis.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/views/dashboard/analysis/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/Calendar.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 42 | 43 | 54 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/NoticeList.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | 24 | 37 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/QuickNav.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 64 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/components/TodoList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 28 | 29 | 60 | -------------------------------------------------------------------------------- /src/views/dashboard/workbench/index.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-1/menu1-1-1/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/nested/menu1/menu1-2/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/nested/menu2/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/page/403.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/page/404.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/page/500.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/page/error.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/page/success.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "baseUrl": "./", 16 | "types": ["vite/client"], 17 | "paths": { 18 | "@/*": ["src/*"] 19 | } 20 | }, 21 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/mock/**/*.ts", "src/**/*.vue", "types/**/*.d.ts"], 22 | "references": [{ "path": "./tsconfig.node.json" }] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import type { DefineComponent } from "vue"; 3 | const component: DefineComponent<{}, {}, any>; 4 | export default component; 5 | } 6 | --------------------------------------------------------------------------------