├── .env ├── .env.development ├── .env.production ├── .github └── workflows │ ├── Docker Sz-Admin CI.yml │ └── Docker Sz-Admin Test CI.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.cjs ├── CHANGE.md ├── Dockerfile ├── LICENSE ├── README.md ├── THIRD_PARTY_LICENSE.md ├── env.d.ts ├── eslint.config.mjs ├── index.html ├── nginx └── default.conf ├── package-lock.json ├── package.d.ts ├── package.json ├── pnpm-lock.yaml ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── helper │ │ ├── index.ts │ │ └── prefix.ts │ ├── index.ts │ ├── modules │ │ ├── demo │ │ │ └── demo.ts │ │ ├── system │ │ │ ├── captcha.ts │ │ │ ├── client.ts │ │ │ ├── common.ts │ │ │ ├── config.ts │ │ │ ├── datarole.ts │ │ │ ├── dept.ts │ │ │ ├── dict.ts │ │ │ ├── file.ts │ │ │ ├── login.ts │ │ │ ├── menu.ts │ │ │ ├── message.ts │ │ │ ├── role.ts │ │ │ ├── sysTempFile.ts │ │ │ ├── upload.ts │ │ │ └── user.ts │ │ ├── teacher │ │ │ └── teacherStatistics.ts │ │ └── toolbox │ │ │ └── generator.ts │ └── types │ │ ├── index.ts │ │ ├── system │ │ ├── captcha.ts │ │ ├── client.ts │ │ ├── common.ts │ │ ├── config.ts │ │ ├── datarole.ts │ │ ├── dept.ts │ │ ├── dict.ts │ │ ├── file.ts │ │ ├── login.ts │ │ ├── menu.ts │ │ ├── message.ts │ │ ├── role.ts │ │ ├── sysTempFile.ts │ │ ├── upload.ts │ │ └── user.ts │ │ ├── teacher │ │ └── teacherStatistics.ts │ │ └── toolbox │ │ └── generator.ts ├── assets │ ├── icons │ │ ├── org.svg │ │ ├── scope.svg │ │ └── zip.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 ├── components │ ├── Captcha │ │ ├── SliderCaptcha.vue │ │ └── index.scss │ ├── ErrorMessage │ │ ├── 403.vue │ │ ├── 404.vue │ │ ├── 500.vue │ │ └── index.scss │ ├── Grid │ │ ├── components │ │ │ └── GridItem.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── HighCode │ │ ├── code.scss │ │ ├── index.scss │ │ ├── index.vue │ │ └── line.ts │ ├── IconChoose │ │ └── index.vue │ ├── ImportExcel │ │ ├── index.scss │ │ └── index.vue │ ├── Loading │ │ ├── index.scss │ │ └── index.vue │ ├── ProTable │ │ ├── components │ │ │ ├── ColSetting.vue │ │ │ ├── Pagination.vue │ │ │ └── TableColumn.vue │ │ ├── index.vue │ │ └── interface │ │ │ └── index.ts │ ├── RemoteSearchSelect │ │ └── index.vue │ ├── SearchForm │ │ ├── components │ │ │ └── SearchFormItem.vue │ │ └── index.vue │ ├── SelectFilter │ │ ├── index.scss │ │ └── index.vue │ ├── SimplifyUpload │ │ └── index.vue │ ├── SvgIcon │ │ └── index.vue │ ├── SwitchDark │ │ └── index.vue │ └── Upload │ │ ├── Img.vue │ │ ├── Imgs.vue │ │ └── file.vue ├── config │ ├── consts.ts │ ├── fullScreen.ts │ ├── index.ts │ ├── nprogress.ts │ ├── typings.ts │ └── validator.ts ├── directives │ ├── index.ts │ └── modules │ │ ├── auth.ts │ │ └── copy.ts ├── hooks │ ├── types │ │ └── index.ts │ ├── useDict.ts │ ├── useDictOptions.ts │ ├── useDownload.ts │ ├── useHandleData.ts │ ├── useSelection.ts │ ├── useTable.ts │ └── useTheme.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 ├── router │ ├── index.ts │ └── modules │ │ ├── dynamicRouter.ts │ │ └── staticRouter.ts ├── stores │ ├── helper │ │ └── persist.ts │ ├── index.ts │ ├── interface │ │ ├── app.ts │ │ ├── tabs.ts │ │ └── user.ts │ └── modules │ │ ├── app.ts │ │ ├── auth.ts │ │ ├── keepAlive.ts │ │ ├── options.ts │ │ ├── socket.ts │ │ ├── tabs.ts │ │ └── user.ts ├── styles │ ├── color.scss │ ├── common.scss │ ├── element-dark.scss │ ├── element.scss │ ├── element │ │ └── index.scss │ ├── fonts │ │ ├── DIN.otf │ │ ├── MetroDF.ttf │ │ ├── YouSheBiaoTiHei.ttf │ │ └── font.scss │ ├── iconfont │ │ ├── iconfont.scss │ │ └── iconfont.ttf │ ├── index.scss │ ├── reset.scss │ ├── theme │ │ ├── aside.ts │ │ ├── header.ts │ │ └── menu.ts │ └── var.scss ├── typings │ ├── global.d.ts │ └── window.d.ts ├── utils │ ├── color.ts │ ├── errorHandler.ts │ ├── index.ts │ ├── is.ts │ └── mittBus.ts └── views │ ├── about │ └── index.vue │ ├── demo │ ├── components │ │ └── SendMessageForm.vue │ ├── index.scss │ └── index.vue │ ├── home │ ├── index.scss │ └── index.vue │ ├── login │ ├── components │ │ └── LoginForm.vue │ ├── index.scss │ └── index.vue │ ├── system │ ├── accountManage │ │ ├── components │ │ │ ├── DeptTree.vue │ │ │ ├── UserAdd.vue │ │ │ ├── UserDataPermissions.vue │ │ │ ├── UserDeptForm.vue │ │ │ ├── UserEdit.vue │ │ │ └── UserPermissions.vue │ │ └── index.vue │ ├── clientManage │ │ ├── components │ │ │ └── SysClientForm.vue │ │ └── index.vue │ ├── configManage │ │ ├── components │ │ │ └── ConfigEditForm.vue │ │ └── index.vue │ ├── dataRoleManage │ │ ├── components │ │ │ └── DataRoleForm.vue │ │ └── index.vue │ ├── deptManage │ │ ├── components │ │ │ ├── DeptLeaderTransfer.vue │ │ │ └── SysDeptForm.vue │ │ └── index.vue │ ├── dictManage │ │ ├── components │ │ │ ├── DictData.vue │ │ │ ├── DictDataForm.vue │ │ │ └── DictTypeForm.vue │ │ └── index.vue │ ├── fileManage │ │ └── index.vue │ ├── menuMange │ │ ├── components │ │ │ └── MenuForm.vue │ │ └── index.vue │ ├── message │ │ ├── detail.vue │ │ └── index.vue │ ├── roleManage │ │ ├── components │ │ │ ├── RoleForm.vue │ │ │ └── RolePermissions.vue │ │ └── index.vue │ └── sysTempFile │ │ ├── components │ │ ├── HistoryList.vue │ │ └── SysTempFileForm.vue │ │ └── index.vue │ ├── teacher │ └── teacherStatistics │ │ ├── components │ │ └── TeacherStatisticsForm.vue │ │ └── index.vue │ └── toolbox │ └── generator │ ├── common │ └── Options.ts │ ├── components │ ├── EditForm.vue │ ├── Import.vue │ └── Preview.vue │ └── index.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.mts /.env: -------------------------------------------------------------------------------- 1 | # title 2 | VITE_APP_TITLE='Sz Admin' 3 | 4 | # 本地运行端口号 5 | VITE_PORT=9848 6 | 7 | # 启动时自动打开浏览器 8 | VITE_OPEN=true 9 | # clientId 10 | VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034" 11 | # 请求最大超时时间(ms)120s 12 | VITE_APP_HTTP_TIMEOUT=120000 13 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | # 本地环境 2 | VITE_USER_NODE_ENV=development 3 | 4 | # 公共基础路径 5 | VITE_PUBLIC_PATH=/ 6 | 7 | # 开发环境接口地址 8 | VITE_API_URL=http://127.0.0.1:9991/api 9 | ## 启用WebSocket连接 10 | ## 若需启用WebSocket,请设置VITE_SOCKET_URL为有效的WebSocket地址 11 | ## 若不设置或留空,WebSocket功能将不会启用。例: 12 | # VITE_SOCKET_URL=ws://127.0.0.1:9993/socket 13 | VITE_SOCKET_URL=ws://127.0.0.1:9993/socket 14 | VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034" 15 | # 是否对admin(超管)用户放行前端按钮权限验证,默认放行 16 | VITE_ADMIN_BYPASS_PERMISSION=true 17 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | # 线上环境 2 | VITE_USER_NODE_ENV=production 3 | 4 | # 公共基础路径 5 | VITE_PUBLIC_PATH=/ 6 | 7 | # 线上环境接口地址 8 | ## 这里的线上环境使用了nginx 反向代理来实现了 api 和 websocket的转发 9 | ## 具体请结合/nginx/default.conf 文件查看使用方式 10 | VITE_API_URL="/api" 11 | VITE_APP_CLIENT_ID ="195da9fcce574852b850068771cde034" 12 | # 是否对admin(超管)用户放行前端按钮权限验证,默认放行 13 | VITE_ADMIN_BYPASS_PERMISSION=true 14 | # 是否启用websocket,如不需要请注释 15 | VITE_SOCKET_URL="/socket" 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 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | -------------------------------------------------------------------------------- /.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: true, 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 单文件组件中 12 | 13 | 14 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 9800; 3 | listen [::]:9800; 4 | server_name localhost; 5 | access_log /var/log/nginx/default.access.log; 6 | error_log /var/log/nginx/default.error.log; 7 | 8 | location / { 9 | root /usr/share/nginx/sz-admin; 10 | try_files $uri $uri/ /index.html; 11 | index index.html; 12 | } 13 | 14 | location /api { 15 | client_max_body_size 200M; # 设置最大文件上传大小 16 | proxy_set_header Host $http_host; 17 | proxy_set_header X-Real-IP $remote_addr; 18 | proxy_set_header REMOTE-HOST $remote_addr; 19 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 20 | # proxy_pass $NGINX_PROXY_PASS; 21 | proxy_pass http://sz-service-admin:9991/api; 22 | } 23 | 24 | # websocket 支持,如不需要请手动注释 25 | location /socket { 26 | proxy_pass http://sz-service-websocket:9993; # 后端 WebSocket 服务器的地址 27 | proxy_http_version 1.1; 28 | proxy_set_header Upgrade $http_upgrade; 29 | proxy_set_header Connection "upgrade"; 30 | proxy_set_header Host $host; 31 | proxy_cache_bypass $http_upgrade; 32 | 33 | proxy_read_timeout 3600s; # 设置为1小时 34 | proxy_send_timeout 3600s; # 设置为1小时 35 | } 36 | 37 | error_page 500 502 503 504 /50x.html; 38 | location = /50x.html { 39 | root /usr/share/nginx/sz-admin; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.d.ts: -------------------------------------------------------------------------------- 1 | // 在 package.d.ts 文件中 2 | declare module 'package.json' { 3 | const content: { 4 | version: string; 5 | }; 6 | export = content; 7 | } 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sz-admin", 3 | "version": "1.2.2-beta", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "run-p type-check \"build-only {@}\" --", 9 | "preview": "vite preview", 10 | "build-only": "vite build", 11 | "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", 12 | "lint": "eslint --fix", 13 | "format": "prettier --write src/" 14 | }, 15 | "dependencies": { 16 | "@element-plus/icons-vue": "^2.3.1", 17 | "@eslint/js": "^9.26.0", 18 | "@highlightjs/vue-plugin": "^2.1.0", 19 | "@rushstack/eslint-patch": "^1.11.0", 20 | "@vueuse/core": "^10.11.1", 21 | "axios": "^1.9.0", 22 | "element-plus": "^2.9.9", 23 | "eslint-plugin-prettier": "^5.4.0", 24 | "eslint-plugin-vue": "^10.1.0", 25 | "globals": "^16.1.0", 26 | "highlight.js": "^11.11.1", 27 | "mitt": "^3.0.1", 28 | "nprogress": "^0.2.0", 29 | "pinia": "^2.3.1", 30 | "pinia-plugin-persistedstate": "^3.2.3", 31 | "screenfull": "^6.0.2", 32 | "sortablejs": "^1.15.6", 33 | "typescript-eslint": "^8.32.0", 34 | "vite-plugin-svg-icons": "^2.0.1", 35 | "vue": "^3.5.13", 36 | "vue-eslint-parser": "^10.1.3", 37 | "vue-i18n": "^9.14.4", 38 | "vue-router": "^4.5.1" 39 | }, 40 | "devDependencies": { 41 | "@tsconfig/node18": "^18.2.4", 42 | "@types/node": "^18.19.99", 43 | "@types/node-forge": "^1.3.11", 44 | "@types/nprogress": "^0.2.3", 45 | "@types/sortablejs": "^1.15.8", 46 | "@vitejs/plugin-vue": "^4.6.2", 47 | "@vitejs/plugin-vue-jsx": "^3.1.0", 48 | "@vue/eslint-config-prettier": "^8.0.0", 49 | "@vue/eslint-config-typescript": "^12.0.0", 50 | "@vue/tsconfig": "^0.5.1", 51 | "eslint": "^9.26.0", 52 | "node-forge": "^1.3.1", 53 | "npm-run-all2": "^6.2.6", 54 | "prettier": "^3.5.3", 55 | "sass": "~1.87.0", 56 | "typescript": "~5.2.2", 57 | "vite": "6.3.4", 58 | "vite-plugin-compression": "^0.5.1", 59 | "vite-plugin-vue-devtools": "^7.7.6", 60 | "vue-tsc": "^2.2.10" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/public/favicon.ico -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 44 | -------------------------------------------------------------------------------- /src/api/helper/index.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage } from 'element-plus'; 2 | 3 | /** 4 | * 成功状态 5 | * @type {string} 6 | */ 7 | export const CODE_SUCCESS: string = '0000'; 8 | /** 9 | * 无效Token 10 | * @type {string} 11 | */ 12 | export const CODE_TOKEN_FAIL: string = 'C105'; 13 | 14 | /** 15 | * @description: 校验网络请求状态码 16 | * @param {Number} status 17 | * @return void 18 | */ 19 | export const checkStatus = (status: number, message?: string) => { 20 | switch (status) { 21 | case 400: 22 | ElMessage.error(message || '请求失败!请您稍后重试'); 23 | break; 24 | case 401: 25 | ElMessage.error(message || '登录失效!请您重新登录'); 26 | break; 27 | case 403: 28 | ElMessage.error(message || '当前账号无权限访问!'); 29 | break; 30 | case 404: 31 | ElMessage.error(message || '你所访问的资源不存在!'); 32 | break; 33 | case 405: 34 | ElMessage.error(message || '请求方式错误!请您稍后重试'); 35 | break; 36 | case 408: 37 | ElMessage.error(message || '请求超时!请您稍后重试'); 38 | break; 39 | case 422: 40 | ElMessage.error(message || '请求参数异常!'); 41 | break; 42 | case 500: 43 | ElMessage.error(message || '服务异常!'); 44 | break; 45 | case 502: 46 | ElMessage.error(message || '网关错误!'); 47 | break; 48 | case 503: 49 | ElMessage.error(message || '服务不可用!'); 50 | break; 51 | case 504: 52 | ElMessage.error(message || '网关超时!'); 53 | break; 54 | // 自定义响应码,处理 blob流异常时的返回信息 55 | case 1004: 56 | ElMessage.error(message || '模板文件不存在!'); 57 | break; 58 | default: 59 | ElMessage.error(message || '请求失败!'); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/api/helper/prefix.ts: -------------------------------------------------------------------------------- 1 | export const ADMIN_MODULE: string = '/admin'; 2 | -------------------------------------------------------------------------------- /src/api/modules/demo/demo.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { Message } from '@/api/types/system/message'; 4 | 5 | /** 6 | * 发送消息 7 | * @param params 8 | * @returns {*} 9 | */ 10 | export const sendMessageApi = (params: Message) => { 11 | return http.post(ADMIN_MODULE + `/www/message/send`, params); 12 | }; 13 | -------------------------------------------------------------------------------- /src/api/modules/system/captcha.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { CaptchaInfo, CaptchaVerifyImageParams } from '@/api/types/system/captcha'; 4 | 5 | // 获取验证码是否启用 6 | export const getCaptchaStatus = () => { 7 | return http.get(ADMIN_MODULE + `/captcha/status`, {}); 8 | }; 9 | // 获取滑块拼图验证码 10 | export const getImageCodeApi = () => { 11 | return http.post(ADMIN_MODULE + `/captcha/get`, {}); 12 | }; 13 | // 校验滑块拼图验证码 14 | export const verifyImageCodeApi = (params: CaptchaVerifyImageParams) => { 15 | return http.post(ADMIN_MODULE + `/captcha/check`, params); 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/modules/system/client.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { SysClientForm, SysClientQuery, SysClientRow } from '@/api/types/system/client'; 5 | /** 6 | * 查询列表 7 | * @param params 8 | * @returns {*} 9 | */ 10 | export const getSysClientListApi = (params: SysClientQuery) => { 11 | return http.get>(ADMIN_MODULE + `/sys-client`, params); 12 | }; 13 | 14 | /** 15 | * 添加 16 | * @param params 17 | * @returns {*} 18 | */ 19 | export const createSysClientApi = (params: SysClientForm) => { 20 | return http.post(ADMIN_MODULE + `/sys-client`, params); 21 | }; 22 | 23 | /** 24 | * 修改 25 | * @param params 26 | * @returns {*} 27 | */ 28 | export const updateSysClientApi = (params: SysClientForm) => { 29 | return http.put(ADMIN_MODULE + `/sys-client`, params); 30 | }; 31 | 32 | /** 33 | * 删除 34 | * @param params 35 | * @returns {*} 36 | */ 37 | export const removeSysClientApi = (params: { ids: number[] }) => { 38 | return http.delete(ADMIN_MODULE + `/sys-client`, params); 39 | }; 40 | 41 | /** 42 | * 获取详情 43 | * @param params 44 | * @returns {*} 45 | */ 46 | export const getSysClientDetailApi = (params: { id: number }) => { 47 | const { id } = params; 48 | return http.get(ADMIN_MODULE + `/sys-client/${id}`); 49 | }; 50 | -------------------------------------------------------------------------------- /src/api/modules/system/common.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { CommonTemplateDown } from '@/api/types/system/common'; 4 | 5 | /** 6 | * 模版下载 7 | */ 8 | export const downloadTemplate = (params: CommonTemplateDown) => { 9 | return http.template(ADMIN_MODULE + `/common/download/templates`, params); 10 | }; 11 | -------------------------------------------------------------------------------- /src/api/modules/system/config.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { ConfigForm, ConfigInfo, ConfigQuery } from '@/api/types/system/config'; 4 | import type { IPage } from '@/api/types'; 5 | 6 | /** 7 | * 获取列表 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const getConfigList = (params: ConfigQuery) => { 12 | return http.get>(ADMIN_MODULE + `/sys-config`, params); 13 | }; 14 | 15 | /** 16 | * 添加 17 | * @param params 18 | * @returns {*} 19 | */ 20 | export const addConfig = (params: ConfigForm) => { 21 | return http.post(ADMIN_MODULE + `/sys-config`, params); 22 | }; 23 | 24 | /** 25 | * 修改 26 | * @param params 27 | * @returns {*} 28 | */ 29 | export const editConfig = (params: ConfigForm) => { 30 | return http.put(ADMIN_MODULE + `/sys-config`, params); 31 | }; 32 | 33 | /** 34 | * 删除 35 | * @param params 36 | * @returns {*} 37 | */ 38 | export const deleteConfig = (params: { ids: number[] }) => { 39 | return http.delete(ADMIN_MODULE + `/sys-config`, params); 40 | }; 41 | -------------------------------------------------------------------------------- /src/api/modules/system/datarole.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { SysDataRoleQuery, SysDataRoleRow, SysDataRoleForm, SysDataRoleMeta } from '@/api/types/system/datarole'; 5 | /** 6 | * 查询列表 7 | * @param params 8 | * @returns {*} 9 | */ 10 | export const getSysDataRoleListApi = (params: SysDataRoleQuery) => { 11 | return http.get>(ADMIN_MODULE + `/sys-data-role`, params); 12 | }; 13 | 14 | /** 15 | * 添加 16 | * @param params 17 | * @returns {*} 18 | */ 19 | export const createSysDataRoleApi = (params: SysDataRoleForm) => { 20 | return http.post(ADMIN_MODULE + `/sys-data-role`, params); 21 | }; 22 | 23 | /** 24 | * 修改 25 | * @param params 26 | * @returns {*} 27 | */ 28 | export const updateSysDataRoleApi = (params: SysDataRoleForm) => { 29 | return http.put(ADMIN_MODULE + `/sys-data-role`, params); 30 | }; 31 | 32 | /** 33 | * 删除 34 | * @param params 35 | * @returns {*} 36 | */ 37 | export const removeSysDataRoleApi = (params: { ids: (string | number)[] }) => { 38 | return http.delete(ADMIN_MODULE + `/sys-data-role`, params); 39 | }; 40 | 41 | /** 42 | * 获取详情 43 | * @param params 44 | * @returns {*} 45 | */ 46 | export const getSysDataRoleDetailApi = (params: { id: number }) => { 47 | const { id } = params; 48 | return http.get(ADMIN_MODULE + `/sys-data-role/${id}`); 49 | }; 50 | 51 | /** 52 | * 获取数据角色初始菜单信息 53 | * @param params 54 | * @returns {*} 55 | */ 56 | export const getSysDataRoleMenuApi = () => { 57 | return http.get(ADMIN_MODULE + `/sys-data-role/menu`); 58 | }; 59 | -------------------------------------------------------------------------------- /src/api/modules/system/dept.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { SysDeptQuery, SysDeptRow, SysDeptForm, SysDeptTree, SysDeptLeaderData, SysDeptDept } from '@/api/types/system/dept'; 5 | 6 | /** 7 | * 查询列表 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const getSysDeptListApi = (params: SysDeptQuery) => { 12 | return http.get>(ADMIN_MODULE + `/sys-dept`, params); 13 | }; 14 | 15 | /** 16 | * 添加 17 | * @param params 18 | * @returns {*} 19 | */ 20 | export const createSysDeptApi = (params: SysDeptForm) => { 21 | return http.post(ADMIN_MODULE + `/sys-dept`, params); 22 | }; 23 | 24 | /** 25 | * 修改 26 | * @param params 27 | * @returns {*} 28 | */ 29 | export const updateSysDeptApi = (params: SysDeptForm) => { 30 | return http.put(ADMIN_MODULE + `/sys-dept`, params); 31 | }; 32 | 33 | /** 34 | * 删除 35 | * @param params 36 | * @returns {*} 37 | */ 38 | export const removeSysDeptApi = (params: { ids: number[] }) => { 39 | return http.delete(ADMIN_MODULE + `/sys-dept`, params); 40 | }; 41 | 42 | /** 43 | * 获取详情 44 | * @param params 45 | * @returns {*} 46 | */ 47 | export const getSysDeptDetailApi = (params: { id: number }) => { 48 | const { id } = params; 49 | return http.get(ADMIN_MODULE + `/sys-dept/${id}`); 50 | }; 51 | 52 | /** 53 | * 获取上级菜单树 54 | * @param params 55 | * @returns {*} 56 | */ 57 | export const getMenuTree = (params: { excludeNodeId?: number; appendRoot?: boolean }) => { 58 | return http.get(ADMIN_MODULE + `/sys-dept/tree`, params); 59 | }; 60 | 61 | /** 62 | * 获取部门负责人关系 63 | * @param params 64 | * @returns {*} 65 | */ 66 | export const getSysDeptLeaderApi = () => { 67 | return http.get(ADMIN_MODULE + `/sys-dept/leader`); 68 | }; 69 | 70 | /** 71 | * 获取部门树 72 | * @param params 73 | * @returns {*} 74 | */ 75 | export const getDeptTrees = (params: { deptId?: number }) => { 76 | return http.get(ADMIN_MODULE + `/sys-dept/datascope`, params); 77 | }; 78 | -------------------------------------------------------------------------------- /src/api/modules/system/dict.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { DictTypeQuery, DictType, DictQuery, Dict, DictCustom } from '@/api/types/system/dict'; 4 | import type { IPage } from '@/api/types'; 5 | 6 | /** 7 | * 字典类别列表 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const getDictType = (params: DictTypeQuery) => { 12 | return http.get>(ADMIN_MODULE + `/sys-dict-type`, params); 13 | }; 14 | 15 | /** 16 | * 添加字典类别 17 | * @param params 18 | * @returns {*} 19 | */ 20 | export const addDictType = (params: DictType) => { 21 | return http.post(ADMIN_MODULE + `/sys-dict-type`, params); 22 | }; 23 | 24 | /** 25 | * 修改字典类型 26 | * @param params 27 | * @returns {*} 28 | */ 29 | export const editDictType = (params: DictType) => { 30 | return http.put(ADMIN_MODULE + `/sys-dict-type`, params); 31 | }; 32 | 33 | /** 34 | * 删除字典类型 35 | * @param params 36 | * @returns {*} 37 | */ 38 | export const deleteDictType = (params: { ids: number[] }) => { 39 | return http.delete(ADMIN_MODULE + `/sys-dict-type`, params); 40 | }; 41 | 42 | /** 43 | * 获取字典数据列表 44 | * @param params 45 | * @returns {*} 46 | */ 47 | export const getDictData = (params: DictQuery) => { 48 | return http.get>(ADMIN_MODULE + `/sys-dict`, params); 49 | }; 50 | 51 | /** 52 | * 添加字典类别 53 | * @param params 54 | * @returns {*} 55 | */ 56 | export const addDictData = (params: Dict) => { 57 | return http.post(ADMIN_MODULE + `/sys-dict`, params); 58 | }; 59 | 60 | /** 61 | * 修改字典类型 62 | * @param params 63 | * @returns {*} 64 | */ 65 | export const editDictData = (params: Dict) => { 66 | return http.put(ADMIN_MODULE + `/sys-dict`, params); 67 | }; 68 | 69 | /** 70 | * 删除字典 71 | * @param params 72 | * @returns {*} 73 | */ 74 | export const deleteDictData = (params: { ids: number[] }) => { 75 | return http.delete(ADMIN_MODULE + `/sys-dict`, params); 76 | }; 77 | 78 | /** 79 | * 获取所有字典信息 80 | * @returns {*} 81 | */ 82 | export const getAllDict = () => { 83 | return http.get>(ADMIN_MODULE + `/sys-dict/dict`, {}); 84 | }; 85 | 86 | export const getDictTypeOptions = () => { 87 | return http.get(ADMIN_MODULE + `/sys-dict-type/selectOptionsType`); 88 | }; 89 | 90 | /** 91 | * 导出字典sql 92 | * @param params 93 | */ 94 | export const exportDictSql = (params: { ids: number[] }) => { 95 | return http.post(ADMIN_MODULE + `/sys-dict/sql/export`, params); 96 | }; 97 | 98 | /** 99 | * 获取指定字典 100 | * @param params 101 | * @returns {*} 102 | */ 103 | export const getDictByCode = (params: { typeCode: string[] }) => { 104 | const searchParams = new URLSearchParams(); 105 | params.typeCode.forEach(code => searchParams.append('typeCode', code)); 106 | return http.get>(ADMIN_MODULE + `/sys-dict/code?${searchParams.toString()}`); 107 | }; 108 | -------------------------------------------------------------------------------- /src/api/modules/system/file.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { SysFileQuery, SysFileRow } from '@/api/types/system/file'; 5 | 6 | /** 7 | * 查询列表 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const getSysFileListApi = (params: SysFileQuery) => { 12 | return http.get>(ADMIN_MODULE + `/sys-file`, params); 13 | }; 14 | -------------------------------------------------------------------------------- /src/api/modules/system/login.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { LoginParams, LoginInfo } from '@/api/types/system/login'; 4 | 5 | // 用户登录 6 | export const loginApi = (params: LoginParams) => { 7 | params.grantType = 'password'; 8 | params.clientId = import.meta.env.VITE_APP_CLIENT_ID; 9 | return http.post(ADMIN_MODULE + `/auth/login`, params); // 正常 post json 请求 ==> application/json 10 | // return http.post(`/login`, params, { loading: false }); // 控制当前请求不显示 loading 11 | // return http.post(`/login`, {}, { params }); // post 请求携带 query 参数 ==> ?username=admin&password=123456 12 | // return http.post(`/login`, qs.stringify(params)); // post 请求携带表单参数 ==> application/x-www-form-urlencoded 13 | // return http.get(`/login?${qs.stringify(params, { arrayFormat: "repeat" })}`); // get 请求可以携带数组等复杂参数 14 | }; 15 | 16 | // 获取菜单列表 17 | export const getAuthMenuListApi = () => { 18 | return http.get(ADMIN_MODULE + `/sys-menu/menu`, {}); 19 | }; 20 | 21 | // 获取按钮权限 22 | export const getAuthButtonListApi = () => { 23 | return http.get(ADMIN_MODULE + `/sys-menu/btn/permissions`, {}); 24 | }; 25 | 26 | // 用户退出登录 27 | export const logoutApi = () => { 28 | return http.post(ADMIN_MODULE + `/auth/logout`); 29 | }; 30 | 31 | // 获取用户角色 32 | export const getAuthRoleListApi = () => { 33 | return http.get(ADMIN_MODULE + `/sys-menu/user/roles`, {}); 34 | }; 35 | -------------------------------------------------------------------------------- /src/api/modules/system/menu.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { MenuQuery, MenuForm, MenuTree, MenuPermissionQuery } from '@/api/types/system/menu'; 4 | 5 | /** 6 | * 获取菜单列表 7 | * @param params 8 | * @returns {*} 9 | */ 10 | export const getMenuList = (params: MenuQuery) => { 11 | return http.get(ADMIN_MODULE + `/sys-menu`, params); 12 | }; 13 | 14 | /** 15 | * 添加菜单 16 | * @param params 17 | * @returns {*} 18 | */ 19 | export const addMenu = (params: MenuForm) => { 20 | return http.post(ADMIN_MODULE + `/sys-menu`, params); 21 | }; 22 | 23 | /** 24 | * 修改菜单 25 | * @param params 26 | * @returns {*} 27 | */ 28 | export const editMenu = (params: MenuForm) => { 29 | return http.put(ADMIN_MODULE + `/sys-menu`, params); 30 | }; 31 | 32 | /** 33 | * 删除菜单 34 | * @param params 35 | * @returns {*} 36 | */ 37 | export const deleteMenu = (params: { ids: string[] }) => { 38 | return http.delete(ADMIN_MODULE + `/sys-menu`, params); 39 | }; 40 | 41 | /** 42 | * 获取菜单详情 43 | * @param params 44 | * @returns {*} 45 | */ 46 | export const getMenuInfo = (params: { id: string }) => { 47 | const { id } = params; 48 | return http.get(ADMIN_MODULE + `/sys-menu/${id}`); 49 | }; 50 | 51 | /** 52 | * 获取上级菜单树 53 | * @param params 54 | * @returns {*} 55 | */ 56 | export const getMenuTree = (params: { nodeId?: string }) => { 57 | return http.get(ADMIN_MODULE + `/sys-menu/tree`, params); 58 | }; 59 | 60 | /** 61 | * 菜单权限是否存在验证 62 | * @param params 63 | * @returns {*} 64 | */ 65 | export const getBtnExits = (params: MenuPermissionQuery) => { 66 | return http.get<{ permissionCount: number }>(ADMIN_MODULE + `/sys-menu/btn/exists`, params); 67 | }; 68 | 69 | /** 70 | * 导出菜单sql 71 | * @param params 72 | */ 73 | export const exportMenuSql = (params: { ids: string[] }) => { 74 | return http.post(ADMIN_MODULE + `/sys-menu/sql/export`, params); 75 | }; 76 | 77 | /** 78 | * 修改菜单数据权限 79 | * @param params 80 | * @returns {*} 81 | */ 82 | export const chaneDataRole = (params: { id: string }) => { 83 | const { id } = params; 84 | return http.put(ADMIN_MODULE + `/sys-menu/datarole/change/${id}`); 85 | }; 86 | -------------------------------------------------------------------------------- /src/api/modules/system/message.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { MessageQuery, MessageRow, UnreadMessageCount } from '@/api/types/system/message'; 5 | 6 | /** 7 | * 列表 8 | */ 9 | export const getMessageListApi = (params: MessageQuery) => { 10 | return http.get>(ADMIN_MODULE + `/sys-message`, params); 11 | }; 12 | 13 | /** 14 | * 详情 15 | */ 16 | export const getMessageInfoApi = (id: string | number) => { 17 | return http.get(ADMIN_MODULE + `/sys-message/${id}`); 18 | }; 19 | 20 | /** 21 | * 我的待办消息 22 | */ 23 | export const getTodoMessageListApi = () => { 24 | return http.get(ADMIN_MODULE + `/sys-message/list/todo`); 25 | }; 26 | 27 | /** 28 | * 我的消息 29 | */ 30 | export const getNoticeMessageListApi = () => { 31 | return http.get(ADMIN_MODULE + `/sys-message/list/msg`); 32 | }; 33 | 34 | /** 35 | * 未读消息数量 36 | */ 37 | export const getUnreadMessageCountApi = () => { 38 | return http.get(ADMIN_MODULE + `/sys-message/count`); 39 | }; 40 | -------------------------------------------------------------------------------- /src/api/modules/system/role.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { RoleQuery, RoleInfo, RoleForm, RoleMenu, RoleMenuForm } from '@/api/types/system/role'; 4 | import type { IPage } from '@/api/types'; 5 | 6 | /** 7 | * 获取角色列表 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const getRoleList = (params: RoleQuery) => { 12 | return http.get>(ADMIN_MODULE + `/sys-role`, params); 13 | }; 14 | 15 | /** 16 | * 添加角色 17 | * @param params 18 | * @returns {*} 19 | */ 20 | export const addRole = (params: RoleForm) => { 21 | return http.post(ADMIN_MODULE + `/sys-role`, params); 22 | }; 23 | 24 | /** 25 | * 修改角色 26 | * @param params 27 | * @returns {*} 28 | */ 29 | export const editRole = (params: RoleForm) => { 30 | return http.put(ADMIN_MODULE + `/sys-role`, params); 31 | }; 32 | 33 | /** 34 | * 删除角色 35 | * @param params 36 | * @returns {*} 37 | */ 38 | export const deleteRole = (params: { ids: number[] }) => { 39 | return http.delete(ADMIN_MODULE + `/sys-role`, params); 40 | }; 41 | 42 | /** 43 | * 获取角色菜单权限 44 | * @param params 45 | * @returns {*} 46 | */ 47 | export const getRoleMenus = (params: { roleId: number }) => { 48 | return http.get(ADMIN_MODULE + `/sys-role/menu`, params); 49 | }; 50 | 51 | /** 52 | * 设置角色菜单权限 53 | * @param params 54 | * @returns {*} 55 | */ 56 | export const setRoleMenus = (params: RoleMenuForm) => { 57 | return http.put(ADMIN_MODULE + `/sys-role/menu`, params); 58 | }; 59 | -------------------------------------------------------------------------------- /src/api/modules/system/sysTempFile.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { 5 | SysTempFileQuery, 6 | SysTempFileRow, 7 | SysTempFileForm, 8 | SysTempFileHistoryQuery, 9 | SysTempFileHistory 10 | } from '@/api/types/system/sysTempFile'; 11 | 12 | /** 13 | * 查询列表 14 | * @param params 15 | * @returns {*} 16 | */ 17 | export const getSysTempFileListApi = (params: SysTempFileQuery) => { 18 | return http.get>(ADMIN_MODULE + `/sys-temp-file`, params); 19 | }; 20 | 21 | /** 22 | * 添加 23 | * @param params 24 | * @returns {*} 25 | */ 26 | export const createSysTempFileApi = (params: SysTempFileForm) => { 27 | return http.post(ADMIN_MODULE + `/sys-temp-file`, params); 28 | }; 29 | 30 | /** 31 | * 修改 32 | * @param params 33 | * @returns {*} 34 | */ 35 | export const updateSysTempFileApi = (params: SysTempFileForm) => { 36 | return http.put(ADMIN_MODULE + `/sys-temp-file`, params); 37 | }; 38 | 39 | /** 40 | * 删除 41 | * @param params 42 | * @returns {*} 43 | */ 44 | export const removeSysTempFileApi = (params: { ids: (string | number)[] }) => { 45 | return http.delete(ADMIN_MODULE + `/sys-temp-file`, params); 46 | }; 47 | 48 | /** 49 | * 获取详情 50 | * @param params 51 | * @returns {*} 52 | */ 53 | export const getSysTempFileDetailApi = (params: { id: number }) => { 54 | const { id } = params; 55 | return http.get(ADMIN_MODULE + `/sys-temp-file/${id}`); 56 | }; 57 | 58 | /** 59 | * 获取历史记录 60 | * @param params 61 | */ 62 | export const getSysTempFileHistoryListApi = (params: SysTempFileHistoryQuery) => { 63 | return http.get>(ADMIN_MODULE + `/sys-temp-file-history/history`, params); 64 | }; 65 | -------------------------------------------------------------------------------- /src/api/modules/system/upload.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IUploadResult, UploadFile, UploadResult } from '@/api/types/system/upload'; 4 | import type { AxiosRequestConfig } from 'axios'; 5 | 6 | /** 7 | * 上传文件 8 | * @param params 9 | * @returns {*} 10 | */ 11 | export const uploadFile = (params: UploadFile, config?: AxiosRequestConfig | undefined) => { 12 | return http.upload(ADMIN_MODULE + `/sys-file/upload`, params, config); 13 | }; 14 | 15 | /** 16 | * 上传模板文件 17 | * @param params 18 | * @param config 19 | * @returns {*} 20 | */ 21 | export const uploadTmpFile = (params: UploadFile, config?: AxiosRequestConfig | undefined) => { 22 | return http.upload(ADMIN_MODULE + `/sys-temp-file/upload`, params, config); 23 | }; 24 | -------------------------------------------------------------------------------- /src/api/modules/teacher/teacherStatistics.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage } from '@/api/types'; 4 | import type { TeacherStatisticsQuery, TeacherStatisticsRow, TeacherStatisticsForm } from '@/api/types/teacher/teacherStatistics'; 5 | import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload'; 6 | import type { AxiosRequestConfig } from 'axios'; 7 | /** 8 | * 查询列表 9 | * @param params 10 | * @returns {*} 11 | */ 12 | export const getTeacherStatisticsListApi = (params: TeacherStatisticsQuery) => { 13 | return http.get>(ADMIN_MODULE + `/teacher-statistics`, params); 14 | }; 15 | 16 | /** 17 | * 添加 18 | * @param params 19 | * @returns {*} 20 | */ 21 | export const createTeacherStatisticsApi = (params: TeacherStatisticsForm) => { 22 | return http.post(ADMIN_MODULE + `/teacher-statistics`, params); 23 | }; 24 | 25 | /** 26 | * 修改 27 | * @param params 28 | * @returns {*} 29 | */ 30 | export const updateTeacherStatisticsApi = (params: TeacherStatisticsForm) => { 31 | return http.put(ADMIN_MODULE + `/teacher-statistics`, params); 32 | }; 33 | 34 | /** 35 | * 删除 36 | * @param params 37 | * @returns {*} 38 | */ 39 | export const removeTeacherStatisticsApi = (params: { ids: (string | number)[] }) => { 40 | return http.delete(ADMIN_MODULE + `/teacher-statistics`, params); 41 | }; 42 | 43 | /** 44 | * 获取详情 45 | * @param params 46 | * @returns {*} 47 | */ 48 | export const getTeacherStatisticsDetailApi = (params: { id: number }) => { 49 | const { id } = params; 50 | return http.get(ADMIN_MODULE + `/teacher-statistics/${id}`); 51 | }; 52 | 53 | /** 54 | * 导入excel 55 | * @param params 56 | */ 57 | export const importTeacherStatisticsExcelApi = (params: UploadRawFile, config?: AxiosRequestConfig | undefined) => { 58 | return http.upload(ADMIN_MODULE + `/teacher-statistics/import`, params, config); 59 | }; 60 | 61 | /** 62 | * 导出excel 63 | * @param params 64 | * @returns {*} 65 | */ 66 | export const exportTeacherStatisticsExcelApi = (params: TeacherStatisticsQuery) => { 67 | return http.download(ADMIN_MODULE + `/teacher-statistics/export`, params); 68 | }; 69 | 70 | /** 71 | * 远程搜索 72 | * @param params 73 | */ 74 | export const remoteTeacherStaticsSearchApi = (params: { keyword: string }) => { 75 | const { keyword } = params; 76 | return http.get(ADMIN_MODULE + `/teacher-statistics/remote/${keyword}`); 77 | }; 78 | -------------------------------------------------------------------------------- /src/api/modules/toolbox/generator.ts: -------------------------------------------------------------------------------- 1 | import http from '@/api'; 2 | import { ADMIN_MODULE } from '@/api/helper/prefix'; 3 | import type { IPage, IPageQuery } from '@/api/types'; 4 | import type { 5 | GeneratorCheckInfo, 6 | GeneratorForm, 7 | GeneratorInfo, 8 | GeneratorPreviewInfo, 9 | GeneratorQuery 10 | } from '@/api/types/toolbox/generator'; 11 | 12 | /** 13 | * 查询已经导入的表列表 14 | * @param params 15 | * @returns {*} 16 | */ 17 | export const getGeneratorList = (params: GeneratorQuery) => { 18 | return http.get>(ADMIN_MODULE + `/generator/list`, params); 19 | }; 20 | 21 | /** 22 | * 查询要导入的表列表(排除已经导入的表) 23 | * @param params 24 | */ 25 | export const getGeneratorSchemaList = (params: IPageQuery) => { 26 | return http.get>(ADMIN_MODULE + `/generator/schema/list`, params); 27 | }; 28 | 29 | /** 30 | * 导入指定表 31 | * @param params 32 | */ 33 | export const importGenerator = (params: { tableName: (string | number)[] }) => { 34 | return http.post(ADMIN_MODULE + `/generator/import`, params); 35 | }; 36 | 37 | /** 38 | * 更新代码生成配置 39 | * @param params 40 | */ 41 | export const saveGenerator = (params: GeneratorForm) => { 42 | return http.put(ADMIN_MODULE + `/generator`, params); 43 | }; 44 | 45 | /** 46 | * 代码生成参数详情 47 | * @param tableName 48 | */ 49 | export const getGeneratorInfo = (tableName: string) => { 50 | return http.get(ADMIN_MODULE + `/generator/${tableName}`); 51 | }; 52 | 53 | /** 54 | * 代码生成 55 | * @param params 56 | */ 57 | export const codeGenerator = (tableName: string) => { 58 | return http.post(ADMIN_MODULE + `/generator/generator/${tableName}`); 59 | }; 60 | 61 | /** 62 | * 删除导入的表 63 | * @param params 64 | */ 65 | export const deleteGenerator = (params: { tableNames: (string | number)[] }) => { 66 | return http.delete(ADMIN_MODULE + `/generator`, params); 67 | }; 68 | 69 | /** 70 | * zip下载 71 | * @param params 72 | * @returns {*} 73 | */ 74 | export const downloadZip = (params: { tableNames: string[] }) => { 75 | return http.download(ADMIN_MODULE + `/generator/zip`, params); 76 | }; 77 | 78 | /** 79 | * 代码预览 80 | * @param tableName 81 | */ 82 | export const previewCode = (tableName: string) => { 83 | return http.get(ADMIN_MODULE + `/generator/preview/${tableName}`); 84 | }; 85 | /** 86 | * 验证磁盘 87 | * @param tableName 88 | */ 89 | export const checkDisk = (tableName: string) => { 90 | return http.get(ADMIN_MODULE + `/generator/check/${tableName}`); 91 | }; 92 | -------------------------------------------------------------------------------- /src/api/types/index.ts: -------------------------------------------------------------------------------- 1 | // 请求响应参数(不包含data) 2 | export type IResult = { 3 | code: string; 4 | message: string; 5 | }; 6 | 7 | // 请求响应参数(包含data) 8 | export type IResultData = IResult & { 9 | data: T; 10 | }; 11 | 12 | export type IPage = { 13 | current: number; 14 | limit: number; 15 | totalPage: number; 16 | total: number; 17 | rows: T[]; 18 | param?: { [key: string]: any } | string; 19 | }; 20 | 21 | export type IPageQuery = { 22 | page: number; 23 | limit: number; 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/types/system/captcha.ts: -------------------------------------------------------------------------------- 1 | // 登录模块 2 | export type CaptchaInfo = { 3 | bigImageBase64: string; 4 | bigWidth: number; 5 | bigHeight: number; 6 | smallImageBase64: string; 7 | smallWidth: number; 8 | smallHeight: number; 9 | requestId: string; 10 | posY: number; 11 | secretKey: string; 12 | }; 13 | export type CaptchaVerifyImageParams = { 14 | requestId: string; 15 | moveEncrypted: string; 16 | }; 17 | -------------------------------------------------------------------------------- /src/api/types/system/client.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type SysClientQuery = IPageQuery & { 5 | clientKey?: string; 6 | clientSecret?: string; 7 | grantTypeCd?: string; 8 | deviceTypeCd?: string; 9 | activeTimeout?: number; 10 | version?: number; 11 | }; 12 | 13 | // 编辑form表单 14 | export type SysClientForm = { 15 | clientKey?: string; 16 | clientSecret?: string; 17 | grantTypeCdList?: string[]; 18 | grantTypeCd?: string; 19 | deviceTypeCd?: string; 20 | activeTimeout?: number; 21 | timeout?: number; 22 | clientStatusCd?: string; 23 | version?: number; 24 | remark?: string; 25 | }; 26 | 27 | // list或detail返回结构 28 | export type SysClientRow = { 29 | clientId?: string; 30 | clientKey?: string; 31 | clientSecret?: string; 32 | grantTypeCd?: string; 33 | grantTypeCdList?: string[]; 34 | deviceTypeCd?: string; 35 | activeTimeout?: number; 36 | timeout?: number; 37 | clientStatusCd?: string; 38 | version?: number; 39 | remark?: string; 40 | isLock?: string; 41 | }; 42 | -------------------------------------------------------------------------------- /src/api/types/system/common.ts: -------------------------------------------------------------------------------- 1 | // 登录模块 2 | export type CommonTemplateDown = { 3 | templateName: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/api/types/system/config.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | export type ConfigQuery = IPageQuery & { 4 | configName?: string; 5 | configKey?: string; 6 | }; 7 | 8 | export type ConfigForm = { 9 | id?: number; 10 | configName: string; 11 | configKey: string; 12 | configValue: string; 13 | remark: string; 14 | }; 15 | 16 | export type ConfigInfo = { 17 | id: number; 18 | configName: string; 19 | configKey: string; 20 | configValue: string; 21 | remark: string; 22 | createId: number; 23 | createTime: string; 24 | updateId: number; 25 | updateTime: string; 26 | isLock?: string; 27 | }; 28 | -------------------------------------------------------------------------------- /src/api/types/system/datarole.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type SysDataRoleQuery = IPageQuery & { 5 | roleName?: string; 6 | isLock?: string; 7 | }; 8 | 9 | // 编辑form表单 10 | export type SysDataRoleForm = { 11 | id?: number; 12 | roleName?: string; 13 | remark?: string; 14 | isLock?: string; 15 | }; 16 | 17 | // list或detail返回结构 18 | export type SysDataRoleRow = { 19 | id?: number; 20 | roleName?: string; 21 | remark?: string; 22 | isLock?: string; 23 | }; 24 | 25 | // 数据角色Form初始化数据:菜单树、部门树、用户列等 26 | export type SysDataRoleMeta = { 27 | menuLists: SysDataRoleMenuTree[]; 28 | }; 29 | 30 | export type SysDataRoleMenuTree = { 31 | id: string; 32 | pid: string; 33 | title: string; 34 | children: SysDataRoleMenuTree[]; 35 | }; 36 | -------------------------------------------------------------------------------- /src/api/types/system/dept.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type SysDeptQuery = IPageQuery & { 5 | name?: string; 6 | }; 7 | 8 | // 编辑form表单 9 | export type SysDeptForm = { 10 | id?: number; 11 | name?: string; 12 | pid?: number; 13 | sort?: number; 14 | remark?: string; 15 | leaders: number[]; 16 | }; 17 | 18 | // list或detail返回结构 19 | export type SysDeptRow = { 20 | id: number; 21 | pid: number; 22 | name: string; 23 | deep?: number; 24 | sort?: number; 25 | remark?: string; 26 | isLock?: string; 27 | leaders: number[]; 28 | }; 29 | 30 | export type SysDeptTree = { 31 | id: number; 32 | pid: number; 33 | name: string; 34 | deep?: number; 35 | sort?: number; 36 | children: SysDeptTree[]; 37 | userTotal?: number; 38 | }; 39 | 40 | export type SysDeptLeader = { 41 | id: number; 42 | nickname: string; 43 | }; 44 | 45 | export type SysDeptLeaderData = { 46 | leaderInfoVOS: SysDeptLeader[]; 47 | }; 48 | 49 | export type SysDeptDeptSetting = { 50 | userIds: number[]; 51 | deptIds: number[]; 52 | }; 53 | 54 | export type SysDeptDept = { 55 | deptLists: SysDeptDeptTree[]; 56 | selectIds: number[]; 57 | }; 58 | 59 | export type SysDeptDeptTree = { 60 | id: number; 61 | pid: number; 62 | name: string; 63 | children: SysDeptDeptTree[]; 64 | }; 65 | -------------------------------------------------------------------------------- /src/api/types/system/dict.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 获取所有字典信息 4 | export type DictCustom = { 5 | callbackShowStyle: string; 6 | codeName: string; 7 | id: string; 8 | isLock: string; 9 | isShow: string; 10 | sort: number; 11 | sysDictTypeId: number; 12 | alias?: string; 13 | }; 14 | 15 | // 字典类别列表查询 16 | export type DictTypeQuery = IPageQuery & { 17 | typeName: string; 18 | typeCode: string; 19 | }; 20 | 21 | // 字典类别列表 22 | export type DictType = { 23 | id?: number; 24 | typeName: string; 25 | typeCode: string; 26 | isLock?: string; 27 | isShow?: string; 28 | delFlag?: string; 29 | remark: string; 30 | createTime?: string; 31 | updateTime?: string; 32 | isDynamic: boolean; 33 | }; 34 | 35 | // 字典分类option 36 | export type DictOption = { 37 | value: string; 38 | label: string; 39 | }; 40 | 41 | // 字典分类类型 42 | export type DictCategory = { 43 | label: string; 44 | options: DictOption[]; 45 | }; 46 | 47 | // 字典列表查询 48 | export type DictQuery = IPageQuery & { 49 | sysDictTypeId: number; 50 | codeName: string; 51 | }; 52 | 53 | export type Dict = { 54 | id?: number; 55 | sysDictTypeId?: number; 56 | codeName: string; 57 | alias?: string; 58 | sort?: number; 59 | callbackShowStyle: string; 60 | remark: string; 61 | isLock?: string; 62 | isShow?: string; 63 | delFlag?: string; 64 | createTime?: string; 65 | updateTime?: string; 66 | }; 67 | -------------------------------------------------------------------------------- /src/api/types/system/file.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type SysFileQuery = IPageQuery & { 5 | filename?: string; 6 | dirTag?: string; 7 | objectName?: string; 8 | }; 9 | 10 | // 编辑form表单 11 | export type SysFileForm = {}; 12 | 13 | // list或detail返回结构 14 | export type SysFileRow = { 15 | id?: number; 16 | filename?: string; 17 | dirTag?: string; 18 | size?: number; 19 | url?: string; 20 | objectName?: string; 21 | contextType?: string; 22 | eTag?: string; 23 | fileId?: number; 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/types/system/login.ts: -------------------------------------------------------------------------------- 1 | // 登录模块 2 | export type LoginParams = { 3 | username: string; 4 | password: string; 5 | clientId: string; 6 | grantType: string; 7 | }; 8 | 9 | export type LoginInfo = { 10 | name: string; 11 | avatar: string; 12 | introduction: string; 13 | accessToken: string; 14 | refreshToken: string; 15 | roles: string[]; 16 | userInfo: UserInfo; 17 | permissions: string[]; 18 | }; 19 | 20 | export type UserInfo = { 21 | id?: number; 22 | username: string; 23 | phone?: string; 24 | nickname?: string; 25 | logo?: string; 26 | age?: number; 27 | sex?: number; 28 | idCard?: string; 29 | email?: string; 30 | accountStatusCd?: string; 31 | userTagCd?: string; 32 | lastLoginTime?: string; 33 | createTime?: string; 34 | updateTime?: string; 35 | }; 36 | -------------------------------------------------------------------------------- /src/api/types/system/menu.ts: -------------------------------------------------------------------------------- 1 | export type MenuQuery = { 2 | isShowButton: boolean; 3 | }; 4 | 5 | export type MenuForm = { 6 | id?: string; 7 | title?: string; 8 | pid?: string; 9 | path?: string; 10 | name?: string; 11 | icon?: string; 12 | component?: string; 13 | redirect?: string; 14 | sort?: number; 15 | deep?: number; 16 | menuTypeCd?: string; 17 | permissions?: string; 18 | isHidden?: string; 19 | hasChildren?: string; 20 | isLink?: string; 21 | isFull?: string; 22 | isAffix?: string; 23 | isKeepAlive?: string; 24 | }; 25 | 26 | export type MenuTree = { 27 | id: string; 28 | pid: string; 29 | title: string; 30 | children: MenuTree[]; 31 | }; 32 | 33 | export type MenuPermissionQuery = { 34 | id: string; 35 | permissions: string; 36 | }; 37 | -------------------------------------------------------------------------------- /src/api/types/system/message.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | export type MessageQuery = IPageQuery & { 4 | messageTypeCd?: string; 5 | readType?: string; 6 | }; 7 | 8 | export type MessageRow = { 9 | id: number; 10 | messageTypeCd: string; 11 | senderId: number; 12 | title: string; 13 | content: string; 14 | isRead: string; 15 | createTime: string; 16 | }; 17 | 18 | export type UnreadMessageCount = { 19 | all: number; 20 | todo: number; 21 | msg: number; 22 | }; 23 | 24 | export type Message = { 25 | messageTypeCd?: string; 26 | senderId?: number; 27 | title?: string; 28 | content?: string; 29 | receiverIds?: string[]; 30 | }; 31 | -------------------------------------------------------------------------------- /src/api/types/system/role.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | export type RoleQuery = IPageQuery & { 4 | roleName?: string; 5 | }; 6 | 7 | export type RoleForm = { 8 | id?: number; 9 | roleName: string; 10 | remark: string; 11 | }; 12 | 13 | export type RoleInfo = { 14 | id: number; 15 | roleName: string; 16 | remark: string; 17 | delFlag: string; 18 | createTime: string; 19 | updateTime: string; 20 | isLock?: string; 21 | permissions?: string; 22 | }; 23 | 24 | export type RoleMenu = { 25 | menuLists: RoleMenuTree[]; 26 | selectIds: string[]; 27 | }; 28 | 29 | export type RoleMenuTree = { 30 | id: string; 31 | pid: string; 32 | title: string; 33 | children: RoleMenuTree[]; 34 | }; 35 | 36 | export type RoleMenuForm = { 37 | menuIds: string[]; 38 | roleId: number; 39 | }; 40 | -------------------------------------------------------------------------------- /src/api/types/system/sysTempFile.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type SysTempFileQuery = IPageQuery & { 5 | tempName?: string; 6 | }; 7 | 8 | // 历史记录查询条件 9 | export type SysTempFileHistoryQuery = IPageQuery & { 10 | sysTempFileId: number; 11 | }; 12 | 13 | // 编辑form表单 14 | export type SysTempFileForm = { 15 | id?: number; 16 | sysFileId?: number; 17 | tempName?: string; 18 | url?: string; 19 | remark?: string; 20 | }; 21 | 22 | // list或detail返回结构 23 | export type SysTempFileRow = { 24 | id?: number; 25 | sysFileId?: number; 26 | tempName?: string; 27 | url?: string; 28 | remark?: string; 29 | delFlag?: string; 30 | createId?: number; 31 | createTime?: string; 32 | updateId?: number; 33 | updateTime?: string; 34 | }; 35 | 36 | // 历史记录 37 | export type SysTempFileHistory = { 38 | id?: number; 39 | sysTempFileId?: number; 40 | sysFileId?: number; 41 | tempName?: string; 42 | url?: string; 43 | remark?: string; 44 | delFlag?: string; 45 | createId?: number; 46 | createTime?: string; 47 | }; 48 | -------------------------------------------------------------------------------- /src/api/types/system/upload.ts: -------------------------------------------------------------------------------- 1 | import type { UploadRawFile } from 'element-plus/es/components/upload/src/upload'; 2 | 3 | export type UploadFile = { 4 | file: UploadRawFile; 5 | dirTag?: string; 6 | }; 7 | 8 | export type UploadResult = { 9 | url: string; 10 | filename: string; 11 | eTag: string; 12 | objectName: string; 13 | dirTag: string; 14 | contextType: string; 15 | size: number; 16 | fileId: number; 17 | }; 18 | 19 | export type IUploadResult = UploadResult | null; 20 | -------------------------------------------------------------------------------- /src/api/types/system/user.ts: -------------------------------------------------------------------------------- 1 | // 登录模块 2 | import type { IPageQuery } from '@/api/types'; 3 | 4 | export type UserQuery = IPageQuery & { 5 | username?: string; 6 | phone?: string; 7 | accountStatusCd?: string; 8 | startDate?: string; 9 | endDate?: string; 10 | nickname?: string; 11 | deptId?: number; 12 | isThisDeep?: boolean; 13 | }; 14 | 15 | export type UserForm = { 16 | id?: number; 17 | username?: string; 18 | pwd?: string; 19 | phone: string; 20 | nickname: string; 21 | logo: string; 22 | age: number; 23 | sex: number; 24 | idCard: string; 25 | email: string; 26 | accountStatusCd: string; 27 | userTagCd: string; 28 | birthday: string; 29 | }; 30 | 31 | export type UserInfo = { 32 | id?: number; 33 | username: string; 34 | phone: string; 35 | nickname: string; 36 | logo: string; 37 | age: number; 38 | sex: number; 39 | idCard: string; 40 | email: string; 41 | accountStatusCd: string; 42 | userTagCd: string; 43 | lastLoginTime: string; 44 | createTime: string; 45 | updateTime: string; 46 | delFlag: string; 47 | birthday: string; 48 | }; 49 | 50 | export type UserRoleForm = { 51 | roleIds: number[]; 52 | userId: number; 53 | }; 54 | 55 | export type UserRoleData = { 56 | selectIds: number[]; 57 | roleInfoVOS: UserRoleInfo[]; 58 | }; 59 | 60 | export type UserRoleInfo = { 61 | id: number; 62 | roleName: string; 63 | }; 64 | 65 | export type UserPasswordForm = { 66 | oldPwd: string; 67 | newPwd: string; 68 | }; 69 | 70 | export type UserOptions = { 71 | id: number; 72 | username: string; 73 | nickname: string; 74 | }; 75 | -------------------------------------------------------------------------------- /src/api/types/teacher/teacherStatistics.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | // 查询条件 4 | export type TeacherStatisticsQuery = IPageQuery & { 5 | year?: string; 6 | month?: string; 7 | duringTime?: string; 8 | teacherId?: string; 9 | teacherCommonType?: number; 10 | totalTeaching?: number; 11 | totalClassCount?: number; 12 | totalHours?: number; 13 | checkStatus?: number; 14 | checkTimeStart?: string; 15 | checkTimeEnd?: string; 16 | lastSyncTimeStart?: string; 17 | lastSyncTimeEnd?: string; 18 | }; 19 | 20 | // 编辑form表单 21 | export type TeacherStatisticsForm = { 22 | id?: number; 23 | year?: string; 24 | month?: string; 25 | duringTime?: string; 26 | teacherId?: string; 27 | teacherCommonType?: number; 28 | totalTeaching?: number; 29 | totalClassCount?: number; 30 | totalHours?: number; 31 | checkStatus?: number; 32 | checkTime?: string; 33 | lastSyncTime?: string; 34 | remark?: string; 35 | }; 36 | 37 | // list或detail返回结构 38 | export type TeacherStatisticsRow = { 39 | id?: number; 40 | year?: string; 41 | month?: string; 42 | duringTime?: string; 43 | teacherId?: string; 44 | teacherCommonType?: number; 45 | totalTeaching?: number; 46 | totalClassCount?: number; 47 | totalHours?: number; 48 | checkStatus?: number; 49 | checkTime?: string; 50 | lastSyncTime?: string; 51 | remark?: string; 52 | createId?: number; 53 | createTime?: string; 54 | updateId?: number; 55 | updateTime?: string; 56 | }; 57 | -------------------------------------------------------------------------------- /src/api/types/toolbox/generator.ts: -------------------------------------------------------------------------------- 1 | import type { IPageQuery } from '@/api/types'; 2 | 3 | export type GeneratorQuery = IPageQuery & { 4 | tableName?: string; 5 | tableComment?: string; 6 | }; 7 | 8 | export type GeneratorInfo = { 9 | tableId: number; 10 | tableName: string; 11 | tableComment: string; 12 | className: string; 13 | camelClassName: string; 14 | tplCategory: string; 15 | packageName: string; 16 | moduleName: string; 17 | businessName: string; 18 | functionName: string; 19 | functionAuthor: string; 20 | type: string; 21 | options: string; 22 | path: string; 23 | createId: number; 24 | createTime: string; 25 | updateId: number; 26 | updateTime: string; 27 | }; 28 | 29 | export type GeneratorForm = { 30 | baseInfo: GeneratorBaseInfo; 31 | columns: GeneratorColumnInfo[]; 32 | generatorInfo: GeneratorGeneratorInfo; 33 | }; 34 | 35 | export type GeneratorBaseInfo = { 36 | tableId?: number; 37 | tableName: string; 38 | tableComment: string; 39 | className: string; 40 | functionAuthor: string; 41 | remark: string; 42 | }; 43 | 44 | export type GeneratorColumnInfo = { 45 | autofillType: string; 46 | columnComment: string; 47 | columnId: number; 48 | columnName: string; 49 | columnType: string; 50 | dictType: string; 51 | htmlType: string; 52 | isAutofill: string; 53 | isEdit: string; 54 | isIncrement: string; 55 | isInsert: string; 56 | isList: string; 57 | isLogicDel: string; 58 | isPk: string; 59 | isQuery: string; 60 | isRequired: string; 61 | isUniqueValid: string; 62 | javaField: string; 63 | javaType: string; 64 | javaTypePackage: string; 65 | options: string; 66 | queryType: string; 67 | specialPackages: string; 68 | tableId: number; 69 | upCamelField: string; 70 | }; 71 | 72 | export type GeneratorGeneratorInfo = { 73 | tplCategory?: string; 74 | packageName: string; 75 | moduleName: string; 76 | businessName: string; 77 | functionName: string; 78 | options: string; 79 | type: string; 80 | parentMenuId: string; 81 | hasImport: string; 82 | hasExport: string; 83 | pathApi: string; 84 | pathWeb: string; 85 | generateType: string; 86 | menuInitType: string; 87 | btnPermissionType: string; 88 | isAutofill: string; 89 | }; 90 | 91 | export type GeneratorPreviewInfo = { 92 | name: string; 93 | code: string; 94 | language: string; 95 | alias: string; 96 | }; 97 | 98 | export type GeneratorCheckInfo = { 99 | checkedApiPath: boolean; 100 | checkedWebPath: boolean; 101 | pathApi: string; 102 | pathWeb: string; 103 | }; 104 | -------------------------------------------------------------------------------- /src/assets/icons/org.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/icons/scope.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | 9 | 11 | 13 | 14 | -------------------------------------------------------------------------------- /src/assets/icons/zip.svg: -------------------------------------------------------------------------------- 1 | 3 | 5 | 7 | -------------------------------------------------------------------------------- /src/assets/images/403.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/403.png -------------------------------------------------------------------------------- /src/assets/images/404.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/404.png -------------------------------------------------------------------------------- /src/assets/images/500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/500.png -------------------------------------------------------------------------------- /src/assets/images/avatar.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/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/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left.png -------------------------------------------------------------------------------- /src/assets/images/login_left1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left1.png -------------------------------------------------------------------------------- /src/assets/images/login_left2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left2.png -------------------------------------------------------------------------------- /src/assets/images/login_left3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left3.png -------------------------------------------------------------------------------- /src/assets/images/login_left4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left4.png -------------------------------------------------------------------------------- /src/assets/images/login_left5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/login_left5.png -------------------------------------------------------------------------------- /src/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/images/msg01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/msg01.png -------------------------------------------------------------------------------- /src/assets/images/msg02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/msg02.png -------------------------------------------------------------------------------- /src/assets/images/msg03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/msg03.png -------------------------------------------------------------------------------- /src/assets/images/msg04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/msg04.png -------------------------------------------------------------------------------- /src/assets/images/msg05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/msg05.png -------------------------------------------------------------------------------- /src/assets/images/notData.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/notData.png -------------------------------------------------------------------------------- /src/assets/images/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/assets/images/welcome.png -------------------------------------------------------------------------------- /src/components/Captcha/index.scss: -------------------------------------------------------------------------------- 1 | .sliderBox { 2 | user-select: none; 3 | width: 320px; 4 | padding: 20px; 5 | background-color: #fff; 6 | box-shadow: 0 0 6px rgba(0, 0, 0, 0.2); // 盒阴影增强立体感 7 | border-radius: 4px; 8 | } 9 | 10 | .sliderBox_title { 11 | position: relative; 12 | font-size: 15px; 13 | text-align: center; // 居中标题 14 | } 15 | 16 | .sliderBox_refresh { 17 | position: absolute; 18 | top: 10px; 19 | right: 10px; 20 | width: 18px; 21 | height: 18px; 22 | cursor: pointer; 23 | color: #ffffff; 24 | transition: color 0.3s; // 添加过渡效果 25 | &:hover { 26 | color: #007bff; // 悬停时改变颜色 27 | } 28 | .el-icon { 29 | width: 100%; 30 | height: 100%; 31 | font-size: 18px; // 确保图标大小一致 32 | } 33 | } 34 | 35 | .sliderBox_content { 36 | position: relative; 37 | width: 100%; 38 | height: 160px; 39 | border: 1px solid #c6dbf5; // 使用较浅的蓝色边框 40 | 41 | .bigImg { 42 | width: 100%; 43 | height: 100%; 44 | object-fit: cover; // 确保图片覆盖整个容器 45 | } 46 | 47 | .smallImg { 48 | position: absolute; 49 | width: 50px; 50 | height: 50px; 51 | cursor: pointer; 52 | } 53 | } 54 | 55 | .btnBox { 56 | position: relative; 57 | width: 100%; 58 | height: 30px; 59 | margin-top: 22px; 60 | background-color: var(--el-color-primary-light-7); // 使用Element Plus的浅色主色 61 | border-radius: 8px; 62 | display: flex; 63 | align-items: center; 64 | justify-content: center; 65 | 66 | .sliderBox_text { 67 | font-size: 14px; 68 | color: #333; 69 | } 70 | 71 | .sliderBox_track { 72 | position: absolute; 73 | top: 0; 74 | left: 0; 75 | height: 100%; 76 | background-color: var(--el-color-primary-light-2); // 使用浅色主色 77 | border-radius: 8px; 78 | } 79 | 80 | .sliderBox_btn { 81 | position: absolute; 82 | top: -5px; 83 | left: 0; 84 | width: 50px; 85 | height: 40px; 86 | line-height: 40px; 87 | text-align: center; 88 | color: #fff; 89 | background-color: var(--el-color-primary); // 使用主色 90 | border-radius: 8px; 91 | box-shadow: 0 0 6px #ce9f7f; // 适度阴影 92 | cursor: pointer; 93 | } 94 | } 95 | 96 | .overlay { 97 | position: absolute; 98 | bottom: 0; 99 | width: 100%; 100 | height: 30px; 101 | display: flex; 102 | justify-content: center; 103 | align-items: center; 104 | color: #fff; 105 | font-size: 14px; 106 | background-color: rgba(0, 0, 0, 0.5); // 半透明黑色背景 107 | transition: background-color 0.3s; // 添加过渡效果 108 | &.success { 109 | background-color: rgba(92, 184, 92, 0.5); // 成功时的背景颜色 110 | } 111 | &.failure { 112 | background-color: rgba(217, 83, 79, 0.5); // 失败时的背景颜色 113 | } 114 | } 115 | 116 | .sliderBox_btn { 117 | cursor: pointer; 118 | user-select: none; 119 | outline: none; 120 | } 121 | .sliderBox_btn:active { 122 | background: #eee; 123 | } 124 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/403.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/404.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /src/components/ErrorMessage/500.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 21 | -------------------------------------------------------------------------------- /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 | 8 | .not-img { 9 | margin-right: 120px; 10 | } 11 | 12 | .not-detail { 13 | display: flex; 14 | flex-direction: column; 15 | 16 | h2, 17 | h4 { 18 | padding: 0; 19 | margin: 0; 20 | } 21 | 22 | h2 { 23 | font-size: 60px; 24 | color: var(--el-text-color-primary); 25 | } 26 | 27 | h4 { 28 | margin: 30px 0 20px; 29 | font-size: 19px; 30 | font-weight: normal; 31 | color: var(--el-text-color-regular); 32 | } 33 | 34 | .el-button { 35 | width: 100px; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/Grid/components/GridItem.vue: -------------------------------------------------------------------------------- 1 | 6 | 73 | -------------------------------------------------------------------------------- /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/HighCode/code.scss: -------------------------------------------------------------------------------- 1 | /* 语法高亮 */ 2 | .hljs-container { 3 | position: relative; 4 | display: flex; 5 | flex-direction: column; 6 | width: max-content; 7 | overflow-x: hidden; 8 | font-size: 14px; 9 | line-height: 24px; 10 | text-align: left; 11 | background: #21252b; 12 | 13 | .hljs-header { 14 | width: 100%; 15 | background-color: #21252b; 16 | display: flex; 17 | justify-content: flex-end; 18 | align-items: center; 19 | position: sticky; 20 | top: 0; 21 | left: 0; 22 | z-index: 100; 23 | } 24 | 25 | .hljs-wrapper { 26 | display: flex; 27 | align-items: flex-start; 28 | } 29 | 30 | /** 行数样式 */ 31 | .hljs-code-number { 32 | padding: 17px 10px 0; 33 | color: #d1d8e6; 34 | font-size: 12px; 35 | list-style: none; 36 | background: #21252b; 37 | position: sticky; 38 | left: 0; 39 | } 40 | 41 | pre { 42 | flex: 1; 43 | } 44 | } 45 | 46 | /** 3个点 */ 47 | .hljs-container .hljs-header::before { 48 | position: absolute; 49 | top: 10px; 50 | left: 15px; 51 | width: 12px; 52 | height: 12px; 53 | overflow: visible; 54 | font-weight: 700; 55 | font-size: 16px; 56 | line-height: 12px; 57 | white-space: nowrap; 58 | text-indent: 75px; 59 | background-color: #fc625d; 60 | border-radius: 16px; 61 | box-shadow: 62 | 20px 0 #fdbc40, 63 | 40px 0 #35cd4b; 64 | content: attr(codetype); 65 | } 66 | 67 | /** 滚动条 */ 68 | :deep(.hljs) { 69 | overflow-x: auto; 70 | } 71 | 72 | :deep(.hljs::-webkit-scrollbar) { 73 | width: 12px !important; 74 | height: 12px !important; 75 | } 76 | 77 | :deep(.hljs::-webkit-scrollbar-thumb) { 78 | height: 30px !important; 79 | background: #d1d8e6; 80 | background-clip: content-box; 81 | border: 2px solid transparent; 82 | border-radius: 19px; 83 | opacity: 0.8; 84 | } 85 | 86 | :deep(.hljs::-webkit-scrollbar-thumb:hover) { 87 | background: #a5b3cf; 88 | background-clip: content-box; 89 | border: 2px solid transparent; 90 | } 91 | 92 | :deep(.hljs::-webkit-scrollbar-track-piece) { 93 | width: 30px; 94 | height: 30px; 95 | background: #333; 96 | } 97 | 98 | ::-webkit-scrollbar-button { 99 | display: none; 100 | } 101 | 102 | /** 复制样式 */ 103 | .hljs-copy { 104 | width: auto; 105 | height: 30px; 106 | color: #d7d7e1; 107 | cursor: pointer; 108 | user-select: none; 109 | border-radius: 4px; 110 | transition: background-color 0.3s; 111 | margin: 0 2px; 112 | 113 | .sql-box { 114 | text-align: unset; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/components/HighCode/index.scss: -------------------------------------------------------------------------------- 1 | .hljs-container { 2 | position: relative; 3 | margin: 20px 0; 4 | background-color: #f3f3f3; 5 | border-radius: 8px; 6 | font-size: 14px; 7 | line-height: 1.6; 8 | overflow-x: auto; 9 | 10 | pre { 11 | margin: 0; 12 | } 13 | } 14 | 15 | .hljs-container.sql-box { 16 | text-align: unset; 17 | } 18 | 19 | .hljs-header { 20 | display: flex; 21 | justify-content: space-between; 22 | align-items: center; 23 | background-color: #343541; 24 | padding: 4px; 25 | position: relative; /* 添加 relative 定位 */ 26 | } 27 | 28 | .hljs-separator { 29 | height: 1px; 30 | width: 100%; 31 | background-color: #ccc; 32 | margin: 10px 0; 33 | } 34 | 35 | .hljs-language { 36 | color: #d7d7e1; 37 | font-weight: bold; 38 | margin-left: 10px; 39 | } 40 | 41 | .hljs-code-number { 42 | position: absolute; 43 | top: 0; /* 调整为 0,即头部的底部 */ 44 | left: 0; 45 | padding: 0 10px; 46 | color: #757575; 47 | user-select: none; 48 | 49 | li { 50 | list-style: none; 51 | margin: 0; 52 | padding: 0; 53 | } 54 | } 55 | 56 | .hljs-code { 57 | font-family: 'Courier New', monospace; 58 | } 59 | 60 | .hljs-copy { 61 | margin-right: 10px; 62 | color: #d7d7e1; 63 | cursor: pointer; 64 | user-select: none; 65 | background-color: #333; 66 | border-radius: 4px; 67 | transition: background-color 0.3s; 68 | 69 | &:hover { 70 | background-color: #555; 71 | } 72 | 73 | .sql-box { 74 | text-align: unset; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/components/HighCode/index.vue: -------------------------------------------------------------------------------- 1 | 15 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/components/HighCode/line.ts: -------------------------------------------------------------------------------- 1 | import './code.scss'; 2 | 3 | const vCode = { 4 | mounted(el: any) { 5 | buildLineNumber(el); 6 | }, 7 | updated(el: any) { 8 | buildLineNumber(el); 9 | } 10 | }; 11 | 12 | const buildLineNumber = (el: any) => { 13 | //获取代码片段 14 | const code = el.querySelector('code.hljs'); 15 | const pre = el.querySelector('pre'); 16 | const html = code?.innerHTML; 17 | const size = html.split('\n').length; 18 | const codeNumber = el.querySelector('.hljs-code-number'); 19 | if (codeNumber) { 20 | el.removeChild(codeNumber); 21 | } 22 | //插入行数 23 | const ul = document.createElement('ul'); 24 | for (let i = 1; i <= size; i++) { 25 | const li = document.createElement('li'); 26 | li.innerText = i + ''; 27 | ul.appendChild(li); 28 | } 29 | 30 | ul.classList.add('hljs-code-number'); 31 | 32 | // 确保 pre 是 el 的子元素后再进行插入 33 | if (pre && pre.parentNode === el) { 34 | el.insertBefore(ul, pre); 35 | } 36 | }; 37 | 38 | export default vCode; 39 | -------------------------------------------------------------------------------- /src/components/ImportExcel/index.scss: -------------------------------------------------------------------------------- 1 | .upload { 2 | width: 80%; 3 | } 4 | -------------------------------------------------------------------------------- /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 | 9 | .loading-wrap { 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | padding: 98px; 14 | } 15 | } 16 | 17 | .dot { 18 | position: relative; 19 | box-sizing: border-box; 20 | display: inline-block; 21 | width: 32px; 22 | height: 32px; 23 | font-size: 32px; 24 | transform: rotate(45deg); 25 | animation: ant-rotate 1.2s infinite linear; 26 | } 27 | 28 | .dot i { 29 | position: absolute; 30 | display: block; 31 | width: 14px; 32 | height: 14px; 33 | background-color: var(--el-color-primary); 34 | border-radius: 100%; 35 | opacity: 0.3; 36 | transform: scale(0.75); 37 | transform-origin: 50% 50%; 38 | animation: ant-spin-move 1s infinite linear alternate; 39 | } 40 | 41 | .dot i:nth-child(1) { 42 | top: 0; 43 | left: 0; 44 | } 45 | 46 | .dot i:nth-child(2) { 47 | top: 0; 48 | right: 0; 49 | animation-delay: 0.4s; 50 | } 51 | 52 | .dot i:nth-child(3) { 53 | right: 0; 54 | bottom: 0; 55 | animation-delay: 0.8s; 56 | } 57 | 58 | .dot i:nth-child(4) { 59 | bottom: 0; 60 | left: 0; 61 | animation-delay: 1.2s; 62 | } 63 | 64 | @keyframes ant-rotate { 65 | to { 66 | transform: rotate(405deg); 67 | } 68 | } 69 | 70 | @keyframes ant-spin-move { 71 | to { 72 | opacity: 1; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /src/components/ProTable/components/ColSetting.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 44 | 45 | 50 | -------------------------------------------------------------------------------- /src/components/ProTable/components/Pagination.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /src/components/ProTable/components/TableColumn.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 63 | -------------------------------------------------------------------------------- /src/components/RemoteSearchSelect/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 82 | -------------------------------------------------------------------------------- /src/components/SelectFilter/index.scss: -------------------------------------------------------------------------------- 1 | .select-filter { 2 | width: 100%; 3 | 4 | .select-filter-item { 5 | display: flex; 6 | align-items: center; 7 | border-bottom: 1px dashed var(--el-border-color-light); 8 | 9 | &:last-child { 10 | border-bottom: none; 11 | } 12 | 13 | .select-filter-item-title { 14 | margin-top: -2px; 15 | 16 | span { 17 | font-size: 14px; 18 | color: var(--el-text-color-regular); 19 | white-space: nowrap; 20 | } 21 | } 22 | 23 | .select-filter-notData { 24 | margin: 18px 0; 25 | font-size: 14px; 26 | color: var(--el-text-color-regular); 27 | } 28 | 29 | .select-filter-list { 30 | display: flex; 31 | flex: 1; 32 | padding: 0; 33 | margin: 13px 0; 34 | 35 | li { 36 | display: flex; 37 | align-items: center; 38 | padding: 5px 15px; 39 | margin-right: 16px; 40 | font-size: 13px; 41 | color: var(--el-color-primary); 42 | list-style: none; 43 | cursor: pointer; 44 | background: var(--el-color-primary-light-9); 45 | border: 1px solid var(--el-color-primary-light-5); 46 | border-radius: 32px; 47 | 48 | &:hover { 49 | color: #ffffff; 50 | background: var(--el-color-primary); 51 | border-color: var(--el-color-primary); 52 | transition: 0.1s; 53 | } 54 | 55 | &.active { 56 | font-weight: bold; 57 | color: #ffffff; 58 | background: var(--el-color-primary); 59 | border-color: var(--el-color-primary); 60 | } 61 | 62 | .el-icon { 63 | margin-right: 4px; 64 | font-size: 16px; 65 | font-weight: bold; 66 | } 67 | 68 | span { 69 | white-space: nowrap; 70 | } 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/components/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 36 | -------------------------------------------------------------------------------- /src/components/SwitchDark/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 13 | -------------------------------------------------------------------------------- /src/config/consts.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '@/config/typings'; 2 | 3 | /** 4 | * 目录 5 | * @type {string} 6 | */ 7 | export const MENU_DIR: string = '1002001'; 8 | /** 9 | * 菜单 10 | * @type {string} 11 | */ 12 | export const MENU_PAGE: string = '1002002'; 13 | /** 14 | * 按钮 15 | * @type {string} 16 | */ 17 | export const MENU_BTN: string = '1002003'; 18 | 19 | export const CHANNEL_DEFAULT: string = 'DEFAULTS'; 20 | 21 | export const CHANNEL_KICK_OFF: string = 'KICK_OFF'; 22 | 23 | export const UPGRADE_CHANNEL: string = 'UPGRADE_CHANNEL'; 24 | 25 | /** 26 | * 是否选项 27 | * @type {[Options,Options]} 28 | */ 29 | export const yesNoOptions: Options[] = [ 30 | { 31 | label: '是', 32 | value: 'T' 33 | }, 34 | { 35 | label: '否', 36 | value: 'F' 37 | } 38 | ]; 39 | 40 | export const dictBusinessType: Options[] = [ 41 | { 42 | label: '业务字典', 43 | value: 'business' 44 | }, 45 | { 46 | label: '系统字典', 47 | value: 'system' 48 | } 49 | ]; 50 | 51 | export const dictBusinessTypeLabel = (value: string): string => { 52 | const item = dictBusinessType.find(item => item.value === value); 53 | return item?.label ?? ''; 54 | }; 55 | 56 | /** 57 | * 是否选项label显示 58 | * @param value 59 | * @returns {string} 60 | */ 61 | export const yesNoOptionsLabel = (value: string): string => { 62 | const item = yesNoOptions.find(item => item.value === value); 63 | return item?.label ?? ''; 64 | }; 65 | 66 | /** 67 | * 标签类型 68 | * @type {[Options,Options]} 69 | */ 70 | export const tagsTypeOptions: Options[] = [ 71 | { 72 | id: 1, 73 | label: 'Default', 74 | value: 'primary' 75 | }, 76 | { 77 | id: 2, 78 | label: 'Success', 79 | value: 'success' 80 | }, 81 | { 82 | id: 3, 83 | label: 'Info', 84 | value: 'info' 85 | }, 86 | { 87 | id: 4, 88 | label: 'Warning', 89 | value: 'warning' 90 | }, 91 | { 92 | id: 5, 93 | label: 'Danger', 94 | value: 'danger' 95 | } 96 | ]; 97 | 98 | /** 99 | * 标签类型 显示文本 100 | * @param value 101 | * @returns {string|string} 102 | */ 103 | export const tagsTypeOptionsLabel = (value: string): string => { 104 | const item = tagsTypeOptions.find(item => item.value === value); 105 | return item?.label ?? ''; 106 | }; 107 | -------------------------------------------------------------------------------- /src/config/fullScreen.ts: -------------------------------------------------------------------------------- 1 | /* 全局请求 loading */ 2 | import { ElLoading } from 'element-plus'; 3 | 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/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 | // 是否是预览环境 21 | export const IS_PREVIEW: boolean = import.meta.env.VITE_PREVIEW === 'true'; 22 | -------------------------------------------------------------------------------- /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: false, // 是否显示加载ico 8 | trickleSpeed: 200, // 自动递增间隔 9 | minimum: 0.3 // 初始化时的最小百分比 10 | }); 11 | 12 | export default NProgress; 13 | -------------------------------------------------------------------------------- /src/config/typings.ts: -------------------------------------------------------------------------------- 1 | export interface Options { 2 | id?: number; 3 | label: string; 4 | value: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/config/validator.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 验证密码格式 3 | * @param rule 4 | * @param value 5 | * @param callback 6 | */ 7 | export const validatePasswordFormat = (rule: any, value: any, callback: (error?: string | Error) => void) => { 8 | // 验证密码格式 9 | const reg = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[^]{8,16}$/; 10 | // 密码要求: 11 | // 1、长度为8-16位 12 | // 2、必须包含大小写字母及数字 13 | if (!reg.test(value)) { 14 | // 密码不符合要求 15 | callback(new Error('必须包含大小写字母及数字且长度为8-16位')); 16 | return; 17 | } 18 | // 密码符合要求 19 | callback(); 20 | }; 21 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | import type { App, Directive } from 'vue'; 2 | import auth from '@/directives/modules/auth'; 3 | import copy from '@/directives/modules/copy'; 4 | 5 | const directivesList: { [key: string]: Directive } = { 6 | auth, 7 | copy 8 | }; 9 | 10 | const directives = { 11 | install: function (app: App) { 12 | Object.keys(directivesList).forEach(key => { 13 | app.directive(key, directivesList[key]); 14 | }); 15 | } 16 | }; 17 | 18 | export default directives; 19 | -------------------------------------------------------------------------------- /src/directives/modules/auth.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-auth 3 | * 按钮权限指令 4 | * 5 | * 使用方式:分为三种:1. 单一权限;2. 多权限and;3. 多权限or 6 | * 7 | * 1. v-auth="'sys.menu.create_btn'" 具有指定字符串''内的权限 8 | * 2. v-auth="[{ type: 'and', conditions: ['sys.role.setting_btn', 'sys.role.update_btn'] }]" 必须同时具有conditions数组包含的权限 9 | * 3. v-auth="[{ type: 'or', conditions: ['sys.user.unlock_btn','sys.user.role_set_btn','sys.user.delete_btn' ] }]" 具有conditions数组内的任一权限 10 | * 11 | * 12 | */ 13 | import { useAuthStore } from '@/stores/modules/auth'; 14 | import type { Directive, DirectiveBinding } from 'vue'; 15 | 16 | const auth: Directive = { 17 | mounted(el: HTMLElement, binding: DirectiveBinding) { 18 | const { value } = binding; 19 | const authStore = useAuthStore(); 20 | const currentBtnPermissions = authStore.authButtonListGet ?? []; 21 | const currentPageRoles: string[] = authStore.authRoleListGet ?? []; 22 | 23 | const ADMIN_ROLE = 'admin'; 24 | const ADMIN_BYPASS = import.meta.env.VITE_ADMIN_BYPASS_PERMISSION || 'true'; 25 | 26 | // 如果配置允许对admin用户放行,并且当前用户角色包含admin,则放行 27 | if (ADMIN_BYPASS === 'true' && currentPageRoles.includes(ADMIN_ROLE)) { 28 | return; 29 | } 30 | 31 | // 处理基础的单条件认证 32 | if (typeof value === 'string') { 33 | if (!currentBtnPermissions.includes(value)) { 34 | el.remove(); 35 | } 36 | return; 37 | } 38 | // 处理 AND 条件的权限认证 39 | if (Array.isArray(value) && value.length === 1 && value[0].type === 'and' && Array.isArray(value[0].conditions)) { 40 | if (!value[0].conditions.every((item: string) => currentBtnPermissions.includes(item))) { 41 | el.remove(); 42 | } 43 | return; 44 | } 45 | 46 | // 处理 OR 条件的权限认证 47 | if (Array.isArray(value) && value.length === 1 && value[0].type === 'or' && Array.isArray(value[0].conditions)) { 48 | if (!value[0].conditions.some((item: string) => currentBtnPermissions.includes(item))) { 49 | el.remove(); 50 | } 51 | return; 52 | } 53 | // 如果传入的条件不符合以上任何一种格式,则移除元素 54 | el.remove(); 55 | } 56 | }; 57 | 58 | export default auth; 59 | -------------------------------------------------------------------------------- /src/directives/modules/copy.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * v-copy 3 | * 复制某个值至剪贴板 4 | * 接收参数:string类型/Ref类型/Reactive类型 5 | */ 6 | import type { Directive, DirectiveBinding } from 'vue'; 7 | import { ElMessage } from 'element-plus'; 8 | interface ElType extends HTMLElement { 9 | copyData: string | number; 10 | __handleClick__: any; 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', el.__handleClick__); 22 | } 23 | }; 24 | async function handleClick(this: any) { 25 | try { 26 | if (navigator.clipboard) { 27 | // 安全域 28 | await navigator.clipboard.writeText(this.copyData); 29 | } else { 30 | // 非安全域,回退到 document.execCommand 31 | const tempTextarea = document.createElement('textarea'); 32 | tempTextarea.value = this.copyData; 33 | 34 | document.body.appendChild(tempTextarea); 35 | tempTextarea.select(); 36 | document.execCommand('copy'); 37 | document.body.removeChild(tempTextarea); 38 | } 39 | 40 | ElMessage({ 41 | type: 'success', 42 | message: '复制成功' 43 | }); 44 | } catch (err) { 45 | console.error('复制操作不被支持或失败: ', err); 46 | ElMessage({ 47 | type: 'error', 48 | message: '复制操作不被支持或失败' 49 | }); 50 | } 51 | } 52 | 53 | export default copy; 54 | -------------------------------------------------------------------------------- /src/hooks/types/index.ts: -------------------------------------------------------------------------------- 1 | export type Pageable = { 2 | pageNum: number; 3 | pageSize: number; 4 | total: number; 5 | }; 6 | export type StateProps = { 7 | tableData: any[]; 8 | pageable: Pageable; 9 | searchParam: { 10 | [key: string]: any; 11 | }; 12 | searchInitParam: { 13 | [key: string]: any; 14 | }; 15 | totalParam: { 16 | [key: string]: any; 17 | }; 18 | icon?: { 19 | [key: string]: any; 20 | }; 21 | loading?: boolean; 22 | }; 23 | 24 | export type HandleDataMessageType = '' | 'success' | 'warning' | 'info' | 'error'; 25 | 26 | export type ThemeType = 'light' | 'inverted' | 'dark'; 27 | export type GreyOrWeakType = 'grey' | 'weak'; 28 | -------------------------------------------------------------------------------- /src/hooks/useDict.ts: -------------------------------------------------------------------------------- 1 | import { onMounted } from 'vue'; 2 | import { useOptionsStore } from '@/stores/modules/options'; 3 | 4 | /** 5 | * 用于主动触发接口来获取并更新(缓存)指定类型的字典信息。 6 | * @param typeCode 字典类型数组 7 | */ 8 | export function useDict(typeCode: string[]) { 9 | const optionsStore = useOptionsStore(); 10 | onMounted(async () => { 11 | await optionsStore.getDictByCodes(typeCode); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /src/hooks/useDictOptions.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useOptionsStore } from '@/stores/modules/options'; 3 | import type { DictCustom } from '@/api/types/system/dict'; 4 | 5 | /** 6 | * 自定义 Hook,用于从 store 中获取字典选项 7 | * @param dictName 字典名称 8 | * @returns 返回字典选项 9 | */ 10 | export const useDictOptions = (dictName: string) => { 11 | const optionsStore = useOptionsStore(); 12 | return computed(() => optionsStore.getDictOptions(dictName) || []); 13 | }; 14 | -------------------------------------------------------------------------------- /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 | ) => { 17 | if (isNotify) { 18 | ElNotification({ 19 | title: '温馨提示', 20 | message: '如果数据庞大会导致下载缓慢哦,请您耐心等待!', 21 | type: 'info', 22 | duration: 3000 23 | }); 24 | } 25 | try { 26 | // 如果params为空,构建请求参数 27 | if (!params) { 28 | params = { 29 | templateName: tempName 30 | }; 31 | } 32 | const res = await api(params); 33 | 34 | // 提取文件名 35 | const contentDisposition = res.headers['content-disposition']; 36 | const filename = decodeURIComponent( 37 | contentDisposition?.match(/filename\*?=(?:UTF-8'')?([^;]+)?/)?.[1]?.replace(/['"]/g, '') || '下载文件' 38 | ); 39 | 40 | // 下载文件 41 | const blob = new Blob([res.data], { type: res.headers['content-type'] }); 42 | const blobUrl = window.URL.createObjectURL(blob); 43 | 44 | // 兼容 edge 不支持 createObjectURL 方法 45 | if ('msSaveOrOpenBlob' in navigator) return window.navigator.msSaveOrOpenBlob(blob, filename); 46 | const exportFile = document.createElement('a'); 47 | exportFile.style.display = 'none'; 48 | exportFile.download = filename; 49 | exportFile.href = blobUrl; 50 | document.body.appendChild(exportFile); 51 | exportFile.click(); 52 | // 去除下载对 url 的影响 53 | document.body.removeChild(exportFile); 54 | window.URL.revokeObjectURL(blobUrl); 55 | } catch (error) { 56 | console.log(error); 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/hooks/useHandleData.ts: -------------------------------------------------------------------------------- 1 | import type { HandleDataMessageType } from '@/hooks/types'; 2 | import { ElMessage, ElMessageBox } from 'element-plus'; 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: HandleDataMessageType = 'warning' 17 | ) => { 18 | return new Promise((resolve, reject) => { 19 | ElMessageBox.confirm(`是否${message}?`, '温馨提示', { 20 | confirmButtonText: '确定', 21 | cancelButtonText: '取消', 22 | type: confirmType, 23 | draggable: true 24 | }).then(async () => { 25 | const res = await api(params); 26 | if (!res) return reject(false); 27 | ElMessage({ 28 | type: 'success', 29 | message: `${message}成功!` 30 | }); 31 | resolve(true); 32 | }); 33 | }); 34 | }; 35 | -------------------------------------------------------------------------------- /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 | number]: any }[]>([]); 10 | 11 | // 当前选中的所有 ids 数组 12 | const selectedListIds = computed(() => { 13 | const ids: (string | number)[] = []; 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 | isSelected.value = !!rowArr.length; 25 | selectedList.value = rowArr; 26 | }; 27 | 28 | return { 29 | isSelected, 30 | selectedList, 31 | selectedListIds, 32 | selectionChange 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /src/languages/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; 2 | import zh from './modules/zh'; 3 | import en from './modules/en'; 4 | import { getBrowserLang } from '@/utils'; 5 | 6 | const i18n = createI18n({ 7 | // Use Composition API, Set to false 8 | allowComposition: true, 9 | legacy: false, 10 | locale: getBrowserLang(), 11 | messages: { 12 | zh, 13 | en 14 | } 15 | }); 16 | 17 | export default i18n; 18 | -------------------------------------------------------------------------------- /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 | margin-right: 6px; 26 | } 27 | .logo-text { 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 | 63 | 64 | 67 | -------------------------------------------------------------------------------- /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 | margin-right: 6px; 20 | } 21 | .logo-text { 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 | 38 | 39 | 64 | 65 | 68 | -------------------------------------------------------------------------------- /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 | margin-right: 6px; 28 | } 29 | .logo-text { 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 | 40 | 41 | 75 | 76 | 79 | -------------------------------------------------------------------------------- /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 | 6 | a { 7 | font-size: 14px; 8 | color: var(--el-text-color-secondary); 9 | text-decoration: none; 10 | letter-spacing: 0.5px; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/layouts/components/Footer/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 11 | 14 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarLeft.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /src/layouts/components/Header/ToolBarRight.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 30 | 31 | 55 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/AssemblySize.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 38 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/CollapseIcon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 13 | 14 | 22 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Fullscreen.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 25 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/InfoDialog.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 69 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/Language.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 39 | -------------------------------------------------------------------------------- /src/layouts/components/Header/components/ThemeSetting.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | -------------------------------------------------------------------------------- /src/layouts/components/Main/components/Maximize.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 42 | -------------------------------------------------------------------------------- /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/Main/index.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 72 | 73 | 76 | -------------------------------------------------------------------------------- /src/layouts/components/Menu/SubMenu.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 41 | 42 | 106 | -------------------------------------------------------------------------------- /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 | 6 | 7 | 35 | 36 | 41 | -------------------------------------------------------------------------------- /src/layouts/indexAsync.vue: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import pinia from '@/stores'; 3 | 4 | import '@/styles/index.scss'; 5 | 6 | // element plus 7 | import ElementPlus from 'element-plus'; 8 | // element css 9 | import 'element-plus/dist/index.css'; 10 | // element dark css 11 | import 'element-plus/theme-chalk/dark/css-vars.css'; 12 | 13 | // element icons 14 | import * as Icons from '@element-plus/icons-vue'; 15 | 16 | // vue i18n 17 | import I18n from '@/languages'; 18 | 19 | import App from '@/App.vue'; 20 | import router from '@/router'; 21 | 22 | // custom directives 23 | import directives from '@/directives/index'; 24 | 25 | // errorHandler 26 | import errorHandler from '@/utils/errorHandler'; 27 | // svg icons 28 | import 'virtual:svg-icons-register'; 29 | 30 | // highlight 高亮 31 | import 'highlight.js/styles/atom-one-dark.css'; 32 | import 'highlight.js/lib/common'; 33 | import hljsVuePlugin from '@highlightjs/vue-plugin'; 34 | import { isLocalEnv } from '@/utils'; 35 | 36 | const app = createApp(App); 37 | 38 | if (!isLocalEnv()) { 39 | app.config.errorHandler = errorHandler; 40 | } 41 | 42 | // register the element Icons component 43 | Object.keys(Icons).forEach(key => { 44 | app.component(key, Icons[key as keyof typeof Icons]); 45 | }); 46 | 47 | app.use(ElementPlus); 48 | app.use(pinia); 49 | app.use(router); 50 | app.use(directives); 51 | app.use(I18n); 52 | 53 | // app.use(router) 54 | app.use(hljsVuePlugin); 55 | 56 | app.mount('#app'); 57 | -------------------------------------------------------------------------------- /src/router/modules/dynamicRouter.ts: -------------------------------------------------------------------------------- 1 | import { useUserStore } from '@/stores/modules/user'; 2 | import { useAuthStore } from '@/stores/modules/auth'; 3 | import { useOptionsStore } from '@/stores/modules/options'; 4 | import { LOGIN_URL } from '@/config'; 5 | import router from '@/router'; 6 | import type { RouteRecordRaw } from 'vue-router'; 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 | const optionsStore = useOptionsStore(); 18 | 19 | try { 20 | if (authStore.isLoaded) return; 21 | // 1.获取菜单列表 && 按钮权限列表 && 字典列表 22 | await authStore.getAuthMenuList(); 23 | await authStore.getAuthButtonList(); 24 | await authStore.getAuthRoleList(); 25 | optionsStore.setReloadOptions(); 26 | await optionsStore.getAllDictList(); 27 | await authStore.setLoaded(); 28 | 29 | // 2.判断当前用户有没有菜单权限 30 | // if (!authStore.authMenuListGet.length) { 31 | // ElNotification({ 32 | // title: '无权限访问', 33 | // message: '当前账号无任何菜单权限,请联系系统管理员!', 34 | // type: 'warning', 35 | // duration: 3000 36 | // }) 37 | // userStore.setToken('') 38 | // router.replace(LOGIN_URL) 39 | // return Promise.reject('No permission') 40 | // } 41 | 42 | // 3.添加动态路由 43 | authStore.flatMenuListGet.forEach(item => { 44 | if (item.children) delete item.children; 45 | if (item.component && typeof item.component == 'string') { 46 | item.component = modules['/src/views' + item.component + '.vue']; 47 | } 48 | if (item.meta.isFull === 'T') { 49 | router.addRoute(item as unknown as RouteRecordRaw); 50 | } else { 51 | router.addRoute('layout', item as unknown as RouteRecordRaw); 52 | } 53 | }); 54 | } catch (error) { 55 | // 当按钮 || 菜单请求出错时,重定向到登陆页 56 | userStore.setToken(''); 57 | router.replace(LOGIN_URL); 58 | return Promise.reject(error); 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /src/router/modules/staticRouter.ts: -------------------------------------------------------------------------------- 1 | import { HOME_URL, LOGIN_URL } from '@/config'; 2 | import type { RouteRecordRaw } from 'vue-router'; 3 | 4 | export const staticRouter: RouteRecordRaw[] = [ 5 | { 6 | path: '/', 7 | redirect: HOME_URL 8 | }, 9 | { 10 | path: LOGIN_URL, 11 | name: 'login', 12 | component: () => import('@/views/login/index.vue'), 13 | meta: { 14 | title: '登录' 15 | } 16 | }, 17 | { 18 | path: '/layout', 19 | name: 'layout', 20 | component: () => import('@/layouts/index.vue'), 21 | redirect: HOME_URL, 22 | children: [ 23 | { 24 | path: HOME_URL, 25 | name: 'home', 26 | component: () => import('@/views/home/index.vue'), 27 | meta: { 28 | title: '首页', 29 | icon: 'HomeFilled', 30 | isAffix: 'T', 31 | isFull: 'F', 32 | isHidden: 'F', 33 | isKeepAlive: 'T', 34 | isLink: '' 35 | } 36 | } 37 | ] 38 | } 39 | ]; 40 | 41 | /** 42 | * errorRouter (错误页面路由) 43 | */ 44 | export const errorRouter: RouteRecordRaw[] = [ 45 | { 46 | path: '/403', 47 | name: '403', 48 | component: () => import('@/components/ErrorMessage/403.vue'), 49 | meta: { 50 | title: '403页面' 51 | } 52 | }, 53 | { 54 | path: '/404', 55 | name: '404', 56 | component: () => import('@/components/ErrorMessage/404.vue'), 57 | meta: { 58 | title: '404页面' 59 | } 60 | }, 61 | { 62 | path: '/500', 63 | name: '500', 64 | component: () => import('@/components/ErrorMessage/500.vue'), 65 | meta: { 66 | title: '500页面' 67 | } 68 | }, 69 | // Resolve refresh page, route warnings 70 | { 71 | path: '/:pathMatch(.*)*', 72 | component: () => import('@/components/ErrorMessage/404.vue') 73 | } 74 | ]; 75 | -------------------------------------------------------------------------------- /src/stores/helper/persist.ts: -------------------------------------------------------------------------------- 1 | import type { 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/app.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 | -------------------------------------------------------------------------------- /src/stores/interface/tabs.ts: -------------------------------------------------------------------------------- 1 | /* tabsMenuProps */ 2 | export interface TabsMenuProps { 3 | icon: string; 4 | title: string; 5 | path: string; 6 | name: string; 7 | close: boolean; 8 | isKeepAlive: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /src/stores/interface/user.ts: -------------------------------------------------------------------------------- 1 | export interface UserInfo { 2 | name: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/stores/modules/keepAlive.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { ref } from 'vue'; 3 | 4 | export const useKeepAliveStore = defineStore('keep-alive', () => { 5 | const keepAliveName = ref([]); 6 | 7 | // Add KeepAliveName 8 | async function addKeepAliveName(name: string) { 9 | if (!keepAliveName.value.includes(name)) { 10 | keepAliveName.value.push(name); 11 | } 12 | } 13 | 14 | // Remove KeepAliveName 15 | async function removeKeepAliveName(name: string) { 16 | keepAliveName.value = keepAliveName.value.filter(item => item !== name); 17 | } 18 | 19 | // Set KeepAliveName 20 | async function setKeepAliveName(keepAliveNames: string[] = []) { 21 | keepAliveName.value = keepAliveNames; 22 | } 23 | 24 | return { 25 | keepAliveName, 26 | addKeepAliveName, 27 | removeKeepAliveName, 28 | setKeepAliveName 29 | }; 30 | }); 31 | -------------------------------------------------------------------------------- /src/stores/modules/options.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { getAllDict, getDictByCode } from '@/api/modules/system/dict'; 3 | import piniaPersistConfig from '@/stores/helper/persist'; 4 | import type { DictCustom } from '@/api/types/system/dict'; 5 | import { ref } from 'vue'; 6 | 7 | export const useOptionsStore = defineStore( 8 | 'options', 9 | () => { 10 | const isLoaded = ref(false); 11 | const dictOptions = ref>({}); 12 | 13 | async function getAllDictList() { 14 | if (isLoaded.value) return; 15 | const { data } = await getAllDict(); 16 | dictOptions.value = data; 17 | isLoaded.value = true; 18 | } 19 | 20 | function getDictOptions(type: string): DictCustom[] { 21 | return dictOptions.value[type] || []; 22 | } 23 | 24 | function setReloadOptions() { 25 | isLoaded.value = false; 26 | } 27 | 28 | async function getDictByCodes(typeCodes: string[]) { 29 | const { data } = await getDictByCode({ typeCode: typeCodes }); 30 | typeCodes.forEach(code => { 31 | dictOptions.value[code] = data[code]; 32 | }); 33 | } 34 | 35 | return { 36 | isLoaded, 37 | dictOptions, 38 | getAllDictList, 39 | getDictOptions, 40 | setReloadOptions, 41 | getDictByCodes 42 | }; 43 | }, 44 | { 45 | persist: piniaPersistConfig('options') 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /src/stores/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { ref } from 'vue'; 3 | import piniaPersistConfig from '@/stores/helper/persist'; 4 | import type { UserInfo } from '@/api/types/system/login'; 5 | 6 | export const useUserStore = defineStore( 7 | 'user', 8 | () => { 9 | const token = ref(''); 10 | const userInfo = ref({ 11 | username: '' 12 | }); 13 | function setToken(tokenStr: string) { 14 | token.value = tokenStr; 15 | } 16 | 17 | // Set setUserInfo 18 | function setUserInfo(info: UserInfo) { 19 | userInfo.value = info; 20 | } 21 | 22 | function clear() { 23 | token.value = ''; 24 | userInfo.value = { 25 | username: '' 26 | }; 27 | } 28 | 29 | return { 30 | token, 31 | userInfo, 32 | setToken, 33 | setUserInfo, 34 | clear 35 | }; 36 | }, 37 | { 38 | persist: piniaPersistConfig('user') 39 | } 40 | ); 41 | -------------------------------------------------------------------------------- /src/styles/color.scss: -------------------------------------------------------------------------------- 1 | body { 2 | --div-bg-box: #f5f7fa; 3 | } 4 | -------------------------------------------------------------------------------- /src/styles/common.scss: -------------------------------------------------------------------------------- 1 | /* flex */ 2 | .flx-center { 3 | display: flex; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .flx-justify-between { 9 | display: flex; 10 | align-items: center; 11 | justify-content: space-between; 12 | } 13 | 14 | .flx-align-center { 15 | display: flex; 16 | align-items: center; 17 | } 18 | 19 | /* clearfix */ 20 | .clearfix::after { 21 | display: block; 22 | height: 0; 23 | overflow: hidden; 24 | clear: both; 25 | content: ''; 26 | } 27 | 28 | /* 文字单行省略号 */ 29 | .sle { 30 | overflow: hidden; 31 | text-overflow: ellipsis; 32 | white-space: nowrap; 33 | } 34 | 35 | /* 文字多行省略号 */ 36 | .mle { 37 | display: -webkit-box; 38 | overflow: hidden; 39 | -webkit-box-orient: vertical; 40 | -webkit-line-clamp: 2; 41 | } 42 | 43 | /* 文字多了自动換行 */ 44 | .break-word { 45 | word-break: break-all; 46 | word-wrap: break-word; 47 | } 48 | 49 | /* fade-transform */ 50 | .fade-transform-leave-active, 51 | .fade-transform-enter-active { 52 | transition: all 0.2s; 53 | } 54 | 55 | .fade-transform-enter-from { 56 | opacity: 0; 57 | transition: all 0.2s; 58 | transform: translateX(-30px); 59 | } 60 | 61 | .fade-transform-leave-to { 62 | opacity: 0; 63 | transition: all 0.2s; 64 | transform: translateX(30px); 65 | } 66 | 67 | /* breadcrumb-transform */ 68 | .breadcrumb-enter-active { 69 | transition: all 0.2s; 70 | } 71 | 72 | .breadcrumb-enter-from, 73 | .breadcrumb-leave-active { 74 | opacity: 0; 75 | transform: translateX(10px); 76 | } 77 | 78 | /* scroll bar */ 79 | ::-webkit-scrollbar { 80 | width: 6px; 81 | height: 6px; 82 | } 83 | 84 | ::-webkit-scrollbar-thumb { 85 | background-color: var(--el-border-color-darker); 86 | border-radius: 20px; 87 | } 88 | 89 | /* nprogress */ 90 | #nprogress .bar { 91 | background: var(--el-color-primary) !important; 92 | } 93 | 94 | #nprogress .spinner-icon { 95 | border-top-color: var(--el-color-primary) !important; 96 | border-left-color: var(--el-color-primary) !important; 97 | } 98 | 99 | #nprogress .peg { 100 | box-shadow: 101 | 0 0 10px var(--el-color-primary), 102 | 0 0 5px var(--el-color-primary) !important; 103 | } 104 | 105 | /* 外边距、内边距全局样式 */ 106 | @for $i from 0 through 100 { 107 | .mt#{$i} { 108 | margin-top: #{$i}px !important; 109 | } 110 | .mr#{$i} { 111 | margin-right: #{$i}px !important; 112 | } 113 | .mb#{$i} { 114 | margin-bottom: #{$i}px !important; 115 | } 116 | .ml#{$i} { 117 | margin-left: #{$i}px !important; 118 | } 119 | .pt#{$i} { 120 | padding-top: #{$i}px !important; 121 | } 122 | .pr#{$i} { 123 | padding-right: #{$i}px !important; 124 | } 125 | .pb#{$i} { 126 | padding-bottom: #{$i}px !important; 127 | } 128 | .pl#{$i} { 129 | padding-left: #{$i}px !important; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /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 | 11 | .w-e-bar-item button:hover, 12 | .w-e-menu-tooltip-v5::before { 13 | color: #eeeeee; 14 | } 15 | 16 | /* login */ 17 | .login-container { 18 | background-color: #191919 !important; 19 | 20 | .login-box { 21 | background-color: rgb(0 0 0 / 80%) !important; 22 | 23 | .login-form { 24 | box-shadow: rgb(255 255 255 / 12%) 0 2px 10px 2px !important; 25 | 26 | .logo-text { 27 | color: var(--el-text-color-primary) !important; 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/element/index.scss: -------------------------------------------------------------------------------- 1 | @forward 'element-plus/theme-chalk/src/common/var.scss' with ( 2 | $colors: ( 3 | 'primary': ( 4 | 'base': #009688 5 | ), 6 | 'success': ( 7 | 'base': #52c41a 8 | ), 9 | 'warning': ( 10 | 'base': #faad14 11 | ), 12 | 'danger': ( 13 | 'base': #ff4d4f 14 | ), 15 | 'error': ( 16 | 'base': #ff4d4f 17 | ) 18 | ) 19 | ); 20 | 21 | // 引入暗黑模式 22 | @use 'element-plus/theme-chalk/src/dark/css-vars.scss'; 23 | -------------------------------------------------------------------------------- /src/styles/fonts/DIN.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/styles/fonts/DIN.otf -------------------------------------------------------------------------------- /src/styles/fonts/MetroDF.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/styles/fonts/MetroDF.ttf -------------------------------------------------------------------------------- /src/styles/fonts/YouSheBiaoTiHei.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/styles/fonts/YouSheBiaoTiHei.ttf -------------------------------------------------------------------------------- /src/styles/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/styles/iconfont/iconfont.scss: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: iconfont; /* Project id 2667653 */ 3 | src: url('iconfont.ttf?t=1691032190261') format('truetype'); 4 | } 5 | 6 | .iconfont { 7 | font-family: iconfont !important; 8 | font-size: 20px; 9 | font-style: normal; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | cursor: pointer; 13 | } 14 | 15 | .icon-yiwen::before { 16 | font-size: 15px; 17 | content: '\e693'; 18 | } 19 | 20 | .icon-xiala::before { 21 | content: '\e62b'; 22 | } 23 | 24 | .icon-tuichu::before { 25 | content: '\e645'; 26 | } 27 | 28 | .icon-xiaoxi::before { 29 | font-size: 21.2px; 30 | content: '\e61f'; 31 | } 32 | 33 | .icon-zhuti::before { 34 | font-size: 22.4px; 35 | content: '\e638'; 36 | } 37 | 38 | .icon-sousuo::before { 39 | content: '\e611'; 40 | } 41 | 42 | .icon-contentright::before { 43 | content: '\e8c9'; 44 | } 45 | 46 | .icon-contentleft::before { 47 | content: '\e8ca'; 48 | } 49 | 50 | .icon-fangda::before { 51 | content: '\e826'; 52 | } 53 | 54 | .icon-suoxiao::before { 55 | content: '\e641'; 56 | } 57 | 58 | .icon-zhongyingwen::before { 59 | content: '\e8cb'; 60 | } 61 | -------------------------------------------------------------------------------- /src/styles/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feiyuchuixue/sz-admin/d5523763a07b863c60c281e6d31a0a9ee0319703/src/styles/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | // reset style sheet 2 | // color css 3 | @use '@/styles/color'; 4 | @use '@/styles/reset'; 5 | // CSS common style sheet 6 | @use '@/styles/common'; 7 | // iconfont css 8 | @use '@/styles/iconfont/iconfont'; 9 | // font css 10 | @use '@/styles/fonts/font'; 11 | // custom element dark css 12 | @use '@/styles/element-dark'; 13 | // custom element css 14 | @use '@/styles/element'; 15 | -------------------------------------------------------------------------------- /src/styles/reset.scss: -------------------------------------------------------------------------------- 1 | /* Reset style sheet */ 2 | html, 3 | body, 4 | #app { 5 | width: 100%; 6 | height: 100%; 7 | padding: 0; 8 | margin: 0; 9 | } 10 | 11 | /* 解决 h1 标签在 webkit 内核浏览器中文字大小失效问题 */ 12 | :-webkit-any(article, aside, nav, section) h1 { 13 | font-size: 2em; 14 | } 15 | -------------------------------------------------------------------------------- /src/styles/theme/aside.ts: -------------------------------------------------------------------------------- 1 | import type { ThemeType } from '@/hooks/types'; 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 type { ThemeType } from '@/hooks/types'; 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 type { ThemeType } from '@/hooks/types'; 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 | declare namespace Menu { 2 | interface MenuOptions { 3 | id: string; 4 | pid: string; 5 | path: string; 6 | name: string; 7 | sort: number; 8 | menuTypeCd: string; 9 | component?: string | (() => Promise); 10 | redirect?: string; 11 | permissions?: string; 12 | meta: MetaProps; 13 | children?: MenuOptions[]; 14 | } 15 | 16 | interface MetaProps { 17 | icon: string; 18 | title: string; 19 | isLink?: string; 20 | isHidden: string; 21 | isFull: string; 22 | isAffix: string; 23 | isKeepAlive: string; 24 | useDataScope: string; 25 | } 26 | } 27 | 28 | declare namespace View { 29 | interface DefaultParams { 30 | // Dialog Title 31 | title: string; 32 | // Dialog 中数据信息 33 | row: { [key: string]: any }; 34 | // Dialog中调用接口API 35 | api: ((params: any) => Promise) | undefined; 36 | // 刷新列表数据使用 37 | getTableList?: (() => Promise) | undefined; 38 | // 是否是新增 39 | isAdd?: boolean; 40 | // 其他附加信息 41 | [key: string]: any; 42 | } 43 | } 44 | 45 | declare namespace File { 46 | type ImageMimeType = 47 | | 'image/apng' 48 | | 'image/bmp' 49 | | 'image/gif' 50 | | 'image/jpeg' 51 | | 'image/pjpeg' 52 | | 'image/png' 53 | | 'image/svg+xml' 54 | | 'image/tiff' 55 | | 'image/webp' 56 | | 'image/x-icon'; 57 | 58 | type ExcelMimeType = 'application/vnd.ms-excel' | 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; 59 | 60 | type ExcelFileType = '.xlsx' | '.xls'; 61 | } 62 | -------------------------------------------------------------------------------- /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 | const reg = /^#?[0-9A-Fa-f]{6}$/; 11 | if (!reg.test(str)) { 12 | return ElMessage.warning('输入错误的hex'); 13 | } 14 | str = str.replace('#', ''); 15 | hexs = str.match(/../g); 16 | for (let i = 0; i < 3; i++) { 17 | hexs[i] = parseInt(hexs[i], 16); 18 | } 19 | return hexs; 20 | } 21 | 22 | /** 23 | * @description rgb颜色转Hex颜色 24 | * @param {*} r 代表红色 25 | * @param {*} g 代表绿色 26 | * @param {*} b 代表蓝色 27 | * @returns {String} 返回处理后的颜色值 28 | */ 29 | export function rgbToHex(r: any, g: any, b: any) { 30 | const reg = /^\d{1,3}$/; 31 | if (!reg.test(r) || !reg.test(g) || !reg.test(b)) { 32 | return ElMessage.warning('输入错误的rgb颜色值'); 33 | } 34 | const hexs = [r.toString(16), g.toString(16), b.toString(16)]; 35 | for (let i = 0; i < 3; i++) { 36 | if (hexs[i].length === 1) { 37 | hexs[i] = `0${hexs[i]}`; 38 | } 39 | } 40 | return `#${hexs.join('')}`; 41 | } 42 | 43 | /** 44 | * @description 加深颜色值 45 | * @param {String} color 颜色值字符串 46 | * @param {Number} level 加深的程度,限0-1之间 47 | * @returns {String} 返回处理后的颜色值 48 | */ 49 | export function getDarkColor(color: string, level: number) { 50 | const reg = /^#?[0-9A-Fa-f]{6}$/; 51 | if (!reg.test(color)) { 52 | return ElMessage.warning('输入错误的hex颜色值'); 53 | } 54 | const rgb = hexToRgb(color); 55 | for (let i = 0; i < 3; i++) { 56 | rgb[i] = Math.round(20.5 * level + rgb[i] * (1 - level)); 57 | } 58 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 59 | } 60 | 61 | /** 62 | * @description 变浅颜色值 63 | * @param {String} color 颜色值字符串 64 | * @param {Number} level 加深的程度,限0-1之间 65 | * @returns {String} 返回处理后的颜色值 66 | */ 67 | export function getLightColor(color: string, level: number) { 68 | const reg = /^#?[0-9A-Fa-f]{6}$/; 69 | if (!reg.test(color)) { 70 | return ElMessage.warning('输入错误的hex颜色值'); 71 | } 72 | const rgb = hexToRgb(color); 73 | for (let i = 0; i < 3; i++) { 74 | rgb[i] = Math.round(255 * level + rgb[i] * (1 - level)); 75 | } 76 | return rgbToHex(rgb[0], rgb[1], rgb[2]); 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/errorHandler.ts: -------------------------------------------------------------------------------- 1 | import { ElNotification } from 'element-plus'; 2 | 3 | /** 4 | * @description 全局代码错误捕捉 5 | * */ 6 | const errorHandler = (error: any): void => { 7 | // 过滤 HTTP 请求错误 8 | if (!error || error.status || error.status === 0) return; 9 | const errorMap: { [key: string]: string } = { 10 | InternalError: 'Javascript引擎内部错误', 11 | ReferenceError: '未找到对象', 12 | TypeError: '使用了错误的类型或对象', 13 | RangeError: '使用内置对象时,参数超范围', 14 | SyntaxError: '语法错误', 15 | EvalError: '错误的使用了Eval', 16 | URIError: 'URI错误' 17 | }; 18 | const errorName = errorMap[error.name] || '未知错误'; 19 | ElNotification({ 20 | title: errorName, 21 | message: error, 22 | type: 'error', 23 | duration: 3000 24 | }); 25 | }; 26 | export default errorHandler; 27 | -------------------------------------------------------------------------------- /src/utils/mittBus.ts: -------------------------------------------------------------------------------- 1 | import mitt from 'mitt'; 2 | 3 | const mittBus = mitt(); 4 | 5 | export default mittBus; 6 | -------------------------------------------------------------------------------- /src/views/demo/index.scss: -------------------------------------------------------------------------------- 1 | .content-box { 2 | align-items: flex-start; 3 | span { 4 | width: 100%; 5 | text-align: center; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/views/demo/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | 35 | 38 | -------------------------------------------------------------------------------- /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 | 8 | .home-bg { 9 | width: 70%; 10 | max-width: 1200px; 11 | margin-bottom: 20px; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/views/home/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 12 | -------------------------------------------------------------------------------- /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: cover; 7 | 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 | 20 | .dark { 21 | position: absolute; 22 | top: 13px; 23 | right: 18px; 24 | } 25 | 26 | .login-left { 27 | width: 800px; 28 | margin-right: 10px; 29 | 30 | .login-left-img { 31 | width: 100%; 32 | height: 100%; 33 | } 34 | } 35 | 36 | .login-form { 37 | width: 420px; 38 | padding: 50px 40px 45px; 39 | background-color: var(--el-bg-color); 40 | border-radius: 10px; 41 | box-shadow: rgb(0 0 0 / 10%) 0 2px 10px 2px; 42 | 43 | .login-logo { 44 | display: flex; 45 | align-items: center; 46 | justify-content: center; 47 | margin-bottom: 45px; 48 | 49 | .login-icon { 50 | width: 60px; 51 | height: 52px; 52 | } 53 | 54 | .logo-text { 55 | padding: 0 0 0 25px; 56 | margin: 0; 57 | font-size: 42px; 58 | font-weight: bold; 59 | color: #34495e; 60 | white-space: nowrap; 61 | } 62 | } 63 | 64 | .el-form-item { 65 | margin-bottom: 40px; 66 | } 67 | 68 | .login-btn { 69 | display: flex; 70 | justify-content: space-between; 71 | width: 100%; 72 | margin-top: 40px; 73 | white-space: nowrap; 74 | 75 | .el-button { 76 | width: 185px; 77 | } 78 | } 79 | } 80 | } 81 | } 82 | 83 | @media screen and (width <= 1250px) { 84 | .login-left { 85 | display: none; 86 | } 87 | } 88 | 89 | @media screen and (width <= 600px) { 90 | .login-form { 91 | width: 97% !important; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 23 | 24 | 27 | -------------------------------------------------------------------------------- /src/views/system/accountManage/components/UserDataPermissions.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/views/system/accountManage/components/UserPermissions.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/views/system/configManage/components/ConfigEditForm.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /src/views/system/deptManage/components/DeptLeaderTransfer.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/views/system/dictManage/components/DictTypeForm.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/views/system/fileManage/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 75 | -------------------------------------------------------------------------------- /src/views/system/message/detail.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 91 | -------------------------------------------------------------------------------- /src/views/system/roleManage/components/RoleForm.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /src/views/toolbox/generator/common/Options.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from '@/config/typings'; 2 | 3 | export const javaTypeOptions: Options[] = [ 4 | { 5 | label: 'String', 6 | value: 'String' 7 | }, 8 | { 9 | label: 'Integer', 10 | value: 'Integer' 11 | }, 12 | { 13 | label: 'Long', 14 | value: 'Long' 15 | }, 16 | { 17 | label: 'Double', 18 | value: 'Double' 19 | }, 20 | { 21 | label: 'BigDecimal', 22 | value: 'BigDecimal' 23 | }, 24 | { 25 | label: 'LocalDateTime', 26 | value: 'LocalDateTime' 27 | }, 28 | { 29 | label: 'LocalDate', 30 | value: 'LocalDate' 31 | }, 32 | { 33 | label: 'LocalTime', 34 | value: 'LocalTime' 35 | } 36 | ]; 37 | 38 | export const queryTypeOptions: Options[] = [ 39 | { 40 | label: '等于', 41 | value: 'EQ' 42 | }, 43 | { 44 | label: '不等于', 45 | value: 'NEQ' 46 | }, 47 | { 48 | label: '大于', 49 | value: 'GT' 50 | }, 51 | { 52 | label: '小于', 53 | value: 'LT' 54 | }, 55 | { 56 | label: '范围', 57 | value: 'BETWEEN' 58 | }, 59 | { 60 | label: '大于等于', 61 | value: 'GTE' 62 | }, 63 | { 64 | label: '小于等于', 65 | value: 'LTE' 66 | }, 67 | { 68 | label: '模糊', 69 | value: 'LIKE' 70 | } 71 | ]; 72 | 73 | export const htmlTypeOptions: Options[] = [ 74 | { 75 | label: 'input', 76 | value: 'input' 77 | }, 78 | { 79 | label: 'input-number', 80 | value: 'input-number' 81 | }, 82 | { 83 | label: 'textarea', 84 | value: 'textarea' 85 | }, 86 | { 87 | label: 'select', 88 | value: 'select' 89 | }, 90 | { 91 | label: 'radio-group [未完待续]', 92 | value: 'radio-group' 93 | }, 94 | { 95 | label: 'checkbox [未完待续]', 96 | value: 'checkbox' 97 | }, 98 | { 99 | label: 'datetime ', 100 | value: 'datetime' 101 | }, 102 | { 103 | label: 'date [未完待续]', 104 | value: 'date' 105 | }, 106 | { 107 | label: 'time [未完待续]', 108 | value: 'time' 109 | }, 110 | { 111 | label: 'imageUpload [未完待续]', 112 | value: 'imageUpload' 113 | }, 114 | { 115 | label: 'fileUpload [未完待续]', 116 | value: 'fileUpload' 117 | }, 118 | { 119 | label: 'editor [未完待续]', 120 | value: 'editor' 121 | } 122 | ]; 123 | 124 | export const dictShowWayOptions: Options[] = [ 125 | { 126 | label: '唯一标识', 127 | value: '0' 128 | }, 129 | { 130 | label: '别名', 131 | value: '1' 132 | } 133 | ]; 134 | -------------------------------------------------------------------------------- /src/views/toolbox/generator/components/Import.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/views/toolbox/generator/components/Preview.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 62 | 63 | 71 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.ts", "package.json"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./tsconfig.node.json" 6 | }, 7 | { 8 | "path": "./tsconfig.app.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "nightwatch.conf.*", "playwright.config.*"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "module": "ESNext", 7 | "moduleResolution": "Bundler", 8 | "types": ["node", "vite/client"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /vite.config.mts: -------------------------------------------------------------------------------- 1 | // vite.config.mts 2 | import { resolve } from 'path'; 3 | import { fileURLToPath, URL } from 'node:url'; 4 | import { defineConfig, ConfigEnv, UserConfig, loadEnv } from 'vite'; 5 | import vue from '@vitejs/plugin-vue'; 6 | import vueJsx from '@vitejs/plugin-vue-jsx'; 7 | import vueDevTools from 'vite-plugin-vue-devtools'; 8 | import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'; 9 | import viteCompression from 'vite-plugin-compression'; 10 | import pkg from './package.json'; 11 | 12 | // 由于我们使用的是 ESM,不再需要 const path = require('node:path'); 13 | // 直接使用 import { resolve } from 'path'; 即可 14 | 15 | export default defineConfig(({ mode }: ConfigEnv): UserConfig => { 16 | const root = process.cwd(); 17 | const env = loadEnv(mode, root); 18 | 19 | return { 20 | base: env.VITE_PUBLIC_PATH, 21 | root, 22 | plugins: [ 23 | vue(), 24 | vueJsx(), 25 | vueDevTools(), 26 | // 使用 svg 图标 27 | createSvgIconsPlugin({ 28 | iconDirs: [resolve(process.cwd(), 'src/assets/icons')], 29 | symbolId: 'icon-[dir]-[name]' 30 | }), 31 | // 配置 gzip 压缩插件 32 | viteCompression({ 33 | algorithm: 'gzip', // 使用 gzip 压缩 34 | ext: '.gz', // 压缩文件扩展名 35 | threshold: 10240, // 只有大于 10 KB 的文件才会被压缩 36 | deleteOriginFile: false // 不删除源文件 37 | }) 38 | ], 39 | define: { 40 | __APP_VERSION__: JSON.stringify(pkg.version) 41 | }, 42 | resolve: { 43 | alias: { 44 | '@': fileURLToPath(new URL('./src', import.meta.url)), 45 | 'package.json': new URL('package.json', import.meta.url).pathname 46 | } 47 | }, 48 | css: { 49 | preprocessorOptions: { 50 | scss: { 51 | api: 'modern-compiler', // https://github.com/sass/dart-sass/issues/2395#issuecomment-988870897 52 | additionalData: `@use "@/styles/element/index.scss" as *;` 53 | } 54 | } 55 | }, 56 | server: { 57 | host: '0.0.0.0', 58 | port: Number(env.VITE_PORT), 59 | open: env.VITE_OPEN === 'true', 60 | proxy: { 61 | '/api': { 62 | target: env.VITE_API_URL, 63 | changeOrigin: true, 64 | ws: true, 65 | rewrite: path => path.replace(new RegExp(`^/api`), ''), 66 | // https is require secure=false 67 | ...(/^https:\/\//.test(env.VITE_API_URL) ? { secure: false } : {}) 68 | } 69 | } 70 | } 71 | }; 72 | }); 73 | --------------------------------------------------------------------------------