├── .editorconfig ├── .env ├── .env.development ├── .env.production ├── .env.test ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierignore ├── .prettierrc.cjs ├── .stylelintignore ├── .stylelintrc.cjs ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build ├── getEnv.ts ├── plugins.ts └── proxy.ts ├── commitlint.config.cjs ├── index.html ├── lint-staged.config.cjs ├── package.json ├── pnpm-lock.yaml ├── postcss.config.cjs ├── public ├── logo.png └── vue.svg ├── src ├── App.vue ├── api │ ├── config │ │ └── servicePort.ts │ ├── helper │ │ ├── axiosCancel.ts │ │ └── checkStatus.ts │ ├── index.ts │ ├── interface │ │ └── index.ts │ └── modules │ │ ├── login.ts │ │ ├── upload.ts │ │ └── user.ts ├── assets │ ├── fonts │ │ ├── DIN.otf │ │ ├── MetroDF.ttf │ │ ├── YouSheBiaoTiHei.ttf │ │ └── font.scss │ ├── iconfont │ │ ├── iconfont.scss │ │ └── iconfont.ttf │ ├── icons │ │ ├── xianxingdaoyu.svg │ │ ├── xianxingdiqiu.svg │ │ ├── xianxingditu.svg │ │ ├── xianxingfanchuan.svg │ │ ├── xianxingfeiji.svg │ │ ├── xianxinglvhangriji.svg │ │ ├── xianxingtianqiyubao.svg │ │ ├── xianxingxiangjipaizhao.svg │ │ ├── xianxingxiarilengyin.svg │ │ ├── xianxingyoulun.svg │ │ └── xianxingzijiayou.svg │ ├── images │ │ ├── 403.png │ │ ├── 404.png │ │ ├── 500.png │ │ ├── avatar.gif │ │ ├── login_bg.svg │ │ ├── login_left.png │ │ ├── login_left1.png │ │ ├── login_left2.png │ │ ├── login_left3.png │ │ ├── login_left4.png │ │ ├── login_left5.png │ │ ├── logo.svg │ │ ├── msg01.png │ │ ├── msg02.png │ │ ├── msg03.png │ │ ├── msg04.png │ │ ├── msg05.png │ │ ├── notData.png │ │ └── welcome.png │ ├── json │ │ ├── authButtonList.json │ │ └── authMenuList.json │ └── mock │ │ └── Easy-Mock-API.zip ├── components │ ├── ECharts │ │ ├── config │ │ │ └── index.ts │ │ └── index.vue │ ├── ErrorMessage │ │ ├── 403.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ └── index.scss │ ├── Grid │ │ ├── components │ │ │ └── GridItem.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── ImportExcel │ │ ├── index.scss │ │ └── index.vue │ ├── Loading │ │ ├── fullScreen.ts │ │ ├── index.scss │ │ └── index.vue │ ├── ProTable │ │ ├── components │ │ │ ├── ColSetting.vue │ │ │ ├── Pagination.vue │ │ │ └── TableColumn.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── SearchForm │ │ ├── components │ │ │ └── SearchFormItem.vue │ │ └── index.vue │ ├── SelectFilter │ │ ├── index.scss │ │ └── index.vue │ ├── SelectIcon │ │ ├── index.scss │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── SwitchDark │ │ └── index.vue │ ├── TreeFilter │ │ ├── index.scss │ │ └── index.vue │ ├── Upload │ │ ├── Img.vue │ │ └── Imgs.vue │ └── WangEditor │ │ ├── index.scss │ │ └── index.vue ├── config │ ├── index.ts │ └── nprogress.ts ├── directives │ ├── index.ts │ └── modules │ │ ├── auth.ts │ │ ├── copy.ts │ │ ├── debounce.ts │ │ ├── draggable.ts │ │ ├── longpress.ts │ │ ├── throttle.ts │ │ └── waterMarker.ts ├── enums │ └── httpEnum.ts ├── hooks │ ├── interface │ │ └── index.ts │ ├── useAuthButtons.ts │ ├── useDownload.ts │ ├── useHandleData.ts │ ├── useOnline.ts │ ├── useSelection.ts │ ├── useTable.ts │ ├── useTheme.ts │ └── useTime.ts ├── languages │ ├── index.ts │ └── modules │ │ ├── en.ts │ │ └── zh.ts ├── layouts │ ├── LayoutClassic │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutColumns │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutTransverse │ │ ├── index.scss │ │ └── index.vue │ ├── LayoutVertical │ │ ├── index.scss │ │ └── index.vue │ ├── components │ │ ├── Footer │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── Header │ │ │ ├── ToolBarLeft.vue │ │ │ ├── ToolBarRight.vue │ │ │ └── components │ │ │ │ ├── AssemblySize.vue │ │ │ │ ├── Avatar.vue │ │ │ │ ├── Breadcrumb.vue │ │ │ │ ├── CollapseIcon.vue │ │ │ │ ├── Fullscreen.vue │ │ │ │ ├── InfoDialog.vue │ │ │ │ ├── Language.vue │ │ │ │ ├── Message.vue │ │ │ │ ├── PasswordDialog.vue │ │ │ │ ├── SearchMenu.vue │ │ │ │ └── ThemeSetting.vue │ │ ├── Main │ │ │ ├── components │ │ │ │ └── Maximize.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── Menu │ │ │ └── SubMenu.vue │ │ ├── Tabs │ │ │ ├── components │ │ │ │ └── MoreButton.vue │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── ThemeDrawer │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── index.vue │ └── indexAsync.vue ├── main.ts ├── routers │ ├── index.ts │ └── modules │ │ ├── dynamicRouter.ts │ │ └── staticRouter.ts ├── stores │ ├── helper │ │ └── persist.ts │ ├── index.ts │ ├── interface │ │ └── index.ts │ └── modules │ │ ├── auth.ts │ │ ├── global.ts │ │ ├── keepAlive.ts │ │ ├── tabs.ts │ │ └── user.ts ├── styles │ ├── common.scss │ ├── element-dark.scss │ ├── element.scss │ ├── reset.scss │ ├── theme │ │ ├── aside.ts │ │ ├── header.ts │ │ └── menu.ts │ └── var.scss ├── typings │ ├── global.d.ts │ ├── utils.d.ts │ └── window.d.ts ├── utils │ ├── color.ts │ ├── dict.ts │ ├── eleValidate.ts │ ├── errorHandler.ts │ ├── index.ts │ ├── is │ │ └── index.ts │ ├── mittBus.ts │ └── svg.ts ├── views │ ├── about │ │ └── index.vue │ ├── assembly │ │ ├── batchImport │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── draggable │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── guide │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── selectFilter │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── selectIcon │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── svgIcon │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── tabs │ │ │ ├── detail.vue │ │ │ └── index.vue │ │ ├── treeFilter │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── uploadFile │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── wangEditor │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── auth │ │ ├── button │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── menu │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── dashboard │ │ └── dataVisualize │ │ │ ├── components │ │ │ ├── curve.vue │ │ │ └── pie.vue │ │ │ ├── images │ │ │ ├── 1-bg.png │ │ │ ├── 2-bg.png │ │ │ ├── 3-bg.png │ │ │ ├── 4-bg.png │ │ │ ├── add_person.png │ │ │ ├── add_team.png │ │ │ ├── book-bg.png │ │ │ ├── book-sum.png │ │ │ ├── book_sum.png │ │ │ └── today.png │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── dataScreen │ │ ├── assets │ │ │ ├── alarmList.Json │ │ │ ├── china.json │ │ │ └── ranking-icon.ts │ │ ├── components │ │ │ ├── AgeRatioChart.vue │ │ │ ├── AnnualUseChart.vue │ │ │ ├── ChinaMapChart.vue │ │ │ ├── HotPlateChart.vue │ │ │ ├── MaleFemaleRatioChart.vue │ │ │ ├── OverNext30Chart.vue │ │ │ ├── PlatformSourceChart.vue │ │ │ └── RealTimeAccessChart.vue │ │ ├── images │ │ │ ├── bg.png │ │ │ ├── contrast-bg.png │ │ │ ├── dataScreen-alarm.png │ │ │ ├── dataScreen-header-btn-bg-l.png │ │ │ ├── dataScreen-header-btn-bg-r.png │ │ │ ├── dataScreen-header-center-bg.png │ │ │ ├── dataScreen-header-left-bg.png │ │ │ ├── dataScreen-header-right-bg.png │ │ │ ├── dataScreen-header-warn-bg.png │ │ │ ├── dataScreen-main-cb.png │ │ │ ├── dataScreen-main-lb.png │ │ │ ├── dataScreen-main-lc.png │ │ │ ├── dataScreen-main-lt.png │ │ │ ├── dataScreen-main-rb.png │ │ │ ├── dataScreen-main-rc.png │ │ │ ├── dataScreen-main-rt.png │ │ │ ├── dataScreen-title.png │ │ │ ├── dataScreen-warn-bg.png │ │ │ ├── line-bg.png │ │ │ ├── man-bg.png │ │ │ ├── man.png │ │ │ ├── map-title-bg.png │ │ │ ├── rankingChart-bg.png │ │ │ ├── total.png │ │ │ ├── woman-bg.png │ │ │ └── woman.png │ │ ├── index.scss │ │ └── index.vue │ ├── directives │ │ ├── copyDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── debounceDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── dragDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── longpressDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── throttleDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── watermarkDirect │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── echarts │ │ ├── columnChart │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── lineChart │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── nestedChart │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── pieChart │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── radarChart │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── waterChart │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── form │ │ ├── basicForm │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── dynamicForm │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── proForm │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── validateForm │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── home │ │ ├── index.scss │ │ └── index.vue │ ├── link │ │ ├── bing │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── docs │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── gitee │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── github │ │ │ ├── index.scss │ │ │ └── index.vue │ │ └── juejin │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── login │ │ ├── components │ │ │ └── LoginForm.vue │ │ ├── index.scss │ │ └── index.vue │ ├── menu │ │ ├── menu1 │ │ │ ├── index.scss │ │ │ └── index.vue │ │ ├── menu2 │ │ │ ├── menu21 │ │ │ │ ├── index.scss │ │ │ │ └── index.vue │ │ │ ├── menu22 │ │ │ │ ├── menu221 │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ │ └── menu222 │ │ │ │ │ ├── index.scss │ │ │ │ │ └── index.vue │ │ │ └── menu23 │ │ │ │ ├── index.scss │ │ │ │ └── index.vue │ │ └── menu3 │ │ │ ├── index.scss │ │ │ └── index.vue │ ├── proTable │ │ ├── complexProTable │ │ │ └── index.vue │ │ ├── components │ │ │ └── UserDrawer.vue │ │ ├── document │ │ │ └── index.vue │ │ ├── treeProTable │ │ │ └── index.vue │ │ ├── useProTable │ │ │ ├── detail.vue │ │ │ └── index.vue │ │ ├── useSelectFilter │ │ │ └── index.vue │ │ └── useTreeFilter │ │ │ ├── detail.vue │ │ │ └── index.vue │ └── system │ │ ├── accountManage │ │ └── index.vue │ │ ├── departmentManage │ │ └── index.vue │ │ ├── dictManage │ │ └── index.vue │ │ ├── menuMange │ │ └── index.vue │ │ ├── roleManage │ │ └── index.vue │ │ ├── systemLog │ │ └── index.vue │ │ └── timingTask │ │ └── index.vue └── vite-env.d.ts ├── tsconfig.json └── vite.config.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | # @see: http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] # 表示所有文件适用 6 | charset = utf-8 # 设置文件字符集为 utf-8 7 | end_of_line = lf # 控制换行类型(lf | cr | crlf) 8 | insert_final_newline = true # 始终在文件末尾插入一个新行 9 | indent_style = space # 缩进风格(tab | space) 10 | indent_size = 2 # 缩进大小 11 | max_line_length = 130 # 最大行长度 12 | 13 | [*.md] # 表示仅对 md 文件适用以下规则 14 | max_line_length = off # 关闭最大行长度限制 15 | trim_trailing_whitespace = false # 关闭末尾空格修剪 16 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # title 2 | VITE_GLOB_APP_TITLE = Geeker Admin 3 | 4 | # 本地运行端口号 5 | VITE_PORT = 8848 6 | 7 | # 启动时自动打开浏览器 8 | VITE_OPEN = true 9 | 10 | # 开启 devTools 调试 11 | VITE_DEVTOOLS = false 12 | 13 | # 打包后是否生成包分析文件 14 | VITE_REPORT = false 15 | 16 | # 开启 CodeInspector 调试 17 | VITE_CODEINSPECTOR = false 18 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | VITE_USER_NODE_ENV = development 3 | 4 | # 公共基础路径 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 路由模式 8 | # Optional: hash | history 9 | VITE_ROUTER_MODE = hash 10 | 11 | # 打包时是否删除 console 12 | VITE_DROP_CONSOLE = true 13 | 14 | # 是否开启 VitePWA 15 | VITE_PWA = false 16 | 17 | # 开发环境接口地址 18 | VITE_API_URL = /api 19 | 20 | # 开发环境跨域代理,支持配置多个 21 | VITE_PROXY = [["/api","https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e"]] 22 | # VITE_PROXY = [["/api","https://www.fastmock.site/mock/f81e8333c1a9276214bcdbc170d9e0a0"]] 23 | # VITE_PROXY = [["/api-easymock","https://mock.mengxuegu.com"],["/api-fastmock","https://www.fastmock.site"]] 24 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境 2 | VITE_USER_NODE_ENV = production 3 | 4 | # 公共基础路径 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 路由模式 8 | # Optional: hash | history 9 | VITE_ROUTER_MODE = hash 10 | 11 | # 是否启用 gzip 或 brotli 压缩打包,如果需要多个压缩规则,可以使用 “,” 分隔 12 | # Optional: gzip | brotli | none 13 | VITE_BUILD_COMPRESS = none 14 | 15 | # 打包压缩后是否删除源文件 16 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 17 | 18 | # 打包时是否删除 console 19 | VITE_DROP_CONSOLE = true 20 | 21 | # 是否开启 VitePWA 22 | VITE_PWA = true 23 | 24 | # 线上环境接口地址 25 | VITE_API_URL = "https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e" 26 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # 测试环境 2 | VITE_USER_NODE_ENV = test 3 | 4 | # 公共基础路径 5 | VITE_PUBLIC_PATH = / 6 | 7 | # 路由模式 8 | # Optional: hash | history 9 | VITE_ROUTER_MODE = hash 10 | 11 | # 是否启用 gzip 或 brotli 压缩打包,如果需要多个压缩规则,可以使用 “,” 分隔 12 | # Optional: gzip | brotli | none 13 | VITE_BUILD_COMPRESS = none 14 | 15 | # 打包压缩后是否删除源文件 16 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false 17 | 18 | # 打包时是否删除 console 19 | VITE_DROP_CONSOLE = true 20 | 21 | # 是否开启 VitePWA 22 | VITE_PWA = false 23 | 24 | # 测试环境接口地址 25 | VITE_API_URL = "https://mock.mengxuegu.com/mock/629d727e6163854a32e8307e" 26 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.sh 2 | node_modules 3 | *.md 4 | *.woff 5 | *.ttf 6 | .vscode 7 | .idea 8 | dist 9 | /public 10 | /docs 11 | .husky 12 | .local 13 | /bin 14 | /src/mock/* 15 | stats.html 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | stats.html 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | !.vscode/settings.json 20 | .idea 21 | .DS_Store 22 | *.suo 23 | *.ntvs* 24 | *.njsproj 25 | *.sln 26 | *.sw? 27 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run lint:lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | /node_modules/** 4 | 5 | **/*.svg 6 | **/*.sh 7 | 8 | /public/* 9 | stats.html 10 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | // @see: https://www.prettier.cn 2 | 3 | module.exports = { 4 | // 指定最大换行长度 5 | printWidth: 130, 6 | // 缩进制表符宽度 | 空格数 7 | tabWidth: 2, 8 | // 使用制表符而不是空格缩进行 (true:制表符,false:空格) 9 | useTabs: false, 10 | // 结尾不用分号 (true:有,false:没有) 11 | semi: true, 12 | // 使用单引号 (true:单引号,false:双引号) 13 | singleQuote: false, 14 | // 在对象字面量中决定是否将属性名用引号括起来 可选值 "" 15 | quoteProps: "as-needed", 16 | // 在JSX中使用单引号而不是双引号 (true:单引号,false:双引号) 17 | jsxSingleQuote: false, 18 | // 多行时尽可能打印尾随逗号 可选值"" 19 | trailingComma: "none", 20 | // 在对象,数组括号与文字之间加空格 "{ foo: bar }" (true:有,false:没有) 21 | bracketSpacing: true, 22 | // 将 > 多行元素放在最后一行的末尾,而不是单独放在下一行 (true:放末尾,false:单独一行) 23 | bracketSameLine: false, 24 | // (x) => {} 箭头函数参数只有一个时是否要有小括号 (avoid:省略括号,always:不省略括号) 25 | arrowParens: "avoid", 26 | // 指定要使用的解析器,不需要写文件开头的 @prettier 27 | requirePragma: false, 28 | // 可以在文件顶部插入一个特殊标记,指定该文件已使用 Prettier 格式化 29 | insertPragma: false, 30 | // 用于控制文本是否应该被换行以及如何进行换行 31 | proseWrap: "preserve", 32 | // 在html中空格是否是敏感的 "css" - 遵守 CSS 显示属性的默认值, "strict" - 空格被认为是敏感的 ,"ignore" - 空格被认为是不敏感的 33 | htmlWhitespaceSensitivity: "css", 34 | // 控制在 Vue 单文件组件中 45 | -------------------------------------------------------------------------------- /src/api/config/servicePort.ts: -------------------------------------------------------------------------------- 1 | // 后端微服务模块前缀 2 | export const PORT1 = "/geeker"; 3 | export const PORT2 = "/hooks"; 4 | -------------------------------------------------------------------------------- /src/api/helper/axiosCancel.ts: -------------------------------------------------------------------------------- 1 | import { CustomAxiosRequestConfig } from "../index"; 2 | import qs from "qs"; 3 | 4 | // 声明一个 Map 用于存储每个请求的标识和取消函数 5 | let pendingMap = new Map(); 6 | 7 | // 序列化参数,确保对象属性顺序一致 8 | const sortedStringify = (obj: any) => { 9 | return qs.stringify(obj, { arrayFormat: "repeat", sort: (a, b) => a.localeCompare(b) }); 10 | }; 11 | 12 | // 获取请求的唯一标识 13 | export const getPendingUrl = (config: CustomAxiosRequestConfig) => { 14 | return [config.method, config.url, sortedStringify(config.data), sortedStringify(config.params)].join("&"); 15 | }; 16 | 17 | export class AxiosCanceler { 18 | /** 19 | * @description: 添加请求 20 | * @param {Object} config 21 | * @return void 22 | */ 23 | addPending(config: CustomAxiosRequestConfig) { 24 | // 在请求开始前,对之前的请求做检查取消操作 25 | this.removePending(config); 26 | const url = getPendingUrl(config); 27 | const controller = new AbortController(); 28 | config.signal = controller.signal; 29 | pendingMap.set(url, controller); 30 | } 31 | 32 | /** 33 | * @description: 移除请求 34 | * @param {Object} config 35 | */ 36 | removePending(config: CustomAxiosRequestConfig) { 37 | const url = getPendingUrl(config); 38 | // 如果在 pending 中存在当前请求标识,需要取消当前请求并删除条目 39 | const controller = pendingMap.get(url); 40 | if (controller) { 41 | controller.abort(); 42 | pendingMap.delete(url); 43 | } 44 | } 45 | 46 | /** 47 | * @description: 清空所有pending 48 | */ 49 | removeAllPending() { 50 | pendingMap.forEach(controller => { 51 | controller && controller.abort(); 52 | }); 53 | pendingMap.clear(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/api/helper/checkStatus.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | 3 | /** 4 | * @description: 校验网络请求状态码 5 | * @param {Number} status 6 | * @return void 7 | */ 8 | export const checkStatus = (status: number) => { 9 | switch (status) { 10 | case 400: 11 | ElMessage.error("请求失败!请您稍后重试"); 12 | break; 13 | case 401: 14 | ElMessage.error("登录失效!请您重新登录"); 15 | break; 16 | case 403: 17 | ElMessage.error("当前账号无权限访问!"); 18 | break; 19 | case 404: 20 | ElMessage.error("你所访问的资源不存在!"); 21 | break; 22 | case 405: 23 | ElMessage.error("请求方式错误!请您稍后重试"); 24 | break; 25 | case 408: 26 | ElMessage.error("请求超时!请您稍后重试"); 27 | break; 28 | case 500: 29 | ElMessage.error("服务异常!"); 30 | break; 31 | case 502: 32 | ElMessage.error("网关错误!"); 33 | break; 34 | case 503: 35 | ElMessage.error("服务不可用!"); 36 | break; 37 | case 504: 38 | ElMessage.error("网关超时!"); 39 | break; 40 | default: 41 | ElMessage.error("请求失败!"); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/api/interface/index.ts: -------------------------------------------------------------------------------- 1 | // 请求响应参数(不包含data) 2 | export interface Result { 3 | code: string; 4 | msg: string; 5 | } 6 | 7 | // 请求响应参数(包含data) 8 | export interface ResultData extends Result { 9 | data: T; 10 | } 11 | 12 | // 分页响应参数 13 | export interface ResPage { 14 | list: T[]; 15 | pageNum: number; 16 | pageSize: number; 17 | total: number; 18 | } 19 | 20 | // 分页请求参数 21 | export interface ReqPage { 22 | pageNum: number; 23 | pageSize: number; 24 | } 25 | 26 | // 文件上传模块 27 | export namespace Upload { 28 | export interface ResFileUrl { 29 | fileUrl: string; 30 | } 31 | } 32 | 33 | // 登录模块 34 | export namespace Login { 35 | export interface ReqLoginForm { 36 | username: string; 37 | password: string; 38 | } 39 | export interface ResLogin { 40 | access_token: string; 41 | } 42 | export interface ResAuthButtons { 43 | [key: string]: string[]; 44 | } 45 | } 46 | 47 | // 用户管理模块 48 | export namespace User { 49 | export interface ReqUserParams extends ReqPage { 50 | username: string; 51 | gender: number; 52 | idCard: string; 53 | email: string; 54 | address: string; 55 | createTime: string[]; 56 | status: number; 57 | } 58 | export interface ResUserList { 59 | id: string; 60 | username: string; 61 | gender: number; 62 | user: { detail: { age: number } }; 63 | idCard: string; 64 | email: string; 65 | address: string; 66 | createTime: string; 67 | status: number; 68 | avatar: string; 69 | photo: any[]; 70 | children?: ResUserList[]; 71 | } 72 | export interface ResStatus { 73 | userLabel: string; 74 | userValue: number; 75 | } 76 | export interface ResGender { 77 | genderLabel: string; 78 | genderValue: number; 79 | } 80 | export interface ResDepartment { 81 | id: string; 82 | name: string; 83 | children?: ResDepartment[]; 84 | } 85 | export interface ResRole { 86 | id: string; 87 | name: string; 88 | children?: ResDepartment[]; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/api/modules/login.ts: -------------------------------------------------------------------------------- 1 | import { Login } from "@/api/interface/index"; 2 | import { PORT1 } from "@/api/config/servicePort"; 3 | import authMenuList from "@/assets/json/authMenuList.json"; 4 | import authButtonList from "@/assets/json/authButtonList.json"; 5 | import http from "@/api"; 6 | 7 | /** 8 | * @name 登录模块 9 | */ 10 | // 用户登录 11 | export const loginApi = (params: Login.ReqLoginForm) => { 12 | return http.post(PORT1 + `/login`, params, { loading: false }); // 正常 post json 请求 ==> application/json 13 | // return http.post(PORT1 + `/login`, params, { loading: false }); // 控制当前请求不显示 loading 14 | // return http.post(PORT1 + `/login`, {}, { params }); // post 请求携带 query 参数 ==> ?username=admin&password=123456 15 | // return http.post(PORT1 + `/login`, qs.stringify(params)); // post 请求携带表单参数 ==> application/x-www-form-urlencoded 16 | // return http.get(PORT1 + `/login?${qs.stringify(params, { arrayFormat: "repeat" })}`); // get 请求可以携带数组等复杂参数 17 | }; 18 | 19 | // 获取菜单列表 20 | export const getAuthMenuListApi = () => { 21 | return http.get(PORT1 + `/menu/list`, {}, { loading: false }); 22 | // 如果想让菜单变为本地数据,注释上一行代码,并引入本地 authMenuList.json 数据 23 | return authMenuList; 24 | }; 25 | 26 | // 获取按钮权限 27 | export const getAuthButtonListApi = () => { 28 | return http.get(PORT1 + `/auth/buttons`, {}, { loading: false }); 29 | // 如果想让按钮权限变为本地数据,注释上一行代码,并引入本地 authButtonList.json 数据 30 | return authButtonList; 31 | }; 32 | 33 | // 用户退出登录 34 | export const logoutApi = () => { 35 | return http.post(PORT1 + `/logout`); 36 | }; 37 | -------------------------------------------------------------------------------- /src/api/modules/upload.ts: -------------------------------------------------------------------------------- 1 | import { Upload } from "@/api/interface/index"; 2 | import { PORT1 } from "@/api/config/servicePort"; 3 | import http from "@/api"; 4 | 5 | /** 6 | * @name 文件上传模块 7 | */ 8 | // 图片上传 9 | export const uploadImg = (params: FormData) => { 10 | return http.post(PORT1 + `/file/upload/img`, params, { cancel: false }); 11 | }; 12 | 13 | // 视频上传 14 | export const uploadVideo = (params: FormData) => { 15 | return http.post(PORT1 + `/file/upload/video`, params, { cancel: false }); 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { ResPage, User } from "@/api/interface/index"; 2 | import { PORT1 } from "@/api/config/servicePort"; 3 | import http from "@/api"; 4 | 5 | /** 6 | * @name 用户管理模块 7 | */ 8 | // 获取用户列表 9 | export const getUserList = (params: User.ReqUserParams) => { 10 | return http.post>(PORT1 + `/user/list`, params); 11 | }; 12 | 13 | // 获取树形用户列表 14 | export const getUserTreeList = (params: User.ReqUserParams) => { 15 | return http.post>(PORT1 + `/user/tree/list`, params); 16 | }; 17 | 18 | // 新增用户 19 | export const addUser = (params: { id: string }) => { 20 | return http.post(PORT1 + `/user/add`, params); 21 | }; 22 | 23 | // 批量添加用户 24 | export const BatchAddUser = (params: FormData) => { 25 | return http.post(PORT1 + `/user/import`, params); 26 | }; 27 | 28 | // 编辑用户 29 | export const editUser = (params: { id: string }) => { 30 | return http.post(PORT1 + `/user/edit`, params); 31 | }; 32 | 33 | // 删除用户 34 | export const deleteUser = (params: { id: string[] }) => { 35 | return http.post(PORT1 + `/user/delete`, params); 36 | }; 37 | 38 | // 切换用户状态 39 | export const changeUserStatus = (params: { id: string; status: number }) => { 40 | return http.post(PORT1 + `/user/change`, params); 41 | }; 42 | 43 | // 重置用户密码 44 | export const resetUserPassWord = (params: { id: string }) => { 45 | return http.post(PORT1 + `/user/rest_password`, params); 46 | }; 47 | 48 | // 导出用户数据 49 | export const exportUserInfo = (params: User.ReqUserParams) => { 50 | return http.download(PORT1 + `/user/export`, params); 51 | }; 52 | 53 | // 获取用户状态字典 54 | export const getUserStatus = () => { 55 | return http.get(PORT1 + `/user/status`); 56 | }; 57 | 58 | // 获取用户性别字典 59 | export const getUserGender = () => { 60 | return http.get(PORT1 + `/user/gender`); 61 | }; 62 | 63 | // 获取用户部门列表 64 | export const getUserDepartment = () => { 65 | return http.get(PORT1 + `/user/department`, {}, { cancel: false }); 66 | }; 67 | 68 | // 获取用户角色字典 69 | export const getUserRole = () => { 70 | return http.get(PORT1 + `/user/role`); 71 | }; 72 | -------------------------------------------------------------------------------- /src/assets/fonts/DIN.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/fonts/DIN.otf -------------------------------------------------------------------------------- /src/assets/fonts/MetroDF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/fonts/MetroDF.ttf -------------------------------------------------------------------------------- /src/assets/fonts/YouSheBiaoTiHei.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/fonts/YouSheBiaoTiHei.ttf -------------------------------------------------------------------------------- /src/assets/fonts/font.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: YouSheBiaoTiHei; 3 | src: url("./YouSheBiaoTiHei.ttf"); 4 | } 5 | 6 | @font-face { 7 | font-family: MetroDF; 8 | src: url("./MetroDF.ttf"); 9 | } 10 | 11 | @font-face { 12 | font-family: DIN; 13 | src: url("./DIN.otf"); 14 | } 15 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: iconfont; /* Project id 2667653 */ 3 | src: url("iconfont.ttf?t=1719667796161") format("truetype"); 4 | } 5 | .iconfont { 6 | font-family: iconfont !important; 7 | font-size: 20px; 8 | font-style: normal; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | cursor: pointer; 12 | } 13 | .icon-yiwen::before { 14 | font-size: 15px; 15 | content: "\e693"; 16 | } 17 | .icon-xiala::before { 18 | content: "\e62b"; 19 | } 20 | .icon-tuichu::before { 21 | content: "\e645"; 22 | } 23 | .icon-xiaoxi::before { 24 | font-size: 21.2px; 25 | content: "\e61f"; 26 | } 27 | .icon-zhuti::before { 28 | font-size: 22.4px; 29 | content: "\e638"; 30 | } 31 | .icon-sousuo::before { 32 | content: "\e611"; 33 | } 34 | .icon-contentright::before { 35 | content: "\e8c9"; 36 | } 37 | .icon-contentleft::before { 38 | content: "\e8ca"; 39 | } 40 | .icon-fangda::before { 41 | content: "\e826"; 42 | } 43 | .icon-suoxiao::before { 44 | content: "\e641"; 45 | } 46 | .icon-zhongyingwen::before { 47 | content: "\e8cb"; 48 | } 49 | .icon-huiche::before { 50 | content: "\e637"; 51 | } 52 | -------------------------------------------------------------------------------- /src/assets/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/assets/icons/xianxingfanchuan.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/images/403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/403.png -------------------------------------------------------------------------------- /src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/404.png -------------------------------------------------------------------------------- /src/assets/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/500.png -------------------------------------------------------------------------------- /src/assets/images/avatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/avatar.gif -------------------------------------------------------------------------------- /src/assets/images/login_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/assets/images/login_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left.png -------------------------------------------------------------------------------- /src/assets/images/login_left1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left1.png -------------------------------------------------------------------------------- /src/assets/images/login_left2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left2.png -------------------------------------------------------------------------------- /src/assets/images/login_left3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left3.png -------------------------------------------------------------------------------- /src/assets/images/login_left4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left4.png -------------------------------------------------------------------------------- /src/assets/images/login_left5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/login_left5.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/msg01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/msg01.png -------------------------------------------------------------------------------- /src/assets/images/msg02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/msg02.png -------------------------------------------------------------------------------- /src/assets/images/msg03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/msg03.png -------------------------------------------------------------------------------- /src/assets/images/msg04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/msg04.png -------------------------------------------------------------------------------- /src/assets/images/msg05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/msg05.png -------------------------------------------------------------------------------- /src/assets/images/notData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/notData.png -------------------------------------------------------------------------------- /src/assets/images/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/images/welcome.png -------------------------------------------------------------------------------- /src/assets/json/authButtonList.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 200, 3 | "data": { 4 | "useProTable": ["add", "batchAdd", "export", "batchDelete", "status"], 5 | "authButton": ["add", "edit", "delete", "import", "export"] 6 | }, 7 | "msg": "成功" 8 | } 9 | -------------------------------------------------------------------------------- /src/assets/mock/Easy-Mock-API.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/assets/mock/Easy-Mock-API.zip -------------------------------------------------------------------------------- /src/components/ECharts/config/index.ts: -------------------------------------------------------------------------------- 1 | import * as echarts from "echarts/core"; 2 | import { BarChart, LineChart, LinesChart, PieChart, ScatterChart, RadarChart, GaugeChart } from "echarts/charts"; 3 | import { 4 | TitleComponent, 5 | TooltipComponent, 6 | GridComponent, 7 | DatasetComponent, 8 | TransformComponent, 9 | LegendComponent, 10 | PolarComponent, 11 | GeoComponent, 12 | ToolboxComponent, 13 | DataZoomComponent 14 | } from "echarts/components"; 15 | import { LabelLayout, UniversalTransition } from "echarts/features"; 16 | import { CanvasRenderer } from "echarts/renderers"; 17 | import type { 18 | BarSeriesOption, 19 | LineSeriesOption, 20 | LinesSeriesOption, 21 | PieSeriesOption, 22 | ScatterSeriesOption, 23 | RadarSeriesOption, 24 | GaugeSeriesOption 25 | } from "echarts/charts"; 26 | import type { 27 | TitleComponentOption, 28 | TooltipComponentOption, 29 | GridComponentOption, 30 | DatasetComponentOption 31 | } from "echarts/components"; 32 | import type { ComposeOption } from "echarts/core"; 33 | import "echarts-liquidfill"; 34 | 35 | export type ECOption = ComposeOption< 36 | | BarSeriesOption 37 | | LineSeriesOption 38 | | LinesSeriesOption 39 | | PieSeriesOption 40 | | RadarSeriesOption 41 | | GaugeSeriesOption 42 | | TitleComponentOption 43 | | TooltipComponentOption 44 | | GridComponentOption 45 | | DatasetComponentOption 46 | | ScatterSeriesOption 47 | >; 48 | 49 | echarts.use([ 50 | TitleComponent, 51 | TooltipComponent, 52 | GridComponent, 53 | DatasetComponent, 54 | TransformComponent, 55 | LegendComponent, 56 | PolarComponent, 57 | GeoComponent, 58 | ToolboxComponent, 59 | DataZoomComponent, 60 | BarChart, 61 | LineChart, 62 | LinesChart, 63 | PieChart, 64 | ScatterChart, 65 | RadarChart, 66 | GaugeChart, 67 | LabelLayout, 68 | UniversalTransition, 69 | CanvasRenderer 70 | ]); 71 | 72 | export default echarts; 73 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/403.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/404.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/500.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 16 | 17 | 20 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/index.scss: -------------------------------------------------------------------------------- 1 | .not-container { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | .not-img { 8 | margin-right: 120px; 9 | } 10 | .not-detail { 11 | display: flex; 12 | flex-direction: column; 13 | h2, 14 | h4 { 15 | padding: 0; 16 | margin: 0; 17 | } 18 | h2 { 19 | font-size: 60px; 20 | color: var(--el-text-color-primary); 21 | } 22 | h4 { 23 | margin: 30px 0 20px; 24 | font-size: 19px; 25 | font-weight: normal; 26 | color: var(--el-text-color-regular); 27 | } 28 | .el-button { 29 | width: 100px; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/Grid/components/GridItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 69 | -------------------------------------------------------------------------------- /src/components/Grid/interface/index.ts: -------------------------------------------------------------------------------- 1 | export type BreakPoint = "xs" | "sm" | "md" | "lg" | "xl"; 2 | 3 | export type Responsive = { 4 | span?: number; 5 | offset?: number; 6 | }; 7 | -------------------------------------------------------------------------------- /src/components/ImportExcel/index.scss: -------------------------------------------------------------------------------- 1 | .upload { 2 | width: 80%; 3 | } 4 | -------------------------------------------------------------------------------- /src/components/Loading/fullScreen.ts: -------------------------------------------------------------------------------- 1 | import { ElLoading } from "element-plus"; 2 | 3 | /* 全局请求 loading */ 4 | let loadingInstance: ReturnType; 5 | 6 | /** 7 | * @description 开启 Loading 8 | * */ 9 | const startLoading = () => { 10 | loadingInstance = ElLoading.service({ 11 | fullscreen: true, 12 | lock: true, 13 | text: "Loading", 14 | background: "rgba(0, 0, 0, 0.7)" 15 | }); 16 | }; 17 | 18 | /** 19 | * @description 结束 Loading 20 | * */ 21 | const endLoading = () => { 22 | loadingInstance.close(); 23 | }; 24 | 25 | /** 26 | * @description 显示全屏加载 27 | * */ 28 | let needLoadingRequestCount = 0; 29 | export const showFullScreenLoading = () => { 30 | if (needLoadingRequestCount === 0) { 31 | startLoading(); 32 | } 33 | needLoadingRequestCount++; 34 | }; 35 | 36 | /** 37 | * @description 隐藏全屏加载 38 | * */ 39 | export const tryHideFullScreenLoading = () => { 40 | if (needLoadingRequestCount <= 0) return; 41 | needLoadingRequestCount--; 42 | if (needLoadingRequestCount === 0) { 43 | endLoading(); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/components/Loading/index.scss: -------------------------------------------------------------------------------- 1 | .loading-box { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | justify-content: center; 6 | width: 100%; 7 | height: 100%; 8 | .loading-wrap { 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | padding: 98px; 13 | } 14 | } 15 | .dot { 16 | position: relative; 17 | box-sizing: border-box; 18 | display: inline-block; 19 | width: 32px; 20 | height: 32px; 21 | font-size: 32px; 22 | transform: rotate(45deg); 23 | animation: ant-rotate 1.2s infinite linear; 24 | } 25 | .dot i { 26 | position: absolute; 27 | display: block; 28 | width: 14px; 29 | height: 14px; 30 | background-color: var(--el-color-primary); 31 | border-radius: 100%; 32 | opacity: 0.3; 33 | transform: scale(0.75); 34 | transform-origin: 50% 50%; 35 | animation: ant-spin-move 1s infinite linear alternate; 36 | } 37 | .dot i:nth-child(1) { 38 | top: 0; 39 | left: 0; 40 | } 41 | .dot i:nth-child(2) { 42 | top: 0; 43 | right: 0; 44 | animation-delay: 0.4s; 45 | } 46 | .dot i:nth-child(3) { 47 | right: 0; 48 | bottom: 0; 49 | animation-delay: 0.8s; 50 | } 51 | .dot i:nth-child(4) { 52 | bottom: 0; 53 | left: 0; 54 | animation-delay: 1.2s; 55 | } 56 | 57 | @keyframes ant-rotate { 58 | to { 59 | transform: rotate(405deg); 60 | } 61 | } 62 | 63 | @keyframes ant-spin-move { 64 | to { 65 | opacity: 1; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /src/components/ProTable/components/ColSetting.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 40 | 41 | 46 | -------------------------------------------------------------------------------- /src/components/ProTable/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 34 | -------------------------------------------------------------------------------- /src/components/ProTable/components/TableColumn.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 59 | -------------------------------------------------------------------------------- /src/components/SelectFilter/index.scss: -------------------------------------------------------------------------------- 1 | .select-filter { 2 | width: 100%; 3 | .select-filter-item { 4 | display: flex; 5 | align-items: center; 6 | border-bottom: 1px dashed var(--el-border-color-light); 7 | &:last-child { 8 | border-bottom: none; 9 | } 10 | .select-filter-item-title { 11 | margin-top: -2px; 12 | span { 13 | font-size: 14px; 14 | color: var(--el-text-color-regular); 15 | white-space: nowrap; 16 | } 17 | } 18 | .select-filter-notData { 19 | margin: 18px 0; 20 | font-size: 14px; 21 | color: var(--el-text-color-regular); 22 | } 23 | .select-filter-list { 24 | display: flex; 25 | flex: 1; 26 | padding: 0; 27 | margin: 13px 0; 28 | li { 29 | display: flex; 30 | align-items: center; 31 | padding: 5px 15px; 32 | margin-right: 16px; 33 | font-size: 13px; 34 | color: var(--el-color-primary); 35 | list-style: none; 36 | cursor: pointer; 37 | background: var(--el-color-primary-light-9); 38 | border: 1px solid var(--el-color-primary-light-5); 39 | border-radius: 32px; 40 | &:hover { 41 | color: #ffffff; 42 | background: var(--el-color-primary); 43 | border-color: var(--el-color-primary); 44 | transition: 0.1s; 45 | } 46 | &.active { 47 | font-weight: bold; 48 | color: #ffffff; 49 | background: var(--el-color-primary); 50 | border-color: var(--el-color-primary); 51 | } 52 | .el-icon { 53 | margin-right: 4px; 54 | font-size: 16px; 55 | font-weight: bold; 56 | } 57 | span { 58 | white-space: nowrap; 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/components/SelectIcon/index.scss: -------------------------------------------------------------------------------- 1 | .icon-box { 2 | width: 100%; 3 | .el-button { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | font-size: 18px; 8 | color: var(--el-text-color-regular); 9 | } 10 | :deep(.el-dialog__body) { 11 | padding: 25px 20px 20px; 12 | .el-input { 13 | margin-bottom: 10px; 14 | } 15 | .icon-list { 16 | display: grid; 17 | grid-template-columns: repeat(auto-fill, 115px); 18 | justify-content: space-evenly; 19 | max-height: 70vh; 20 | .icon-item { 21 | display: flex; 22 | flex-direction: column; 23 | align-items: center; 24 | width: 42px; 25 | padding: 20px 30px; 26 | cursor: pointer; 27 | transition: all 0.2s; 28 | &:hover { 29 | transform: scale(1.3); 30 | } 31 | span { 32 | margin-top: 5px; 33 | line-height: 20px; 34 | text-align: center; 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 23 | -------------------------------------------------------------------------------- /src/components/SwitchDark/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/components/TreeFilter/index.scss: -------------------------------------------------------------------------------- 1 | .filter { 2 | box-sizing: border-box; 3 | width: 220px; 4 | height: 100%; 5 | padding: 18px; 6 | margin-right: 10px; 7 | .title { 8 | margin: 0 0 15px; 9 | font-size: 18px; 10 | font-weight: bold; 11 | color: var(--el-color-info-dark-2); 12 | letter-spacing: 0.5px; 13 | } 14 | .search { 15 | display: flex; 16 | align-items: center; 17 | margin: 0 0 15px; 18 | .el-icon { 19 | cursor: pointer; 20 | transform: rotate(90deg) translateY(-8px); 21 | } 22 | } 23 | .el-scrollbar { 24 | :deep(.el-tree) { 25 | height: 80%; 26 | overflow: auto; 27 | .el-tree-node__content { 28 | height: 33px; 29 | } 30 | } 31 | :deep(.el-tree--highlight-current) { 32 | .el-tree-node.is-current > .el-tree-node__content { 33 | background-color: var(--el-color-primary); 34 | .el-tree-node__label, 35 | .el-tree-node__expand-icon { 36 | color: white; 37 | } 38 | .is-leaf { 39 | color: transparent; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/WangEditor/index.scss: -------------------------------------------------------------------------------- 1 | /* 富文本组件校验失败样式 */ 2 | .is-error { 3 | .editor-box { 4 | border-color: var(--el-color-danger); 5 | .editor-toolbar { 6 | border-bottom-color: var(--el-color-danger); 7 | } 8 | } 9 | } 10 | 11 | /* 富文本组件禁用样式 */ 12 | .editor-disabled { 13 | cursor: not-allowed !important; 14 | } 15 | 16 | /* 富文本组件样式 */ 17 | .editor-box { 18 | /* 防止富文本编辑器全屏时 tabs组件 在其层级之上 */ 19 | z-index: 2; 20 | width: 100%; 21 | border: 1px solid var(--el-border-color-darker); 22 | .editor-toolbar { 23 | border-bottom: 1px solid var(--el-border-color-darker); 24 | } 25 | .editor-content { 26 | overflow-y: hidden; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | // ? 全局默认配置项 2 | 3 | // 首页地址(默认) 4 | export const HOME_URL: string = "/home/index"; 5 | 6 | // 登录页地址(默认) 7 | export const LOGIN_URL: string = "/login"; 8 | 9 | // 默认主题颜色 10 | export const DEFAULT_PRIMARY: string = "#009688"; 11 | 12 | // 路由白名单地址(本地存在的路由 staticRouter.ts 中) 13 | export const ROUTER_WHITE_LIST: string[] = ["/500"]; 14 | 15 | // 高德地图 key 16 | export const AMAP_MAP_KEY: string = ""; 17 | 18 | // 百度地图 key 19 | export const BAIDU_MAP_KEY: string = ""; 20 | -------------------------------------------------------------------------------- /src/config/nprogress.ts: -------------------------------------------------------------------------------- 1 | import NProgress from "nprogress"; 2 | import "nprogress/nprogress.css"; 3 | 4 | NProgress.configure({ 5 | easing: "ease", // 动画方式 6 | speed: 500, // 递增进度条的速度 7 | showSpinner: true, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }); 11 | 12 | export default NProgress; 13 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Directive } from "vue"; 2 | import auth from "./modules/auth"; 3 | import copy from "./modules/copy"; 4 | import waterMarker from "./modules/waterMarker"; 5 | import draggable from "./modules/draggable"; 6 | import debounce from "./modules/debounce"; 7 | import throttle from "./modules/throttle"; 8 | import longpress from "./modules/longpress"; 9 | 10 | const directivesList: { [key: string]: Directive } = { 11 | auth, 12 | copy, 13 | waterMarker, 14 | draggable, 15 | debounce, 16 | throttle, 17 | longpress 18 | }; 19 | 20 | const directives = { 21 | install: function (app: App) { 22 | Object.keys(directivesList).forEach(key => { 23 | app.directive(key, directivesList[key]); 24 | }); 25 | } 26 | }; 27 | 28 | export default directives; 29 | -------------------------------------------------------------------------------- /src/directives/modules/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-auth 3 | * 按钮权限指令 4 | */ 5 | import { useAuthStore } from "@/stores/modules/auth"; 6 | import type { Directive, DirectiveBinding } from "vue"; 7 | 8 | const auth: Directive = { 9 | mounted(el: HTMLElement, binding: DirectiveBinding) { 10 | const { value } = binding; 11 | const authStore = useAuthStore(); 12 | const currentPageRoles = authStore.authButtonListGet[authStore.routeName] ?? []; 13 | if (value instanceof Array && value.length) { 14 | const hasPermission = value.every(item => currentPageRoles.includes(item)); 15 | if (!hasPermission) el.remove(); 16 | } else { 17 | if (!currentPageRoles.includes(value)) el.remove(); 18 | } 19 | } 20 | }; 21 | 22 | export default auth; 23 | -------------------------------------------------------------------------------- /src/directives/modules/copy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-copy 3 | * 复制某个值至剪贴板 4 | * 接收参数:string类型/Ref类型/Reactive类型 5 | */ 6 | 7 | import type { Directive, DirectiveBinding } from "vue"; 8 | import { ElMessage } from "element-plus"; 9 | interface ElType extends HTMLElement { 10 | copyData: string | number; 11 | } 12 | const copy: Directive = { 13 | mounted(el: ElType, binding: DirectiveBinding) { 14 | el.copyData = binding.value; 15 | el.addEventListener("click", handleClick); 16 | }, 17 | updated(el: ElType, binding: DirectiveBinding) { 18 | el.copyData = binding.value; 19 | }, 20 | beforeUnmount(el: ElType) { 21 | el.removeEventListener("click", handleClick); 22 | } 23 | }; 24 | 25 | async function handleClick(this: any) { 26 | try { 27 | await navigator.clipboard.writeText(this.copyData); 28 | ElMessage({ 29 | type: "success", 30 | message: "复制成功" 31 | }); 32 | } catch (err) { 33 | console.error("复制操作不被支持或失败: ", err); 34 | } 35 | } 36 | 37 | export default copy; 38 | -------------------------------------------------------------------------------- /src/directives/modules/debounce.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-debounce 3 | * 按钮防抖指令,可自行扩展至input 4 | * 接收参数:function类型 5 | */ 6 | import type { Directive, DirectiveBinding } from "vue"; 7 | interface ElType extends HTMLElement { 8 | __handleClick__: () => any; 9 | } 10 | const debounce: Directive = { 11 | mounted(el: ElType, binding: DirectiveBinding) { 12 | if (typeof binding.value !== "function") { 13 | throw "callback must be a function"; 14 | } 15 | let timer: NodeJS.Timeout | null = null; 16 | el.__handleClick__ = function () { 17 | if (timer) { 18 | clearInterval(timer); 19 | } 20 | timer = setTimeout(() => { 21 | binding.value(); 22 | }, 500); 23 | }; 24 | el.addEventListener("click", el.__handleClick__); 25 | }, 26 | beforeUnmount(el: ElType) { 27 | el.removeEventListener("click", el.__handleClick__); 28 | } 29 | }; 30 | 31 | export default debounce; 32 | -------------------------------------------------------------------------------- /src/directives/modules/draggable.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。 3 | 4 | 思路: 5 | 1、设置需要拖拽的元素为absolute,其父元素为relative。 6 | 2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。 7 | 3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值 8 | 4、鼠标松开(onmouseup)时完成一次拖拽 9 | 10 | 使用:在 Dom 上加上 v-draggable 即可 11 |
12 | */ 13 | import type { Directive } from "vue"; 14 | interface ElType extends HTMLElement { 15 | parentNode: any; 16 | } 17 | const draggable: Directive = { 18 | mounted: function (el: ElType) { 19 | el.style.cursor = "move"; 20 | el.style.position = "absolute"; 21 | el.onmousedown = function (e) { 22 | let disX = e.pageX - el.offsetLeft; 23 | let disY = e.pageY - el.offsetTop; 24 | document.onmousemove = function (e) { 25 | let x = e.pageX - disX; 26 | let y = e.pageY - disY; 27 | let maxX = el.parentNode.offsetWidth - el.offsetWidth; 28 | let maxY = el.parentNode.offsetHeight - el.offsetHeight; 29 | if (x < 0) { 30 | x = 0; 31 | } else if (x > maxX) { 32 | x = maxX; 33 | } 34 | 35 | if (y < 0) { 36 | y = 0; 37 | } else if (y > maxY) { 38 | y = maxY; 39 | } 40 | el.style.left = x + "px"; 41 | el.style.top = y + "px"; 42 | }; 43 | document.onmouseup = function () { 44 | document.onmousemove = document.onmouseup = null; 45 | }; 46 | }; 47 | } 48 | }; 49 | export default draggable; 50 | -------------------------------------------------------------------------------- /src/directives/modules/longpress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-longpress 3 | * 长按指令,长按时触发事件 4 | */ 5 | import type { Directive, DirectiveBinding } from "vue"; 6 | 7 | const directive: Directive = { 8 | mounted(el: HTMLElement, binding: DirectiveBinding) { 9 | if (typeof binding.value !== "function") { 10 | throw "callback must be a function"; 11 | } 12 | // 定义变量 13 | let pressTimer: any = null; 14 | // 创建计时器( 2秒后执行函数 ) 15 | const start = (e: any) => { 16 | if (e.button) { 17 | if (e.type === "click" && e.button !== 0) { 18 | return; 19 | } 20 | } 21 | if (pressTimer === null) { 22 | pressTimer = setTimeout(() => { 23 | handler(e); 24 | }, 1000); 25 | } 26 | }; 27 | // 取消计时器 28 | const cancel = () => { 29 | if (pressTimer !== null) { 30 | clearTimeout(pressTimer); 31 | pressTimer = null; 32 | } 33 | }; 34 | // 运行函数 35 | const handler = (e: MouseEvent | TouchEvent) => { 36 | binding.value(e); 37 | }; 38 | // 添加事件监听器 39 | el.addEventListener("mousedown", start); 40 | el.addEventListener("touchstart", start); 41 | // 取消计时器 42 | el.addEventListener("click", cancel); 43 | el.addEventListener("mouseout", cancel); 44 | el.addEventListener("touchend", cancel); 45 | el.addEventListener("touchcancel", cancel); 46 | } 47 | }; 48 | 49 | export default directive; 50 | -------------------------------------------------------------------------------- /src/directives/modules/throttle.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。 3 | 4 | 思路: 5 | 1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮 6 | 2、将需要触发的方法绑定在指令上 7 | 8 | 使用:给 Dom 加上 v-throttle 及回调函数即可 9 | 10 | */ 11 | import type { Directive, DirectiveBinding } from "vue"; 12 | interface ElType extends HTMLElement { 13 | __handleClick__: () => any; 14 | disabled: boolean; 15 | } 16 | const throttle: Directive = { 17 | mounted(el: ElType, binding: DirectiveBinding) { 18 | if (typeof binding.value !== "function") { 19 | throw "callback must be a function"; 20 | } 21 | let timer: NodeJS.Timeout | null = null; 22 | el.__handleClick__ = function () { 23 | if (timer) { 24 | clearTimeout(timer); 25 | } 26 | if (!el.disabled) { 27 | el.disabled = true; 28 | binding.value(); 29 | timer = setTimeout(() => { 30 | el.disabled = false; 31 | }, 1000); 32 | } 33 | }; 34 | el.addEventListener("click", el.__handleClick__); 35 | }, 36 | beforeUnmount(el: ElType) { 37 | el.removeEventListener("click", el.__handleClick__); 38 | } 39 | }; 40 | 41 | export default throttle; 42 | -------------------------------------------------------------------------------- /src/directives/modules/waterMarker.ts: -------------------------------------------------------------------------------- 1 | /* 2 | 需求:给整个页面添加背景水印。 3 | 4 | 思路: 5 | 1、使用 canvas 特性生成 base64 格式的图片文件,设置其字体大小,颜色等。 6 | 2、将其设置为背景图片,从而实现页面或组件水印效果 7 | 8 | 使用:设置水印文案,颜色,字体大小即可 9 |
10 | */ 11 | 12 | import type { Directive, DirectiveBinding } from "vue"; 13 | const addWaterMarker: Directive = (str: string, parentNode: any, font: any, textColor: string) => { 14 | // 水印文字,父元素,字体,文字颜色 15 | let can: HTMLCanvasElement = document.createElement("canvas"); 16 | parentNode.appendChild(can); 17 | can.width = 205; 18 | can.height = 140; 19 | can.style.display = "none"; 20 | let cans = can.getContext("2d") as CanvasRenderingContext2D; 21 | cans.rotate((-20 * Math.PI) / 180); 22 | cans.font = font || "16px Microsoft JhengHei"; 23 | cans.fillStyle = textColor || "rgba(180, 180, 180, 0.3)"; 24 | cans.textAlign = "left"; 25 | cans.textBaseline = "Middle" as CanvasTextBaseline; 26 | cans.fillText(str, can.width / 10, can.height / 2); 27 | parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")"; 28 | }; 29 | 30 | const waterMarker = { 31 | mounted(el: DirectiveBinding, binding: DirectiveBinding) { 32 | addWaterMarker(binding.value.text, el, binding.value.font, binding.value.textColor); 33 | } 34 | }; 35 | 36 | export default waterMarker; 37 | -------------------------------------------------------------------------------- /src/enums/httpEnum.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description:请求配置 3 | */ 4 | export enum ResultEnum { 5 | SUCCESS = 200, 6 | ERROR = 500, 7 | OVERDUE = 401, 8 | TIMEOUT = 30000, 9 | TYPE = "success" 10 | } 11 | 12 | /** 13 | * @description:请求方法 14 | */ 15 | export enum RequestEnum { 16 | GET = "GET", 17 | POST = "POST", 18 | PATCH = "PATCH", 19 | PUT = "PUT", 20 | DELETE = "DELETE" 21 | } 22 | 23 | /** 24 | * @description:常用的 contentTyp 类型 25 | */ 26 | export enum ContentTypeEnum { 27 | // json 28 | JSON = "application/json;charset=UTF-8", 29 | // text 30 | TEXT = "text/plain;charset=UTF-8", 31 | // form-data 一般配合qs 32 | FORM_URLENCODED = "application/x-www-form-urlencoded;charset=UTF-8", 33 | // form-data 上传 34 | FORM_DATA = "multipart/form-data;charset=UTF-8" 35 | } 36 | -------------------------------------------------------------------------------- /src/hooks/interface/index.ts: -------------------------------------------------------------------------------- 1 | export namespace Table { 2 | export interface Pageable { 3 | pageNum: number; 4 | pageSize: number; 5 | total: number; 6 | } 7 | export interface StateProps { 8 | tableData: any[]; 9 | pageable: Pageable; 10 | searchParam: { 11 | [key: string]: any; 12 | }; 13 | searchInitParam: { 14 | [key: string]: any; 15 | }; 16 | totalParam: { 17 | [key: string]: any; 18 | }; 19 | icon?: { 20 | [key: string]: any; 21 | }; 22 | } 23 | } 24 | 25 | export namespace HandleData { 26 | export type MessageType = "" | "success" | "warning" | "info" | "error"; 27 | } 28 | 29 | export namespace Theme { 30 | export type ThemeType = "light" | "inverted" | "dark"; 31 | export type GreyOrWeakType = "grey" | "weak"; 32 | } 33 | -------------------------------------------------------------------------------- /src/hooks/useAuthButtons.ts: -------------------------------------------------------------------------------- 1 | import { computed } from "vue"; 2 | import { useRoute } from "vue-router"; 3 | import { useAuthStore } from "@/stores/modules/auth"; 4 | 5 | /** 6 | * @description 页面按钮权限 7 | * */ 8 | export const useAuthButtons = () => { 9 | const route = useRoute(); 10 | const authStore = useAuthStore(); 11 | const authButtons = authStore.authButtonListGet[route.name as string] || []; 12 | 13 | const BUTTONS = computed(() => { 14 | let currentPageAuthButton: { [key: string]: boolean } = {}; 15 | authButtons.forEach(item => (currentPageAuthButton[item] = true)); 16 | return currentPageAuthButton; 17 | }); 18 | 19 | return { 20 | BUTTONS 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /src/hooks/useDownload.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | 3 | /** 4 | * @description 接收数据流生成 blob,创建链接,下载文件 5 | * @param {Function} api 导出表格的api方法 (必传) 6 | * @param {String} tempName 导出的文件名 (必传) 7 | * @param {Object} params 导出的参数 (默认{}) 8 | * @param {Boolean} isNotify 是否有导出消息提示 (默认为 true) 9 | * @param {String} fileType 导出的文件格式 (默认为.xlsx) 10 | * */ 11 | export const useDownload = async ( 12 | api: (param: any) => Promise, 13 | tempName: string, 14 | params: any = {}, 15 | isNotify: boolean = true, 16 | fileType: string = ".xlsx" 17 | ) => { 18 | if (isNotify) { 19 | ElNotification({ 20 | title: "温馨提示", 21 | message: "如果数据庞大会导致下载缓慢哦,请您耐心等待!", 22 | type: "info", 23 | duration: 3000 24 | }); 25 | } 26 | try { 27 | const res = await api(params); 28 | const blob = new Blob([res]); 29 | // 兼容 edge 不支持 createObjectURL 方法 30 | if ("msSaveOrOpenBlob" in navigator) return window.navigator.msSaveOrOpenBlob(blob, tempName + fileType); 31 | const blobUrl = window.URL.createObjectURL(blob); 32 | const exportFile = document.createElement("a"); 33 | exportFile.style.display = "none"; 34 | exportFile.download = `${tempName}${fileType}`; 35 | exportFile.href = blobUrl; 36 | document.body.appendChild(exportFile); 37 | exportFile.click(); 38 | // 去除下载对 url 的影响 39 | document.body.removeChild(exportFile); 40 | window.URL.revokeObjectURL(blobUrl); 41 | } catch (error) { 42 | console.log(error); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/hooks/useHandleData.ts: -------------------------------------------------------------------------------- 1 | import { ElMessageBox, ElMessage } from "element-plus"; 2 | import { HandleData } from "./interface"; 3 | 4 | /** 5 | * @description 操作单条数据信息 (二次确认【删除、禁用、启用、重置密码】) 6 | * @param {Function} api 操作数据接口的api方法 (必传) 7 | * @param {Object} params 携带的操作数据参数 {id,params} (必传) 8 | * @param {String} message 提示信息 (必传) 9 | * @param {String} confirmType icon类型 (不必传,默认为 warning) 10 | * @returns {Promise} 11 | */ 12 | export const useHandleData = ( 13 | api: (params: any) => Promise, 14 | params: any = {}, 15 | message: string, 16 | confirmType: HandleData.MessageType = "warning" 17 | ) => { 18 | return new Promise((resolve, reject) => { 19 | ElMessageBox.confirm(`是否${message}?`, "温馨提示", { 20 | confirmButtonText: "确定", 21 | cancelButtonText: "取消", 22 | type: confirmType, 23 | draggable: true 24 | }) 25 | .then(async () => { 26 | const res = await api(params); 27 | if (!res) return reject(false); 28 | ElMessage({ 29 | type: "success", 30 | message: `${message}成功!` 31 | }); 32 | resolve(true); 33 | }) 34 | .catch(() => { 35 | // cancel operation 36 | }); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /src/hooks/useOnline.ts: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from "vue"; 2 | 3 | /** 4 | * @description 网络是否可用 5 | * */ 6 | export const useOnline = () => { 7 | const online = ref(true); 8 | const showStatus = (val: any) => { 9 | online.value = typeof val == "boolean" ? val : val.target.online; 10 | }; 11 | // 在页面加载后,设置正确的网络状态 12 | navigator.onLine ? showStatus(true) : showStatus(false); 13 | 14 | onMounted(() => { 15 | // 开始监听网络状态的变化 16 | window.addEventListener("online", showStatus); 17 | window.addEventListener("offline", showStatus); 18 | }); 19 | 20 | onUnmounted(() => { 21 | // 移除监听网络状态的变化 22 | window.removeEventListener("online", showStatus); 23 | window.removeEventListener("offline", showStatus); 24 | }); 25 | 26 | return { online }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/hooks/useSelection.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from "vue"; 2 | 3 | /** 4 | * @description 表格多选数据操作 5 | * @param {String} rowKey 当表格可以多选时,所指定的 id 6 | * */ 7 | export const useSelection = (rowKey: string = "id") => { 8 | const isSelected = ref(false); 9 | const selectedList = ref<{ [key: string]: any }[]>([]); 10 | 11 | // 当前选中的所有 ids 数组 12 | const selectedListIds = computed((): string[] => { 13 | let ids: string[] = []; 14 | selectedList.value.forEach(item => ids.push(item[rowKey])); 15 | return ids; 16 | }); 17 | 18 | /** 19 | * @description 多选操作 20 | * @param {Array} rowArr 当前选择的所有数据 21 | * @return void 22 | */ 23 | const selectionChange = (rowArr: { [key: string]: any }[]) => { 24 | rowArr.length ? (isSelected.value = true) : (isSelected.value = false); 25 | selectedList.value = rowArr; 26 | }; 27 | 28 | return { 29 | isSelected, 30 | selectedList, 31 | selectedListIds, 32 | selectionChange 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/hooks/useTime.ts: -------------------------------------------------------------------------------- 1 | import { ref } from "vue"; 2 | 3 | /** 4 | * @description 获取本地时间 5 | */ 6 | export const useTime = () => { 7 | const year = ref(0); // 年份 8 | const month = ref(0); // 月份 9 | const week = ref(""); // 星期几 10 | const day = ref(0); // 天数 11 | const hour = ref(0); // 小时 12 | const minute = ref(0); // 分钟 13 | const second = ref(0); // 秒 14 | const nowTime = ref(""); // 当前时间 15 | 16 | // 更新时间 17 | const updateTime = () => { 18 | const date = new Date(); 19 | year.value = date.getFullYear(); 20 | month.value = date.getMonth() + 1; 21 | week.value = "日一二三四五六".charAt(date.getDay()); 22 | day.value = date.getDate(); 23 | hour.value = 24 | (date.getHours() + "")?.padStart(2, "0") || 25 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getHours()); 26 | minute.value = 27 | (date.getMinutes() + "")?.padStart(2, "0") || 28 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getMinutes()); 29 | second.value = 30 | (date.getSeconds() + "")?.padStart(2, "0") || 31 | new Intl.NumberFormat(undefined, { minimumIntegerDigits: 2 }).format(date.getSeconds()); 32 | nowTime.value = `${year.value}年${month.value}月${day.value} ${hour.value}:${minute.value}:${second.value}`; 33 | }; 34 | 35 | updateTime(); 36 | 37 | return { year, month, day, hour, minute, second, week, nowTime }; 38 | }; 39 | -------------------------------------------------------------------------------- /src/languages/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from "vue-i18n"; 2 | import { getBrowserLang } from "@/utils"; 3 | 4 | import zh from "./modules/zh"; 5 | import en from "./modules/en"; 6 | 7 | const i18n = createI18n({ 8 | // Use Composition API, Set to false 9 | allowComposition: true, 10 | legacy: false, 11 | locale: getBrowserLang(), 12 | messages: { 13 | zh, 14 | en 15 | } 16 | }); 17 | 18 | export default i18n; 19 | -------------------------------------------------------------------------------- /src/languages/modules/en.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | welcome: "Welcome" 4 | }, 5 | tabs: { 6 | refresh: "Refresh", 7 | maximize: "Maximize", 8 | closeCurrent: "Close current", 9 | closeLeft: "Close Left", 10 | closeRight: "Close Right", 11 | closeOther: "Close other", 12 | closeAll: "Close All" 13 | }, 14 | header: { 15 | componentSize: "Component size", 16 | language: "Language", 17 | theme: "theme", 18 | layoutConfig: "Layout config", 19 | primary: "primary", 20 | darkMode: "Dark Mode", 21 | greyMode: "Grey mode", 22 | weakMode: "Weak mode", 23 | fullScreen: "Full Screen", 24 | exitFullScreen: "Exit Full Screen", 25 | personalData: "Personal Data", 26 | changePassword: "Change Password", 27 | logout: "Logout" 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/languages/modules/zh.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | home: { 3 | welcome: "欢迎使用" 4 | }, 5 | tabs: { 6 | refresh: "刷新", 7 | maximize: "最大化", 8 | closeCurrent: "关闭当前", 9 | closeLeft: "关闭左侧", 10 | closeRight: "关闭右侧", 11 | closeOther: "关闭其它", 12 | closeAll: "关闭所有" 13 | }, 14 | header: { 15 | componentSize: "组件大小", 16 | language: "国际化", 17 | theme: "全局主题", 18 | layoutConfig: "布局设置", 19 | primary: "primary", 20 | darkMode: "暗黑模式", 21 | greyMode: "灰色模式", 22 | weakMode: "色弱模式", 23 | fullScreen: "全屏", 24 | exitFullScreen: "退出全屏", 25 | personalData: "个人信息", 26 | changePassword: "修改密码", 27 | logout: "退出登录" 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /src/layouts/LayoutClassic/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | :deep(.el-header) { 5 | box-sizing: border-box; 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | height: 55px; 10 | padding: 0 15px 0 0; 11 | background-color: var(--el-header-bg-color); 12 | border-bottom: 1px solid var(--el-header-border-color); 13 | .header-lf { 14 | display: flex; 15 | align-items: center; 16 | overflow: hidden; 17 | white-space: nowrap; 18 | .logo { 19 | flex-shrink: 0; 20 | width: 210px; 21 | margin-right: 16px; 22 | .logo-img { 23 | width: 28px; 24 | object-fit: contain; 25 | } 26 | .logo-text { 27 | margin-left: 6px; 28 | font-size: 21.5px; 29 | font-weight: bold; 30 | color: var(--el-header-logo-text-color); 31 | white-space: nowrap; 32 | } 33 | } 34 | } 35 | } 36 | .classic-content { 37 | display: flex; 38 | height: calc(100% - 55px); 39 | :deep(.el-aside) { 40 | width: auto; 41 | background-color: var(--el-menu-bg-color); 42 | border-right: 1px solid var(--el-aside-border-color); 43 | .aside-box { 44 | display: flex; 45 | flex-direction: column; 46 | height: 100%; 47 | transition: width 0.3s ease; 48 | .el-menu { 49 | width: 100%; 50 | overflow-x: hidden; 51 | border-right: none; 52 | } 53 | } 54 | } 55 | .classic-main { 56 | display: flex; 57 | flex-direction: column; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/layouts/LayoutClassic/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 38 | 39 | 59 | 60 | 63 | -------------------------------------------------------------------------------- /src/layouts/LayoutColumns/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | .aside-split { 5 | display: flex; 6 | flex-direction: column; 7 | flex-shrink: 0; 8 | width: 70px; 9 | height: 100%; 10 | background-color: var(--el-menu-bg-color); 11 | border-right: 1px solid var(--el-aside-border-color); 12 | .logo { 13 | box-sizing: border-box; 14 | height: 55px; 15 | .logo-img { 16 | width: 32px; 17 | object-fit: contain; 18 | } 19 | } 20 | .el-scrollbar { 21 | height: calc(100% - 55px); 22 | .split-list { 23 | flex: 1; 24 | .split-item { 25 | display: flex; 26 | flex-direction: column; 27 | align-items: center; 28 | justify-content: center; 29 | height: 70px; 30 | cursor: pointer; 31 | transition: all 0.3s ease; 32 | .el-icon { 33 | font-size: 20px; 34 | } 35 | .title { 36 | margin-top: 6px; 37 | font-size: 12px; 38 | } 39 | .el-icon, 40 | .title { 41 | color: var(--el-menu-text-color); 42 | } 43 | } 44 | .split-active { 45 | background-color: var(--el-color-primary) !important; 46 | .el-icon, 47 | .title { 48 | color: #ffffff !important; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | .not-aside { 55 | width: 0 !important; 56 | border-right: none !important; 57 | } 58 | .el-aside { 59 | display: flex; 60 | flex-direction: column; 61 | height: 100%; 62 | overflow: hidden; 63 | background-color: var(--el-menu-bg-color); 64 | border-right: 1px solid var(--el-aside-border-color); 65 | transition: width 0.3s ease; 66 | .el-scrollbar { 67 | height: calc(100% - 55px); 68 | .el-menu { 69 | width: 100%; 70 | overflow-x: hidden; 71 | border-right: none; 72 | } 73 | } 74 | .logo { 75 | box-sizing: border-box; 76 | height: 55px; 77 | .logo-text { 78 | font-size: 24px; 79 | font-weight: bold; 80 | color: var(--el-aside-logo-text-color); 81 | white-space: nowrap; 82 | } 83 | } 84 | } 85 | .el-header { 86 | box-sizing: border-box; 87 | display: flex; 88 | align-items: center; 89 | justify-content: space-between; 90 | height: 55px; 91 | padding: 0 15px; 92 | background-color: var(--el-header-bg-color); 93 | border-bottom: 1px solid var(--el-border-color-light); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/layouts/LayoutTransverse/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | :deep(.el-header) { 5 | box-sizing: border-box; 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | height: 55px; 10 | padding: 0 15px 0 0; 11 | background-color: var(--el-header-bg-color); 12 | border-bottom: 1px solid var(--el-header-border-color); 13 | .logo { 14 | width: 210px; 15 | margin-right: 30px; 16 | .logo-img { 17 | width: 28px; 18 | object-fit: contain; 19 | } 20 | .logo-text { 21 | margin-left: 6px; 22 | font-size: 21.5px; 23 | font-weight: bold; 24 | color: var(--el-header-logo-text-color); 25 | white-space: nowrap; 26 | } 27 | } 28 | .el-menu { 29 | flex: 1; 30 | height: 100%; 31 | overflow: hidden; 32 | border-bottom: none; 33 | .el-sub-menu__hide-arrow { 34 | width: 65px; 35 | height: 55px; 36 | } 37 | .el-menu-item.is-active { 38 | color: #ffffff !important; 39 | } 40 | .is-active { 41 | background-color: var(--el-color-primary) !important; 42 | border-bottom-color: var(--el-color-primary) !important; 43 | &::before { 44 | width: 0; 45 | } 46 | .el-sub-menu__title { 47 | color: #ffffff !important; 48 | background-color: var(--el-color-primary) !important; 49 | border-bottom-color: var(--el-color-primary) !important; 50 | } 51 | } 52 | } 53 | } 54 | 55 | @media screen and (width <= 730px) { 56 | .logo { 57 | display: none !important; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/layouts/LayoutTransverse/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 36 | 37 | 58 | 59 | 62 | -------------------------------------------------------------------------------- /src/layouts/LayoutVertical/index.scss: -------------------------------------------------------------------------------- 1 | .el-container { 2 | width: 100%; 3 | height: 100%; 4 | :deep(.el-aside) { 5 | width: auto; 6 | background-color: var(--el-menu-bg-color); 7 | border-right: 1px solid var(--el-aside-border-color); 8 | .aside-box { 9 | display: flex; 10 | flex-direction: column; 11 | height: 100%; 12 | transition: width 0.3s ease; 13 | .el-scrollbar { 14 | height: calc(100% - 55px); 15 | .el-menu { 16 | width: 100%; 17 | overflow-x: hidden; 18 | border-right: none; 19 | } 20 | } 21 | .logo { 22 | box-sizing: border-box; 23 | height: 55px; 24 | .logo-img { 25 | width: 28px; 26 | object-fit: contain; 27 | } 28 | .logo-text { 29 | margin-left: 6px; 30 | font-size: 21.5px; 31 | font-weight: bold; 32 | color: var(--el-aside-logo-text-color); 33 | white-space: nowrap; 34 | } 35 | } 36 | } 37 | } 38 | .el-header { 39 | box-sizing: border-box; 40 | display: flex; 41 | align-items: center; 42 | justify-content: space-between; 43 | height: 55px; 44 | padding: 0 15px; 45 | background-color: var(--el-header-bg-color); 46 | border-bottom: 1px solid var(--el-header-border-color); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/layouts/LayoutVertical/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 32 | 33 | 53 | 54 | 57 | -------------------------------------------------------------------------------- /src/layouts/components/Footer/index.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | height: 30px; 3 | background-color: var(--el-bg-color); 4 | border-top: 1px solid var(--el-border-color-light); 5 | a { 6 | font-size: 14px; 7 | color: var(--el-text-color-secondary); 8 | text-decoration: none; 9 | letter-spacing: 0.5px; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/layouts/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 10 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarLeft.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 24 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarRight.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 52 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/AssemblySize.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 38 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Avatar.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 66 | 67 | 80 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/CollapseIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 26 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/InfoDialog.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Language.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/PasswordDialog.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 23 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/ThemeSetting.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | -------------------------------------------------------------------------------- /src/layouts/components/Main/components/Maximize.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 40 | -------------------------------------------------------------------------------- /src/layouts/components/Main/index.scss: -------------------------------------------------------------------------------- 1 | .el-main { 2 | box-sizing: border-box; 3 | padding: 10px 12px; 4 | overflow-x: hidden; 5 | background-color: var(--el-bg-color-page); 6 | } 7 | .el-footer { 8 | height: auto; 9 | padding: 0; 10 | } 11 | -------------------------------------------------------------------------------- /src/layouts/components/Menu/SubMenu.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 34 | 35 | 86 | -------------------------------------------------------------------------------- /src/layouts/components/Tabs/index.scss: -------------------------------------------------------------------------------- 1 | .tabs-box { 2 | background-color: var(--el-bg-color); 3 | .tabs-menu { 4 | position: relative; 5 | width: 100%; 6 | .el-dropdown { 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | .more-button { 12 | display: flex; 13 | align-items: center; 14 | justify-content: center; 15 | width: 43px; 16 | cursor: pointer; 17 | border-left: 1px solid var(--el-border-color-light); 18 | transition: all 0.3s; 19 | &:hover { 20 | background-color: var(--el-color-info-light-9); 21 | } 22 | .iconfont { 23 | font-size: 12.5px; 24 | } 25 | } 26 | } 27 | :deep(.el-tabs) { 28 | .el-tabs__header { 29 | box-sizing: border-box; 30 | height: 40px; 31 | padding: 0 10px; 32 | margin: 0; 33 | .el-tabs__nav-wrap { 34 | position: absolute; 35 | width: calc(100% - 70px); 36 | .el-tabs__nav { 37 | display: flex; 38 | border: none; 39 | .el-tabs__item { 40 | display: flex; 41 | align-items: center; 42 | justify-content: center; 43 | color: #afafaf; 44 | border: none; 45 | .tabs-icon { 46 | margin: 1.5px 4px 0 0; 47 | font-size: 15px; 48 | } 49 | .is-icon-close { 50 | margin-top: 1px; 51 | } 52 | &.is-active { 53 | color: var(--el-color-primary); 54 | &::before { 55 | position: absolute; 56 | bottom: 0; 57 | width: 100%; 58 | height: 0; 59 | content: ""; 60 | border-bottom: 2px solid var(--el-color-primary) !important; 61 | } 62 | } 63 | } 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/layouts/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 37 | 38 | 43 | -------------------------------------------------------------------------------- /src/layouts/indexAsync.vue: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 41 | 42 | 47 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue"; 2 | import App from "./App.vue"; 3 | // reset style sheet 4 | import "@/styles/reset.scss"; 5 | // CSS common style sheet 6 | import "@/styles/common.scss"; 7 | // iconfont css 8 | import "@/assets/iconfont/iconfont.scss"; 9 | // font css 10 | import "@/assets/fonts/font.scss"; 11 | // element css 12 | import "element-plus/dist/index.css"; 13 | // element dark css 14 | import "element-plus/theme-chalk/dark/css-vars.css"; 15 | // custom element dark css 16 | import "@/styles/element-dark.scss"; 17 | // custom element css 18 | import "@/styles/element.scss"; 19 | // svg icons 20 | import "virtual:svg-icons-register"; 21 | // element plus 22 | import ElementPlus from "element-plus"; 23 | // element icons 24 | import * as Icons from "@element-plus/icons-vue"; 25 | // custom directives 26 | import directives from "@/directives/index"; 27 | // vue Router 28 | import router from "@/routers"; 29 | // vue i18n 30 | import I18n from "@/languages/index"; 31 | // pinia store 32 | import pinia from "@/stores"; 33 | // errorHandler 34 | import errorHandler from "@/utils/errorHandler"; 35 | 36 | const app = createApp(App); 37 | 38 | app.config.errorHandler = errorHandler; 39 | 40 | // register the element Icons component 41 | Object.keys(Icons).forEach(key => { 42 | app.component(key, Icons[key as keyof typeof Icons]); 43 | }); 44 | 45 | app.use(ElementPlus).use(directives).use(router).use(I18n).use(pinia).mount("#app"); 46 | -------------------------------------------------------------------------------- /src/routers/modules/dynamicRouter.ts: -------------------------------------------------------------------------------- 1 | import router from "@/routers/index"; 2 | import { LOGIN_URL } from "@/config"; 3 | import { RouteRecordRaw } from "vue-router"; 4 | import { ElNotification } from "element-plus"; 5 | import { useUserStore } from "@/stores/modules/user"; 6 | import { useAuthStore } from "@/stores/modules/auth"; 7 | 8 | // 引入 views 文件夹下所有 vue 文件 9 | const modules = import.meta.glob("@/views/**/*.vue"); 10 | 11 | /** 12 | * @description 初始化动态路由 13 | */ 14 | export const initDynamicRouter = async () => { 15 | const userStore = useUserStore(); 16 | const authStore = useAuthStore(); 17 | 18 | try { 19 | // 1.获取菜单列表 && 按钮权限列表 20 | await authStore.getAuthMenuList(); 21 | await authStore.getAuthButtonList(); 22 | 23 | // 2.判断当前用户有没有菜单权限 24 | if (!authStore.authMenuListGet.length) { 25 | ElNotification({ 26 | title: "无权限访问", 27 | message: "当前账号无任何菜单权限,请联系系统管理员!", 28 | type: "warning", 29 | duration: 3000 30 | }); 31 | userStore.setToken(""); 32 | router.replace(LOGIN_URL); 33 | return Promise.reject("No permission"); 34 | } 35 | 36 | // 3.添加动态路由 37 | authStore.flatMenuListGet.forEach(item => { 38 | item.children && delete item.children; 39 | if (item.component && typeof item.component == "string") { 40 | item.component = modules["/src/views" + item.component + ".vue"]; 41 | } 42 | if (item.meta.isFull) { 43 | router.addRoute(item as unknown as RouteRecordRaw); 44 | } else { 45 | router.addRoute("layout", item as unknown as RouteRecordRaw); 46 | } 47 | }); 48 | } catch (error) { 49 | // 当按钮 || 菜单请求出错时,重定向到登陆页 50 | userStore.setToken(""); 51 | router.replace(LOGIN_URL); 52 | return Promise.reject(error); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /src/routers/modules/staticRouter.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from "vue-router"; 2 | import { HOME_URL, LOGIN_URL } from "@/config"; 3 | 4 | /** 5 | * staticRouter (静态路由) 6 | */ 7 | export const staticRouter: RouteRecordRaw[] = [ 8 | { 9 | path: "/", 10 | redirect: HOME_URL 11 | }, 12 | { 13 | path: LOGIN_URL, 14 | name: "login", 15 | component: () => import("@/views/login/index.vue"), 16 | meta: { 17 | title: "登录" 18 | } 19 | }, 20 | { 21 | path: "/layout", 22 | name: "layout", 23 | component: () => import("@/layouts/index.vue"), 24 | // component: () => import("@/layouts/indexAsync.vue"), 25 | redirect: HOME_URL, 26 | children: [] 27 | } 28 | ]; 29 | 30 | /** 31 | * errorRouter (错误页面路由) 32 | */ 33 | export const errorRouter = [ 34 | { 35 | path: "/403", 36 | name: "403", 37 | component: () => import("@/components/ErrorMessage/403.vue"), 38 | meta: { 39 | title: "403页面" 40 | } 41 | }, 42 | { 43 | path: "/404", 44 | name: "404", 45 | component: () => import("@/components/ErrorMessage/404.vue"), 46 | meta: { 47 | title: "404页面" 48 | } 49 | }, 50 | { 51 | path: "/500", 52 | name: "500", 53 | component: () => import("@/components/ErrorMessage/500.vue"), 54 | meta: { 55 | title: "500页面" 56 | } 57 | }, 58 | // Resolve refresh page, route warnings 59 | { 60 | path: "/:pathMatch(.*)*", 61 | component: () => import("@/components/ErrorMessage/404.vue") 62 | } 63 | ]; 64 | -------------------------------------------------------------------------------- /src/stores/helper/persist.ts: -------------------------------------------------------------------------------- 1 | import { PersistedStateOptions } from "pinia-plugin-persistedstate"; 2 | 3 | /** 4 | * @description pinia 持久化参数配置 5 | * @param {String} key 存储到持久化的 name 6 | * @param {Array} paths 需要持久化的 state name 7 | * @return persist 8 | * */ 9 | const piniaPersistConfig = (key: string, paths?: string[]) => { 10 | const persist: PersistedStateOptions = { 11 | key, 12 | storage: localStorage, 13 | // storage: sessionStorage, 14 | paths 15 | }; 16 | return persist; 17 | }; 18 | 19 | export default piniaPersistConfig; 20 | -------------------------------------------------------------------------------- /src/stores/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from "pinia"; 2 | import piniaPluginPersistedstate from "pinia-plugin-persistedstate"; 3 | 4 | // pinia persist 5 | const pinia = createPinia(); 6 | pinia.use(piniaPluginPersistedstate); 7 | 8 | export default pinia; 9 | -------------------------------------------------------------------------------- /src/stores/interface/index.ts: -------------------------------------------------------------------------------- 1 | export type LayoutType = "vertical" | "classic" | "transverse" | "columns"; 2 | 3 | export type AssemblySizeType = "large" | "default" | "small"; 4 | 5 | export type LanguageType = "zh" | "en" | null; 6 | 7 | /* GlobalState */ 8 | export interface GlobalState { 9 | layout: LayoutType; 10 | assemblySize: AssemblySizeType; 11 | language: LanguageType; 12 | maximize: boolean; 13 | primary: string; 14 | isDark: boolean; 15 | isGrey: boolean; 16 | isWeak: boolean; 17 | asideInverted: boolean; 18 | headerInverted: boolean; 19 | isCollapse: boolean; 20 | accordion: boolean; 21 | watermark: boolean; 22 | breadcrumb: boolean; 23 | breadcrumbIcon: boolean; 24 | tabs: boolean; 25 | tabsIcon: boolean; 26 | footer: boolean; 27 | } 28 | 29 | /* UserState */ 30 | export interface UserState { 31 | token: string; 32 | userInfo: { name: string }; 33 | } 34 | 35 | /* tabsMenuProps */ 36 | export interface TabsMenuProps { 37 | icon: string; 38 | title: string; 39 | path: string; 40 | name: string; 41 | close: boolean; 42 | isKeepAlive: boolean; 43 | } 44 | 45 | /* TabsState */ 46 | export interface TabsState { 47 | tabsMenuList: TabsMenuProps[]; 48 | } 49 | 50 | /* AuthState */ 51 | export interface AuthState { 52 | routeName: string; 53 | authButtonList: { 54 | [key: string]: string[]; 55 | }; 56 | authMenuList: Menu.MenuOptions[]; 57 | } 58 | 59 | /* KeepAliveState */ 60 | export interface KeepAliveState { 61 | keepAliveName: string[]; 62 | } 63 | -------------------------------------------------------------------------------- /src/stores/modules/auth.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { AuthState } from "@/stores/interface"; 3 | import { getAuthButtonListApi, getAuthMenuListApi } from "@/api/modules/login"; 4 | import { getFlatMenuList, getShowMenuList, getAllBreadcrumbList } from "@/utils"; 5 | 6 | export const useAuthStore = defineStore({ 7 | id: "geeker-auth", 8 | state: (): AuthState => ({ 9 | // 按钮权限列表 10 | authButtonList: {}, 11 | // 菜单权限列表 12 | authMenuList: [], 13 | // 当前页面的 router name,用来做按钮权限筛选 14 | routeName: "" 15 | }), 16 | getters: { 17 | // 按钮权限列表 18 | authButtonListGet: state => state.authButtonList, 19 | // 菜单权限列表 ==> 这里的菜单没有经过任何处理 20 | authMenuListGet: state => state.authMenuList, 21 | // 菜单权限列表 ==> 左侧菜单栏渲染,需要剔除 isHide == true 22 | showMenuListGet: state => getShowMenuList(state.authMenuList), 23 | // 菜单权限列表 ==> 扁平化之后的一维数组菜单,主要用来添加动态路由 24 | flatMenuListGet: state => getFlatMenuList(state.authMenuList), 25 | // 递归处理后的所有面包屑导航列表 26 | breadcrumbListGet: state => getAllBreadcrumbList(state.authMenuList) 27 | }, 28 | actions: { 29 | // Get AuthButtonList 30 | async getAuthButtonList() { 31 | const { data } = await getAuthButtonListApi(); 32 | this.authButtonList = data; 33 | }, 34 | // Get AuthMenuList 35 | async getAuthMenuList() { 36 | const { data } = await getAuthMenuListApi(); 37 | this.authMenuList = data; 38 | }, 39 | // Set RouteName 40 | async setRouteName(name: string) { 41 | this.routeName = name; 42 | } 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/stores/modules/global.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { GlobalState } from "@/stores/interface"; 3 | import { DEFAULT_PRIMARY } from "@/config"; 4 | import piniaPersistConfig from "@/stores/helper/persist"; 5 | 6 | export const useGlobalStore = defineStore({ 7 | id: "geeker-global", 8 | // 修改默认值之后,需清除 localStorage 数据 9 | state: (): GlobalState => ({ 10 | // 布局模式 (纵向:vertical | 经典:classic | 横向:transverse | 分栏:columns) 11 | layout: "vertical", 12 | // element 组件大小 13 | assemblySize: "default", 14 | // 当前系统语言 15 | language: null, 16 | // 当前页面是否全屏 17 | maximize: false, 18 | // 主题颜色 19 | primary: DEFAULT_PRIMARY, 20 | // 深色模式 21 | isDark: false, 22 | // 灰色模式 23 | isGrey: false, 24 | // 色弱模式 25 | isWeak: false, 26 | // 侧边栏反转 27 | asideInverted: false, 28 | // 头部反转 29 | headerInverted: false, 30 | // 折叠菜单 31 | isCollapse: false, 32 | // 菜单手风琴 33 | accordion: true, 34 | // 页面水印 35 | watermark: false, 36 | // 面包屑导航 37 | breadcrumb: true, 38 | // 面包屑导航图标 39 | breadcrumbIcon: true, 40 | // 标签页 41 | tabs: true, 42 | // 标签页图标 43 | tabsIcon: true, 44 | // 页脚 45 | footer: true 46 | }), 47 | getters: {}, 48 | actions: { 49 | // Set GlobalState 50 | setGlobalState(...args: ObjToKeyValArray) { 51 | this.$patch({ [args[0]]: args[1] }); 52 | } 53 | }, 54 | persist: piniaPersistConfig("geeker-global") 55 | }); 56 | -------------------------------------------------------------------------------- /src/stores/modules/keepAlive.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { KeepAliveState } from "@/stores/interface"; 3 | 4 | export const useKeepAliveStore = defineStore({ 5 | id: "geeker-keepAlive", 6 | state: (): KeepAliveState => ({ 7 | keepAliveName: [] 8 | }), 9 | actions: { 10 | // Add KeepAliveName 11 | async addKeepAliveName(name: string) { 12 | !this.keepAliveName.includes(name) && this.keepAliveName.push(name); 13 | }, 14 | // Remove KeepAliveName 15 | async removeKeepAliveName(name: string) { 16 | this.keepAliveName = this.keepAliveName.filter(item => item !== name); 17 | }, 18 | // Set KeepAliveName 19 | async setKeepAliveName(keepAliveName: string[] = []) { 20 | this.keepAliveName = keepAliveName; 21 | } 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /src/stores/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from "pinia"; 2 | import { UserState } from "@/stores/interface"; 3 | import piniaPersistConfig from "@/stores/helper/persist"; 4 | 5 | export const useUserStore = defineStore({ 6 | id: "geeker-user", 7 | state: (): UserState => ({ 8 | token: "", 9 | userInfo: { name: "Geeker" } 10 | }), 11 | getters: {}, 12 | actions: { 13 | // Set Token 14 | setToken(token: string) { 15 | this.token = token; 16 | }, 17 | // Set setUserInfo 18 | setUserInfo(userInfo: UserState["userInfo"]) { 19 | this.userInfo = userInfo; 20 | } 21 | }, 22 | persist: piniaPersistConfig("geeker-user") 23 | }); 24 | -------------------------------------------------------------------------------- /src/styles/element-dark.scss: -------------------------------------------------------------------------------- 1 | /* 自定义 element 暗黑模式 */ 2 | html.dark { 3 | /* wangEditor */ 4 | --w-e-toolbar-color: #eeeeee; 5 | --w-e-toolbar-bg-color: #141414; 6 | --w-e-textarea-bg-color: #141414; 7 | --w-e-textarea-color: #eeeeee; 8 | --w-e-toolbar-active-bg-color: #464646; 9 | --w-e-toolbar-border-color: var(--el-border-color-darker); 10 | .w-e-bar-item button:hover, 11 | .w-e-menu-tooltip-v5::before { 12 | color: #eeeeee; 13 | } 14 | 15 | /* login */ 16 | .login-container { 17 | background-color: #191919 !important; 18 | .login-box { 19 | background-color: rgb(0 0 0 / 80%) !important; 20 | .login-form { 21 | box-shadow: rgb(255 255 255 / 12%) 0 2px 10px 2px !important; 22 | .logo-text { 23 | color: var(--el-text-color-primary) !important; 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | /* Reset style sheet */ 2 | 3 | /* 目前项目中使用富文本编辑器需要注释,如果你项目中没有使用富文本编辑器,可以取消注释 */ 4 | // html, 5 | // body, 6 | // div, 7 | // span, 8 | // applet, 9 | // object, 10 | // iframe, 11 | // h1, 12 | // h2, 13 | // h3, 14 | // h4, 15 | // h5, 16 | // h6, 17 | // p, 18 | // blockquote, 19 | // pre, 20 | // a, 21 | // abbr, 22 | // acronym, 23 | // address, 24 | // big, 25 | // cite, 26 | // code, 27 | // del, 28 | // dfn, 29 | // em, 30 | // img, 31 | // ins, 32 | // kbd, 33 | // q, 34 | // s, 35 | // samp, 36 | // small, 37 | // strike, 38 | // strong, 39 | // sub, 40 | // sup, 41 | // tt, 42 | // var, 43 | // b, 44 | // u, 45 | // i, 46 | // center, 47 | // dl, 48 | // dt, 49 | // dd, 50 | // ol, 51 | // ul, 52 | // li, 53 | // fieldset, 54 | // form, 55 | // label, 56 | // legend, 57 | // table, 58 | // caption, 59 | // tbody, 60 | // tfoot, 61 | // thead, 62 | // tr, 63 | // th, 64 | // td, 65 | // article, 66 | // aside, 67 | // canvas, 68 | // details, 69 | // embed, 70 | // figure, 71 | // figcaption, 72 | // footer, 73 | // header, 74 | // hgroup, 75 | // menu, 76 | // nav, 77 | // output, 78 | // ruby, 79 | // section, 80 | // summary, 81 | // time, 82 | // mark, 83 | // audio, 84 | // video { 85 | // padding: 0; 86 | // margin: 0; 87 | // font: inherit; 88 | // font-size: 100%; 89 | // vertical-align: baseline; 90 | // border: 0; 91 | // } 92 | 93 | // /* HTML5 display-role reset for older browsers */ 94 | // article, 95 | // aside, 96 | // details, 97 | // figcaption, 98 | // figure, 99 | // footer, 100 | // header, 101 | // hgroup, 102 | // menu, 103 | // nav, 104 | // section { 105 | // display: block; 106 | // } 107 | // body { 108 | // padding: 0; 109 | // margin: 0; 110 | // } 111 | // ol, 112 | // ul { 113 | // list-style: none; 114 | // } 115 | // blockquote, 116 | // q { 117 | // quotes: none; 118 | // } 119 | // blockquote::before, 120 | // blockquote::after, 121 | // q::before, 122 | // q::after { 123 | // content: ""; 124 | // content: none; 125 | // } 126 | // table { 127 | // border-spacing: 0; 128 | // border-collapse: collapse; 129 | // } 130 | html, 131 | body, 132 | #app, 133 | #watermark { 134 | width: 100%; 135 | height: 100%; 136 | padding: 0; 137 | margin: 0; 138 | } 139 | 140 | /* 解决 h1 标签在 webkit 内核浏览器中文字大小失效问题 */ 141 | :-webkit-any(article, aside, nav, section) h1 { 142 | font-size: 2em; 143 | } 144 | -------------------------------------------------------------------------------- /src/styles/theme/aside.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@/hooks/interface"; 2 | 3 | export const asideTheme: Record = { 4 | light: { 5 | "--el-aside-logo-text-color": "#303133", 6 | "--el-aside-border-color": "#e4e7ed" 7 | }, 8 | inverted: { 9 | "--el-aside-logo-text-color": "#dadada", 10 | "--el-aside-border-color": "#414243" 11 | }, 12 | dark: { 13 | "--el-aside-logo-text-color": "#dadada", 14 | "--el-aside-border-color": "#414243" 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/styles/theme/header.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@/hooks/interface"; 2 | 3 | export const headerTheme: Record = { 4 | light: { 5 | "--el-header-logo-text-color": "#303133", 6 | "--el-header-bg-color": "#ffffff", 7 | "--el-header-text-color": "#303133", 8 | "--el-header-text-color-regular": "#606266", 9 | "--el-header-border-color": "#e4e7ed" 10 | }, 11 | inverted: { 12 | "--el-header-logo-text-color": "#dadada", 13 | "--el-header-bg-color": "#191a20", 14 | "--el-header-text-color": "#e5eaf3", 15 | "--el-header-text-color-regular": "#cfd3dc", 16 | "--el-header-border-color": "#414243" 17 | }, 18 | dark: { 19 | "--el-header-logo-text-color": "#dadada", 20 | "--el-header-bg-color": "#141414", 21 | "--el-header-text-color": "#e5eaf3", 22 | "--el-header-text-color-regular": "#cfd3dc", 23 | "--el-header-border-color": "#414243" 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /src/styles/theme/menu.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@/hooks/interface"; 2 | 3 | export const menuTheme: Record = { 4 | light: { 5 | "--el-menu-bg-color": "#ffffff", 6 | "--el-menu-hover-bg-color": "#cccccc", 7 | "--el-menu-active-bg-color": "var(--el-color-primary-light-9)", 8 | "--el-menu-text-color": "#333333", 9 | "--el-menu-active-color": "var(--el-color-primary)", 10 | "--el-menu-hover-text-color": "#333333", 11 | "--el-menu-horizontal-sub-item-height": "50px" 12 | }, 13 | inverted: { 14 | "--el-menu-bg-color": "#191a20", 15 | "--el-menu-hover-bg-color": "#000000", 16 | "--el-menu-active-bg-color": "#000000", 17 | "--el-menu-text-color": "#bdbdc0", 18 | "--el-menu-active-color": "#ffffff", 19 | "--el-menu-hover-text-color": "#ffffff", 20 | "--el-menu-horizontal-sub-item-height": "50px" 21 | }, 22 | dark: { 23 | "--el-menu-bg-color": "#141414", 24 | "--el-menu-hover-bg-color": "#000000", 25 | "--el-menu-active-bg-color": "#000000", 26 | "--el-menu-text-color": "#bdbdc0", 27 | "--el-menu-active-color": "#ffffff", 28 | "--el-menu-hover-text-color": "#ffffff", 29 | "--el-menu-horizontal-sub-item-height": "50px" 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/styles/var.scss: -------------------------------------------------------------------------------- 1 | /* global css variable */ 2 | $primary-color: var(--el-color-primary); 3 | -------------------------------------------------------------------------------- /src/typings/global.d.ts: -------------------------------------------------------------------------------- 1 | /* Menu */ 2 | declare namespace Menu { 3 | interface MenuOptions { 4 | path: string; 5 | name: string; 6 | component?: string | (() => Promise); 7 | redirect?: string; 8 | meta: MetaProps; 9 | children?: MenuOptions[]; 10 | } 11 | interface MetaProps { 12 | icon: string; 13 | title: string; 14 | activeMenu?: string; 15 | isLink?: string; 16 | isHide: boolean; 17 | isFull: boolean; 18 | isAffix: boolean; 19 | isKeepAlive: boolean; 20 | } 21 | } 22 | 23 | /* FileType */ 24 | declare namespace File { 25 | type ImageMimeType = 26 | | "image/apng" 27 | | "image/bmp" 28 | | "image/gif" 29 | | "image/jpeg" 30 | | "image/pjpeg" 31 | | "image/png" 32 | | "image/svg+xml" 33 | | "image/tiff" 34 | | "image/webp" 35 | | "image/x-icon"; 36 | 37 | type ExcelMimeType = "application/vnd.ms-excel" | "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; 38 | } 39 | 40 | /* Vite */ 41 | declare type Recordable = Record; 42 | 43 | declare interface ViteEnv { 44 | VITE_USER_NODE_ENV: "development" | "production" | "test"; 45 | VITE_GLOB_APP_TITLE: string; 46 | VITE_PORT: number; 47 | VITE_OPEN: boolean; 48 | VITE_REPORT: boolean; 49 | VITE_ROUTER_MODE: "hash" | "history"; 50 | VITE_BUILD_COMPRESS: "gzip" | "brotli" | "gzip,brotli" | "none"; 51 | VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean; 52 | VITE_DROP_CONSOLE: boolean; 53 | VITE_PWA: boolean; 54 | VITE_DEVTOOLS: boolean; 55 | VITE_PUBLIC_PATH: string; 56 | VITE_API_URL: string; 57 | VITE_PROXY: [string, string][]; 58 | VITE_CODEINSPECTOR: boolean; 59 | } 60 | 61 | interface ImportMetaEnv extends ViteEnv { 62 | __: unknown; 63 | } 64 | 65 | /* __APP_INFO__ */ 66 | declare const __APP_INFO__: { 67 | pkg: { 68 | name: string; 69 | version: string; 70 | dependencies: Recordable; 71 | devDependencies: Recordable; 72 | }; 73 | lastBuildTime: string; 74 | }; 75 | -------------------------------------------------------------------------------- /src/typings/utils.d.ts: -------------------------------------------------------------------------------- 1 | type ObjToKeyValUnion = { 2 | [K in keyof T]: { key: K; value: T[K] }; 3 | }[keyof T]; 4 | 5 | type ObjToKeyValArray = { 6 | [K in keyof T]: [K, T[K]]; 7 | }[keyof T]; 8 | 9 | type ObjToSelectedValueUnion = { 10 | [K in keyof T]: T[K]; 11 | }[keyof T]; 12 | 13 | type Optional = Omit & Partial>; 14 | 15 | type GetOptional = { 16 | [P in keyof T as T[P] extends Required[P] ? never : P]: T[P]; 17 | }; 18 | -------------------------------------------------------------------------------- /src/typings/window.d.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Navigator { 3 | msSaveOrOpenBlob: (blob: Blob, fileName: string) => void; 4 | browserLanguage: string; 5 | } 6 | } 7 | 8 | export {}; 9 | -------------------------------------------------------------------------------- /src/utils/color.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from "element-plus"; 2 | 3 | /** 4 | * @description hex颜色转rgb颜色 5 | * @param {String} str 颜色值字符串 6 | * @returns {String} 返回处理后的颜色值 7 | */ 8 | export function hexToRgb(str: any) { 9 | let hexs: any = ""; 10 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 11 | if (!reg.test(str)) return ElMessage.warning("输入错误的hex"); 12 | str = str.replace("#", ""); 13 | hexs = str.match(/../g); 14 | for (let i = 0; i < 3; i++) hexs[i] = parseInt(hexs[i], 16); 15 | return hexs; 16 | } 17 | 18 | /** 19 | * @description rgb颜色转Hex颜色 20 | * @param {*} r 代表红色 21 | * @param {*} g 代表绿色 22 | * @param {*} b 代表蓝色 23 | * @returns {String} 返回处理后的颜色值 24 | */ 25 | export function rgbToHex(r: any, g: any, b: any) { 26 | let reg = /^\d{1,3}$/; 27 | if (!reg.test(r) || !reg.test(g) || !reg.test(b)) return ElMessage.warning("输入错误的rgb颜色值"); 28 | let hexs = [r.toString(16), g.toString(16), b.toString(16)]; 29 | for (let i = 0; i < 3; i++) if (hexs[i].length == 1) hexs[i] = `0${hexs[i]}`; 30 | return `#${hexs.join("")}`; 31 | } 32 | 33 | /** 34 | * @description 加深颜色值 35 | * @param {String} color 颜色值字符串 36 | * @param {Number} level 加深的程度,限0-1之间 37 | * @returns {String} 返回处理后的颜色值 38 | */ 39 | export function getDarkColor(color: string, level: number) { 40 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 41 | if (!reg.test(color)) return ElMessage.warning("输入错误的hex颜色值"); 42 | let rgb = hexToRgb(color); 43 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level)); 44 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 45 | } 46 | 47 | /** 48 | * @description 变浅颜色值 49 | * @param {String} color 颜色值字符串 50 | * @param {Number} level 加深的程度,限0-1之间 51 | * @returns {String} 返回处理后的颜色值 52 | */ 53 | export function getLightColor(color: string, level: number) { 54 | let reg = /^\#?[0-9A-Fa-f]{6}$/; 55 | if (!reg.test(color)) return ElMessage.warning("输入错误的hex颜色值"); 56 | let rgb = hexToRgb(color); 57 | for (let i = 0; i < 3; i++) rgb[i] = Math.round(255 * level + rgb[i] * (1 - level)); 58 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 59 | } 60 | -------------------------------------------------------------------------------- /src/utils/dict.ts: -------------------------------------------------------------------------------- 1 | // ? 系统全局字典 2 | 3 | /** 4 | * @description:用户性别 5 | */ 6 | export const genderType = [ 7 | { label: "男", value: 1 }, 8 | { label: "女", value: 2 } 9 | ]; 10 | 11 | /** 12 | * @description:用户状态 13 | */ 14 | export const userStatus = [ 15 | { label: "启用", value: 1, tagType: "success" }, 16 | { label: "禁用", value: 0, tagType: "danger" } 17 | ]; 18 | -------------------------------------------------------------------------------- /src/utils/eleValidate.ts: -------------------------------------------------------------------------------- 1 | // ? Element 常用表单校验规则 2 | 3 | /** 4 | * @rule 手机号 5 | */ 6 | export function checkPhoneNumber(rule: any, value: any, callback: any) { 7 | const regexp = /^(((13[0-9]{1})|(15[0-9]{1})|(16[0-9]{1})|(17[3-8]{1})|(18[0-9]{1})|(19[0-9]{1})|(14[5-7]{1}))+\d{8})$/; 8 | if (value === "") callback("请输入手机号码"); 9 | if (!regexp.test(value)) { 10 | callback(new Error("请输入正确的手机号码")); 11 | } else { 12 | return callback(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/errorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from "element-plus"; 2 | 3 | /** 4 | * @description 全局代码错误捕捉 5 | * */ 6 | const errorHandler = (error: any) => { 7 | // 过滤 HTTP 请求错误 8 | if (error.status || error.status == 0) return false; 9 | let errorMap: { [key: string]: string } = { 10 | InternalError: "Javascript引擎内部错误", 11 | ReferenceError: "未找到对象", 12 | TypeError: "使用了错误的类型或对象", 13 | RangeError: "使用内置对象时,参数超范围", 14 | SyntaxError: "语法错误", 15 | EvalError: "错误的使用了Eval", 16 | URIError: "URI错误" 17 | }; 18 | let errorName = errorMap[error.name] || "未知错误"; 19 | ElNotification({ 20 | title: errorName, 21 | message: error, 22 | type: "error", 23 | duration: 3000 24 | }); 25 | }; 26 | 27 | export default errorHandler; 28 | -------------------------------------------------------------------------------- /src/utils/mittBus.ts: -------------------------------------------------------------------------------- 1 | import mitt from "mitt"; 2 | 3 | const mittBus = mitt(); 4 | 5 | export default mittBus; 6 | -------------------------------------------------------------------------------- /src/utils/svg.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description Loading Svg 3 | */ 4 | export const loadingSvg = ` 5 | 13 | `; 14 | -------------------------------------------------------------------------------- /src/views/assembly/batchImport/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/assembly/batchImport/index.scss -------------------------------------------------------------------------------- /src/views/assembly/batchImport/index.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 36 | 37 | 40 | -------------------------------------------------------------------------------- /src/views/assembly/draggable/index.scss: -------------------------------------------------------------------------------- 1 | .grid-container { 2 | display: grid; 3 | grid-template-rows: 33.3% 33.3% 33.3%; 4 | grid-template-columns: 33.3% 33.3% 33.3%; 5 | height: 100%; 6 | .item { 7 | box-sizing: border-box; 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | font-size: 25px; 12 | color: #ffffff; 13 | cursor: move; 14 | border: 1px solid #e5e4e9; 15 | } 16 | } 17 | .item-1 { 18 | background-color: #87bba2; 19 | } 20 | .item-2 { 21 | background-color: #ff9b54; 22 | } 23 | .item-3 { 24 | background-color: #73628a; 25 | } 26 | .item-4 { 27 | background-color: #76c893; 28 | } 29 | .item-5 { 30 | background-color: #619b8a; 31 | } 32 | .item-6 { 33 | background-color: #55828b; 34 | } 35 | .item-7 { 36 | background-color: #427aa1; 37 | } 38 | .item-8 { 39 | background-color: #38a3a5; 40 | } 41 | .item-9 { 42 | background-color: #fcca46; 43 | } 44 | .chosen { 45 | border: 2px solid #89cffd !important; 46 | } 47 | -------------------------------------------------------------------------------- /src/views/assembly/draggable/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /src/views/assembly/guide/index.scss: -------------------------------------------------------------------------------- 1 | .el-button { 2 | margin-top: 20px; 3 | } 4 | -------------------------------------------------------------------------------- /src/views/assembly/guide/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 92 | 93 | 96 | -------------------------------------------------------------------------------- /src/views/assembly/selectFilter/index.scss: -------------------------------------------------------------------------------- 1 | .result { 2 | margin-top: 20px; 3 | font-size: 17px; 4 | font-weight: bold; 5 | color: var(--el-text-color-regular); 6 | } 7 | -------------------------------------------------------------------------------- /src/views/assembly/selectFilter/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 88 | 89 | 92 | -------------------------------------------------------------------------------- /src/views/assembly/selectIcon/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/assembly/selectIcon/index.scss -------------------------------------------------------------------------------- /src/views/assembly/selectIcon/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/views/assembly/svgIcon/index.scss: -------------------------------------------------------------------------------- 1 | .icon-list { 2 | box-sizing: border-box; 3 | display: flex; 4 | flex-wrap: wrap; 5 | justify-content: space-between; 6 | width: 100%; 7 | padding: 40px 100px 0; 8 | } 9 | -------------------------------------------------------------------------------- /src/views/assembly/svgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | 33 | 36 | -------------------------------------------------------------------------------- /src/views/assembly/tabs/detail.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 20 | -------------------------------------------------------------------------------- /src/views/assembly/treeFilter/index.scss: -------------------------------------------------------------------------------- 1 | .content-box { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: flex-start; 5 | .descriptions-box { 6 | display: flex; 7 | flex: 1; 8 | flex-direction: column; 9 | align-items: center; 10 | height: 100%; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/views/assembly/treeFilter/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 51 | 52 | 55 | -------------------------------------------------------------------------------- /src/views/assembly/uploadFile/index.scss: -------------------------------------------------------------------------------- 1 | .upload { 2 | height: auto; 3 | .card { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | width: 100%; 8 | .upload-list { 9 | display: flex; 10 | flex-wrap: wrap; 11 | justify-content: space-around; 12 | width: 90%; 13 | margin: 10px 0; 14 | } 15 | } 16 | .img-box { 17 | margin-bottom: 10px; 18 | } 19 | .form-box { 20 | display: flex; 21 | justify-content: space-around; 22 | width: 100%; 23 | .card { 24 | margin-right: 10px; 25 | &:last-child { 26 | margin-right: 0; 27 | } 28 | .el-form { 29 | width: 100%; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/views/assembly/wangEditor/index.scss: -------------------------------------------------------------------------------- 1 | .el-button { 2 | margin-top: 20px; 3 | } 4 | :deep(.el-dialog__body) { 5 | height: 700px !important; 6 | overflow: auto; 7 | } 8 | -------------------------------------------------------------------------------- /src/views/assembly/wangEditor/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 35 | 36 | 39 | -------------------------------------------------------------------------------- /src/views/auth/button/index.scss: -------------------------------------------------------------------------------- 1 | .content-box { 2 | align-items: flex-start; 3 | span { 4 | width: 100%; 5 | text-align: center; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/auth/button/index.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 43 | 44 | 47 | -------------------------------------------------------------------------------- /src/views/auth/menu/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/auth/menu/index.scss -------------------------------------------------------------------------------- /src/views/auth/menu/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/1-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/1-bg.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/2-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/2-bg.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/3-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/3-bg.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/4-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/4-bg.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/add_person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/add_person.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/add_team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/add_team.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/book-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/book-bg.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/book-sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/book-sum.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/book_sum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/book_sum.png -------------------------------------------------------------------------------- /src/views/dashboard/dataVisualize/images/today.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dashboard/dataVisualize/images/today.png -------------------------------------------------------------------------------- /src/views/dataScreen/assets/alarmList.Json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "warnMsg": "2022-04-25 14:09-23:09 预约人数 1,006 人次,已达到最大承载量的 99 %", 5 | "label": "峨眉山" 6 | }, 7 | { 8 | "id": 2, 9 | "warnMsg": "2022-04-12 19:30-20:30 预约人数 66,666 人次,已达到最大承载量的 25 %", 10 | "label": "稻城亚丁" 11 | }, 12 | { 13 | "id": 3, 14 | "warnMsg": "2022-04-09 14:09-23:09 预约人数 5,813 人次,已达到最大承载量的 3 %", 15 | "label": "九寨沟" 16 | }, 17 | { 18 | "id": 4, 19 | "warnMsg": "2022-04-07 22:39-23:39 预约人数 999 人次,已达到最大承载量的 13 %", 20 | "label": "万里长城" 21 | }, 22 | { 23 | "id": 5, 24 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 123,368 人次,已达到最大承载量的 46 %", 25 | "label": "北京故宫" 26 | }, 27 | { 28 | "id": 6, 29 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 869 人次,已达到最大承载量的 95 %", 30 | "label": "阆中古城" 31 | }, 32 | { 33 | "id": 7, 34 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 6,985 人次,已达到最大承载量的 80 %", 35 | "label": "乐山大佛" 36 | }, 37 | { 38 | "id": 8, 39 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 25,696 人次,已达到最大承载量的 70 %", 40 | "label": "阿坝州黄龙" 41 | }, 42 | { 43 | "id": 9, 44 | "warnMsg": "2022-03-29 09:00-12:00 预约人数 45,987 人次,已达到最大承载量的 55 %", 45 | "label": "青城山" 46 | } 47 | ] 48 | -------------------------------------------------------------------------------- /src/views/dataScreen/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/contrast-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/contrast-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-alarm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-alarm.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-btn-bg-l.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-btn-bg-l.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-btn-bg-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-btn-bg-r.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-center-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-center-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-left-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-left-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-right-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-right-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-header-warn-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-header-warn-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-cb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-cb.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-lb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-lb.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-lc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-lc.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-lt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-lt.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-rb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-rb.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-rc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-rc.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-main-rt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-main-rt.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-title.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/dataScreen-warn-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/dataScreen-warn-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/line-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/line-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/man-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/man-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/man.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/man.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/map-title-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/map-title-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/rankingChart-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/rankingChart-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/total.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/total.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/woman-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/woman-bg.png -------------------------------------------------------------------------------- /src/views/dataScreen/images/woman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/dataScreen/images/woman.png -------------------------------------------------------------------------------- /src/views/directives/copyDirect/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/directives/copyDirect/index.scss -------------------------------------------------------------------------------- /src/views/directives/copyDirect/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | 20 | 23 | -------------------------------------------------------------------------------- /src/views/directives/debounceDirect/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/directives/debounceDirect/index.scss -------------------------------------------------------------------------------- /src/views/directives/debounceDirect/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/directives/dragDirect/index.scss: -------------------------------------------------------------------------------- 1 | .content-box { 2 | position: relative; 3 | .drag-box { 4 | position: absolute; 5 | top: 110px; 6 | width: 300px; 7 | height: 300px; 8 | font-size: 23px; 9 | font-weight: bold; 10 | color: var(--el-color-primary-light-3); 11 | background: var(--el-color-primary-light-9); 12 | border-radius: 50%; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/views/directives/dragDirect/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 13 | -------------------------------------------------------------------------------- /src/views/directives/longpressDirect/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/directives/longpressDirect/index.scss -------------------------------------------------------------------------------- /src/views/directives/longpressDirect/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/directives/throttleDirect/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/directives/throttleDirect/index.scss -------------------------------------------------------------------------------- /src/views/directives/throttleDirect/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/views/directives/watermarkDirect/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/directives/watermarkDirect/index.scss -------------------------------------------------------------------------------- /src/views/directives/watermarkDirect/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/views/echarts/columnChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/columnChart/index.scss -------------------------------------------------------------------------------- /src/views/echarts/lineChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/lineChart/index.scss -------------------------------------------------------------------------------- /src/views/echarts/nestedChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/nestedChart/index.scss -------------------------------------------------------------------------------- /src/views/echarts/pieChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/pieChart/index.scss -------------------------------------------------------------------------------- /src/views/echarts/pieChart/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 65 | 66 | 69 | -------------------------------------------------------------------------------- /src/views/echarts/radarChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/radarChart/index.scss -------------------------------------------------------------------------------- /src/views/echarts/radarChart/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 53 | 54 | 57 | -------------------------------------------------------------------------------- /src/views/echarts/waterChart/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/echarts/waterChart/index.scss -------------------------------------------------------------------------------- /src/views/form/basicForm/index.scss: -------------------------------------------------------------------------------- 1 | .el-form { 2 | width: 100%; 3 | .text-center { 4 | text-align: center; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/views/form/dynamicForm/index.scss: -------------------------------------------------------------------------------- 1 | .el-form { 2 | width: 100%; 3 | } 4 | .add { 5 | margin-bottom: 20px; 6 | } 7 | -------------------------------------------------------------------------------- /src/views/form/proForm/index.scss: -------------------------------------------------------------------------------- 1 | .el-form { 2 | width: 100%; 3 | margin-top: 20px; 4 | } 5 | -------------------------------------------------------------------------------- /src/views/form/proForm/index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 81 | 82 | 85 | -------------------------------------------------------------------------------- /src/views/form/validateForm/index.scss: -------------------------------------------------------------------------------- 1 | .el-form { 2 | width: 100%; 3 | .text-center { 4 | text-align: center; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/views/home/index.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | width: 100%; 6 | height: 100%; 7 | .home-bg { 8 | width: 70%; 9 | max-width: 1200px; 10 | margin-bottom: 20px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/views/link/bing/index.scss: -------------------------------------------------------------------------------- 1 | .full-iframe { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | -------------------------------------------------------------------------------- /src/views/link/bing/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/views/link/docs/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/link/docs/index.scss -------------------------------------------------------------------------------- /src/views/link/docs/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /src/views/link/gitee/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/link/gitee/index.scss -------------------------------------------------------------------------------- /src/views/link/gitee/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/link/github/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/link/github/index.scss -------------------------------------------------------------------------------- /src/views/link/github/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 15 | -------------------------------------------------------------------------------- /src/views/link/juejin/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/link/juejin/index.scss -------------------------------------------------------------------------------- /src/views/link/juejin/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/login/index.scss: -------------------------------------------------------------------------------- 1 | .login-container { 2 | height: 100%; 3 | min-height: 550px; 4 | background-color: #eeeeee; 5 | background-image: url("@/assets/images/login_bg.svg"); 6 | background-size: 100% 100%; 7 | background-size: cover; 8 | .login-box { 9 | position: relative; 10 | box-sizing: border-box; 11 | display: flex; 12 | align-items: center; 13 | justify-content: space-around; 14 | width: 96.5%; 15 | height: 94%; 16 | padding: 0 50px; 17 | background-color: rgb(255 255 255 / 80%); 18 | border-radius: 10px; 19 | .dark { 20 | position: absolute; 21 | top: 13px; 22 | right: 18px; 23 | } 24 | .login-left { 25 | width: 800px; 26 | margin-right: 10px; 27 | .login-left-img { 28 | width: 100%; 29 | height: 100%; 30 | } 31 | } 32 | .login-form { 33 | width: 420px; 34 | padding: 50px 40px 45px; 35 | background-color: var(--el-bg-color); 36 | border-radius: 10px; 37 | box-shadow: rgb(0 0 0 / 10%) 0 2px 10px 2px; 38 | .login-logo { 39 | display: flex; 40 | align-items: center; 41 | justify-content: center; 42 | margin-bottom: 45px; 43 | .login-icon { 44 | width: 60px; 45 | height: 52px; 46 | } 47 | .logo-text { 48 | padding: 0 0 0 25px; 49 | margin: 0; 50 | font-size: 42px; 51 | font-weight: bold; 52 | color: #34495e; 53 | white-space: nowrap; 54 | } 55 | } 56 | .el-form-item { 57 | margin-bottom: 40px; 58 | } 59 | .login-btn { 60 | display: flex; 61 | justify-content: space-between; 62 | width: 100%; 63 | margin-top: 40px; 64 | white-space: nowrap; 65 | .el-button { 66 | width: 185px; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | @media screen and (width <= 1250px) { 74 | .login-left { 75 | display: none; 76 | } 77 | } 78 | 79 | @media screen and (width <= 600px) { 80 | .login-form { 81 | width: 97% !important; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/views/menu/menu1/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu1/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu1/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu21/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu2/menu21/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu2/menu21/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu22/menu221/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu2/menu22/menu221/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu2/menu22/menu221/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu22/menu222/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu2/menu22/menu222/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu2/menu22/menu222/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/menu/menu2/menu23/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu2/menu23/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu2/menu23/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/menu/menu3/index.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HalseySpicy/Geeker-Admin/d05424c2acda8dc3ffb25a47e1e3692c3bac5a17/src/views/menu/menu3/index.scss -------------------------------------------------------------------------------- /src/views/menu/menu3/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 16 | -------------------------------------------------------------------------------- /src/views/proTable/document/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/proTable/useProTable/detail.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /src/views/proTable/useTreeFilter/detail.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /src/views/system/accountManage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/departmentManage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/dictManage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/menuMange/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 44 | -------------------------------------------------------------------------------- /src/views/system/roleManage/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/systemLog/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/system/timingTask/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "types": ["vite/client", "element-plus/global"], 8 | 9 | /* Strict Type-Checking Options */ 10 | "strict": true /* Enable all strict type-checking options. */, 11 | "noImplicitAny": false /* Raise error on expressions and declarations with an implied 'any' type. */, 12 | // "strictNullChecks": true, /* Enable strict null checks. */ 13 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 14 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 15 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 16 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 17 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 18 | 19 | "jsx": "preserve", 20 | "resolveJsonModule": true, 21 | "isolatedModules": true, 22 | "esModuleInterop": true, 23 | "lib": ["ESNext", "DOM"], 24 | "skipLibCheck": true, 25 | "noEmit": true, 26 | "baseUrl": "./", 27 | "paths": { 28 | "@": ["src"], 29 | "@/*": ["src/*"] 30 | } 31 | }, 32 | "include": [ 33 | "src/**/*.ts", 34 | "src/**/*.d.ts", 35 | "src/**/*.tsx", 36 | "src/**/*.vue", 37 | "build/**/*.ts", 38 | "build/**/*.d.ts", 39 | "vite.config.ts" 40 | ], 41 | "exclude": ["node_modules", "dist", "**/*.js"] 42 | } 43 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv, ConfigEnv, UserConfig } from "vite"; 2 | import { resolve } from "path"; 3 | import { wrapperEnv } from "./build/getEnv"; 4 | import { createProxy } from "./build/proxy"; 5 | import { createVitePlugins } from "./build/plugins"; 6 | import pkg from "./package.json"; 7 | import dayjs from "dayjs"; 8 | 9 | const { dependencies, devDependencies, name, version } = pkg; 10 | const __APP_INFO__ = { 11 | pkg: { dependencies, devDependencies, name, version }, 12 | lastBuildTime: dayjs().format("YYYY-MM-DD HH:mm:ss") 13 | }; 14 | 15 | // @see: https://vitejs.dev/config/ 16 | export default defineConfig(({ mode }: ConfigEnv): UserConfig => { 17 | const root = process.cwd(); 18 | const env = loadEnv(mode, root); 19 | const viteEnv = wrapperEnv(env); 20 | 21 | return { 22 | base: viteEnv.VITE_PUBLIC_PATH, 23 | root, 24 | resolve: { 25 | alias: { 26 | "@": resolve(__dirname, "./src"), 27 | "vue-i18n": "vue-i18n/dist/vue-i18n.cjs.js" 28 | } 29 | }, 30 | define: { 31 | __APP_INFO__: JSON.stringify(__APP_INFO__) 32 | }, 33 | css: { 34 | preprocessorOptions: { 35 | scss: { 36 | additionalData: `@import "@/styles/var.scss";` 37 | } 38 | } 39 | }, 40 | server: { 41 | host: "0.0.0.0", 42 | port: viteEnv.VITE_PORT, 43 | open: viteEnv.VITE_OPEN, 44 | cors: true, 45 | // Load proxy configuration from .env.development 46 | proxy: createProxy(viteEnv.VITE_PROXY) 47 | }, 48 | plugins: createVitePlugins(viteEnv), 49 | esbuild: { 50 | pure: viteEnv.VITE_DROP_CONSOLE ? ["console.log", "debugger"] : [] 51 | }, 52 | build: { 53 | outDir: "dist", 54 | minify: "esbuild", 55 | // esbuild 打包更快,但是不能去除 console.log,terser打包慢,但能去除 console.log 56 | // minify: "terser", 57 | // terserOptions: { 58 | // compress: { 59 | // drop_console: viteEnv.VITE_DROP_CONSOLE, 60 | // drop_debugger: true 61 | // } 62 | // }, 63 | sourcemap: false, 64 | // 禁用 gzip 压缩大小报告,可略微减少打包时间 65 | reportCompressedSize: false, 66 | // 规定触发警告的 chunk 大小 67 | chunkSizeWarningLimit: 2000, 68 | rollupOptions: { 69 | output: { 70 | // Static resource classification and packaging 71 | chunkFileNames: "assets/js/[name]-[hash].js", 72 | entryFileNames: "assets/js/[name]-[hash].js", 73 | assetFileNames: "assets/[ext]/[name]-[hash].[ext]" 74 | } 75 | } 76 | } 77 | }; 78 | }); 79 | --------------------------------------------------------------------------------