├── .dockerignore ├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .env.staging ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .husky ├── commit-msg ├── common.sh ├── lintstagedrc.js └── pre-commit ├── .markdownlint.json ├── .npmrc ├── .prettierrc.js ├── .stylelintignore ├── .vscode ├── extensions.json ├── settings.json ├── vue3.0.code-snippets ├── vue3.2.code-snippets └── vue3.3.code-snippets ├── Dockerfile ├── LICENSE ├── README.en-US.md ├── README.md ├── build ├── cdn.ts ├── compress.ts ├── index.ts ├── info.ts ├── optimize.ts └── plugins.ts ├── commitlint.config.js ├── index.html ├── mock ├── asyncRoutes.ts ├── login.ts └── refreshToken.ts ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── public ├── favicon.ico ├── logo.svg └── serverConfig.json ├── src ├── App.vue ├── api │ ├── common │ │ └── login.ts │ └── system │ │ ├── config.ts │ │ ├── dept.ts │ │ ├── log.ts │ │ ├── menu.ts │ │ ├── monitor.ts │ │ ├── notice.ts │ │ ├── post.ts │ │ ├── role.ts │ │ └── user.ts ├── assets │ ├── iconfont │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ ├── iconfont.ttf │ │ ├── iconfont.woff │ │ └── iconfont.woff2 │ ├── login │ │ ├── avatar.svg │ │ ├── bg.png │ │ └── illustration.svg │ ├── status │ │ ├── 403.svg │ │ ├── 404.svg │ │ └── 500.svg │ ├── svg │ │ ├── FullScreenMaximize.svg │ │ ├── FullScreenMinimize.svg │ │ ├── back_top.svg │ │ ├── dark.svg │ │ ├── day.svg │ │ ├── enter_outlined.svg │ │ ├── exit_screen.svg │ │ ├── full_screen.svg │ │ └── keyboard_esc.svg │ └── user.jpg ├── components │ ├── ReAuth │ │ ├── index.ts │ │ └── src │ │ │ └── auth.tsx │ ├── ReCol │ │ └── index.ts │ ├── ReCropper │ │ ├── index.ts │ │ └── src │ │ │ ├── circled.css │ │ │ ├── index.tsx │ │ │ └── svg │ │ │ ├── arrow-down.svg │ │ │ ├── arrow-h.svg │ │ │ ├── arrow-left.svg │ │ │ ├── arrow-right.svg │ │ │ ├── arrow-up.svg │ │ │ ├── arrow-v.svg │ │ │ ├── change.svg │ │ │ ├── download.svg │ │ │ ├── index.ts │ │ │ ├── reload.svg │ │ │ ├── rotate-left.svg │ │ │ ├── rotate-right.svg │ │ │ ├── search-minus.svg │ │ │ ├── search-plus.svg │ │ │ └── upload.svg │ ├── ReDialog │ │ ├── index.ts │ │ ├── index.vue │ │ └── type.ts │ ├── ReIcon │ │ ├── data.ts │ │ ├── index.ts │ │ └── src │ │ │ ├── Select.vue │ │ │ ├── hooks.ts │ │ │ ├── iconfont.ts │ │ │ ├── iconifyIconOffline.ts │ │ │ ├── iconifyIconOnline.ts │ │ │ ├── offlineIcon.ts │ │ │ └── types.ts │ ├── ReImageVerify │ │ ├── index.ts │ │ └── src │ │ │ ├── hooks.ts │ │ │ └── index.vue │ ├── RePureTableBar │ │ ├── index.ts │ │ └── src │ │ │ ├── bar.tsx │ │ │ └── svg │ │ │ ├── collapse.svg │ │ │ ├── drag.svg │ │ │ ├── expand.svg │ │ │ ├── refresh.svg │ │ │ └── settings.svg │ ├── ReQrcode │ │ ├── index.ts │ │ └── src │ │ │ ├── index.scss │ │ │ └── index.tsx │ ├── ReTypeit │ │ └── index.ts │ └── VDialog │ │ ├── VDialog.vue │ │ ├── dialog.css │ │ └── dialog.ts ├── config │ └── index.ts ├── directives │ ├── auth │ │ └── index.ts │ ├── copy │ │ └── index.ts │ ├── index.ts │ ├── longpress │ │ └── index.ts │ └── optimize │ │ └── index.ts ├── layout │ ├── components │ │ ├── appMain.vue │ │ ├── navbar.vue │ │ ├── notice │ │ │ ├── data.ts │ │ │ ├── index.vue │ │ │ ├── noticeItem.vue │ │ │ └── noticeList.vue │ │ ├── panel │ │ │ └── index.vue │ │ ├── search │ │ │ ├── components │ │ │ │ ├── SearchFooter.vue │ │ │ │ ├── SearchModal.vue │ │ │ │ ├── SearchResult.vue │ │ │ │ └── index.ts │ │ │ └── index.vue │ │ ├── setting │ │ │ └── index.vue │ │ ├── sidebar │ │ │ ├── breadCrumb.vue │ │ │ ├── extraIcon.vue │ │ │ ├── horizontal.vue │ │ │ ├── leftCollapse.vue │ │ │ ├── logo.vue │ │ │ ├── mixNav.vue │ │ │ ├── sidebarItem.vue │ │ │ ├── topCollapse.vue │ │ │ └── vertical.vue │ │ └── tag │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── frameView.vue │ ├── hooks │ │ ├── useBoolean.ts │ │ ├── useDataThemeChange.ts │ │ ├── useLayout.ts │ │ ├── useNav.ts │ │ └── useTag.ts │ ├── index.vue │ ├── redirect.vue │ ├── theme │ │ └── index.ts │ └── types.ts ├── main.ts ├── mockProdServer.ts ├── plugins │ ├── echarts │ │ └── index.ts │ └── element-plus │ │ └── index.ts ├── router │ ├── index.ts │ ├── modules │ │ ├── error.ts │ │ ├── global.ts │ │ ├── home.ts │ │ └── remaining.ts │ └── utils.ts ├── store │ ├── index.ts │ └── modules │ │ ├── app.ts │ │ ├── epTheme.ts │ │ ├── multiTags.ts │ │ ├── permission.ts │ │ ├── settings.ts │ │ ├── types.ts │ │ └── user.ts ├── style │ ├── dark.scss │ ├── element-plus.scss │ ├── index.scss │ ├── login.css │ ├── reset.scss │ ├── sidebar.scss │ ├── tailwind.css │ └── transition.scss ├── utils │ ├── auth.ts │ ├── common.ts │ ├── crypt.ts │ ├── globalPolyfills.ts │ ├── http │ │ ├── index.ts │ │ └── types.d.ts │ ├── message.ts │ ├── mitt.ts │ ├── print.ts │ ├── progress │ │ └── index.ts │ ├── propTypes.ts │ ├── responsive.ts │ ├── rootConver.ts │ ├── sso.ts │ └── tree.ts └── views │ ├── error │ ├── 403.vue │ ├── 404.vue │ └── 500.vue │ ├── login │ ├── components │ │ ├── phone.vue │ │ ├── qrCode.vue │ │ ├── register.vue │ │ └── resetPassword.vue │ ├── index.vue │ └── utils │ │ ├── enums.ts │ │ ├── motion.ts │ │ ├── rule.ts │ │ ├── static.ts │ │ └── verifyCode.ts │ ├── permission │ ├── button │ │ └── index.vue │ └── page │ │ └── index.vue │ ├── system │ ├── config │ │ ├── form.vue │ │ ├── index.vue │ │ └── utils │ │ │ ├── hook.tsx │ │ │ └── rule.ts │ ├── dept │ │ ├── form.vue │ │ ├── index.vue │ │ └── utils │ │ │ ├── hook.tsx │ │ │ └── rule.ts │ ├── hooks.ts │ ├── log │ │ ├── loginLog │ │ │ ├── index.vue │ │ │ └── utils │ │ │ │ └── hook.tsx │ │ └── operationLog │ │ │ ├── description.vue │ │ │ ├── index.vue │ │ │ └── utils │ │ │ └── hook.tsx │ ├── menu │ │ ├── form.vue │ │ ├── index.vue │ │ └── utils │ │ │ ├── hook.tsx │ │ │ ├── menuLogic.ts │ │ │ └── rule.ts │ ├── monitor │ │ ├── cache │ │ │ └── index.vue │ │ ├── hooks.ts │ │ ├── onlineUser │ │ │ ├── index.vue │ │ │ └── utils │ │ │ │ └── hook.tsx │ │ └── server │ │ │ └── index.vue │ ├── notice │ │ ├── form.vue │ │ ├── index.vue │ │ └── utils │ │ │ ├── hook.tsx │ │ │ ├── rule.ts │ │ │ └── types.ts │ ├── post │ │ ├── index.vue │ │ ├── post-form-modal.vue │ │ └── utils │ │ │ └── hook.tsx │ ├── role │ │ ├── index.vue │ │ ├── role-form-modal.vue │ │ └── utils │ │ │ └── hook.tsx │ └── user │ │ ├── form.vue │ │ ├── hook.tsx │ │ ├── index.vue │ │ ├── passwordForm.vue │ │ ├── profile │ │ ├── index.vue │ │ ├── resetPwd.vue │ │ ├── userAvatar.vue │ │ └── userInfo.vue │ │ ├── rule.ts │ │ ├── svg │ │ ├── expand.svg │ │ └── unexpand.svg │ │ ├── tree.vue │ │ ├── uploadForm.vue │ │ └── utils │ │ └── hook.tsx │ └── welcome │ └── index.vue ├── stylelint.config.js ├── tailwind.config.js ├── tsconfig.json ├── types ├── global-components.d.ts ├── global.d.ts ├── index.d.ts ├── router.d.ts ├── shims-tsx.d.ts └── shims-vue.d.ts └── vite.config.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache 7 | report.html 8 | 9 | yarn.lock 10 | npm-debug.log* 11 | .pnpm-error.log* 12 | .pnpm-debug.log 13 | tests/**/coverage/ 14 | 15 | # Editor directories and files 16 | .idea 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | tsconfig.tsbuildinfo 22 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = false 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # 平台本地运行端口号 2 | VITE_PORT = 8848 3 | 4 | # 是否隐藏首页 隐藏 true 不隐藏 false (勿删除,VITE_HIDE_HOME只需在.env文件配置) 5 | VITE_HIDE_HOME = false 6 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 平台本地运行端口号 2 | VITE_PORT = 80 3 | 4 | # 开发环境读取配置文件路径 5 | VITE_PUBLIC_PATH = ./ 6 | 7 | # 开发环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 8 | VITE_ROUTER_HISTORY = "hash" 9 | 10 | # 后端地址 11 | VITE_APP_BASE_API = '/dev-api' 12 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境平台打包路径 2 | VITE_PUBLIC_PATH = ./ 3 | 4 | # 线上环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 5 | VITE_ROUTER_HISTORY = "hash" 6 | 7 | # 是否在打包时使用cdn替换本地库 替换 true 不替换 false 8 | VITE_CDN = false 9 | 10 | # 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) 11 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 12 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 13 | VITE_COMPRESSION = "none" 14 | 15 | # 后端地址 16 | VITE_APP_BASE_API = '/prod-api' -------------------------------------------------------------------------------- /.env.staging: -------------------------------------------------------------------------------- 1 | # 预发布也需要生产环境的行为 2 | # https://cn.vitejs.dev/guide/env-and-mode.html#modes 3 | # NODE_ENV = development 4 | 5 | VITE_PUBLIC_PATH = ./ 6 | 7 | # 预发布环境路由历史模式(Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数") 8 | VITE_ROUTER_HISTORY = "hash" 9 | 10 | # 是否在打包时使用cdn替换本地库 替换 true 不替换 false 11 | VITE_CDN = true 12 | 13 | # 是否启用gzip压缩或brotli压缩(分两种情况,删除原始文件和不删除原始文件) 14 | # 压缩时不删除原始文件的配置:gzip、brotli、both(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 15 | # 压缩时删除原始文件的配置:gzip-clear、brotli-clear、both-clear(同时开启 gzip 与 brotli 压缩)、none(不开启压缩,默认) 16 | VITE_COMPRESSION = "none" 17 | 18 | # 后端地址 19 | VITE_APP_BASE_API = '/stage-api' -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | public 2 | dist 3 | *.d.ts 4 | /src/assets 5 | package.json 6 | .eslintrc.js 7 | .prettierrc.js 8 | commitlint.config.js 9 | postcss.config.js 10 | tailwind.config.js 11 | stylelint.config.js -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | .eslintcache 7 | report.html 8 | 9 | yarn.lock 10 | npm-debug.log* 11 | .pnpm-error.log* 12 | .pnpm-debug.log 13 | tests/**/coverage/ 14 | 15 | # 本机调试debug配置文件 16 | .vscode/launch.json 17 | 18 | # Editor directories and files 19 | .idea 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | tsconfig.tsbuildinfo -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # shellcheck source=./_/husky.sh 4 | . "$(dirname "$0")/_/husky.sh" 5 | 6 | npx --no-install commitlint --edit "$1" 7 | -------------------------------------------------------------------------------- /.husky/common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | command_exists () { 3 | command -v "$1" >/dev/null 2>&1 4 | } 5 | 6 | # Workaround for Windows 10, Git Bash and Pnpm 7 | if command_exists winpty && test -t 1; then 8 | exec < /dev/tty 9 | fi 10 | -------------------------------------------------------------------------------- /.husky/lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "*.{js,jsx,ts,tsx}": ["eslint --fix", "prettier --write"], 3 | "{!(package)*.json}": ["prettier --write--parser json"], 4 | "package.json": ["prettier --write"], 5 | "*.vue": ["eslint --fix", "prettier --write", "stylelint --fix"], 6 | "*.{vue,css,scss,postcss,less}": ["stylelint --fix", "prettier --write"], 7 | "*.md": ["prettier --write"] 8 | }; 9 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | . "$(dirname "$0")/common.sh" 4 | 5 | [ -n "$CI" ] && exit 0 6 | 7 | # Format and submit code according to lintstagedrc.js configuration 8 | npm run lint:lint-staged 9 | 10 | npm run lint:pretty 11 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD003": false, 4 | "MD033": false, 5 | "MD013": false, 6 | "MD001": false, 7 | "MD025": false, 8 | "MD024": false, 9 | "MD007": { "indent": 4 }, 10 | "no-hard-tabs": false 11 | } 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | shell-emulator=true -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | singleQuote: false, 4 | arrowParens: "avoid", 5 | trailingComma: "none" 6 | }; 7 | -------------------------------------------------------------------------------- /.stylelintignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | /public/* 3 | public/* 4 | src/style/reset.scss -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "akamud.vscode-theme-onedark", 4 | "antfu.iconify", 5 | "bradlc.vscode-tailwindcss", 6 | "christian-kohler.npm-intellisense", 7 | "christian-kohler.path-intellisense", 8 | "Codeium.codeium", 9 | "csstools.postcss", 10 | "DavidAnson.vscode-markdownlint", 11 | "dbaeumer.vscode-eslint", 12 | "donjayamanne.githistory", 13 | "dsznajder.es7-react-js-snippets", 14 | "eamodio.gitlens", 15 | "ecmel.vscode-html-css", 16 | "esbenp.prettier-vscode", 17 | "genieai.chatgpt-vscode", 18 | "hollowtree.vue-snippets", 19 | "lokalise.i18n-ally", 20 | "mhutchie.git-graph", 21 | "mikestead.dotenv", 22 | "pmneo.tsimporter", 23 | "streetsidesoftware.code-spell-checker", 24 | "stylelint.vscode-stylelint", 25 | "syler.sass-indented", 26 | "sysoev.language-stylus", 27 | "vscode-icons-team.vscode-icons", 28 | "Vue.volar", 29 | "xabikos.JavaScriptSnippets" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnType": true, 3 | "editor.formatOnSave": true, 4 | "[vue]": { 5 | "editor.defaultFormatter": "esbenp.prettier-vscode" 6 | }, 7 | "editor.tabSize": 2, 8 | "editor.formatOnPaste": true, 9 | "editor.guides.bracketPairs": "active", 10 | "files.autoSave": "afterDelay", 11 | "git.confirmSync": false, 12 | "workbench.startupEditor": "newUntitledFile", 13 | "editor.suggestSelection": "first", 14 | "editor.acceptSuggestionOnCommitCharacter": false, 15 | "css.lint.propertyIgnoredDueToDisplay": "ignore", 16 | "editor.quickSuggestions": { 17 | "other": true, 18 | "comments": true, 19 | "strings": true 20 | }, 21 | "files.associations": { 22 | "editor.snippetSuggestions": "top" 23 | }, 24 | "[css]": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode" 26 | }, 27 | "editor.codeActionsOnSave": { 28 | "source.fixAll.eslint": true 29 | }, 30 | "iconify.excludes": ["el"], 31 | "cSpell.words": ["iconify", "Qrcode"] 32 | } 33 | -------------------------------------------------------------------------------- /.vscode/vue3.0.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.0快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.0", 5 | "body": [ 6 | "\n", 9 | "\n", 16 | "", 18 | "$2" 19 | ], 20 | "description": "Vue3.0" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/vue3.2.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.2+快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.2+", 5 | "body": [ 6 | "\n", 8 | "\n", 11 | "", 13 | "$2" 14 | ], 15 | "description": "Vue3.2+" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/vue3.3.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Vue3.3+defineOptions快速生成模板": { 3 | "scope": "vue", 4 | "prefix": "Vue3.3+", 5 | "body": [ 6 | "\n", 11 | "\n", 14 | "", 16 | "$2" 17 | ], 18 | "description": "Vue3.3+defineOptions快速生成模板" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine as build-stage 2 | 3 | WORKDIR /app 4 | RUN corepack enable 5 | RUN corepack prepare pnpm@7.32.1 --activate 6 | 7 | RUN npm config set registry https://registry.npmmirror.com 8 | 9 | COPY .npmrc package.json pnpm-lock.yaml ./ 10 | RUN pnpm install --frozen-lockfile 11 | 12 | COPY . . 13 | RUN pnpm build 14 | 15 | FROM nginx:stable-alpine as production-stage 16 | 17 | COPY --from=build-stage /app/dist /usr/share/nginx/html 18 | EXPOSE 80 19 | 20 | CMD ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020-present, pure-admin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.en-US.md: -------------------------------------------------------------------------------- 1 |

vue-pure-admin Lite Edition(no i18n version)

2 | 3 | [![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE) 4 | 5 | **English** | [中文](./README.md) 6 | 7 | ## Introduce 8 | 9 | The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb` 10 | 11 | ## Supporting Video 12 | 13 | - [Click Watch Tutorial](https://www.bilibili.com/video/BV1kg411v7QT) 14 | - [Click Watch UI Design](https://www.bilibili.com/video/BV17g411T7rq) 15 | 16 | ## Docs 17 | 18 | - [documentation site](https://yiming_chang.gitee.io/pure-admin-doc) 19 | 20 | ## Preview 21 | 22 | - [Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login) 23 | 24 | ## Maintainer 25 | 26 | [xiaoxian521](https://github.com/xiaoxian521) 27 | 28 | ## ⚠️ Attention 29 | 30 | - The Lite version does not accept any issues and prs. If you have any questions, please go to the full version [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) to mention, thank you! 31 | 32 | ## License 33 | 34 | In principle, no fees and copyrights are charged, and it is commercially available, but if you need secondary open source (such as using this platform for secondary development and open source, the front-end code must be open source and free), please contact the author for permission! (Free, just take a record) 35 | 36 | [MIT © 2020-present, pure-admin](./LICENSE) 37 | -------------------------------------------------------------------------------- /build/cdn.ts: -------------------------------------------------------------------------------- 1 | import { Plugin as importToCDN } from "vite-plugin-cdn-import"; 2 | 3 | /** 4 | * @description 打包时采用`cdn`模式,仅限外网使用(默认不采用,如果需要采用cdn模式,请在 .env.production 文件,将 VITE_CDN 设置成true) 5 | * 平台采用国内cdn:https://www.bootcdn.cn,当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com 6 | * 提醒:mockjs不能用cdn模式引入,会报错。正确的方式是,生产环境删除mockjs,使用真实的后端请求 7 | * 注意:上面提到的仅限外网使用也不是完全肯定的,如果你们公司内网部署的有相关js、css文件,也可以将下面配置对应改一下,整一套内网版cdn 8 | */ 9 | export const cdn = importToCDN({ 10 | //(prodUrl解释: name: 对应下面modules的name,version: 自动读取本地package.json中dependencies依赖中对应包的版本号,path: 对应下面modules的path,当然也可写完整路径,会替换prodUrl) 11 | prodUrl: "https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}", 12 | modules: [ 13 | { 14 | name: "vue", 15 | var: "Vue", 16 | path: "vue.global.prod.min.js" 17 | }, 18 | { 19 | name: "vue-router", 20 | var: "VueRouter", 21 | path: "vue-router.global.min.js" 22 | }, 23 | // 项目中没有直接安装vue-demi,但是pinia用到了,所以需要在引入pinia前引入vue-demi(https://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77) 24 | { 25 | name: "vue-demi", 26 | var: "VueDemi", 27 | path: "index.iife.min.js" 28 | }, 29 | { 30 | name: "pinia", 31 | var: "Pinia", 32 | path: "pinia.iife.min.js" 33 | }, 34 | { 35 | name: "element-plus", 36 | var: "ElementPlus", 37 | path: "index.full.min.js", 38 | css: "index.min.css" 39 | }, 40 | { 41 | name: "axios", 42 | var: "axios", 43 | path: "axios.min.js" 44 | }, 45 | { 46 | name: "dayjs", 47 | var: "dayjs", 48 | path: "dayjs.min.js" 49 | }, 50 | { 51 | name: "echarts", 52 | var: "echarts", 53 | path: "echarts.min.js" 54 | } 55 | ] 56 | }); 57 | -------------------------------------------------------------------------------- /build/compress.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "vite"; 2 | import { isArray } from "@pureadmin/utils"; 3 | import compressPlugin from "vite-plugin-compression"; 4 | 5 | export const configCompressPlugin = ( 6 | compress: ViteCompression 7 | ): Plugin | Plugin[] => { 8 | if (compress === "none") return null; 9 | 10 | const gz = { 11 | // 生成的压缩包后缀 12 | ext: ".gz", 13 | // 体积大于threshold才会被压缩 14 | threshold: 0, 15 | // 默认压缩.js|mjs|json|css|html后缀文件,设置成true,压缩全部文件 16 | filter: () => true, 17 | // 压缩后是否删除原始文件 18 | deleteOriginFile: false 19 | }; 20 | const br = { 21 | ext: ".br", 22 | algorithm: "brotliCompress", 23 | threshold: 0, 24 | filter: () => true, 25 | deleteOriginFile: false 26 | }; 27 | 28 | const codeList = [ 29 | { k: "gzip", v: gz }, 30 | { k: "brotli", v: br }, 31 | { k: "both", v: [gz, br] } 32 | ]; 33 | 34 | const plugins: Plugin[] = []; 35 | 36 | codeList.forEach(item => { 37 | if (compress.includes(item.k)) { 38 | if (compress.includes("clear")) { 39 | if (isArray(item.v)) { 40 | item.v.forEach(vItem => { 41 | plugins.push( 42 | compressPlugin(Object.assign(vItem, { deleteOriginFile: true })) 43 | ); 44 | }); 45 | } else { 46 | plugins.push( 47 | compressPlugin(Object.assign(item.v, { deleteOriginFile: true })) 48 | ); 49 | } 50 | } else { 51 | if (isArray(item.v)) { 52 | item.v.forEach(vItem => { 53 | plugins.push(compressPlugin(vItem)); 54 | }); 55 | } else { 56 | plugins.push(compressPlugin(item.v)); 57 | } 58 | } 59 | } 60 | }); 61 | 62 | return plugins; 63 | }; 64 | -------------------------------------------------------------------------------- /build/index.ts: -------------------------------------------------------------------------------- 1 | /** 处理环境变量 */ 2 | const wrapperEnv = (envConfigs: Recordable): ViteEnv => { 3 | /** 此处为默认值 */ 4 | const defaultEnvConfigs: ViteEnv = { 5 | VITE_PORT: 8848, 6 | VITE_PUBLIC_PATH: "", 7 | VITE_ROUTER_HISTORY: "", 8 | VITE_CDN: false, 9 | VITE_HIDE_HOME: "false", 10 | VITE_COMPRESSION: "none", 11 | VITE_APP_BASE_API: "" 12 | }; 13 | 14 | for (const configName of Object.keys(envConfigs)) { 15 | let realConfigValue = envConfigs[configName].replace(/\\n/g, "\n"); 16 | realConfigValue = 17 | realConfigValue === "true" 18 | ? true 19 | : realConfigValue === "false" 20 | ? false 21 | : realConfigValue; 22 | 23 | if (configName === "VITE_PORT") { 24 | realConfigValue = Number(realConfigValue); 25 | } 26 | 27 | defaultEnvConfigs[configName] = realConfigValue; 28 | if (typeof realConfigValue === "string") { 29 | process.env[configName] = realConfigValue; 30 | } else if (typeof realConfigValue === "object") { 31 | process.env[configName] = JSON.stringify(realConfigValue); 32 | } 33 | } 34 | return defaultEnvConfigs; 35 | }; 36 | 37 | export { wrapperEnv }; 38 | -------------------------------------------------------------------------------- /build/info.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from "vite"; 2 | import dayjs, { Dayjs } from "dayjs"; 3 | import utils from "@pureadmin/utils"; 4 | import duration from "dayjs/plugin/duration"; 5 | import { green, blue, bold } from "picocolors"; 6 | dayjs.extend(duration); 7 | 8 | export function viteBuildInfo(): Plugin { 9 | let config: { command: string }; 10 | let startTime: Dayjs; 11 | let endTime: Dayjs; 12 | let outDir: string; 13 | return { 14 | name: "vite:buildInfo", 15 | configResolved(resolvedConfig) { 16 | config = resolvedConfig; 17 | outDir = resolvedConfig.build?.outDir ?? "dist"; 18 | }, 19 | buildStart() { 20 | console.log( 21 | bold( 22 | green( 23 | `👏欢迎使用${blue( 24 | "[Agileboot全栈项目]" 25 | )},如果您感觉不错,记得点击后面链接给个star哦💖 https://github.com/valarchie/agileboot-back-end` 26 | ) 27 | ) 28 | ); 29 | if (config.command === "build") { 30 | startTime = dayjs(new Date()); 31 | } 32 | }, 33 | closeBundle() { 34 | if (config.command === "build") { 35 | endTime = dayjs(new Date()); 36 | utils.getPackageSize({ 37 | folder: outDir, 38 | callback: (size: string) => { 39 | console.log( 40 | bold( 41 | green( 42 | `🎉恭喜打包完成(总用时${dayjs 43 | .duration(endTime.diff(startTime)) 44 | .format("mm分ss秒")},打包后的大小为${size})` 45 | ) 46 | ) 47 | ); 48 | } 49 | }); 50 | } 51 | } 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /build/optimize.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 此文件作用于 `vite.config.ts` 的 `optimizeDeps.include` 依赖预构建配置项 3 | * 依赖预构建,`vite` 启动时会将下面 include 里的模块,编译成 esm 格式并缓存到 node_modules/.vite 文件夹,页面加载到对应模块时如果浏览器有缓存就读取浏览器缓存,如果没有会读取本地缓存并按需加载 4 | * 尤其当您禁用浏览器缓存时(这种情况只应该发生在调试阶段)必须将对应模块加入到 include里,否则会遇到开发环境切换页面卡顿的问题(vite 会认为它是一个新的依赖包会重新加载并强制刷新页面),因为它既无法使用浏览器缓存,又没有在本地 node_modules/.vite 里缓存 5 | * 温馨提示:如果您使用的第三方库是全局引入,也就是引入到 src/main.ts 文件里,就不需要再添加到 include 里了,因为 vite 会自动将它们缓存到 node_modules/.vite 6 | */ 7 | const include = [ 8 | "qs", 9 | "mitt", 10 | "xlsx", 11 | "dayjs", 12 | "axios", 13 | "pinia", 14 | "js-cookie", 15 | "sortablejs", 16 | "pinyin-pro", 17 | "@vueuse/core", 18 | "@pureadmin/utils", 19 | "responsive-storage" 20 | ]; 21 | 22 | /** 23 | * 在预构建中强制排除的依赖项 24 | * 温馨提示:所有以 `@iconify-icons/` 开头引入的的本地图标模块,都应该加入到下面的 `exclude` 里,因为平台推荐的使用方式是哪里需要哪里引入而且都是单个的引入,不需要预构建,直接让浏览器加载就好 25 | */ 26 | const exclude = [ 27 | "@iconify-icons/ep", 28 | "@iconify-icons/ri", 29 | "@pureadmin/theme/dist/browser-utils" 30 | ]; 31 | 32 | export { include, exclude }; 33 | -------------------------------------------------------------------------------- /build/plugins.ts: -------------------------------------------------------------------------------- 1 | import { cdn } from "./cdn"; 2 | import vue from "@vitejs/plugin-vue"; 3 | import { viteBuildInfo } from "./info"; 4 | import svgLoader from "vite-svg-loader"; 5 | import vueJsx from "@vitejs/plugin-vue-jsx"; 6 | // import { viteMockServe } from "vite-plugin-mock"; 7 | import { configCompressPlugin } from "./compress"; 8 | // import ElementPlus from "unplugin-element-plus/vite"; 9 | import { visualizer } from "rollup-plugin-visualizer"; 10 | import removeConsole from "vite-plugin-remove-console"; 11 | import { themePreprocessorPlugin } from "@pureadmin/theme"; 12 | import { genScssMultipleScopeVars } from "../src/layout/theme"; 13 | 14 | export function getPluginsList( 15 | command: string, 16 | VITE_CDN: boolean, 17 | VITE_COMPRESSION: ViteCompression 18 | ) { 19 | // const prodMock = true; 20 | const lifecycle = process.env.npm_lifecycle_event; 21 | return [ 22 | vue(), 23 | // jsx、tsx语法支持 24 | vueJsx(), 25 | VITE_CDN ? cdn : null, 26 | configCompressPlugin(VITE_COMPRESSION), 27 | // 线上环境删除console 28 | removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }), 29 | viteBuildInfo(), 30 | // 自定义主题 31 | themePreprocessorPlugin({ 32 | scss: { 33 | multipleScopeVars: genScssMultipleScopeVars(), 34 | extract: true 35 | } 36 | }), 37 | // svg组件化支持 38 | svgLoader(), 39 | // ElementPlus({}), 40 | // mock支持 41 | // viteMockServe({ 42 | // mockPath: "mock", 43 | // localEnabled: command === "serve", 44 | // prodEnabled: command !== "serve" && prodMock, 45 | // injectCode: ` 46 | // import { setupProdMockServer } from './mockProdServer'; 47 | // setupProdMockServer(); 48 | // `, 49 | // logger: false 50 | // }), 51 | // 打包分析 52 | lifecycle === "report" 53 | ? visualizer({ open: true, brotliSize: true, filename: "report.html" }) 54 | : null 55 | ]; 56 | } 57 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ignores: [commit => commit.includes("init")], 3 | extends: ["@commitlint/config-conventional"], 4 | rules: { 5 | "body-leading-blank": [2, "always"], 6 | "footer-leading-blank": [1, "always"], 7 | "header-max-length": [2, "always", 108], 8 | "subject-empty": [2, "never"], 9 | "type-empty": [2, "never"], 10 | "type-enum": [ 11 | 2, 12 | "always", 13 | [ 14 | "feat", 15 | "fix", 16 | "perf", 17 | "style", 18 | "docs", 19 | "test", 20 | "refactor", 21 | "build", 22 | "ci", 23 | "chore", 24 | "revert", 25 | "wip", 26 | "workflow", 27 | "types", 28 | "release" 29 | ] 30 | ] 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | Agileboot管理系统 12 | 13 | 16 | 17 | 18 | 19 |
20 | 83 |
84 |
85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /mock/asyncRoutes.ts: -------------------------------------------------------------------------------- 1 | // 模拟后端动态生成路由 2 | import { MockMethod } from "vite-plugin-mock"; 3 | 4 | /** 5 | * roles:页面级别权限,这里模拟二种 "admin"、"common" 6 | * admin:管理员角色 7 | * common:普通角色 8 | */ 9 | 10 | const permissionRouter = { 11 | path: "/permission", 12 | meta: { 13 | title: "权限管理", 14 | icon: "lollipop", 15 | rank: 10 16 | }, 17 | children: [ 18 | { 19 | path: "/permission/page/index", 20 | name: "PermissionPage", 21 | meta: { 22 | title: "页面权限", 23 | roles: ["admin", "common"] 24 | } 25 | }, 26 | { 27 | path: "/permission/button/index", 28 | name: "PermissionButton", 29 | meta: { 30 | title: "按钮权限", 31 | roles: ["admin", "common"], 32 | auths: ["btn_add", "btn_edit", "btn_delete"] 33 | } 34 | } 35 | ] 36 | }; 37 | 38 | export default [ 39 | { 40 | url: "/getAsyncRoutes", 41 | method: "get", 42 | response: () => { 43 | return { 44 | success: true, 45 | data: [permissionRouter] 46 | }; 47 | } 48 | } 49 | ] as MockMethod[]; 50 | -------------------------------------------------------------------------------- /mock/login.ts: -------------------------------------------------------------------------------- 1 | // 根据角色动态生成路由 2 | import { MockMethod } from "vite-plugin-mock"; 3 | 4 | export default [ 5 | { 6 | url: "/login", 7 | method: "post", 8 | response: ({ body }) => { 9 | if (body.username === "admin") { 10 | return { 11 | success: true, 12 | data: { 13 | username: "admin", 14 | // 一个用户可能有多个角色 15 | roles: ["admin"], 16 | accessToken: "eyJhbGciOiJIUzUxMiJ9.admin", 17 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh", 18 | expires: "2023/10/30 00:00:00" 19 | } 20 | }; 21 | } else { 22 | return { 23 | success: true, 24 | data: { 25 | username: "common", 26 | // 一个用户可能有多个角色 27 | roles: ["common"], 28 | accessToken: "eyJhbGciOiJIUzUxMiJ9.common", 29 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh", 30 | expires: "2023/10/30 00:00:00" 31 | } 32 | }; 33 | } 34 | } 35 | } 36 | ] as MockMethod[]; 37 | -------------------------------------------------------------------------------- /mock/refreshToken.ts: -------------------------------------------------------------------------------- 1 | import { MockMethod } from "vite-plugin-mock"; 2 | 3 | // 模拟刷新token接口 4 | export default [ 5 | { 6 | url: "/refreshToken", 7 | method: "post", 8 | response: ({ body }) => { 9 | if (body.refreshToken) { 10 | return { 11 | success: true, 12 | data: { 13 | accessToken: "eyJhbGciOiJIUzUxMiJ9.newAdmin", 14 | refreshToken: "eyJhbGciOiJIUzUxMiJ9.newAdminRefresh", 15 | // `expires`选择这种日期格式是为了方便调试,后端直接设置时间戳或许更方便(每次都应该递增)。如果后端返回的是时间戳格式,前端开发请来到这个目录`src/utils/auth.ts`,把第`38`行的代码换成expires = data.expires即可。 16 | expires: "2023/10/30 23:59:59" 17 | } 18 | }; 19 | } else { 20 | return { 21 | success: false, 22 | data: {} 23 | }; 24 | } 25 | } 26 | } 27 | ] as MockMethod[]; 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "postcss-import": {}, 4 | "tailwindcss/nesting": {}, 5 | tailwindcss: {}, 6 | autoprefixer: {}, 7 | ...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {}) 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/public/favicon.ico -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/serverConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "4.4.0", 3 | "Title": "Agileboot", 4 | "FixedHeader": true, 5 | "HiddenSideBar": false, 6 | "MultiTagsCache": false, 7 | "KeepAlive": true, 8 | "Layout": "vertical", 9 | "Theme": "default", 10 | "DarkMode": false, 11 | "Grey": false, 12 | "Weak": false, 13 | "HideTabs": false, 14 | "SidebarStatus": true, 15 | "EpThemeColor": "#409EFF", 16 | "ShowLogo": true, 17 | "ShowModel": "smart", 18 | "MenuArrowIconNoTransition": true, 19 | "CachingAsyncRoutes": false, 20 | "TooltipEffect": "light", 21 | "ResponsiveStorageNameSpace": "responsive-" 22 | } 23 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | -------------------------------------------------------------------------------- /src/api/common/login.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export type CaptchaDTO = { 4 | /** 验证码的base64图片 */ 5 | captchaCodeImg: string; 6 | /** 验证码对应的缓存key */ 7 | captchaCodeKey: string; 8 | }; 9 | 10 | export type ConfigDTO = { 11 | /** 验证码开关 */ 12 | isCaptchaOn: boolean; 13 | /** 系统字典配置(下拉选项之类的) */ 14 | dictionary: Map>; 15 | }; 16 | 17 | export type LoginByPasswordDTO = { 18 | /** 用户名 */ 19 | username: string; 20 | /** 密码 */ 21 | password: string; 22 | /** 验证码 */ 23 | captchaCode: string; 24 | /** 验证码对应的缓存key */ 25 | captchaCodeKey: string; 26 | }; 27 | 28 | /** 29 | * 后端token实现 30 | */ 31 | export type TokenDTO = { 32 | /** token */ 33 | token: string; 34 | /** 当前登录的用户 */ 35 | currentUser: CurrentLoginUserDTO; 36 | }; 37 | 38 | export type CurrentLoginUserDTO = { 39 | userInfo: CurrentUserInfoDTO; 40 | roleKey: string; 41 | permissions: Set; 42 | }; 43 | 44 | /** 45 | * 当前User 46 | */ 47 | export interface CurrentUserInfoDTO { 48 | avatar?: string; 49 | createTime?: Date; 50 | creatorId?: number; 51 | creatorName?: string; 52 | deptId?: number; 53 | deptName?: string; 54 | email?: string; 55 | loginDate?: Date; 56 | loginIp?: string; 57 | nickName?: string; 58 | phoneNumber?: string; 59 | postId?: number; 60 | postName?: string; 61 | remark?: string; 62 | roleId?: number; 63 | roleName?: string; 64 | sex?: number; 65 | status?: number; 66 | updaterId?: number; 67 | updaterName?: string; 68 | updateTime?: Date; 69 | userId?: number; 70 | username?: string; 71 | userType?: number; 72 | } 73 | 74 | export type DictionaryData = { 75 | label: string; 76 | value: number; 77 | cssTag: string; 78 | }; 79 | 80 | /** 获取系统配置接口 */ 81 | export const getConfig = () => { 82 | return http.request>("get", "/getConfig"); 83 | }; 84 | 85 | /** 验证码接口 */ 86 | export const getCaptchaCode = () => { 87 | return http.request>("get", "/captchaImage"); 88 | }; 89 | 90 | /** 登录接口 */ 91 | export const loginByPassword = (data: LoginByPasswordDTO) => { 92 | return http.request>("post", "/login", { data }); 93 | }; 94 | 95 | /** 获取当前登录用户接口 */ 96 | export const getLoginUserInfo = () => { 97 | return http.request>("get", "/getLoginUserInfo"); 98 | }; 99 | 100 | type Result = { 101 | success: boolean; 102 | data: Array; 103 | }; 104 | 105 | /** 获取动态菜单 */ 106 | export const getAsyncRoutes = () => { 107 | return http.request("get", "/getRouters"); 108 | }; 109 | -------------------------------------------------------------------------------- /src/api/system/config.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface ConfigQuery extends BasePageQuery { 4 | /** 5 | * 配置key 6 | */ 7 | configKey?: string; 8 | /** 9 | * 配置名称 10 | */ 11 | configName?: string; 12 | /** 13 | * 是否允许更改配置 14 | */ 15 | isAllowChange?: string; 16 | } 17 | 18 | /** 19 | * ConfigDTO, 配置信息 20 | */ 21 | export interface ConfigDTO { 22 | configId?: string; 23 | configKey?: string; 24 | configName?: string; 25 | configOptions?: string[]; 26 | configValue?: string; 27 | createTime?: Date; 28 | isAllowChange?: string; 29 | isAllowChangeStr?: string; 30 | remark?: string; 31 | } 32 | 33 | /** 34 | * ConfigUpdateCommand 35 | */ 36 | export interface UpdateConfigRequest { 37 | configValue: string; 38 | } 39 | 40 | /** 获取配置列表 */ 41 | export const getConfigListApi = (params?: ConfigQuery) => { 42 | return http.request>>( 43 | "get", 44 | "/system/configs", 45 | { 46 | params 47 | } 48 | ); 49 | }; 50 | 51 | /** 获取配置信息 */ 52 | export const getConfigInfoApi = (configId: string) => { 53 | return http.request>( 54 | "get", 55 | `/system/config/${configId}` 56 | ); 57 | }; 58 | 59 | /** 刷新配置缓存 */ 60 | export const updateConfigApi = ( 61 | configId: number, 62 | data: UpdateConfigRequest 63 | ) => { 64 | return http.request>>( 65 | "put", 66 | `/system/config/${configId}`, 67 | { 68 | data 69 | } 70 | ); 71 | }; 72 | 73 | /** 刷新配置缓存 */ 74 | export const refreshConfigCacheApi = () => { 75 | return http.request>("delete", "/system/configs/cache"); 76 | }; 77 | -------------------------------------------------------------------------------- /src/api/system/dept.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface DeptQuery extends BaseQuery { 4 | // TODO 目前不需要这个参数 5 | deptId?: number; 6 | parentId?: number; 7 | } 8 | 9 | /** 10 | * DeptDTO 11 | */ 12 | export interface DeptDTO { 13 | createTime?: Date; 14 | id?: number; 15 | deptName?: string; 16 | email?: string; 17 | leaderName?: string; 18 | orderNum?: number; 19 | parentId?: number; 20 | phone?: string; 21 | status?: number; 22 | statusStr?: string; 23 | } 24 | 25 | /** 26 | * AddDeptCommand 27 | */ 28 | export interface DeptRequest { 29 | deptName: string; 30 | email?: string; 31 | leaderName?: string; 32 | orderNum: number; 33 | parentId: number; 34 | phone?: string; 35 | status: number; 36 | } 37 | 38 | export interface DeptTreeDTO { 39 | id: number; 40 | parentId: number; 41 | label: string; 42 | children: [DeptTreeDTO]; 43 | } 44 | 45 | /** 获取部门列表 */ 46 | export const getDeptListApi = (params?: DeptQuery) => { 47 | return http.request>>("get", "/system/depts", { 48 | params 49 | }); 50 | }; 51 | 52 | /** 新增部门 */ 53 | export const addDeptApi = (data: DeptRequest) => { 54 | console.log(data); 55 | return http.request>("post", "/system/dept", { 56 | data 57 | }); 58 | }; 59 | 60 | /** 部门详情 */ 61 | export const getDeptInfoApi = (deptId: string) => { 62 | return http.request>("get", `/system/dept/${deptId}`); 63 | }; 64 | 65 | /** 修改部门 */ 66 | export const updateDeptApi = (deptId: string, data: DeptRequest) => { 67 | return http.request>("put", `/system/dept/${deptId}`, { 68 | data 69 | }); 70 | }; 71 | 72 | /** 删除部门 */ 73 | export const deleteDeptApi = (deptId: string) => { 74 | return http.request>("delete", `/system/dept/${deptId}`); 75 | }; 76 | 77 | /** 获取部门树级结构 */ 78 | export const getDeptTree = () => { 79 | return http.request>( 80 | "get", 81 | "/system/depts/dropdown" 82 | ); 83 | }; 84 | -------------------------------------------------------------------------------- /src/api/system/log.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface OperationLogsQuery extends BasePageQuery { 4 | businessType?: string; 5 | requestModule?: string; 6 | status?: string; 7 | username?: string; 8 | } 9 | 10 | export interface OperationLogDTO { 11 | businessType?: number; 12 | businessTypeStr?: string; 13 | calledMethod?: string; 14 | deptId?: number; 15 | deptName?: string; 16 | errorStack?: string; 17 | operationId?: number; 18 | operationParam?: string; 19 | operationResult?: string; 20 | operationTime?: Date; 21 | operatorIp?: string; 22 | operatorLocation?: string; 23 | operatorType?: number; 24 | operatorTypeStr?: string; 25 | requestMethod?: string; 26 | requestModule?: string; 27 | requestUrl?: string; 28 | status?: number; 29 | statusStr?: string; 30 | userId?: number; 31 | username?: string; 32 | } 33 | 34 | /** 获取操作日志列表 */ 35 | export const getOperationLogListApi = (params?: OperationLogsQuery) => { 36 | return http.request>>( 37 | "get", 38 | "/logs/operationLogs", 39 | { 40 | params 41 | } 42 | ); 43 | }; 44 | 45 | export const exportOperationLogExcelApi = ( 46 | params: OperationLogsQuery, 47 | fileName: string 48 | ) => { 49 | return http.download("/logs/operationLogs/excel", fileName, { 50 | params 51 | }); 52 | }; 53 | 54 | export const deleteOperationLogApi = (data: Array) => { 55 | return http.request>("delete", "/logs/operationLogs", { 56 | params: { 57 | // 需要将数组转换为字符串 否则Axios会将参数变成 noticeIds[0]:1 noticeIds[1]:2 这种格式,后端接收参数不成功 58 | operationIds: data.toString() 59 | } 60 | }); 61 | }; 62 | 63 | /** 登录日志查询类 */ 64 | export interface LoginLogQuery extends BasePageQuery { 65 | beginTime?: string; 66 | endTime?: string; 67 | ipAddress?: string; 68 | status?: string; 69 | username?: string; 70 | } 71 | 72 | /** 73 | * 登录日志信息 74 | */ 75 | export interface LoginLogsDTO { 76 | browser?: string; 77 | infoId?: string; 78 | ipAddress?: string; 79 | loginLocation?: string; 80 | loginTime?: Date; 81 | msg?: string; 82 | operationSystem?: string; 83 | /** TODO 这个登录状态的设计很奇怪 需要重构掉 */ 84 | status?: number; 85 | statusStr?: string; 86 | username?: string; 87 | } 88 | 89 | /** 获取操作日志列表 */ 90 | export const getLoginLogListApi = (params?: LoginLogQuery) => { 91 | return http.request>>( 92 | "get", 93 | "/logs/loginLogs", 94 | { 95 | params 96 | } 97 | ); 98 | }; 99 | 100 | export const exportLoginLogExcelApi = ( 101 | params: LoginLogQuery, 102 | fileName: string 103 | ) => { 104 | return http.download("/logs/loginLogs/excel", fileName, { 105 | params 106 | }); 107 | }; 108 | 109 | export const deleteLoginLogApi = (data: Array) => { 110 | return http.request>("delete", "/logs/loginLogs", { 111 | params: { 112 | // 需要将数组转换为字符串 否则Axios会将参数变成 noticeIds[0]:1 noticeIds[1]:2 这种格式,后端接收参数不成功 113 | ids: data.toString() 114 | } 115 | }); 116 | }; 117 | -------------------------------------------------------------------------------- /src/api/system/menu.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | import { Tree } from "@/utils/tree"; 3 | 4 | export interface MenuQuery { 5 | isButton: boolean; 6 | } 7 | 8 | /** 9 | * MenuDTO 10 | */ 11 | export interface MenuDTO extends Tree { 12 | createTime?: Date; 13 | isButton?: number; 14 | id?: number; 15 | menuName?: string; 16 | parentId?: number; 17 | menuType: number; 18 | menuTypeStr: string; 19 | path?: string; 20 | permission?: string; 21 | routerName?: string; 22 | status?: number; 23 | statusStr?: string; 24 | } 25 | 26 | /** 27 | * MenuDetailDTO 28 | */ 29 | export interface MenuDetailDTO extends MenuDTO { 30 | meta: MetaDTO; 31 | permission?: string; 32 | } 33 | 34 | /** 35 | * AddMenuCommand 36 | */ 37 | export interface MenuRequest { 38 | id: number; 39 | parentId: number; 40 | menuName: string; 41 | routerName?: string; 42 | path?: string; 43 | permission?: string; 44 | status: number; 45 | isButton: boolean; 46 | menuType: number; 47 | meta: MetaDTO; 48 | } 49 | 50 | /** 51 | * MetaDTO 52 | */ 53 | export interface MetaDTO { 54 | auths?: string[]; 55 | dynamicLevel?: number; 56 | extraIcon?: ExtraIconDTO; 57 | frameLoading?: boolean; 58 | frameSrc?: string; 59 | hiddenTag?: boolean; 60 | icon?: string; 61 | isFrameSrcInternal?: boolean; 62 | keepAlive?: boolean; 63 | rank?: number; 64 | roles?: string[]; 65 | showLink?: boolean; 66 | showParent?: boolean; 67 | title?: string; 68 | transition?: TransitionDTO; 69 | } 70 | 71 | /** 72 | * ExtraIconDTO 73 | */ 74 | export interface ExtraIconDTO { 75 | name?: string; 76 | svg?: boolean; 77 | } 78 | 79 | /** 80 | * TransitionDTO 81 | */ 82 | export interface TransitionDTO { 83 | enterTransition?: string; 84 | leaveTransition?: string; 85 | name?: string; 86 | } 87 | 88 | /** 获取菜单列表 */ 89 | export const getMenuListApi = (params: MenuQuery) => { 90 | return http.request>>("get", "/system/menus", { 91 | params 92 | }); 93 | }; 94 | 95 | /** 添加菜单 */ 96 | export const addMenuApi = (data: MenuRequest) => { 97 | return http.request>("post", "/system/menus", { data }); 98 | }; 99 | 100 | /** 修改菜单 */ 101 | export const updateMenuApi = (menuId: string, data: MenuRequest) => { 102 | return http.request>("put", `/system/menus/${menuId}`, { 103 | data 104 | }); 105 | }; 106 | 107 | /** 删除菜单 */ 108 | export const deleteMenuApi = (menuId: string) => { 109 | return http.request>("delete", `/system/menus/${menuId}`); 110 | }; 111 | 112 | /** 菜单详情 */ 113 | export const getMenuInfoApi = (menuId: string) => { 114 | return http.request>( 115 | "get", 116 | `/system/menus/${menuId}` 117 | ); 118 | }; 119 | -------------------------------------------------------------------------------- /src/api/system/monitor.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface OnlineUserQuery { 4 | ipAddress: string; 5 | username: string; 6 | } 7 | 8 | export interface OnlineUserInfo { 9 | browser?: string; 10 | deptName?: string; 11 | ipAddress?: string; 12 | loginLocation?: string; 13 | loginTime?: number; 14 | operationSystem?: string; 15 | tokenId?: string; 16 | username?: string; 17 | } 18 | 19 | /** 获取操作日志列表 */ 20 | export const getOnlineUserListApi = (params?: OnlineUserQuery) => { 21 | return http.request>>( 22 | "get", 23 | "/monitor/onlineUsers", 24 | { 25 | params 26 | } 27 | ); 28 | }; 29 | 30 | /** 强制登出用户 */ 31 | export const logoutOnlineUserApi = (tokenId: string) => { 32 | return http.request>( 33 | "delete", 34 | `/monitor/onlineUser/${tokenId}` 35 | ); 36 | }; 37 | 38 | /** 39 | * ServerInfo 40 | */ 41 | export interface ServerInfo { 42 | cpuInfo?: CpuInfo; 43 | diskInfos?: DiskInfo[]; 44 | jvmInfo?: JvmInfo; 45 | memoryInfo?: MemoryInfo; 46 | systemInfo?: SystemInfo; 47 | } 48 | 49 | /** 50 | * CpuInfo 51 | */ 52 | export interface CpuInfo { 53 | cpuNum?: number; 54 | free?: number; 55 | sys?: number; 56 | total?: number; 57 | used?: number; 58 | wait?: number; 59 | } 60 | 61 | /** 62 | * DiskInfo 63 | */ 64 | export interface DiskInfo { 65 | dirName?: string; 66 | free?: string; 67 | sysTypeName?: string; 68 | total?: string; 69 | typeName?: string; 70 | usage?: number; 71 | used?: string; 72 | } 73 | 74 | /** 75 | * JvmInfo 76 | */ 77 | export interface JvmInfo { 78 | free?: number; 79 | home?: string; 80 | inputArgs?: string; 81 | max?: number; 82 | name?: string; 83 | runTime?: string; 84 | startTime?: string; 85 | total?: number; 86 | usage?: number; 87 | used?: number; 88 | version?: string; 89 | } 90 | 91 | /** 92 | * MemoryInfo 93 | */ 94 | export interface MemoryInfo { 95 | free?: number; 96 | total?: number; 97 | usage?: number; 98 | used?: number; 99 | } 100 | 101 | /** 102 | * SystemInfo 103 | */ 104 | export interface SystemInfo { 105 | computerIp?: string; 106 | computerName?: string; 107 | osArch?: string; 108 | osName?: string; 109 | userDir?: string; 110 | } 111 | 112 | /** 获取服务器信息 */ 113 | export const getServerInfoApi = () => { 114 | return http.request>("get", "/monitor/serverInfo"); 115 | }; 116 | 117 | /** 118 | * RedisCacheInfoDTO 119 | */ 120 | export interface RedisCacheInfoDTO { 121 | commandStats?: CommandStatusDTO[]; 122 | dbSize?: number; 123 | info?: { [key: string]: string }; 124 | } 125 | 126 | /** 127 | * CommandStatusDTO 128 | */ 129 | export interface CommandStatusDTO { 130 | name?: string; 131 | value?: string; 132 | } 133 | 134 | /** 获取Redis信息 */ 135 | export const getCacheInfoApi = () => { 136 | return http.request>("get", "/monitor/cacheInfo"); 137 | }; 138 | -------------------------------------------------------------------------------- /src/api/system/notice.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface SystemNoticeQuery extends BasePageQuery { 4 | noticeType: string; 5 | noticeTitle: string; 6 | creatorName: string; 7 | } 8 | 9 | type SystemNoticeDTO = { 10 | noticeId: string; 11 | noticeTitle: string; 12 | noticeType: number; 13 | noticeContent: string; 14 | status: number; 15 | createTime: Date; 16 | creatorName: string; 17 | }; 18 | 19 | export type SystemNoticeRequest = { 20 | noticeId?: number; 21 | noticeTitle: string; 22 | noticeType: number; 23 | noticeContent: string; 24 | status: number; 25 | }; 26 | 27 | /** 获取系统通知列表 */ 28 | export const getSystemNoticeListApi = (params?: SystemNoticeQuery) => { 29 | return http.request>>( 30 | "get", 31 | "/system/notices", 32 | { 33 | params 34 | } 35 | ); 36 | }; 37 | 38 | /** 添加系统通知 */ 39 | export const addSystemNoticeApi = (data: SystemNoticeRequest) => { 40 | return http.request>("post", "/system/notices", { 41 | data 42 | }); 43 | }; 44 | 45 | /** 修改系统通知 */ 46 | export const updateSystemNoticeApi = (data: SystemNoticeRequest) => { 47 | return http.request>( 48 | "put", 49 | `/system/notices/${data.noticeId}`, 50 | { 51 | data 52 | } 53 | ); 54 | }; 55 | 56 | /** 删除系统通知 */ 57 | export const deleteSystemNoticeApi = (data: Array) => { 58 | return http.request>("delete", "/system/notices", { 59 | params: { 60 | // 需要将数组转换为字符串 否则Axios会将参数变成 noticeIds[0]:1 noticeIds[1]:2 这种格式,后端接收参数不成功 61 | noticeIds: data.toString() 62 | } 63 | }); 64 | }; 65 | -------------------------------------------------------------------------------- /src/api/system/post.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface PostListCommand extends BasePageQuery { 4 | postCode?: string; 5 | postName?: string; 6 | status?: number; 7 | } 8 | 9 | export interface PostPageResponse { 10 | createTime: string; 11 | postCode: string; 12 | postId: number; 13 | postName: string; 14 | postSort: number; 15 | remark: string; 16 | status: number; 17 | statusStr: string; 18 | } 19 | 20 | export function getPostListApi(params: PostListCommand) { 21 | return http.request>>( 22 | "get", 23 | "/system/post/list", 24 | { 25 | params 26 | } 27 | ); 28 | } 29 | 30 | export const exportPostExcelApi = ( 31 | params: PostListCommand, 32 | fileName: string 33 | ) => { 34 | return http.download("/system/post/excel", fileName, { 35 | params 36 | }); 37 | }; 38 | 39 | export const deletePostApi = (data: Array) => { 40 | return http.request>("delete", "/system/post", { 41 | params: { 42 | // 需要将数组转换为字符串 否则Axios会将参数变成 noticeIds[0]:1 noticeIds[1]:2 这种格式,后端接收参数不成功 43 | ids: data.toString() 44 | } 45 | }); 46 | }; 47 | 48 | export interface AddPostCommand { 49 | postCode: string; 50 | postName: string; 51 | postSort: number; 52 | remark?: string; 53 | status?: string; 54 | } 55 | 56 | export const addPostApi = (data: AddPostCommand) => { 57 | return http.request>("post", "/system/post", { 58 | data 59 | }); 60 | }; 61 | 62 | export interface UpdatePostCommand extends AddPostCommand { 63 | postId: number; 64 | } 65 | 66 | export const updatePostApi = (data: UpdatePostCommand) => { 67 | return http.request>("put", "/system/post", { 68 | data 69 | }); 70 | }; 71 | -------------------------------------------------------------------------------- /src/api/system/role.ts: -------------------------------------------------------------------------------- 1 | import { http } from "@/utils/http"; 2 | 3 | export interface RoleQuery extends BasePageQuery { 4 | roleKey?: string; 5 | roleName?: string; 6 | status?: string; 7 | timeRangeColumn?: string; 8 | } 9 | 10 | export interface RoleDTO { 11 | createTime: Date; 12 | dataScope: number; 13 | remark: string; 14 | roleId: number; 15 | roleKey: string; 16 | roleName: string; 17 | roleSort: number; 18 | selectedDeptList: number[]; 19 | selectedMenuList: number[]; 20 | status: number; 21 | } 22 | 23 | export function getRoleListApi(params: RoleQuery) { 24 | return http.request>>( 25 | "get", 26 | "/system/role/list", 27 | { 28 | params 29 | } 30 | ); 31 | } 32 | 33 | export function getRoleInfoApi(roleId: number) { 34 | return http.request>("get", "/system/role/" + roleId); 35 | } 36 | 37 | export interface AddRoleCommand { 38 | dataScope?: string; 39 | menuIds: number[]; 40 | remark?: string; 41 | roleKey: string; 42 | roleName: string; 43 | roleSort: number; 44 | status?: string; 45 | } 46 | 47 | export function addRoleApi(data: AddRoleCommand) { 48 | return http.request("post", "/system/role", { 49 | data 50 | }); 51 | } 52 | 53 | export interface UpdateRoleCommand extends AddRoleCommand { 54 | roleId: number; 55 | } 56 | 57 | export function updateRoleApi(data: UpdateRoleCommand) { 58 | return http.request("put", "/system/role", { 59 | data 60 | }); 61 | } 62 | 63 | export function deleteRoleApi(roleId: number) { 64 | return http.request("delete", "/system/role/" + roleId); 65 | } 66 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "iconfont"; /* Project id 2208059 */ 3 | src: url("iconfont.woff2?t=1671895108120") format("woff2"), 4 | url("iconfont.woff?t=1671895108120") format("woff"), 5 | url("iconfont.ttf?t=1671895108120") format("truetype"); 6 | } 7 | 8 | .iconfont { 9 | font-family: "iconfont" !important; 10 | font-size: 16px; 11 | font-style: normal; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | .pure-iconfont-tabs:before { 17 | content: "\e63e"; 18 | } 19 | 20 | .pure-iconfont-logo:before { 21 | content: "\e620"; 22 | } 23 | 24 | .pure-iconfont-new:before { 25 | content: "\e615"; 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2208059", 3 | "name": "pure-admin", 4 | "font_family": "iconfont", 5 | "css_prefix_text": "pure-iconfont-", 6 | "description": "pure-admin-iconfont", 7 | "glyphs": [ 8 | { 9 | "icon_id": "20594647", 10 | "name": "Tabs", 11 | "font_class": "tabs", 12 | "unicode": "e63e", 13 | "unicode_decimal": 58942 14 | }, 15 | { 16 | "icon_id": "22129506", 17 | "name": "PureLogo", 18 | "font_class": "logo", 19 | "unicode": "e620", 20 | "unicode_decimal": 58912 21 | }, 22 | { 23 | "icon_id": "7795615", 24 | "name": "New", 25 | "font_class": "new", 26 | "unicode": "e615", 27 | "unicode_decimal": 58901 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/src/assets/iconfont/iconfont.woff -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/src/assets/iconfont/iconfont.woff2 -------------------------------------------------------------------------------- /src/assets/login/avatar.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/login/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/src/assets/login/bg.png -------------------------------------------------------------------------------- /src/assets/svg/FullScreenMaximize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/FullScreenMinimize.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/back_top.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/day.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/enter_outlined.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/exit_screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/full_screen.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/svg/keyboard_esc.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valarchie/AgileBoot-Front-End/7a9a30193550835ca0fd95495c7dd62e00309183/src/assets/user.jpg -------------------------------------------------------------------------------- /src/components/ReAuth/index.ts: -------------------------------------------------------------------------------- 1 | import auth from "./src/auth"; 2 | 3 | const Auth = auth; 4 | 5 | export { Auth }; 6 | -------------------------------------------------------------------------------- /src/components/ReAuth/src/auth.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, Fragment } from "vue"; 2 | import { hasAuth } from "@/router/utils"; 3 | 4 | export default defineComponent({ 5 | name: "Auth", 6 | props: { 7 | value: { 8 | type: undefined, 9 | default: [] 10 | } 11 | }, 12 | setup(props, { slots }) { 13 | return () => { 14 | if (!slots) return null; 15 | return hasAuth(props.value) ? ( 16 | {slots.default?.()} 17 | ) : null; 18 | }; 19 | } 20 | }); 21 | -------------------------------------------------------------------------------- /src/components/ReCol/index.ts: -------------------------------------------------------------------------------- 1 | import { ElCol } from "element-plus"; 2 | import { h, defineComponent } from "vue"; 3 | 4 | // 封装element-plus的el-col组件 5 | export default defineComponent({ 6 | name: "ReCol", 7 | props: { 8 | value: { 9 | type: Number, 10 | default: 24 11 | } 12 | }, 13 | render() { 14 | const attrs = this.$attrs; 15 | const val = this.value; 16 | return h( 17 | ElCol, 18 | { 19 | xs: val, 20 | sm: val, 21 | md: val, 22 | lg: val, 23 | xl: val, 24 | ...attrs 25 | }, 26 | { default: () => this.$slots.default() } 27 | ); 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/components/ReCropper/index.ts: -------------------------------------------------------------------------------- 1 | import reCropper from "./src"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 图片裁剪组件 */ 5 | export const ReCropper = withInstall(reCropper); 6 | 7 | export default ReCropper; 8 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/circled.css: -------------------------------------------------------------------------------- 1 | @import "cropperjs/dist/cropper.css"; 2 | @import "tippy.js/dist/tippy.css"; 3 | @import "tippy.js/themes/light.css"; 4 | @import "tippy.js/animations/perspective.css"; 5 | 6 | .re-circled { 7 | .cropper-view-box, 8 | .cropper-face { 9 | border-radius: 50%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-h.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/arrow-v.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/change.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/download.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/index.ts: -------------------------------------------------------------------------------- 1 | import Reload from "./reload.svg?component"; 2 | import Upload from "./upload.svg?component"; 3 | import ArrowH from "./arrow-h.svg?component"; 4 | import ArrowV from "./arrow-v.svg?component"; 5 | import ArrowUp from "./arrow-up.svg?component"; 6 | import ChangeIcon from "./change.svg?component"; 7 | import ArrowDown from "./arrow-down.svg?component"; 8 | import ArrowLeft from "./arrow-left.svg?component"; 9 | import DownloadIcon from "./download.svg?component"; 10 | import ArrowRight from "./arrow-right.svg?component"; 11 | import RotateLeft from "./rotate-left.svg?component"; 12 | import SearchPlus from "./search-plus.svg?component"; 13 | import RotateRight from "./rotate-right.svg?component"; 14 | import SearchMinus from "./search-minus.svg?component"; 15 | 16 | export { 17 | Reload, 18 | Upload, 19 | ArrowH, 20 | ArrowV, 21 | ArrowUp, 22 | ArrowDown, 23 | ArrowLeft, 24 | ChangeIcon, 25 | ArrowRight, 26 | RotateLeft, 27 | SearchPlus, 28 | RotateRight, 29 | SearchMinus, 30 | DownloadIcon 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/reload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/rotate-left.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/rotate-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/search-minus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/search-plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReCropper/src/svg/upload.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReDialog/index.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | import reDialog from "./index.vue"; 3 | import { useTimeoutFn } from "@vueuse/core"; 4 | import { withInstall } from "@pureadmin/utils"; 5 | import type { 6 | EventType, 7 | ArgsType, 8 | DialogProps, 9 | ButtonProps, 10 | DialogOptions 11 | } from "./type"; 12 | 13 | const dialogStore = ref>([]); 14 | 15 | /** 打开弹框 */ 16 | const addDialog = (options: DialogOptions) => { 17 | const open = () => 18 | dialogStore.value.push(Object.assign(options, { visible: true })); 19 | if (options?.openDelay) { 20 | useTimeoutFn(() => { 21 | open(); 22 | }, options.openDelay); 23 | } else { 24 | open(); 25 | } 26 | }; 27 | 28 | /** 关闭弹框 */ 29 | const closeDialog = (options: DialogOptions, index: number, args?: any) => { 30 | dialogStore.value.splice(index, 1); 31 | options.closeCallBack && options.closeCallBack({ options, index, args }); 32 | }; 33 | 34 | /** 35 | * @description 更改弹框自身属性值 36 | * @param value 属性值 37 | * @param key 属性,默认`title` 38 | * @param index 弹框索引(默认`0`,代表只有一个弹框,对于嵌套弹框要改哪个弹框的属性值就把该弹框索引赋给`index`) 39 | */ 40 | const updateDialog = (value: any, key = "title", index = 0) => { 41 | dialogStore.value[index][key] = value; 42 | }; 43 | 44 | /** 关闭所有弹框 */ 45 | const closeAllDialog = () => { 46 | dialogStore.value = []; 47 | }; 48 | 49 | /** 千万别忘了在下面这三处引入并注册下,放心注册,不使用`addDialog`调用就不会被挂载 50 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L4 51 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L13 52 | * https://github.com/pure-admin/vue-pure-admin/blob/main/src/App.vue#L18 53 | */ 54 | const ReDialog = withInstall(reDialog); 55 | 56 | export type { EventType, ArgsType, DialogProps, ButtonProps, DialogOptions }; 57 | export { 58 | ReDialog, 59 | dialogStore, 60 | addDialog, 61 | closeDialog, 62 | updateDialog, 63 | closeAllDialog 64 | }; 65 | -------------------------------------------------------------------------------- /src/components/ReIcon/index.ts: -------------------------------------------------------------------------------- 1 | import iconifyIconOffline from "./src/iconifyIconOffline"; 2 | import iconifyIconOnline from "./src/iconifyIconOnline"; 3 | import fontIcon from "./src/iconfont"; 4 | 5 | /** 本地图标组件 */ 6 | const IconifyIconOffline = iconifyIconOffline; 7 | /** 在线图标组件 */ 8 | const IconifyIconOnline = iconifyIconOnline; 9 | /** iconfont组件 */ 10 | const FontIcon = fontIcon; 11 | 12 | export { IconifyIconOffline, IconifyIconOnline, FontIcon }; 13 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { iconType } from "./types"; 2 | import { h, defineComponent, Component } from "vue"; 3 | import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index"; 4 | 5 | /** 6 | * 支持 `iconfont`、自定义 `svg` 以及 `iconify` 中所有的图标 7 | * @see 点击查看文档图标篇 {@link https://yiming_chang.gitee.io/pure-admin-doc/pages/icon/} 8 | * @param icon 必传 图标 9 | * @param attrs 可选 iconType 属性 10 | * @returns Component 11 | */ 12 | export function useRenderIcon(icon: any, attrs?: iconType): Component { 13 | // iconfont 14 | const ifReg = /^IF-/; 15 | // typeof icon === "function" 属于SVG 16 | if (ifReg.test(icon)) { 17 | // iconfont 18 | const name = icon.split(ifReg)[1]; 19 | const iconName = name.slice( 20 | 0, 21 | name.indexOf(" ") == -1 ? name.length : name.indexOf(" ") 22 | ); 23 | const iconType = name.slice(name.indexOf(" ") + 1, name.length); 24 | return defineComponent({ 25 | name: "FontIcon", 26 | render() { 27 | return h(FontIcon, { 28 | icon: iconName, 29 | iconType, 30 | ...attrs 31 | }); 32 | } 33 | }); 34 | } else if (typeof icon === "function" || typeof icon?.render === "function") { 35 | // svg 36 | return icon; 37 | } else if (typeof icon === "object") { 38 | return defineComponent({ 39 | name: "OfflineIcon", 40 | render() { 41 | return h(IconifyIconOffline, { 42 | icon: icon, 43 | ...attrs 44 | }); 45 | } 46 | }); 47 | } else { 48 | // 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之 49 | return defineComponent({ 50 | name: "Icon", 51 | render() { 52 | const IconifyIcon = 53 | icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline; 54 | return h(IconifyIcon, { 55 | icon: icon, 56 | ...attrs 57 | }); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconfont.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | 3 | // 封装iconfont组件,默认`font-class`引用模式,支持`unicode`引用、`font-class`引用、`symbol`引用 (https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.20&helptype=code) 4 | export default defineComponent({ 5 | name: "FontIcon", 6 | props: { 7 | icon: { 8 | type: String, 9 | default: "" 10 | } 11 | }, 12 | render() { 13 | const attrs = this.$attrs; 14 | if (Object.keys(attrs).includes("uni") || attrs?.iconType === "uni") { 15 | return h( 16 | "i", 17 | { 18 | class: "iconfont", 19 | ...attrs 20 | }, 21 | this.icon 22 | ); 23 | } else if ( 24 | Object.keys(attrs).includes("svg") || 25 | attrs?.iconType === "svg" 26 | ) { 27 | return h( 28 | "svg", 29 | { 30 | class: "icon-svg", 31 | "aria-hidden": true 32 | }, 33 | { 34 | default: () => [ 35 | h("use", { 36 | "xlink:href": `#${this.icon}` 37 | }) 38 | ] 39 | } 40 | ); 41 | } else { 42 | return h("i", { 43 | class: `iconfont ${this.icon}`, 44 | ...attrs 45 | }); 46 | } 47 | } 48 | }); 49 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconifyIconOffline.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | import { Icon as IconifyIcon, addIcon } from "@iconify/vue/dist/offline"; 3 | 4 | // Iconify Icon在Vue里本地使用(用于内网环境)https://docs.iconify.design/icon-components/vue/offline.html 5 | export default defineComponent({ 6 | name: "IconifyIconOffline", 7 | components: { IconifyIcon }, 8 | props: { 9 | icon: { 10 | default: null 11 | } 12 | }, 13 | render() { 14 | if (typeof this.icon === "object") addIcon(this.icon, this.icon); 15 | const attrs = this.$attrs; 16 | return h( 17 | IconifyIcon, 18 | { 19 | icon: this.icon, 20 | style: attrs?.style 21 | ? Object.assign(attrs.style, { outline: "none" }) 22 | : { outline: "none" }, 23 | ...attrs 24 | }, 25 | { 26 | default: () => [] 27 | } 28 | ); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/iconifyIconOnline.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | import { Icon as IconifyIcon } from "@iconify/vue"; 3 | 4 | // Iconify Icon在Vue里在线使用(用于外网环境) 5 | export default defineComponent({ 6 | name: "IconifyIconOnline", 7 | components: { IconifyIcon }, 8 | props: { 9 | icon: { 10 | type: String, 11 | default: "" 12 | } 13 | }, 14 | render() { 15 | const attrs = this.$attrs; 16 | return h( 17 | IconifyIcon, 18 | { 19 | icon: `${this.icon}`, 20 | style: attrs?.style 21 | ? Object.assign(attrs.style, { outline: "none" }) 22 | : { outline: "none" }, 23 | ...attrs 24 | }, 25 | { 26 | default: () => [] 27 | } 28 | ); 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/offlineIcon.ts: -------------------------------------------------------------------------------- 1 | import { addIcon } from "@iconify/vue/dist/offline"; 2 | 3 | /** 4 | * 这里存放本地图标,在 src/layout/index.vue 文件中加载,避免在首启动加载 5 | */ 6 | 7 | // 本地菜单图标,后端在路由的icon中返回对应的图标字符串并且前端在此处使用addIcon添加即可渲染菜单图标 8 | import UbuntuFill from "@iconify-icons/ri/ubuntu-fill"; 9 | import Menu from "@iconify-icons/ep/menu"; 10 | import Edit from "@iconify-icons/ep/edit"; 11 | import InformationLine from "@iconify-icons/ri/information-line"; 12 | import SetUp from "@iconify-icons/ep/set-up"; 13 | import TerminalWindowLine from "@iconify-icons/ri/terminal-window-line"; 14 | import Guide from "@iconify-icons/ep/guide"; 15 | import HomeFilled from "@iconify-icons/ep/home-filled"; 16 | import Card from "@iconify-icons/ri/bank-card-line"; 17 | import ListCheck from "@iconify-icons/ri/list-check"; 18 | import Histogram from "@iconify-icons/ep/histogram"; 19 | import Ppt from "@iconify-icons/ri/file-ppt-2-line"; 20 | import CheckboxCircleLine from "@iconify-icons/ri/checkbox-circle-line"; 21 | import FlUser from "@iconify-icons/ri/admin-line"; 22 | import Role from "@iconify-icons/ri/admin-fill"; 23 | import Setting from "@iconify-icons/ri/settings-3-line"; 24 | import Dept from "@iconify-icons/ri/git-branch-line"; 25 | import Lollipop from "@iconify-icons/ep/lollipop"; 26 | import Monitor from "@iconify-icons/ep/monitor"; 27 | addIcon("ubuntuFill", UbuntuFill); 28 | addIcon("menu", Menu); 29 | addIcon("edit", Edit); 30 | addIcon("informationLine", InformationLine); 31 | addIcon("setUp", SetUp); 32 | addIcon("terminalWindowLine", TerminalWindowLine); 33 | addIcon("guide", Guide); 34 | addIcon("homeFilled", HomeFilled); 35 | addIcon("card", Card); 36 | addIcon("listCheck", ListCheck); 37 | addIcon("histogram", Histogram); 38 | addIcon("ppt", Ppt); 39 | addIcon("checkboxCircleLine", CheckboxCircleLine); 40 | addIcon("flUser", FlUser); 41 | addIcon("role", Role); 42 | addIcon("setting", Setting); 43 | addIcon("dept", Dept); 44 | addIcon("lollipop", Lollipop); 45 | addIcon("monitor", Monitor); 46 | -------------------------------------------------------------------------------- /src/components/ReIcon/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface iconType { 2 | // iconify (https://docs.iconify.design/icon-components/vue/#properties) 3 | inline?: boolean; 4 | width?: string | number; 5 | height?: string | number; 6 | horizontalFlip?: boolean; 7 | verticalFlip?: boolean; 8 | flip?: string; 9 | rotate?: number | string; 10 | color?: string; 11 | horizontalAlign?: boolean; 12 | verticalAlign?: boolean; 13 | align?: string; 14 | onLoad?: Function; 15 | includes?: Function; 16 | 17 | // all icon 18 | style?: object; 19 | } 20 | -------------------------------------------------------------------------------- /src/components/ReImageVerify/index.ts: -------------------------------------------------------------------------------- 1 | import reImageVerify from "./src/index.vue"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 图形验证码组件 */ 5 | export const ReImageVerify = withInstall(reImageVerify); 6 | 7 | export default ReImageVerify; 8 | -------------------------------------------------------------------------------- /src/components/ReImageVerify/src/hooks.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from "vue"; 2 | 3 | /** 4 | * 绘制图形验证码 5 | * @param width - 图形宽度 6 | * @param height - 图形高度 7 | */ 8 | export const useImageVerify = (width = 120, height = 40) => { 9 | const domRef = ref(); 10 | const imgCode = ref(""); 11 | 12 | function setImgCode(code: string) { 13 | imgCode.value = code; 14 | } 15 | 16 | function getImgCode() { 17 | if (!domRef.value) return; 18 | imgCode.value = draw(domRef.value, width, height); 19 | } 20 | 21 | onMounted(() => { 22 | getImgCode(); 23 | }); 24 | 25 | return { 26 | domRef, 27 | imgCode, 28 | setImgCode, 29 | getImgCode 30 | }; 31 | }; 32 | 33 | /** 前端自己生成验证码 */ 34 | function randomNum(min: number, max: number) { 35 | const num = Math.floor(Math.random() * (max - min) + min); 36 | return num; 37 | } 38 | 39 | function randomColor(min: number, max: number) { 40 | const r = randomNum(min, max); 41 | const g = randomNum(min, max); 42 | const b = randomNum(min, max); 43 | return `rgb(${r},${g},${b})`; 44 | } 45 | 46 | function draw(dom: HTMLCanvasElement, width: number, height: number) { 47 | let imgCode = ""; 48 | 49 | const NUMBER_STRING = "0123456789"; 50 | 51 | const ctx = dom.getContext("2d"); 52 | if (!ctx) return imgCode; 53 | 54 | ctx.fillStyle = randomColor(180, 230); 55 | ctx.fillRect(0, 0, width, height); 56 | for (let i = 0; i < 4; i += 1) { 57 | const text = NUMBER_STRING[randomNum(0, NUMBER_STRING.length)]; 58 | imgCode += text; 59 | const fontSize = randomNum(18, 41); 60 | const deg = randomNum(-30, 30); 61 | ctx.font = `${fontSize}px Simhei`; 62 | ctx.textBaseline = "top"; 63 | ctx.fillStyle = randomColor(80, 150); 64 | ctx.save(); 65 | ctx.translate(30 * i + 15, 15); 66 | ctx.rotate((deg * Math.PI) / 180); 67 | ctx.fillText(text, -15 + 5, -15); 68 | ctx.restore(); 69 | } 70 | for (let i = 0; i < 5; i += 1) { 71 | ctx.beginPath(); 72 | ctx.moveTo(randomNum(0, width), randomNum(0, height)); 73 | ctx.lineTo(randomNum(0, width), randomNum(0, height)); 74 | ctx.strokeStyle = randomColor(180, 230); 75 | ctx.closePath(); 76 | ctx.stroke(); 77 | } 78 | for (let i = 0; i < 41; i += 1) { 79 | ctx.beginPath(); 80 | ctx.arc(randomNum(0, width), randomNum(0, height), 1, 0, 2 * Math.PI); 81 | ctx.closePath(); 82 | ctx.fillStyle = randomColor(150, 200); 83 | ctx.fill(); 84 | } 85 | return imgCode; 86 | } 87 | -------------------------------------------------------------------------------- /src/components/ReImageVerify/src/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 47 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/index.ts: -------------------------------------------------------------------------------- 1 | import pureTableBar from "./src/bar"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 配合 `@pureadmin/table` 实现快速便捷的表格操作 https://github.com/pure-admin/pure-admin-table */ 5 | export const PureTableBar = withInstall(pureTableBar); 6 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/src/svg/collapse.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/src/svg/drag.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/src/svg/expand.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/src/svg/refresh.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/RePureTableBar/src/svg/settings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ReQrcode/index.ts: -------------------------------------------------------------------------------- 1 | import reQrcode from "./src/index"; 2 | import { withInstall } from "@pureadmin/utils"; 3 | 4 | /** 二维码组件 */ 5 | export const ReQrcode = withInstall(reQrcode); 6 | 7 | export default ReQrcode; 8 | -------------------------------------------------------------------------------- /src/components/ReQrcode/src/index.scss: -------------------------------------------------------------------------------- 1 | .qrcode { 2 | &--disabled { 3 | background: rgb(255 255 255 / 95%); 4 | 5 | & > div { 6 | transform: translate(-50%, -50%); 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/ReTypeit/index.ts: -------------------------------------------------------------------------------- 1 | import { h, defineComponent } from "vue"; 2 | import TypeIt from "typeit"; 3 | 4 | // 打字机效果组件(只是简单的封装下,更多配置项参考 https://www.typeitjs.com/docs/vanilla/usage#options) 5 | export default defineComponent({ 6 | name: "TypeIt", 7 | props: { 8 | /** 打字速度,以每一步之间的毫秒数为单位,默认`200` */ 9 | speed: { 10 | type: Number, 11 | default: 200 12 | }, 13 | values: { 14 | type: Array, 15 | defalut: [] 16 | }, 17 | className: { 18 | type: String, 19 | default: "type-it" 20 | }, 21 | cursor: { 22 | type: Boolean, 23 | default: true 24 | } 25 | }, 26 | render() { 27 | return h( 28 | "span", 29 | { 30 | class: this.className 31 | }, 32 | { 33 | default: () => [] 34 | } 35 | ); 36 | }, 37 | mounted() { 38 | new TypeIt(`.${this.className}`, { 39 | strings: this.values, 40 | speed: this.speed, 41 | cursor: this.cursor 42 | }).go(); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/components/VDialog/dialog.css: -------------------------------------------------------------------------------- 1 | html.dark .v-dialog { 2 | --header-bg-color: #171d1e; 3 | --footer-bg-color: #171d1e; 4 | } 5 | 6 | .v-dialog { 7 | --header-bg-color: #f5f7fa; 8 | --footer-bg-color: #f5f7fa; 9 | } 10 | 11 | .v-dialog.el-dialog.hidden-footer .el-dialog__footer { 12 | padding: 0; 13 | border: none; 14 | } 15 | 16 | .el-dialog__footer { 17 | padding: 10px; 18 | /*border-top: 1px solid var(--el-border-color);*/ 19 | /*border-bottom: 1px solid var(--el-border-color);*/ 20 | box-sizing: border-box; 21 | background-color: var(--header-bg-color); 22 | position: relative; /* 防止被表单覆盖底部 */ 23 | z-index: calc(var(--el-index-normal) + 1); 24 | } 25 | 26 | .v-dialog.el-dialog { 27 | box-sizing: border-box; 28 | margin: 15dvh auto; 29 | } 30 | 31 | .v-dialog.el-dialog.is-fullscreen { 32 | box-sizing: border-box; 33 | margin: auto; 34 | } 35 | 36 | .v-dialog.el-dialog .el-dialog__header { 37 | padding: 10px 16px; 38 | /*border-bottom: 1px solid var(--el-border-color);*/ 39 | margin-right: 0; 40 | box-sizing: border-box; 41 | background-color: var(--header-bg-color); 42 | } 43 | 44 | .v-dialog.el-dialog--center .el-dialog__body, 45 | .el-dialog__body { 46 | padding: 16px 20px; 47 | box-sizing: border-box; 48 | } 49 | 50 | .v-dialog.el-dialog.is-fullscreen .el-dialog__body { 51 | height: calc(100dvh - 44px - 52px); 52 | max-height: calc(100dvh - 44px - 52px); 53 | } 54 | 55 | .v-dialog.el-dialog .el-dialog__body { 56 | height: calc(70dvh - 44px - 52px); 57 | } 58 | 59 | .v-dialog.el-dialog.flex-body:not(.is-fullscreen) .el-dialog__body { 60 | height: initial; 61 | max-height: calc(70dvh - 44px - 52px); 62 | } 63 | -------------------------------------------------------------------------------- /src/components/VDialog/dialog.ts: -------------------------------------------------------------------------------- 1 | export interface DialogProps { 2 | /** 3 | * 标题 4 | */ 5 | title: string; 6 | /** 7 | * 显隐 8 | */ 9 | modelValue: boolean; 10 | 11 | /** 12 | * 初始化全屏状态 13 | */ 14 | initFullScreen?: boolean | undefined; 15 | 16 | /** 17 | * 展示全屏按钮 18 | */ 19 | showFullScreen?: boolean; 20 | 21 | /** 22 | * 全屏 23 | */ 24 | fullScreen?: boolean | undefined; 25 | confirmText?: string; 26 | cancelText?: string; 27 | loading?: boolean; 28 | /** 29 | * 使用el-scrollbar包裹对话框body 30 | */ 31 | useBodyScrolling?: boolean; 32 | /** 33 | * 固定对话框body高度 34 | */ 35 | fixedBodyHeight?: boolean; 36 | 37 | draggable?: boolean; 38 | 39 | hiddenFooter?: boolean; 40 | } 41 | 42 | export type DialogEmits = { 43 | "update:modelValue": [val: boolean]; 44 | "update:fullScreen": [val: boolean]; 45 | confirm: []; 46 | cancel: []; 47 | }; 48 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from "vue"; 2 | import axios from "axios"; 3 | import { configConver } from "@/utils/rootConver"; 4 | 5 | let config: object = {}; 6 | 7 | const setConfig = (cfg?: unknown) => { 8 | config = Object.assign(config, cfg); 9 | }; 10 | 11 | const getConfig = (key?: string): ServerConfigs => { 12 | if (typeof key === "string") { 13 | const arr = key.split("."); 14 | if (arr && arr.length) { 15 | let data = config; 16 | arr.forEach(v => { 17 | if (data && typeof data[v] !== "undefined") { 18 | data = data[v]; 19 | } else { 20 | data = null; 21 | } 22 | }); 23 | return data; 24 | } 25 | } 26 | return config; 27 | }; 28 | 29 | /** 获取项目动态全局配置 */ 30 | export const getServerConfig = async (app: App): Promise => { 31 | app.config.globalProperties.$config = getConfig(); 32 | return axios({ 33 | method: "get", 34 | url: `${configConver()}serverConfig.json` 35 | }) 36 | .then(({ data: config }) => { 37 | let $config = app.config.globalProperties.$config; 38 | // 自动注入项目配置 39 | if (app && $config && typeof config === "object") { 40 | $config = Object.assign($config, config); 41 | app.config.globalProperties.$config = $config; 42 | // 设置全局配置 43 | setConfig($config); 44 | } 45 | return $config; 46 | }) 47 | .catch(() => { 48 | throw "请在public文件夹下添加serverConfig.json配置文件"; 49 | }); 50 | }; 51 | 52 | /** 本地响应式存储的命名空间 */ 53 | const responsiveStorageNameSpace = () => getConfig().ResponsiveStorageNameSpace; 54 | 55 | export { getConfig, setConfig, responsiveStorageNameSpace }; 56 | -------------------------------------------------------------------------------- /src/directives/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { hasAuth } from "@/router/utils"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | 4 | export const auth: Directive = { 5 | mounted(el: HTMLElement, binding: DirectiveBinding) { 6 | const { value } = binding; 7 | if (value) { 8 | !hasAuth(value) && el.parentNode?.removeChild(el); 9 | } else { 10 | throw new Error( 11 | "[Directive: auth]: need auths! Like v-auth=\"['btn.add','btn.edit']\"" 12 | ); 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/directives/copy/index.ts: -------------------------------------------------------------------------------- 1 | import { message } from "@/utils/message"; 2 | import { useEventListener } from "@vueuse/core"; 3 | import { copyTextToClipboard } from "@pureadmin/utils"; 4 | import type { Directive, DirectiveBinding } from "vue"; 5 | 6 | interface CopyEl extends HTMLElement { 7 | copyValue: string; 8 | } 9 | 10 | /** 文本复制指令(默认双击复制) */ 11 | export const copy: Directive = { 12 | mounted(el: CopyEl, binding: DirectiveBinding) { 13 | const { value } = binding; 14 | if (value) { 15 | el.copyValue = value; 16 | const arg = binding.arg ?? "dblclick"; 17 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 18 | useEventListener(el, arg, () => { 19 | const success = copyTextToClipboard(el.copyValue); 20 | success 21 | ? message("复制成功", { type: "success" }) 22 | : message("复制失败", { type: "error" }); 23 | }); 24 | } else { 25 | throw new Error( 26 | '[Directive: copy]: need value! Like v-copy="modelValue"' 27 | ); 28 | } 29 | }, 30 | updated(el: CopyEl, binding: DirectiveBinding) { 31 | el.copyValue = binding.value; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth"; 2 | export * from "./copy"; 3 | export * from "./longpress"; 4 | export * from "./optimize"; 5 | -------------------------------------------------------------------------------- /src/directives/longpress/index.ts: -------------------------------------------------------------------------------- 1 | import { useEventListener } from "@vueuse/core"; 2 | import type { Directive, DirectiveBinding } from "vue"; 3 | import { subBefore, subAfter, isFunction } from "@pureadmin/utils"; 4 | 5 | export const longpress: Directive = { 6 | mounted(el: HTMLElement, binding: DirectiveBinding) { 7 | const cb = binding.value; 8 | if (cb && isFunction(cb)) { 9 | let timer = null; 10 | let interTimer = null; 11 | let num = 500; 12 | let interNum = null; 13 | const isInter = binding?.arg?.includes(":") ?? false; 14 | 15 | if (isInter) { 16 | num = Number(subBefore(binding.arg, ":")); 17 | interNum = Number(subAfter(binding.arg, ":")); 18 | } else if (binding.arg) { 19 | num = Number(binding.arg); 20 | } 21 | 22 | const clear = () => { 23 | if (timer) { 24 | clearTimeout(timer); 25 | timer = null; 26 | } 27 | if (interTimer) { 28 | clearInterval(interTimer); 29 | interTimer = null; 30 | } 31 | }; 32 | 33 | const onDownInter = (ev: PointerEvent) => { 34 | ev.preventDefault(); 35 | if (interTimer === null) { 36 | interTimer = setInterval(() => cb(), interNum); 37 | } 38 | }; 39 | 40 | const onDown = (ev: PointerEvent) => { 41 | clear(); 42 | ev.preventDefault(); 43 | if (timer === null) { 44 | timer = isInter 45 | ? setTimeout(() => { 46 | cb(); 47 | onDownInter(ev); 48 | }, num) 49 | : setTimeout(() => cb(), num); 50 | } 51 | }; 52 | 53 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 54 | useEventListener(el, "pointerdown", onDown); 55 | useEventListener(el, "pointerup", clear); 56 | useEventListener(el, "pointerleave", clear); 57 | } else { 58 | throw new Error( 59 | '[Directive: longpress]: need callback and callback must be a function! Like v-longpress="callback"' 60 | ); 61 | } 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /src/directives/optimize/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isFunction, 3 | isObject, 4 | isArray, 5 | debounce, 6 | throttle 7 | } from "@pureadmin/utils"; 8 | import { useEventListener } from "@vueuse/core"; 9 | import type { Directive, DirectiveBinding } from "vue"; 10 | 11 | /** 防抖(v-optimize或v-optimize:debounce)、节流(v-optimize:throttle)指令 */ 12 | export const optimize: Directive = { 13 | mounted(el: HTMLElement, binding: DirectiveBinding) { 14 | const { value } = binding; 15 | const optimizeType = binding.arg ?? "debounce"; 16 | const type = ["debounce", "throttle"].find(t => t === optimizeType); 17 | if (type) { 18 | if (value && value.event && isFunction(value.fn)) { 19 | let params = value?.params; 20 | if (params) { 21 | if (isArray(params) || isObject(params)) { 22 | params = isObject(params) ? Array.of(params) : params; 23 | } else { 24 | throw new Error( 25 | "[Directive: optimize]: `params` must be an array or object" 26 | ); 27 | } 28 | } 29 | // Register using addEventListener on mounted, and removeEventListener automatically on unmounted 30 | useEventListener( 31 | el, 32 | value.event, 33 | type === "debounce" 34 | ? debounce( 35 | params ? () => value.fn(...params) : value.fn, 36 | value?.timeout ?? 200, 37 | value?.immediate ?? false 38 | ) 39 | : throttle( 40 | params ? () => value.fn(...params) : value.fn, 41 | value?.timeout ?? 1000 42 | ) 43 | ); 44 | } else { 45 | throw new Error( 46 | "[Directive: optimize]: `event` and `fn` are required, and `fn` must be a function" 47 | ); 48 | } 49 | } else { 50 | throw new Error( 51 | "[Directive: optimize]: only `debounce` and `throttle` are supported" 52 | ); 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /src/layout/components/notice/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 55 | 56 | 89 | -------------------------------------------------------------------------------- /src/layout/components/notice/noticeList.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | -------------------------------------------------------------------------------- /src/layout/components/search/components/SearchFooter.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 38 | 39 | 63 | -------------------------------------------------------------------------------- /src/layout/components/search/components/index.ts: -------------------------------------------------------------------------------- 1 | import SearchModal from "./SearchModal.vue"; 2 | 3 | export { SearchModal }; 4 | -------------------------------------------------------------------------------- /src/layout/components/search/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 21 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/breadCrumb.vue: -------------------------------------------------------------------------------- 1 | 89 | 90 | 105 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/extraIcon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 21 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/horizontal.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 90 | 91 | 106 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/leftCollapse.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 61 | 62 | 72 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/logo.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 38 | 39 | 72 | -------------------------------------------------------------------------------- /src/layout/components/sidebar/topCollapse.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 34 | -------------------------------------------------------------------------------- /src/layout/frameView.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 |