├── .babelrc ├── .cz-config.js ├── .env ├── .env.development ├── .env.production ├── .eslintrc.js ├── .gitignore ├── .husky └── pre-commit ├── .lintstagedrc.json ├── .prettierrc.js ├── README.md ├── build └── proxy.ts ├── commitlint.config.js ├── components.d.ts ├── doc ├── api.png ├── authority.png ├── dictionary-usage.png ├── dictionary.png ├── menu.png └── user.png ├── index.html ├── package.json ├── postcss.config.cjs ├── public ├── logo.png └── vite.svg ├── src ├── App.vue ├── api │ ├── http.ts │ ├── modules │ │ ├── api.ts │ │ ├── authority.ts │ │ ├── casbin.ts │ │ ├── dictionary.ts │ │ ├── loginApi.ts │ │ ├── menuApi.ts │ │ └── userList.ts │ ├── request.ts │ └── type.ts ├── assets │ ├── images │ │ └── login │ │ │ └── checkcode.png │ ├── logo.png │ └── svg │ │ └── login │ │ └── bg.svg ├── components │ ├── Layout │ │ ├── content │ │ │ └── index.vue │ │ ├── footer │ │ │ └── index.vue │ │ ├── header │ │ │ ├── index.vue │ │ │ └── modules │ │ │ │ ├── breadcrumb.vue │ │ │ │ ├── sysTools │ │ │ │ ├── index.vue │ │ │ │ └── utils │ │ │ │ │ ├── fullScreem.vue │ │ │ │ │ └── refresh.vue │ │ │ │ ├── tabs.vue │ │ │ │ └── userTools.vue │ │ ├── index.vue │ │ └── sysMenu │ │ │ ├── index.vue │ │ │ └── menuItem │ │ │ └── menuItem.vue │ ├── Loading │ │ ├── createLoading.ts │ │ ├── index.vue │ │ └── type.ts │ ├── SysModal │ │ ├── index.vue │ │ └── 使用.md │ ├── sysDict │ │ ├── dictUtils.ts │ │ ├── modules │ │ │ ├── dictCheckbox.vue │ │ │ ├── dictRadio.vue │ │ │ ├── dictSelect.vue │ │ │ └── dictText.vue │ │ ├── sysDict.vue │ │ └── 使用.md │ ├── sysRemoveBtn │ │ └── index.vue │ ├── sysSearch │ │ ├── index.vue │ │ └── 使用.md │ ├── sysTable │ │ ├── index.vue │ │ └── 使用方式.md │ ├── sysUpload │ │ ├── index.vue │ │ └── 使用.md │ └── widget │ │ └── Buoy.vue ├── config │ └── config.ts ├── directives │ ├── index.ts │ └── loading.ts ├── env.d.ts ├── hooks │ └── baseSelectHooks.ts ├── main.ts ├── router │ ├── error.ts │ ├── index.ts │ ├── modules │ │ ├── layout.ts │ │ └── login.ts │ ├── routerBefore.ts │ └── routes.ts ├── store │ ├── index.ts │ └── modules │ │ ├── layout.ts │ │ ├── system.ts │ │ └── user.ts ├── style.css ├── styles │ ├── index.scss │ ├── mixin.scss │ └── nprogress.scss ├── tailwind.css ├── types │ ├── api.ts │ ├── authority.ts │ ├── base.ts │ ├── casbin.ts │ ├── dictionary.ts │ ├── layout.ts │ ├── login.ts │ ├── menu.ts │ └── userList.ts ├── utils │ ├── asyncRouter.ts │ ├── auth.ts │ ├── changeTheme.ts │ ├── createIcon.ts │ ├── flexible.ts │ ├── getFile.ts │ ├── iconList.ts │ ├── isPhone.ts │ ├── lang.ts │ ├── storage.ts │ └── styles.ts ├── views │ ├── error │ │ ├── 404.vue │ │ ├── 500.vue │ │ └── reload.vue │ ├── index.vue │ ├── login.vue │ ├── superAdmin │ │ ├── api │ │ │ ├── api.vue │ │ │ └── modules │ │ │ │ ├── addModal.vue │ │ │ │ └── data.ts │ │ ├── authority │ │ │ ├── authority.vue │ │ │ └── modules │ │ │ │ ├── addModal.vue │ │ │ │ ├── data.ts │ │ │ │ └── editAuth │ │ │ │ ├── authApi.vue │ │ │ │ ├── authList.vue │ │ │ │ └── index.vue │ │ ├── dictionary │ │ │ ├── dictionary.vue │ │ │ └── modules │ │ │ │ ├── leftList │ │ │ │ ├── addModal.vue │ │ │ │ ├── data.ts │ │ │ │ └── index.vue │ │ │ │ └── rightList │ │ │ │ ├── addModal.vue │ │ │ │ ├── data.ts │ │ │ │ └── index.vue │ │ ├── index.vue │ │ ├── menu │ │ │ ├── menu.vue │ │ │ └── modules │ │ │ │ ├── addMenuModal.vue │ │ │ │ └── data.ts │ │ └── user │ │ │ ├── modules │ │ │ ├── addModal.vue │ │ │ └── data.ts │ │ │ └── user.vue │ └── test │ │ └── index.vue └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json ├── types ├── env.d.ts ├── global.d.ts └── index.d.ts ├── vite.config.ts ├── yarn-error.log └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "import", 5 | { 6 | "libraryName": "view-ui-plus", 7 | "libraryDirectory": "src/components" 8 | }, 9 | "view-ui-plus" 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /.cz-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | types: [ 3 | { value: 'feat✨', name: '特性: 一个新的特性' }, 4 | { value: 'fix🐞', name: '修复: 修复一个Bug' }, 5 | { value: 'docs📚', name: '文档: 变更的只有文档' }, 6 | { value: 'style💅', name: '格式: 空格, 分号等格式修复' }, 7 | { value: 'refactor🛠', name: '重构: 代码重构,注意和特性、修复区分开' }, 8 | { value: 'perf🐎', name: '性能: 提升性能' }, 9 | { value: 'test🏁', name: '测试: 添加一个测试' }, 10 | { value: 'revert⏪', name: '回滚: 代码回退' }, 11 | { value: 'chore🗯', name: '工具:开发工具变动(构建、脚手架工具等)' }, 12 | { value: 'merge⌛', name: '合并:合并代码' }, 13 | { value: 'build📦', name: '打包: 打包发布' }, 14 | { value: 'ci🔧', name: '集成: 持续集成' }, 15 | { value: 'release🚀', name: '发布: 发布新版本' }, 16 | { value: 'other🌈', name: '其他: 其他改动,比如构建流程, 依赖管理' }, 17 | ], 18 | messages: { 19 | type: '选择一种你的提交类型:', 20 | customScope: '请输入修改范围(可选):', 21 | subject: '短说明:', 22 | body: '长说明,使用"|"换行(可选):', 23 | footer: '关联关闭的issue,例如:#31, #34(可选):', 24 | confirmCommit: '确定提交说明?', 25 | }, 26 | allowCustomScopes: true, 27 | allowBreakingChanges: ['特性', '修复'], 28 | subjectLimit: 100, 29 | }; 30 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | ## 公共的 2 | 3 | # 端口 4 | VITE_PORT= 3200 5 | 6 | VITE_HTTPS = false 7 | 8 | VITE_TITLE = wenl 9 | 10 | #token key 11 | VITE_TOKEN_KEY = tokenWenl 12 | 13 | -------------------------------------------------------------------------------- /.env.development: -------------------------------------------------------------------------------- 1 | ## 开发环境 2 | # 指定输出路径(相对于 项目根目录). 默认:dist 3 | VITE_OUT_DIR = 'dist' 4 | 5 | VITE_ASSET_DIR = 'assets' 6 | #端口 7 | VITE_PORT=5175 8 | #axios baseURL 9 | VITE_BASEURL = http://127.0.0.1:7001 10 | # VITE_BASEURL = http://192.168.10.157:7001 11 | VITE_PUBLIC_PATH = '/' 12 | 13 | #路由base 14 | VITE_BASEROUTER='' -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | ## 生产环境e3 ny7b6 2 | # 指定输出路径(相对于 项目根目录). 默认:dist 3 | VITE_OUT_DIR = 'dist' 4 | 5 | VITE_ASSET_DIR = 'assets' 6 | 7 | VITE_BASEURL = 'www.wenl.com' 8 | 9 | VITE_PUBLIC_PATH = '/wenl/' -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es2021: true, 5 | node: true, 6 | 'vue/setup-compiler-macros': true, 7 | }, 8 | extends: [ 9 | 'eslint:recommended', 10 | 'plugin:vue/vue3-essential', 11 | 'plugin:@typescript-eslint/recommended', 12 | ], 13 | parser: 'vue-eslint-parser', //👈解析template 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | parser: '@typescript-eslint/parser', //👈解析script 17 | sourceType: 'module', 18 | }, 19 | plugins: ['vue', '@typescript-eslint'], 20 | rules: { 21 | '@typescript-eslint/ban-types': ['off'], //👈 22 | '@typescript-eslint/no-explicit-any': ['off'], //👈允许使用any 23 | '@typescript-eslint/no-unused-vars': 'off', // 允许使用未使用变量 24 | 'vue/multi-word-component-names': 0, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "src/**/*.{js,cjs,ts,html,json,css,scss,vue}": ["npx prettier --write"], 3 | "src/**/*.{js,cjs,ts,vue}": ["npx eslint --fix"], 4 | "./*.{js,cjs,ts,html,json}": ["npx prettier --write"], 5 | "./*.{js,cjs,ts}": ["npx eslint --fix"] 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 120, // 每行代码长度(默认80) 3 | tabWidth: 2, // 每个tab相当于多少个空格(默认2) 4 | useTabs: false, // 是否使用tab进行缩进(默认false) 5 | semi: true, // 声明结尾使用分号(默认true) 6 | singleQuote: true, // 使用单引号(默认false) 7 | trailingComma: 'all', // 多行使用拖尾逗号(默认none) 8 | bracketSpacing: true, // 对象字面量的大括号间使用空格(默认true) 9 | jsxBracketSameLine: false, // 多行JSX中的>放置在最后一行的结尾,而不是另起一行(默认false) 10 | arrowParens: 'avoid', // 只有一个参数的箭头函数的参数是否带圆括号(默认avoid) 11 | }; 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## go-zero-admin 2 | 3 | #### 后端:[https://github.com/yh-zero/go-zero-admin](https://github.com/yh-zero/go-zero-admin) 4 | #### 前端:[https://github.com/yh-zero/go-zero-admin-vue3](https://github.com/yh-zero/go-zero-admin-vue3) 5 | 6 | ## vite + pinia + ts + antDesignVue4.x + tailwindCss + formily2.x 7 | 8 | ## 安装依赖 9 | ```js 10 | yarn install 11 | ``` 12 | 13 | ## 启动项目 14 | ```js 15 | yarn dev 16 | ``` 17 | 18 | ### 目录结构 19 | ```js 20 | |-- go-zero-admin # 项目根目录 21 | |-- vite.config.ts # Vite 配置文件 22 | |-- build # 构建相关文件夹 23 | | |-- proxy.ts # 代理配置文件 24 | |-- public # 静态资源文件夹 25 | |-- src # 源代码文件夹 26 | | |-- App.vue # 根组件 27 | | |-- env.d.ts # 环境声明文件 28 | | |-- main.ts # 入口文件 29 | | |-- style.css # 公共样式文件 30 | | |-- tailwind.css # Tailwind CSS 文件 31 | | |-- vite-env.d.ts # Vite 环境声明文件 32 | | |-- api # API相关文件夹 33 | | | |-- http.ts # HTTP 请求封装 34 | | | |-- modules # API模块文件夹 35 | | | |-- api.ts # API模块文件 36 | | |-- assets # 资源文件夹 37 | | |-- components # 组件文件夹 38 | | | |-- Layout # 布局组件文件夹 39 | | | |-- Loading # 加载组件文件夹 40 | | | |-- sysDict # 系统字典组件文件夹 41 | | |-- config # 配置文件夹 42 | | |-- directives # 自定义指令文件夹 43 | | |-- hooks # 自定义钩子文件夹 44 | | |-- router # 路由配置文件夹 45 | | |-- store # 状态管理文件夹 46 | | |-- styles # 样式文件夹 47 | | |-- types # 类型声明文件夹 48 | | |-- utils # 工具函数文件夹 49 | | |-- views # 视图文件夹 50 | |-- types # 全局类型声明文件夹 51 | ``` 52 | ### 提交代码 yarn commit 命令 53 | -------------------------------------------------------------------------------- /build/proxy.ts: -------------------------------------------------------------------------------- 1 | export const proxy = (target: string) => { 2 | return { 3 | '/v1': { 4 | target: target, 5 | secure: false, 6 | changeOrigin: true, 7 | }, 8 | '/api': { 9 | target: 'https://demo.gin-vue-admin.com', 10 | secure: false, 11 | changeOrigin: true, 12 | }, 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['cz'], 3 | }; 4 | -------------------------------------------------------------------------------- /components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | export {} 7 | 8 | declare module 'vue' { 9 | export interface GlobalComponents { 10 | ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb'] 11 | ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem'] 12 | AButton: typeof import('ant-design-vue/es')['Button'] 13 | ACard: typeof import('ant-design-vue/es')['Card'] 14 | ACheckboxGroup: typeof import('ant-design-vue/es')['CheckboxGroup'] 15 | ACol: typeof import('ant-design-vue/es')['Col'] 16 | AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider'] 17 | ADrawer: typeof import('ant-design-vue/es')['Drawer'] 18 | ADropdown: typeof import('ant-design-vue/es')['Dropdown'] 19 | AForm: typeof import('ant-design-vue/es')['Form'] 20 | AFormItem: typeof import('ant-design-vue/es')['FormItem'] 21 | AInput: typeof import('ant-design-vue/es')['Input'] 22 | AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] 23 | AInputPassword: typeof import('ant-design-vue/es')['InputPassword'] 24 | ALayout: typeof import('ant-design-vue/es')['Layout'] 25 | ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] 26 | ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] 27 | ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] 28 | AMenu: typeof import('ant-design-vue/es')['Menu'] 29 | AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] 30 | AModal: typeof import('ant-design-vue/es')['Modal'] 31 | APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] 32 | ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] 33 | ARow: typeof import('ant-design-vue/es')['Row'] 34 | ASelect: typeof import('ant-design-vue/es')['Select'] 35 | ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] 36 | ASubMenu: typeof import('ant-design-vue/es')['SubMenu'] 37 | ASwitch: typeof import('ant-design-vue/es')['Switch'] 38 | ATable: typeof import('ant-design-vue/es')['Table'] 39 | ATabPane: typeof import('ant-design-vue/es')['TabPane'] 40 | ATabs: typeof import('ant-design-vue/es')['Tabs'] 41 | ATree: typeof import('ant-design-vue/es')['Tree'] 42 | ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect'] 43 | AUpload: typeof import('ant-design-vue/es')['Upload'] 44 | Breadcrumb: typeof import('./src/components/Layout/header/modules/breadcrumb.vue')['default'] 45 | Buoy: typeof import('./src/components/widget/Buoy.vue')['default'] 46 | Content: typeof import('./src/components/Layout/content/index.vue')['default'] 47 | DictCheckbox: typeof import('./src/components/sysDict/modules/dictCheckbox.vue')['default'] 48 | DictRadio: typeof import('./src/components/sysDict/modules/dictRadio.vue')['default'] 49 | DictSelect: typeof import('./src/components/sysDict/modules/dictSelect.vue')['default'] 50 | DictText: typeof import('./src/components/sysDict/modules/dictText.vue')['default'] 51 | Footer: typeof import('./src/components/Layout/footer/index.vue')['default'] 52 | FullScreem: typeof import('./src/components/Layout/header/modules/sysTools/utils/fullScreem.vue')['default'] 53 | Header: typeof import('./src/components/Layout/header/index.vue')['default'] 54 | Layout: typeof import('./src/components/Layout/index.vue')['default'] 55 | Loading: typeof import('./src/components/Loading/index.vue')['default'] 56 | MenuItem: typeof import('./src/components/Layout/sysMenu/menuItem/menuItem.vue')['default'] 57 | Refresh: typeof import('./src/components/Layout/header/modules/sysTools/utils/refresh.vue')['default'] 58 | RouterLink: typeof import('vue-router')['RouterLink'] 59 | RouterView: typeof import('vue-router')['RouterView'] 60 | SysDict: typeof import('./src/components/sysDict/sysDict.vue')['default'] 61 | SysMenu: typeof import('./src/components/Layout/sysMenu/index.vue')['default'] 62 | SysModal: typeof import('./src/components/SysModal/index.vue')['default'] 63 | SysRemoveBtn: typeof import('./src/components/sysRemoveBtn/index.vue')['default'] 64 | SysSearch: typeof import('./src/components/sysSearch/index.vue')['default'] 65 | SysTable: typeof import('./src/components/sysTable/index.vue')['default'] 66 | SysTools: typeof import('./src/components/Layout/header/modules/sysTools/index.vue')['default'] 67 | SysUpload: typeof import('./src/components/sysUpload/index.vue')['default'] 68 | Tabs: typeof import('./src/components/Layout/header/modules/tabs.vue')['default'] 69 | UserTools: typeof import('./src/components/Layout/header/modules/userTools.vue')['default'] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /doc/api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/api.png -------------------------------------------------------------------------------- /doc/authority.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/authority.png -------------------------------------------------------------------------------- /doc/dictionary-usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/dictionary-usage.png -------------------------------------------------------------------------------- /doc/dictionary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/dictionary.png -------------------------------------------------------------------------------- /doc/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/menu.png -------------------------------------------------------------------------------- /doc/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/doc/user.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Go-Zero-Admin 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "game", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite --host", 7 | "build": "vite build", 8 | "build-dev": "vite build --mode staging", 9 | "preview": "vite preview", 10 | "prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"", 11 | "eslint": "eslint . --ext .vue,.js,.ts,.jsx,.tsx --fix", 12 | "stylelint": "stylelint \"src/**/*.{css,scss,vue}\"", 13 | "stylefix": "stylelint \"src/**/*.{css,scss,vue}\" --fix", 14 | "prepare": "git init && husky install", 15 | "commit": "git add . && cz-customizable && git push", 16 | "scriptname": "cmd" 17 | }, 18 | "dependencies": { 19 | "@ant-design/icons-vue": "^7.0.1", 20 | "@formily/antd": "^2.3.1", 21 | "@formily/core": "^2.3.1", 22 | "@formily/vue": "^2.3.1", 23 | "amfe-flexible": "^2.2.1", 24 | "ant-design-vue": "4.x", 25 | "axios": "^1.2.6", 26 | "core-js": "^3.8.3", 27 | "lodash": "^4.17.21", 28 | "moment": "^2.29.4", 29 | "nprogress": "^0.2.0", 30 | "pinia": "^2.0.29", 31 | "pinia-plugin-persistedstate": "^3.0.2", 32 | "screenfull": "^6.0.2", 33 | "vue": "^3.2.45", 34 | "vue-request": "^2.0.4", 35 | "vue-router": "^4.0.3", 36 | "vue-touch": "^2.0.0-beta.4" 37 | }, 38 | "devDependencies": { 39 | "@commitlint/cli": "^17.4.4", 40 | "@commitlint/config-conventional": "^17.4.4", 41 | "@types/lodash": "^4.14.202", 42 | "@types/nprogress": "^0.2.3", 43 | "@typescript-eslint/eslint-plugin": "^5.36.1", 44 | "@typescript-eslint/parser": "^5.36.1", 45 | "@vitejs/plugin-vue": "^4.0.0", 46 | "@vue/cli-plugin-babel": "~5.0.0", 47 | "@vue/cli-plugin-eslint": "~5.0.0", 48 | "@vue/cli-plugin-router": "~5.0.0", 49 | "@vue/cli-plugin-typescript": "~5.0.0", 50 | "@vue/cli-service": "~5.0.0", 51 | "@vue/eslint-config-typescript": "^9.1.0", 52 | "autoprefixer": "^10.4.13", 53 | "babel-plugin-import": "^1.13.8", 54 | "commitizen": "^4.3.0", 55 | "commitlint": "^17.4.4", 56 | "commitlint-config-cz": "^0.13.3", 57 | "cz-conventional-changelog": "^3.3.0", 58 | "cz-customizable": "^7.0.0", 59 | "cz-git": "^1.5.0", 60 | "eslint": "^8.35.0", 61 | "eslint-config-prettier": "^8.6.0", 62 | "eslint-plugin-prettier": "^4.2.1", 63 | "eslint-plugin-vue": "^9.4.0", 64 | "husky": "^8.0.3", 65 | "less": "^4.2.0", 66 | "less-loader": "^11.1.3", 67 | "lint-staged": "^13.1.2", 68 | "postcss": "^8.4.21", 69 | "postcss-import": "^15.1.0", 70 | "postcss-pxtorem": "^6.0.0", 71 | "prettier": "^2.8.4", 72 | "sass": "^1.32.7", 73 | "sass-loader": "^12.0.0", 74 | "stylelint": "^13.13.1", 75 | "stylelint-config-standard": "^22.0.0", 76 | "stylelint-order": "^4.1.0", 77 | "stylelint-scss": "^3.19.0", 78 | "tailwindcss": "^3.2.4", 79 | "typescript": "*", 80 | "unplugin-vue-components": "^0.26.0", 81 | "vite": "^4.0.0", 82 | "vite-plugin-mock": "^2.9.6", 83 | "vite-plugin-windicss": "^1.8.8", 84 | "vue-tsc": "^1.0.11" 85 | }, 86 | "lint-staged": { 87 | "src/**/*.{js,cjs,ts,html,json,css,scss,vue}": [ 88 | "npx prettier --write" 89 | ], 90 | "src/**/*.{js,cjs,ts,vue}": [ 91 | "npx eslint --fix" 92 | ], 93 | "./*.{js,cjs,ts,html,json}": [ 94 | "npx prettier --write" 95 | ], 96 | "./*.{js,cjs,ts}": [ 97 | "npx eslint --fix" 98 | ] 99 | }, 100 | "config": { 101 | "commitizen": { 102 | "path": "node_modules/cz-customizable" 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('postcss-import'), 4 | require('tailwindcss'), 5 | // require('postcss-pxtorem')({ 6 | // rootValue: 10, 7 | // propList: ['*'], 8 | // exclude: /node_modules/, 9 | // selectorBlackList: ['.van-', '.norem-'], // 排除移动端使用了vant库 10 | // }), 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/public/logo.png -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/api/http.ts: -------------------------------------------------------------------------------- 1 | //http.ts 2 | import axios from 'axios'; 3 | import { useUserStoreWithOut } from '@/store/modules/user'; 4 | import { baseUrl, mode } from '@/config/config'; 5 | import { message } from 'ant-design-vue'; 6 | // 设置请求头和请求路径 7 | axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8'; 8 | axios.defaults.headers.put['Content-Type'] = 'application/json;charset=UTF-8'; 9 | axios.defaults.headers.delete['Content-Type'] = 'application/json;charset=UTF-8'; 10 | const instance = axios.create({ 11 | baseURL: mode.IS_DEV ? '/' : baseUrl, 12 | timeout: 10000, 13 | headers: mode.IS_DEV 14 | ? { 15 | Isdev: 1, 16 | } 17 | : {}, 18 | }); 19 | instance.interceptors.request.use( 20 | config => { 21 | // 添加token请求头 22 | const token = useUserStoreWithOut().getToken; 23 | if (token) { 24 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 25 | //@ts-ignore 26 | config.headers.Authorization = token; 27 | } 28 | return config; 29 | }, 30 | error => { 31 | return error; 32 | }, 33 | ); 34 | // 响应拦截 35 | instance.interceptors.response.use(({ data }): any => { 36 | if (data.code === 200) { 37 | return Promise.resolve(data.result); 38 | } else if (data.code === 100003) { 39 | // token失效 清理登录信息 40 | useUserStoreWithOut().resetState(); 41 | toMessage('登录过期,请重新登录'); 42 | return Promise.reject(data.message); 43 | } else { 44 | if (data.result && data.result.message) { 45 | toMessage(data?.result.message); 46 | return Promise.reject(data.message); 47 | } else { 48 | toMessage(data.message + '请联系管理员!'); 49 | return Promise.reject(data.message); 50 | } 51 | } 52 | }); 53 | 54 | // 消息通知 55 | function toMessage(msg: string) { 56 | message.error(msg); 57 | } 58 | 59 | export default instance; 60 | -------------------------------------------------------------------------------- /src/api/modules/api.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { ApiListRespType, ListSearchType } from '@/types/api'; 3 | 4 | // 新增APi 5 | export const createApi = (data: ApiListRespType): Promise => { 6 | return http.post('/v1/sys/api/createApi', data); 7 | }; 8 | // 编辑 9 | export const updateApi = (data: ApiListRespType): Promise => { 10 | return http.put('/v1/sys/api/updateApi', data); 11 | }; 12 | 13 | // 获取api列表 14 | export const getApiList = (data: IProgressReq & ListSearchType): Promise> => { 15 | return http.get('/v1/sys/api/getApiList', data); 16 | }; 17 | // 获取全部api列表 18 | export const getAllApiList = (): Promise<{ apiList: ApiListRespType[] }> => { 19 | return http.get('/v1/sys/api/getAllApiList'); 20 | }; 21 | // 删除 22 | export const deleteApi = (data: { ids: string[] }): Promise => { 23 | return http.delete('/v1/sys/api/deleteApi', data); 24 | }; 25 | 26 | // 批删除 27 | export const deleteApisByIds = (ids: string[]): Promise => { 28 | return http.delete('/v1/sys/api/deleteApisByIds', { ids }); 29 | }; 30 | -------------------------------------------------------------------------------- /src/api/modules/authority.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { MenuRespType } from '@/types/layout'; 3 | import { AuthorityType, AuthorityMenuRespType } from '@/types/authority'; 4 | // 获取角色列表 5 | export const getAuthorityList = (data: IProgressReq): Promise<{ list: AuthorityType[] }> => { 6 | return http.get('/v1/sys/authority/getAuthorityList', data); 7 | }; 8 | // 角色绑定菜单 9 | export const addAuthorityMenu = (data: AuthorityMenuRespType) => { 10 | return http.post('/v1/sys/authority/addAuthorityMenu', data); 11 | }; 12 | 13 | // 更新角色信息 14 | export const updateAuthority = (data: AuthorityType) => { 15 | return http.put('/v1/sys/authority/updateAuthority', data); 16 | }; 17 | // 创建角色信息 18 | export const createAuthority = (data: AuthorityType) => { 19 | return http.post('/v1/sys/authority/createAuthority', data); 20 | }; 21 | // 删除角色信息 22 | export const deleteAuthority = (data: { id: number }) => { 23 | return http.delete('/v1/sys/authority/deleteAuthority', data); 24 | }; 25 | -------------------------------------------------------------------------------- /src/api/modules/casbin.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { CasbinRespType } from '@/types/casbin'; 3 | 4 | // 更新角色casbin数据 5 | export const updateCasbinDataByApiIds = (data: { authorityId: number; apiIds: number[] }): Promise => { 6 | return http.put('/v1/sys/casbin/updateCasbinDataByApiIds', data); 7 | }; 8 | // 获取角色绑定的casbin数据 9 | export const getPathByAuthorityId = (data: { authorityId: number }): Promise<{ list: CasbinRespType[] }> => { 10 | return http.get('/v1/sys/casbin/getPathByAuthorityId', data); 11 | }; 12 | -------------------------------------------------------------------------------- /src/api/modules/dictionary.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { DictionaryDetailListResp, DictionaryListResp, DiceDetail, DiceDetailReq } from '@/types/dictionary'; 3 | 4 | //新增字典列表 5 | export const createSysDictionary = (data: DictionaryListResp): Promise => { 6 | return http.post('/v1/sys/dictionary/createSysDictionary', data); 7 | }; 8 | // 获取字典列表 全部 9 | export const getSysDictionaryList = (): Promise<{ list: DictionaryListResp[] }> => { 10 | return http.get('/v1/sys/dictionary/getSysDictionaryList'); 11 | }; 12 | // 修改字典列表 13 | export const updateSysDictionary = (data: DictionaryListResp): Promise => { 14 | return http.put('/v1/sys/dictionary/updateSysDictionary', data); 15 | }; 16 | 17 | // 删除字典 18 | export const deleteSysDictionary = (data: { id: number }): Promise => { 19 | return http.delete('/v1/sys/dictionary/deleteSysDictionary', data); 20 | }; 21 | 22 | // ====================== 23 | // 获取字典详情列表 24 | export const getSysDictionaryInfoList = ( 25 | data: IProgressReq & { sysDictionaryID: number }, 26 | ): Promise> => { 27 | return http.get('/v1/sys/dictionary/getSysDictionaryInfoList', data); 28 | }; 29 | 30 | // 新增字典详情 31 | export const createSysDictionaryInfo = (data: DictionaryDetailListResp): Promise => { 32 | return http.post('/v1/sys/dictionary/createSysDictionaryInfo', data); 33 | }; 34 | // 修改字典详情 35 | export const updateSysDictionaryInfo = (data: DictionaryDetailListResp): Promise => { 36 | return http.put('/v1/sys/dictionary/updateSysDictionaryInfo', data); 37 | }; 38 | 39 | // 删除字典详情 40 | export const deleteSysDictionaryInfo = (data: { id: number }): Promise => { 41 | return http.delete('/v1/sys/dictionary/deleteSysDictionaryInfo', data); 42 | }; 43 | // ====================== 44 | // 根据类型获取字典详情 45 | export const getSysDictionaryDetails = (data: DiceDetailReq): Promise => { 46 | return http.get('/v1/sys/dictionary/getSysDictionaryDetails', data); 47 | }; 48 | -------------------------------------------------------------------------------- /src/api/modules/loginApi.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { CaptchaType, LoginType, LoginRespType } from '@/types/login'; 3 | 4 | // 获取验证码 5 | export const getCaptcha = (): Promise => { 6 | return http.get('/v1/sys/randomImage/'); 7 | }; 8 | 9 | // 获取验证码 10 | export const login = (data: LoginType): Promise => { 11 | return http.post('/v1/sys/login', data); 12 | }; 13 | -------------------------------------------------------------------------------- /src/api/modules/menuApi.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { MenuRespType } from '@/types/layout'; 3 | import { MenuDataType } from '@/types/menu'; 4 | // 获取菜单 5 | export const asyncMenu = (): Promise<{ menus: MenuRespType[] }> => { 6 | return http.get('/v1/sys/menu/getMenu'); 7 | }; 8 | 9 | // 分页获取菜单 10 | export const asyncMenuList = (): Promise<{ list: MenuDataType[] }> => { 11 | return http.get('/v1/sys/menu/getMenuList'); 12 | }; 13 | // 添加菜单 14 | export const addBaseMenu = (data: MenuDataType): Promise<{ list: MenuDataType[] }> => { 15 | return http.post('/v1/sys/menu/addBaseMenu', data); 16 | }; 17 | // 更新菜单 18 | export const updateBaseMenu = (data: MenuDataType): Promise<{ menus: MenuRespType[] }> => { 19 | return http.put('v1/sys/menu/updateBaseMenu', data); 20 | }; 21 | 22 | // 删除菜单 23 | export const deleteBaseMenu = (data: { id: number }): Promise<{ menus: MenuRespType[] }> => { 24 | return http.delete('/v1/sys/menu/deleteBaseMenu', data); 25 | }; 26 | // 分页获取菜单[树状结构] 27 | export const getBaseMenuTree = (): Promise<{ list: MenuDataType[] }> => { 28 | return http.get('/v1/sys/menu/getBaseMenuTree'); 29 | }; 30 | -------------------------------------------------------------------------------- /src/api/modules/userList.ts: -------------------------------------------------------------------------------- 1 | import http from '../request'; 2 | import { UserListType, EditUserInfoType } from '@/types/userList'; 3 | 4 | // 新增用户 5 | export const register = (data: UserListType): Promise => { 6 | return http.post('/v1/sys/register', data); 7 | }; 8 | 9 | // 获取用户列表 10 | export const getUserList = (data: IProgressReq): Promise> => { 11 | return http.get('/v1/sys/getUserList', data); 12 | }; 13 | 14 | // 修改用户信息 15 | export const editUserList = (data: EditUserInfoType) => { 16 | return http.put('/v1/sys/updateUserInfo', data); 17 | }; 18 | 19 | // 重置密码 20 | export const resetUserPassword = (data: { userId: number }) => { 21 | return http.put('/v1/sys/resetUserPassword', data); 22 | }; 23 | 24 | // 删除用户 25 | export const deleteUser = (data: { userId: number }) => { 26 | return http.delete('/v1/sys/deleteUser', data); 27 | }; 28 | -------------------------------------------------------------------------------- /src/api/request.ts: -------------------------------------------------------------------------------- 1 | import axios from './http'; 2 | import NProgress from 'nprogress'; 3 | NProgress.configure({ showSpinner: false }); 4 | 5 | interface Http { 6 | get(url: string, params?: unknown): Promise; 7 | post(url: string, params?: unknown): Promise; 8 | put(url: string, params?: unknown): Promise; 9 | delete(url: string, params?: unknown): Promise; 10 | upload(url: string, params: unknown): Promise; 11 | download(url: string): void; 12 | } 13 | 14 | const http: Http = { 15 | get(url, params) { 16 | return new Promise((resolve, reject) => { 17 | NProgress.start(); 18 | axios 19 | .get(url, { params }) 20 | .then(res => { 21 | NProgress.done(); 22 | resolve(res); 23 | }) 24 | .catch(err => { 25 | NProgress.done(); 26 | reject(err); 27 | }); 28 | }); 29 | }, 30 | post(url, params) { 31 | return new Promise((resolve, reject) => { 32 | NProgress.start(); 33 | axios 34 | .post(url, JSON.stringify(params)) 35 | .then(res => { 36 | NProgress.done(); 37 | resolve(res); 38 | }) 39 | .catch(err => { 40 | NProgress.done(); 41 | reject(err); 42 | }); 43 | }); 44 | }, 45 | put(url, params) { 46 | return new Promise((resolve, reject) => { 47 | NProgress.start(); 48 | axios 49 | .put(url, JSON.stringify(params)) 50 | .then(res => { 51 | NProgress.done(); 52 | resolve(res); 53 | }) 54 | .catch(err => { 55 | NProgress.done(); 56 | reject(err); 57 | }); 58 | }); 59 | }, 60 | delete(url, params) { 61 | return new Promise((resolve, reject) => { 62 | NProgress.start(); 63 | axios 64 | .delete(url, { data: params }) 65 | .then(res => { 66 | NProgress.done(); 67 | resolve(res); 68 | }) 69 | .catch(err => { 70 | NProgress.done(); 71 | reject(err); 72 | }); 73 | }); 74 | }, 75 | upload(url, file) { 76 | return new Promise((resolve, reject) => { 77 | NProgress.start(); 78 | axios 79 | .post(url, file, { 80 | headers: { 'Content-Type': 'multipart/form-data' }, 81 | }) 82 | .then(res => { 83 | NProgress.done(); 84 | resolve(res); 85 | }) 86 | .catch(err => { 87 | NProgress.done(); 88 | reject(err); 89 | }); 90 | }); 91 | }, 92 | download(url) { 93 | const iframe = document.createElement('iframe'); 94 | iframe.style.display = 'none'; 95 | iframe.src = url; 96 | iframe.onload = function () { 97 | document.body.removeChild(iframe); 98 | }; 99 | document.body.appendChild(iframe); 100 | }, 101 | }; 102 | export default http; 103 | -------------------------------------------------------------------------------- /src/api/type.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/src/api/type.ts -------------------------------------------------------------------------------- /src/assets/images/login/checkcode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/src/assets/images/login/checkcode.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yh-zero/go-zero-admin-vue3/1a0c804f7ceb0bc4334dba4d38a6e579435c547a/src/assets/logo.png -------------------------------------------------------------------------------- /src/assets/svg/login/bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Group 21 5 | Created with Sketch. 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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/components/Layout/content/index.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/components/Layout/footer/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/Layout/header/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 19 | 24 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/breadcrumb.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/sysTools/index.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/sysTools/utils/fullScreem.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/sysTools/utils/refresh.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 39 | 40 | 66 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/tabs.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/components/Layout/header/modules/userTools.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/Layout/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/Layout/sysMenu/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 38 | 39 | 42 | -------------------------------------------------------------------------------- /src/components/Layout/sysMenu/menuItem/menuItem.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/components/Loading/createLoading.ts: -------------------------------------------------------------------------------- 1 | import { VNode, defineComponent } from 'vue'; 2 | import { createVNode, render, reactive, h } from 'vue'; 3 | import type { LoadingProps } from './type'; 4 | import Loading from './index.vue'; 5 | 6 | declare type Nullable = T | null; 7 | 8 | export function createLoading(props: LoadingProps, target?: HTMLElement) { 9 | let vm: Nullable = null; 10 | const data = reactive({ 11 | ...props, 12 | }); 13 | 14 | const LoadingWrap = defineComponent({ 15 | render() { 16 | return h(Loading, { ...data }); 17 | }, 18 | }); 19 | 20 | vm = createVNode(LoadingWrap); 21 | render(vm, document.createElement('div')); 22 | 23 | function close() { 24 | if (vm?.el && vm.el.parentNode) { 25 | vm.el.parentNode.removeChild(vm.el); 26 | } 27 | } 28 | 29 | function open(target: HTMLElement = document.body) { 30 | if (!vm || !vm.el) { 31 | return; 32 | } 33 | target.appendChild(vm.el as HTMLElement); 34 | } 35 | if (target) { 36 | open(target); 37 | } 38 | return { 39 | vm, 40 | close, 41 | open, 42 | setTip: (tip: string) => { 43 | data.tip = tip; 44 | }, 45 | setLoading: (loading: boolean) => { 46 | data.loading = loading; 47 | }, 48 | get loading() { 49 | return data.loading; 50 | }, 51 | get $el() { 52 | return vm?.el as HTMLElement; 53 | }, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/components/Loading/index.vue: -------------------------------------------------------------------------------- 1 | 8 | 42 | 113 | -------------------------------------------------------------------------------- /src/components/Loading/type.ts: -------------------------------------------------------------------------------- 1 | export interface LoadingProps { 2 | tip: string; 3 | size: 'default' | 'small' | 'large'; 4 | absolute: boolean; 5 | loading: boolean; 6 | background: string; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/SysModal/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/SysModal/使用.md: -------------------------------------------------------------------------------- 1 | # 弹窗组件 2 | 3 | ## 使用方式 4 | 1. 引入组件 5 | ```js 6 | import SysModal from '@/components/sysDict/sysModal.vue'; 7 | ``` 8 | 9 | 2. 使用组件 10 | **两个写入属性** 11 | - open: boolean; //打开弹窗 12 | - formRef?: any; //校验表单Ref 【如果弹窗里面有表单需要校验 可以传过来进行确定按钮校验】 13 | - @callBackOk // 确定按钮回调 14 | - @cancel // 取消按钮回调 15 | `** 除了上面属性 其余API均与antd-vue V4x中对应的UI组件文档一致 **` -------------------------------------------------------------------------------- /src/components/sysDict/dictUtils.ts: -------------------------------------------------------------------------------- 1 | import { DiceDetail, DiceDetailReq } from '@/types/dictionary'; 2 | import { getSysDictionaryDetails } from '@/api/modules/dictionary'; 3 | // 根据类型获取字典详情 后续可以使用pinia进行缓存 4 | export async function getDictDetailByType(req: DiceDetailReq): Promise { 5 | const res = await getSysDictionaryDetails(req); 6 | return res; 7 | } 8 | -------------------------------------------------------------------------------- /src/components/sysDict/modules/dictCheckbox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/sysDict/modules/dictRadio.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/components/sysDict/modules/dictSelect.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/sysDict/modules/dictText.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/components/sysDict/sysDict.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/sysDict/使用.md: -------------------------------------------------------------------------------- 1 | # 字典组件 2 | 3 | ## 使用方式 4 | 1. 引入组件 5 | ```js 6 | import SysDict from '@/components/sysDict/sysDict.vue'; 7 | ``` 8 | 9 | 2. 使用组件 10 | **四个写入属性** 11 | - dict: string; //字典类型 【必填】 12 | - type: 'select' | 'checkbox' | 'radio'|'text'; //可选类型 【默认select】 13 | - needForm?: boolean; //是否需要form表单格式 【默认false】 14 | - labelName?: string; //表单label名称 【非必填】 15 | - id?: string | number; //DictText组件专用 用于选出id对应的文字 16 | 17 | `** 除了上面属性 其余API均与antd-vue V4x中对应的UI组件文档一致 【text类型除外】 **` 18 | 19 | 20 | 3. 例子 【type=text 可使用插槽自定义显示】 21 | ```vue 22 | 68 | 69 | 77 | 78 | 79 | 80 | ``` -------------------------------------------------------------------------------- /src/components/sysRemoveBtn/index.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/components/sysSearch/index.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 63 | 74 | -------------------------------------------------------------------------------- /src/components/sysSearch/使用.md: -------------------------------------------------------------------------------- 1 | # 表格搜索栏 2 | 3 | ## 参数 4 | - search: Object 搜索的参数 5 | - @toSearch: Function 点击搜索按钮触发事件 6 | ## 默认插槽 里面直接放a-form-item 标签即可 布局会自动排列 7 | 8 | ## 例子 9 | ```js 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ``` 25 | 26 | ## 收缩插槽 #folded 默认收缩起来的搜索栏 里面直接放a-form-item 标签即可 布局会自动排列 27 | ## 点击展开才会显示搜索项可放收缩插槽 28 | ## 例子 29 | ```js 30 | 31 | 32 | 33 | 34 | {/* 收缩插槽 */} 35 | 43 | 44 | ``` -------------------------------------------------------------------------------- /src/components/sysTable/index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 106 | 116 | -------------------------------------------------------------------------------- /src/components/sysTable/使用方式.md: -------------------------------------------------------------------------------- 1 | ## 使用方法 1 除插槽外 其他方法函数均可与 a-table用法一致 ,请求方法 数据处理均需要自己实现 2 | 插槽使用方式 3 | ```vue 4 | 13 | 18 | ``` 19 | 20 | ## 使用封装方法 21 | 22 | **可写入属性** 23 | 24 | - getList?: Function; //请求列表的方法 25 | - searchData?: any; //列表搜索参数 26 | - pageSize?: number; //每页显示条数 【10】 27 | - pagination?:boolean //是否使用分页 【默认使用】 28 | 29 | - @asyncListCallback; //更新列表回调 【请求成功后回调】 30 | `** 除了上面属性 其余API均与antd-vue V4x中对应的UI组件文档一致 **` 31 | 32 | 33 | ```vue 34 | 44 | 60 | 61 | ``` 62 | 63 | ## 不使用分页 :pagination=false 即可 64 | ```js 65 | 66 | 67 | ``` -------------------------------------------------------------------------------- /src/components/sysUpload/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 159 | -------------------------------------------------------------------------------- /src/components/sysUpload/使用.md: -------------------------------------------------------------------------------- 1 | # 上传组件 2 | 3 | ## 参数 4 | - btnText?: string; //按钮文字 5 | - listType: 'text' | 'picture' | 'picture-card'; //文件类型 6 | - fileNumber?: number; //文件数量 7 | - value: string | string[]; //双向绑定字段 8 | 9 | `** 除了上面属性 其余API均与antd-vue V4x中对应的UI组件文档一致 **` 10 | 11 | ```vue 12 | 15 | 19 | 20 | ``` -------------------------------------------------------------------------------- /src/components/widget/Buoy.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 100 | 144 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | const _mode = import.meta.env.MODE; 2 | // 当前环境 3 | export const mode = { 4 | IS_DEV: _mode === 'development', 5 | IS_STAGING: _mode === 'staging', 6 | IS_PROD: _mode === 'production', 7 | }; 8 | // 当前环境的请求api接口域名 9 | export const baseUrl = import.meta.env.VITE_BASEURL; 10 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 注册指令 3 | */ 4 | import type { App } from 'vue'; 5 | 6 | import { setupLoadingDirective } from './loading'; 7 | 8 | export function setupGlobDirectives(app: App) { 9 | setupLoadingDirective(app, 'load'); 10 | } 11 | -------------------------------------------------------------------------------- /src/directives/loading.ts: -------------------------------------------------------------------------------- 1 | import type { Directive, App } from 'vue'; 2 | import { createLoading } from '@/components/Loading/createLoading'; 3 | const loadingDirective: Directive = { 4 | mounted(el: any, binding: any) { 5 | // 获取使用指令的el 身上所绑定的属性 6 | const tip = el.getAttribute('loading-tip'); 7 | const background = el.getAttribute('loading-background'); 8 | const size = el.getAttribute('loading-size'); 9 | //是否全屏loading 使用时候可以 v-load:foo.fullscreen="true|false" 10 | const fullscreen = !!binding.modifiers.fullscreen; 11 | const instance = createLoading( 12 | { 13 | tip: tip || '', 14 | background, 15 | size: size || 'large', 16 | loading: !!binding.value, 17 | absolute: !fullscreen, 18 | }, 19 | fullscreen ? document.body : el, 20 | ); 21 | // 更新时候用到 所以将loading实例绑定在el上 22 | el.instance = instance; 23 | }, 24 | 25 | updated(el, binding) { 26 | const instance = el.instance; 27 | if (!instance) return; 28 | instance.setTip(el.getAttribute('loading-tip')); 29 | if (binding.oldValue !== binding.value) { 30 | instance.setLoading?.(binding.value && !instance.loading); 31 | } 32 | }, 33 | 34 | unmounted(el) { 35 | el?.instance?.close(); 36 | }, 37 | }; 38 | 39 | export function setupLoadingDirective(app: App, name: string) { 40 | app.directive(name, loadingDirective); 41 | } 42 | 43 | export default loadingDirective; 44 | -------------------------------------------------------------------------------- /src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import { ComponentOptions } from 'vue'; 3 | const ComponentOptions: ComponentOptions; 4 | export default ComponentOptions; 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/baseSelectHooks.ts: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue'; 2 | import { getIcon } from '@/utils/iconList'; 3 | import type { AuthorityType } from '@/types/authority'; 4 | import { getAuthorityList } from '@/api/modules/authority'; 5 | export const useSelectHooks = () => { 6 | // 是否选项 7 | const selectYesNo = ref([ 8 | { 9 | value: true, 10 | label: '是', 11 | }, 12 | { 13 | value: false, 14 | label: '否', 15 | }, 16 | ]); 17 | 18 | // 下拉图标列表 19 | const selectIcon = computed(() => { 20 | const list = getIcon(); 21 | return list.map(res => { 22 | return { 23 | value: res, 24 | label: res, 25 | }; 26 | }); 27 | }); 28 | 29 | // 角色列表【树形结构】 30 | const authorityListTree = ref([]); 31 | async function getAuthList() { 32 | const res = await getAuthorityList({ 33 | pageNo: 1, 34 | pageSize: 99999, 35 | }); 36 | authorityListTree.value = res.list; 37 | } 38 | 39 | return { 40 | selectYesNo, 41 | selectIcon, 42 | authorityListTree, 43 | getAuthList, 44 | }; 45 | }; 46 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | // css 3 | import './style.css'; 4 | import './styles/index.scss'; 5 | import './styles/nprogress.scss'; 6 | import './tailwind.css'; 7 | import App from './App.vue'; 8 | import router from './router'; 9 | import { store } from '@/store'; 10 | import { setupGlobDirectives } from '@/directives'; 11 | import loadAntIcons from './utils/createIcon'; 12 | // UI 13 | import 'ant-design-vue/dist/reset.css'; 14 | 15 | // main.js 16 | const app = createApp(App); 17 | app.use(router); 18 | // 配置 store 19 | app.use(store); 20 | loadAntIcons(app); 21 | // 注册全局指令 22 | setupGlobDirectives(app); 23 | app.mount('#app'); 24 | -------------------------------------------------------------------------------- /src/router/error.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | 3 | const errorRouter: RouteRecordRaw[] = [ 4 | { 5 | name: '404', 6 | path: '/404', 7 | component: () => import('@/views/error/404.vue'), 8 | meta: { 9 | keepAlive: true, 10 | title: '页面不存在', 11 | }, 12 | }, 13 | { 14 | name: '500', 15 | path: '/500', 16 | component: () => import('@/views/error/500.vue'), 17 | meta: { 18 | keepAlive: true, 19 | title: '出错了', 20 | }, 21 | }, 22 | { 23 | path: '/:catchAll(.*)', 24 | meta: { 25 | closeTab: true, 26 | }, 27 | component: () => import('@/views/error/404.vue'), 28 | }, 29 | ]; 30 | 31 | export default errorRouter; 32 | -------------------------------------------------------------------------------- /src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, createRouter, createWebHistory } from 'vue-router'; 2 | import { routerBeforeEach } from './routerBefore'; 3 | import routeModuleList from './routes'; 4 | const { VITE_BASEROUTER } = import.meta.env; 5 | 6 | const router: Router = createRouter({ 7 | history: createWebHistory(VITE_BASEROUTER), 8 | routes: routeModuleList, 9 | }); 10 | routerBeforeEach(router); 11 | // 导出路由 12 | export const routeList = router.getRoutes(); 13 | export default router; 14 | -------------------------------------------------------------------------------- /src/router/modules/layout.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import Layout from '@/components/Layout/index.vue'; 3 | const layoutRouter: RouteRecordRaw[] = [ 4 | { 5 | name: 'sys', 6 | path: '/sys', 7 | component: Layout, 8 | meta: { 9 | title: '管理系统', 10 | }, 11 | children: [ 12 | // 这里的是放 layout里面的页面 动态获取 13 | ], 14 | }, 15 | ]; 16 | 17 | export default layoutRouter; 18 | -------------------------------------------------------------------------------- /src/router/modules/login.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | 3 | const loginRouter: RouteRecordRaw[] = [ 4 | { 5 | name: 'baserouter', 6 | path: '/', 7 | redirect: '/login', 8 | children: [ 9 | { 10 | name: 'login', 11 | path: '/login', 12 | component: () => import('@/views/login.vue'), 13 | meta: { 14 | keepAlive: true, 15 | title: '登录', 16 | }, 17 | }, 18 | ], 19 | }, 20 | ]; 21 | 22 | export default loginRouter; 23 | -------------------------------------------------------------------------------- /src/router/routerBefore.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | import { useLayoutStore } from '@/store/modules/layout'; 3 | import { useUserStore } from '@/store/modules/user'; 4 | import NProgress from 'nprogress'; 5 | NProgress.configure({ showSpinner: false }); 6 | 7 | const whiteList = ['login', '404', '500']; 8 | export const routerBeforeEach = (router: Router) => { 9 | router.beforeEach(async (to: any, from) => { 10 | if (!NProgress.isStarted()) { 11 | NProgress.start(); 12 | } 13 | document.title = to.meta.title || ''; 14 | // 校验路由 15 | const layoutStore = useLayoutStore(); 16 | const userStore = useUserStore(); 17 | const token = userStore.getToken; 18 | // 存在白名单 直接放行 19 | if (whiteList.indexOf(to.name) > -1) { 20 | return true; 21 | } else { 22 | if (!layoutStore.isRefresh) { 23 | // 刷新 24 | await layoutStore.getMenu(); 25 | await layoutStore.setRouter(); 26 | userStore.setColor(); 27 | return { ...to, replace: true }; 28 | } 29 | //不存在白名单 查看是否存在token 30 | if (token) { 31 | // 判断当前登录用户的角色是否拥有跳转的页面 32 | return true; 33 | } else { 34 | // 不存在 token 跳转到权限页 | 登录页 35 | } 36 | } 37 | }); 38 | // 路由加载完成后关闭进度条 39 | router.afterEach((to, from) => { 40 | NProgress.done(); 41 | }); 42 | router.onError(() => { 43 | // 路由发生错误后销毁进度条 44 | NProgress.remove(); 45 | }); 46 | }; 47 | -------------------------------------------------------------------------------- /src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import { RouteRecordRaw } from 'vue-router'; 2 | import errorRouter from './error'; 3 | const modules = import.meta.glob('./modules/**/*.ts', { 4 | eager: true, 5 | import: 'default', 6 | }); 7 | const routeModuleList: RouteRecordRaw[] = []; 8 | 9 | Object.keys(modules).forEach(key => { 10 | const mod = modules[key] || {}; 11 | const modList = Array.isArray(mod) ? [...mod] : [mod]; 12 | routeModuleList.push(...modList); 13 | }); 14 | routeModuleList.push(...errorRouter); 15 | export default routeModuleList; 16 | -------------------------------------------------------------------------------- /src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | 3 | const store = createPinia(); 4 | // 引入数据持久化插件 5 | import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; 6 | store.use(piniaPluginPersistedstate); 7 | export { store }; 8 | 9 | // 因为 pinia的实现也是通过vue的各种api(ref/reactive/computed等) 10 | // 所以,不要求一定要在Vue上挂载注册,可以随便在组件中使用,组件外使用也有对应方案 11 | // 不过,app.use(store) 可以把store实例挂载 12 | -------------------------------------------------------------------------------- /src/store/modules/layout.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../index'; 2 | import { RouteRecordRaw } from 'vue-router'; 3 | import { defineStore } from 'pinia'; 4 | import { MenuRespType, RouterType } from '@/types/layout'; 5 | import { asyncMenu } from '@/api/modules/menuApi'; 6 | import layoutRouter from '@/router/modules/layout'; 7 | // 8 | import { asyncRouterHandle, asyncMenuToRouter, setAsyncRouter } from '@/utils/asyncRouter'; 9 | 10 | interface LayoutType { 11 | isRefresh: boolean; //记录是否被刷新 被刷新了需要重新 addRoute 12 | menuStatus: { 13 | collapsed: boolean; // 菜单栏收起状态 14 | openKeys: string[]; //导航栏展开 15 | selectedKeys: string[]; ////导航栏展开 16 | }; 17 | 18 | menuResList: MenuRespType[]; // 菜单栏数据 19 | } 20 | /** 21 | * 参数1,容器的id 必须唯一 22 | */ 23 | export const useLayoutStore = defineStore({ 24 | id: 'layout', 25 | state: (): LayoutType => ({ 26 | isRefresh: false, 27 | menuStatus: { 28 | collapsed: false, 29 | openKeys: [], 30 | selectedKeys: [], 31 | }, 32 | menuResList: [], 33 | }), 34 | getters: { 35 | // 路由格式的列表 36 | menuList(): RouterType[] { 37 | return asyncMenuToRouter(this.menuResList); 38 | }, 39 | }, 40 | actions: { 41 | async getMenu() { 42 | const res = await asyncMenu(); 43 | this.menuResList = res.menus; //保存返回的数据 44 | // 添加一个用于刷新的路由页面 45 | this.menuResList.push({ 46 | path: 'reload', 47 | name: 'Reload', 48 | meta: { 49 | title: '', 50 | closeTab: true, 51 | keepAlive: false, 52 | defaultMenu: false, 53 | icon: '', 54 | }, 55 | component: 'views/error/reload.vue', 56 | authoritys: null, 57 | btns: [], 58 | children: [], 59 | hidden: true, 60 | menuBtn: [], 61 | menuId: 0, 62 | parameters: [], 63 | parentId: '', 64 | sort: 0, 65 | activeName: '', 66 | }); 67 | // 获取对应的路由 68 | this.setRouter(); 69 | return true; 70 | }, 71 | // 添加动态路由 72 | async setRouter() { 73 | const routerList = asyncRouterHandle(this.menuList); 74 | layoutRouter[0].children = routerList; 75 | await setAsyncRouter(layoutRouter[0]); 76 | this.isRefresh = true; 77 | return true; 78 | }, 79 | setMenuStatus(menuStatus: LayoutType['menuStatus']) { 80 | this.menuStatus = menuStatus; 81 | }, 82 | }, 83 | persist: { 84 | paths: ['collapsed', 'menuStatus', 'menuResList'], 85 | }, 86 | }); 87 | 88 | // Need to be used outside the setup 89 | export function useLayoutStoreWithOut() { 90 | return useLayoutStore(store); 91 | } 92 | -------------------------------------------------------------------------------- /src/store/modules/system.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../index'; 2 | import { RouteRecordRaw } from 'vue-router'; 3 | import { defineStore } from 'pinia'; 4 | 5 | interface SystemType { 6 | iconList: string[]; //保存ant design图标名字 7 | reloadFlag: boolean; //刷新页面 8 | } 9 | /** 10 | * 参数1,容器的id 必须唯一 11 | */ 12 | export const useSystemStore = defineStore({ 13 | id: 'system', 14 | state: (): SystemType => ({ 15 | iconList: [], 16 | reloadFlag: true, 17 | }), 18 | getters: {}, 19 | actions: { 20 | setReloadFlag(flag: boolean) { 21 | this.reloadFlag = flag; 22 | }, 23 | pushIcon(list: string[]) { 24 | this.iconList = list; 25 | }, 26 | }, 27 | persist: true, 28 | }); 29 | 30 | // Need to be used outside the setup 31 | export function useLayoutStoreWithOut() { 32 | return useSystemStore(store); 33 | } 34 | -------------------------------------------------------------------------------- /src/store/modules/user.ts: -------------------------------------------------------------------------------- 1 | import { store } from '../index'; 2 | import { defineStore } from 'pinia'; 3 | import { getStorageToken, setStorageToken, removeStorageToken } from '@/utils/auth'; 4 | import { checkThemebg, checkTextColor } from '@/utils/changeTheme'; 5 | import router from '@/router/index'; 6 | import { LoginRespType, LoginType } from '@/types/login'; 7 | import lodash from 'lodash'; 8 | const defaultUserInfo: LoginRespType = { 9 | accessExpire: 0, 10 | accessToken: '', 11 | userInfo: { 12 | userName: '', 13 | nickName: '', 14 | baseColor: '#001529', 15 | textColor: '#ffffff', 16 | activeColor: '', 17 | authority: { 18 | authorityId: 0, 19 | authorityName: '', 20 | parentId: 0, 21 | dataAuthorityId: [], 22 | menus: [], 23 | defaultRouter: '', 24 | showMenuIds: '', 25 | children: [], 26 | }, 27 | headerImg: '', 28 | authorities: [], 29 | }, 30 | }; 31 | type UserState = { 32 | loginResp: LoginRespType; 33 | contentId: String; 34 | }; 35 | /** 36 | * 参数1,容器的id 必须唯一 37 | */ 38 | export const useUserStore = defineStore({ 39 | id: 'userInfo', 40 | state: (): UserState => ({ 41 | loginResp: lodash.cloneDeep(defaultUserInfo), 42 | contentId: '', 43 | }), 44 | getters: { 45 | getToken(): String { 46 | return this.loginResp.accessToken || getStorageToken(); 47 | }, 48 | }, 49 | actions: { 50 | // 清除登录信息 51 | resetState() { 52 | removeStorageToken(); 53 | this.loginResp = lodash.cloneDeep(defaultUserInfo); 54 | // 跳转到登录页 55 | router.push({ 56 | path: '/', 57 | }); 58 | }, 59 | setUserInfo(info: LoginRespType) { 60 | setStorageToken(info.accessToken); 61 | this.loginResp = info; 62 | this.setColor(); 63 | }, 64 | setColor() { 65 | // 设置颜色 66 | checkThemebg(this.loginResp.userInfo.activeColor); 67 | checkTextColor(this.loginResp.userInfo.baseColor); 68 | }, 69 | }, 70 | persist: true, 71 | }); 72 | 73 | // Need to be used outside the setup 74 | export function useUserStoreWithOut() { 75 | return useUserStore(store); 76 | } 77 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* 全局主题样式 */ 2 | :root { 3 | --themebg: #fff; 4 | --textColor: #000; 5 | } 6 | .themebg { 7 | background-color: var(--themebg) !important; 8 | } 9 | .textColor { 10 | color: var(--textColor) !important; 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/index.scss: -------------------------------------------------------------------------------- 1 | body, 2 | div, 3 | dl, 4 | dt, 5 | dd, 6 | ul, 7 | ol, 8 | li, 9 | h1, 10 | h2, 11 | h3, 12 | h4, 13 | h5, 14 | h6, 15 | form, 16 | fieldset, 17 | legend, 18 | input, 19 | textarea, 20 | p, 21 | blockquote, 22 | th, 23 | td, 24 | hr, 25 | button, 26 | article, 27 | aside, 28 | details, 29 | figcaption, 30 | figure, 31 | footer, 32 | header, 33 | hgroup, 34 | menu, 35 | nav, 36 | section { 37 | margin: 0; 38 | padding: 0; 39 | box-sizing: border-box; 40 | } 41 | 42 | body { 43 | font-size: 16px; 44 | } 45 | 46 | a { 47 | &:active { 48 | opacity: 0.9; 49 | } 50 | } 51 | 52 | [class^='iconfont-ani'], 53 | [class*=' iconfont-ani'] { 54 | width: 1em; 55 | height: 1em; 56 | vertical-align: -0.15em; 57 | fill: currentColor; 58 | overflow: hidden; 59 | } 60 | 61 | .flex-center { 62 | display: flex; 63 | justify-content: center; 64 | align-items: center; 65 | } 66 | 67 | ::-webkit-scrollbar { 68 | width: 2px; /* 纵向滚动条*/ 69 | background-color: #fff; 70 | } 71 | 72 | /*定义滚动条轨道 内阴影*/ 73 | ::-webkit-scrollbar-track { 74 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0); 75 | background-color: #ccc; 76 | } 77 | 78 | /*定义滑块 内阴影*/ 79 | ::-webkit-scrollbar-thumb { 80 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0); 81 | background-color: #544096; 82 | border-radius: 10px; 83 | } 84 | -------------------------------------------------------------------------------- /src/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | // 箭头 方向,尺寸,颜色 2 | // 使用 @include arrow(right, 10px, blue); 3 | @mixin arrow($direction, $size, $color) { 4 | width: 0; 5 | height: 0; 6 | line-height: 0; 7 | font-size: 0; 8 | overflow: hidden; 9 | border-width: $size; 10 | cursor: pointer; 11 | @if $direction==top { 12 | border-style: dashed dashed solid dashed; 13 | border-color: transparent transparent $color transparent; 14 | border-top: none; 15 | } @else if $direction==bottom { 16 | border-style: solid dashed dashed dashed; 17 | border-color: $color transparent transparent transparent; 18 | border-bottom: none; 19 | } @else if $direction==right { 20 | border-style: dashed dashed dashed solid; 21 | border-color: transparent transparent transparent $color; 22 | border-right: none; 23 | } @else if $direction==left { 24 | border-style: dashed solid dashed dashed; 25 | border-color: transparent $color transparent transparent; 26 | border-left: none; 27 | } 28 | } 29 | 30 | // clearfix 31 | @mixin clr { 32 | &:after { 33 | clear: both; 34 | content: '.'; 35 | display: block; 36 | height: 0; 37 | line-height: 0; 38 | overflow: hidden; 39 | } 40 | *height: 1%; 41 | } 42 | 43 | /*渐变(方向,颜色1,颜色2,颜色3)*/ 44 | // 使用 @include linear-gradient(right, green, red, blue); 45 | @mixin linear-gradient($direction: bottom, $color1: transparent, $color2: #306eff, $color3: transparent) { 46 | //background: -webkit-linear-gradient($direction,$colorTop, $colorCenter, $colorBottom); /* Safari 5.1 - 6.0 */ 47 | background: -o-linear-gradient($direction, $color1, $color2, $color3); 48 | /* Opera 11.1 - 12.0 */ 49 | background: -moz-linear-gradient($direction, $color1, $color2, $color3); 50 | /* Firefox 3.6 - 15 */ 51 | background: linear-gradient(to $direction, $color1, $color2, $color3); 52 | /* 标准的语法 */ 53 | } 54 | /*渐变(角度,颜色1,颜色2,颜色3)*/ 55 | // 使用 @include linear-gradient(90edg, green, red, blue); 56 | @mixin linear-gradient-edg($edg: 0edg, $color1: transparent, $color2: #306eff, $color3: transparent) { 57 | //background: -webkit-linear-gradient($direction,$colorTop, $colorCenter, $colorBottom); /* Safari 5.1 - 6.0 */ 58 | background: -o-linear-gradient($edg, $color1, $color2, $color3); 59 | /* Opera 11.1 - 12.0 */ 60 | background: -moz-linear-gradient($edg, $color1, $color2, $color3); 61 | /* Firefox 3.6 - 15 */ 62 | background: linear-gradient($edg, $color1, $color2, $color3); 63 | /* 标准的语法 */ 64 | } 65 | 66 | /* 定义滚动条样式 圆角和阴影不需要则传入null */ 67 | @mixin scrollBar($width: 10px, $height: 10px, $outColor: $baseColor, $innerColor: $bgGrey, $radius: 5px, $shadow: null) { 68 | /*定义滚动条高宽及背景 高宽分别对应横竖滚动条的尺寸*/ 69 | &::-webkit-scrollbar { 70 | width: $width; 71 | height: $height; 72 | background-color: $outColor; 73 | } 74 | 75 | /*定义滚动条轨道 内阴影+圆角*/ 76 | &::-webkit-scrollbar-track { 77 | @if ($shadow !=null) { 78 | -webkit-box-shadow: $shadow; 79 | } 80 | 81 | @if ($radius !=null) { 82 | border-radius: $radius; 83 | } 84 | 85 | background-color: $outColor; 86 | } 87 | 88 | /*定义滑块 内阴影+圆角*/ 89 | &::-webkit-scrollbar-thumb { 90 | @if ($shadow !=null) { 91 | -webkit-box-shadow: $shadow; 92 | } 93 | 94 | @if ($radius !=null) { 95 | border-radius: $radius; 96 | } 97 | 98 | background-color: $innerColor; 99 | border: 1px solid $innerColor; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/styles/nprogress.scss: -------------------------------------------------------------------------------- 1 | /* Make clicks pass-through */ 2 | #nprogress { 3 | pointer-events: none; 4 | } 5 | 6 | #nprogress .bar { 7 | background: #409eff; 8 | position: fixed; 9 | z-index: 1031; 10 | top: 0; 11 | left: 0; 12 | width: 100%; 13 | height: 2px; 14 | } 15 | 16 | /* Fancy blur effect */ 17 | #nprogress .peg { 18 | display: block; 19 | position: absolute; 20 | right: 0; 21 | width: 100px; 22 | height: 100%; 23 | box-shadow: 0 0 10px #409eff, 0 0 5px #409eff; 24 | opacity: 1; 25 | -webkit-transform: rotate(3deg) translate(0, -4px); 26 | -ms-transform: rotate(3deg) translate(0, -4px); 27 | transform: rotate(3deg) translate(0, -4px); 28 | } 29 | 30 | /* Remove these to get rid of the spinner */ 31 | #nprogress .spinner { 32 | display: block; 33 | position: fixed; 34 | z-index: 1031; 35 | top: 15px; 36 | right: 15px; 37 | } 38 | 39 | #nprogress .spinner-icon { 40 | width: 18px; 41 | height: 18px; 42 | box-sizing: border-box; 43 | border: solid 2px transparent; 44 | border-top-color: #409eff; 45 | border-left-color: #409eff; 46 | border-radius: 50%; 47 | -webkit-animation: nprogress-spinner 400ms linear infinite; 48 | animation: nprogress-spinner 400ms linear infinite; 49 | } 50 | 51 | .nprogress-custom-parent { 52 | overflow: hidden; 53 | position: relative; 54 | } 55 | 56 | .nprogress-custom-parent #nprogress .spinner, 57 | .nprogress-custom-parent #nprogress .bar { 58 | position: absolute; 59 | } 60 | 61 | @-webkit-keyframes nprogress-spinner { 62 | 0% { 63 | -webkit-transform: rotate(0deg); 64 | } 65 | 66 | 100% { 67 | -webkit-transform: rotate(360deg); 68 | } 69 | } 70 | 71 | @keyframes nprogress-spinner { 72 | 0% { 73 | transform: rotate(0deg); 74 | } 75 | 76 | 100% { 77 | transform: rotate(360deg); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/tailwind.css: -------------------------------------------------------------------------------- 1 | /* ./src/index.css */ 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | .anticon svg { 7 | vertical-align: baseline; 8 | } 9 | .ant-btn-primary { 10 | background-color: #1677ff; 11 | } 12 | .ant-btn-default, 13 | .ant-btn-dashed { 14 | background-color: white; 15 | } 16 | /* 默认主题色 */ 17 | .theme-nord { 18 | --color-primary: 94 129 172; 19 | } 20 | .ant-switch { 21 | background-color: #bfbfbf; 22 | } 23 | /* 左侧导航栏 */ 24 | .ant-menu-title-content { 25 | flex: none !important; 26 | } 27 | -------------------------------------------------------------------------------- /src/types/api.ts: -------------------------------------------------------------------------------- 1 | // api列表返回 2 | export interface ApiListRespType { 3 | path: string; //'v1/sys/api/getApiList'; 4 | description: string; //'描述'; 5 | apiGroup: string; //'api组'; 6 | method: keyof typeof ApiMethodEnum; //'GET'; 7 | ID: number; 8 | } 9 | export interface ListSearchType { 10 | path: string; 11 | description: string; 12 | apiGroup: string; 13 | method?: string; 14 | dictIdMethod?: number; 15 | orderKey: string; 16 | desc: boolean; 17 | } 18 | 19 | export enum ApiMethodEnum { 20 | GET = 'GET', 21 | POST = 'POST', 22 | PUT = 'PUT', 23 | DELETE = 'DELETE', 24 | } 25 | -------------------------------------------------------------------------------- /src/types/authority.ts: -------------------------------------------------------------------------------- 1 | export interface AuthorityType { 2 | authorityId: number; 3 | authorityName: string; 4 | parentId: number; 5 | dataAuthorityId: number[]; 6 | showMenuIds: string; 7 | showApiIds: string; 8 | children: AuthorityType[] | null; 9 | menus: []; 10 | defaultRouter: string; 11 | } 12 | 13 | // 角色绑定菜单 14 | export interface AuthorityMenuRespType { 15 | authorityId: number; 16 | menuIds: string; //'1,2,3,4,5,6,7,8,9'; 17 | } 18 | // 更新角色信息 19 | // export interface UpdateAuthType { 20 | // authorityId: number; 21 | // authorityName: string; 22 | // parentId: number; 23 | // defaultRouter: string; 24 | // } 25 | -------------------------------------------------------------------------------- /src/types/base.ts: -------------------------------------------------------------------------------- 1 | interface BaseModel { 2 | ID: number; 3 | createdAt: string; 4 | updatedAt: string; 5 | deletedAt: string; 6 | } 7 | -------------------------------------------------------------------------------- /src/types/casbin.ts: -------------------------------------------------------------------------------- 1 | export interface CasbinRespType { 2 | path: string; 3 | method: string; 4 | } 5 | -------------------------------------------------------------------------------- /src/types/dictionary.ts: -------------------------------------------------------------------------------- 1 | export interface DictionaryListResp { 2 | ID?: number; 3 | name: string; //字典名 中文 4 | type: string; //字典名 英文 5 | status: number; 6 | desc: string; //描述 7 | sysDictionaryInfoList: any[]; 8 | } 9 | 10 | // 详情列表返回 11 | export interface DictionaryDetailListResp { 12 | ID: number; 13 | label: string; //展示值 14 | value: number; //字典值 15 | extend: string; //拓展值 16 | status: number; //启用状态 17 | sort: number; //排序 18 | sysDictionaryID?: number; // 父级Id 19 | } 20 | 21 | // ===================================== 22 | export interface DiceDetailReq { 23 | id?: number; 24 | type?: string; 25 | status?: 1 | 2; 26 | } 27 | // 根据类型获取字典详情 28 | export interface DiceDetail { 29 | ID: number; 30 | name: string; 31 | type: string; 32 | status: 1 | 2; 33 | desc: 'api请求'; 34 | sysDictionaryInfoList: DictionaryDetailListResp[]; 35 | } 36 | -------------------------------------------------------------------------------- /src/types/layout.ts: -------------------------------------------------------------------------------- 1 | import { RouteMeta, _RouteRecordBase } from 'vue-router'; 2 | // 路由菜单返回列表 3 | export interface MenuRespType { 4 | authoritys: null; 5 | btns: {}; 6 | children: MenuRespType[]; 7 | component: string; //页面路径 8 | hidden: boolean; //是否在列表隐藏 9 | menuBtn: MenuBtnType[]; // 10 | menuId: number; 11 | name: string; //路由name 12 | parameters: ParametersType[]; 13 | parentId: string; //父菜单ID 14 | path: string; //路由path" 15 | sort: number; //排序标记 16 | activeName: string; // 高亮菜单 17 | // ========= 18 | meta: MateType; 19 | } 20 | export interface MateType extends RouteMeta { 21 | keepAlive: boolean; //是否缓存 22 | defaultMenu: boolean; //是否是基础路由 23 | title: string; //菜单名 24 | icon: string; //菜单图片 25 | closeTab: boolean; //自动关闭tab 26 | } 27 | 28 | interface MenuBtnType { 29 | name: string; // 按钮关键key 30 | desc: string; //按钮备注 31 | sysBaseMenuID: number; //菜单ID 32 | } 33 | interface ParametersType { 34 | type: string; //址栏携带参数为params还是query 35 | key: string; //地址栏携带参数的key 36 | value: string; //地址栏携带参数的值 37 | } 38 | 39 | // ================ 路由 ================ 40 | //导航栏菜单格式 41 | export interface RouterType extends _RouteRecordBase { 42 | componentPath: string; //页面路径 //'view/layout/index.vue' 43 | meta: MateType; 44 | hidden: boolean; 45 | children: RouterType[]; 46 | } 47 | -------------------------------------------------------------------------------- /src/types/login.ts: -------------------------------------------------------------------------------- 1 | // 登录表单 2 | export interface LoginType { 3 | username: string; 4 | password: string; 5 | captcha: string; 6 | } 7 | 8 | // 验证码返回 9 | export interface CaptchaType { 10 | captchaImg: string; 11 | } 12 | 13 | // 登录返回 14 | export interface LoginRespType { 15 | accessExpire: number; 16 | accessToken: string; 17 | userInfo: { 18 | userName: string; 19 | headerImg: string; 20 | nickName: string; 21 | baseColor: string; 22 | activeColor: string; 23 | textColor: string; 24 | authority: AuthorityType; 25 | authorities: AuthorityType[]; 26 | }; 27 | } 28 | 29 | // 登录返回 30 | export interface AuthorityType { 31 | authorityId: number; 32 | authorityName: string; 33 | parentId: 0; 34 | dataAuthorityId: string[]; 35 | menus: string[]; 36 | defaultRouter: string; 37 | showMenuIds: string; 38 | children: AuthorityType[]; 39 | } 40 | -------------------------------------------------------------------------------- /src/types/menu.ts: -------------------------------------------------------------------------------- 1 | interface MetaType { 2 | activeName: string; 3 | keepAlive: boolean; 4 | defaultMenu: boolean; 5 | title: string; 6 | icon: string; 7 | closeTab: boolean; 8 | } 9 | 10 | interface parameters { 11 | type: 'query' | 'params'; 12 | key: string; //"参数key" 13 | value: string; //参数值 14 | } 15 | interface btns { 16 | name: string; 17 | desc: string; //参数值 18 | } 19 | export interface MenuDataType { 20 | parentId: number; //父菜单ID 21 | path: string; 22 | name: string; 23 | hidden: boolean; 24 | component: string; 25 | sort: number; 26 | authoritys: any[]; // 未提供authoritys的类型信息 27 | children: MenuDataType[] | null; 28 | parameters: parameters[]; // 未提供parameters的类型信息 29 | menuBtn: btns[]; // 未提供menuBtn的类型信息 30 | meta: MetaType; 31 | ID: number; 32 | showMenuIds: string[]; 33 | createdAt: string; 34 | UpdatedAt: string; 35 | DeletedAt: string; 36 | } 37 | -------------------------------------------------------------------------------- /src/types/userList.ts: -------------------------------------------------------------------------------- 1 | import { AuthorityType } from './authority'; 2 | 3 | export interface UserListType { 4 | uuid: string; 5 | userName: string; 6 | nickName: string; 7 | sideMode: string; 8 | headerImg: string; 9 | baseColor: string; 10 | activeColor: string; 11 | authorityId: number; 12 | authority: AuthorityType; 13 | authorities: AuthorityType[]; 14 | phone: string; 15 | email: string; 16 | enable: number; 17 | ID: number; 18 | // 编辑使用 19 | selectIds?: number[]; //用户角色 20 | authorityIds?: number[]; 21 | passWord?: string; 22 | } 23 | 24 | // 修改用户信息 25 | export interface EditUserInfoType { 26 | ID: number; 27 | passWord?: string; 28 | nickName?: string; // 用户昵称 29 | sideMode?: string; // 用户侧边主题 30 | headerImg?: string; 31 | phone?: string; 32 | email?: string; 33 | enable?: number; //用户是否被冻结 1正常 2冻结 34 | authorityIds?: number[]; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/asyncRouter.ts: -------------------------------------------------------------------------------- 1 | import { RouterType, MenuRespType } from '@/types/layout'; 2 | import { RouteRecordRaw } from 'vue-router'; 3 | 4 | import router from '@/router'; 5 | const viewModules = import.meta.glob('../views/**/*.vue'); 6 | const pluginModules = import.meta.glob('../plugins/**/*.vue'); 7 | 8 | // 动态添加路由 9 | export const setAsyncRouter = (route: RouteRecordRaw) => { 10 | router.addRoute(route); 11 | return Promise.resolve(); 12 | }; 13 | 14 | // 路由菜单返回列表转换路由格式 MenuRespType => RouterType 15 | export const asyncMenuToRouter = (menus: MenuRespType[]): RouterType[] => { 16 | const routerList: RouterType[] = []; 17 | menus.forEach(menu => { 18 | const route: RouterType = { 19 | componentPath: menu.component, 20 | path: menu.path, 21 | name: menu.name, 22 | hidden: menu.hidden, 23 | meta: menu.meta, 24 | children: [], 25 | }; 26 | if (menu.children) { 27 | route.children = asyncMenuToRouter(menu.children); 28 | } 29 | routerList.push(route); 30 | }); 31 | return routerList; 32 | }; 33 | 34 | // 根据接口返回的页面路径 获取对应的页面 35 | export const asyncRouterHandle = (asyncRouter: RouterType[]): RouteRecordRaw[] => { 36 | const routerList: RouteRecordRaw[] = []; 37 | asyncRouter.forEach(item => { 38 | const routers: RouteRecordRaw = { 39 | path: item.path, //路由路径 40 | name: item.name, //路由name 41 | component: null, //页面路径 //'view/layout/index.vue' 42 | meta: item.meta, // 43 | children: [], 44 | }; 45 | if (item.componentPath && typeof item.componentPath === 'string') { 46 | if (item.componentPath.split('/')[0] === 'views') { 47 | routers.component = dynamicImport(viewModules, item.componentPath); 48 | } else if (item.componentPath.split('/')[0] === 'plugin') { 49 | routers.component = dynamicImport(pluginModules, item.componentPath); 50 | } 51 | } 52 | if (item.children) { 53 | routers.children = asyncRouterHandle(item.children as RouterType[]); 54 | } 55 | routerList.push(routers); 56 | }); 57 | return routerList; 58 | }; 59 | // 匹配对应的页面 60 | function dynamicImport(dynamicViewsModules: any, component: string) { 61 | const keys = Object.keys(dynamicViewsModules); 62 | const matchKeys = keys.filter(key => { 63 | const k = key.replace('../', ''); 64 | return k === component; 65 | }); 66 | const matchKey = matchKeys[0]; 67 | 68 | return dynamicViewsModules[matchKey]; 69 | } 70 | -------------------------------------------------------------------------------- /src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | import storage from './storage'; 2 | const TokenKey = 'Authorization'; 3 | 4 | export function getStorageToken() { 5 | return storage.get(TokenKey) || ''; 6 | } 7 | 8 | export function setStorageToken(token: string) { 9 | storage.set(TokenKey, token, 23 * 60 * 60 * 1000); 10 | } 11 | 12 | export function removeStorageToken() { 13 | storage.remove(TokenKey); 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/changeTheme.ts: -------------------------------------------------------------------------------- 1 | // src/style.css 2 | 3 | // 修改背景颜色 4 | export function checkThemebg(color: string) { 5 | document.documentElement.style.setProperty(`--themebg`, color); 6 | } 7 | // 修改背景颜色 8 | export function checkTextColor(color: string) { 9 | document.documentElement.style.setProperty(`--textColor`, color); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/createIcon.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import * as antIcons from '@ant-design/icons-vue'; 3 | import { setIcon } from '@/utils/iconList'; 4 | const loadAntIcons = (app: App) => { 5 | const iconList: string[] = []; 6 | Object.keys(antIcons).forEach((iconName: string) => { 7 | iconList.push(iconName); 8 | app.component(iconName, antIcons[iconName as keyof typeof antIcons]); 9 | }); 10 | setIcon(iconList); 11 | }; 12 | export default loadAntIcons; // 导出 13 | -------------------------------------------------------------------------------- /src/utils/flexible.ts: -------------------------------------------------------------------------------- 1 | function flexible(window: any, document: any) { 2 | const docEl = document.documentElement; 3 | const dpr = window.devicePixelRatio || 1; 4 | 5 | // adjust body font size 6 | function setBodyFontSize() { 7 | if (document.body) { 8 | document.body.style.fontSize = 12 * dpr + 'px'; 9 | } else { 10 | document.addEventListener('DOMContentLoaded', setBodyFontSize); 11 | } 12 | } 13 | setBodyFontSize(); 14 | 15 | // set 1rem = viewWidth / 10 16 | function setRemUnit() { 17 | // 是否移动端 宽度小于750 都认为是移动端 18 | const flag = 19 | navigator.userAgent.match( 20 | /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i, 21 | ) || docEl.clientWidth < 750; 22 | // 判断是否横屏 23 | const isWidth = docEl.clientWidth > docEl.clientHeight; 24 | const rem = flag 25 | ? docEl.clientWidth / (isWidth ? 133 : 75) // 133 = 横屏设计稿尺寸/10 ,,, 75 = 竖屏设计稿尺寸/10 26 | : docEl.clientWidth / 192; // 192 = pc端设计稿/10 27 | docEl.style.fontSize = rem + 'px'; 28 | } 29 | 30 | setRemUnit(); 31 | 32 | // reset rem unit on page resize 33 | window.addEventListener('resize', setRemUnit); 34 | window.addEventListener('pageshow', function (e: any) { 35 | if (e.persisted) { 36 | setRemUnit(); 37 | } 38 | }); 39 | 40 | // detect 0.5px supports 41 | if (dpr >= 2) { 42 | const fakeBody = document.createElement('body'); 43 | const testElement = document.createElement('div'); 44 | testElement.style.border = '.5px solid transparent'; 45 | fakeBody.appendChild(testElement); 46 | docEl.appendChild(fakeBody); 47 | if (testElement.offsetHeight === 1) { 48 | docEl.classList.add('hairlines'); 49 | } 50 | docEl.removeChild(fakeBody); 51 | } 52 | } 53 | flexible(window, document); 54 | -------------------------------------------------------------------------------- /src/utils/getFile.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 获取单个assets静态资源 3 | * @param url 4 | * @param fileName 5 | * @returns 6 | */ 7 | export function getAssetsFile(url: string, fileName: string) { 8 | return new URL(`../assets/${url}/${fileName}`, import.meta.url).href; 9 | } 10 | export function getAFile(url: string) { 11 | return new URL(`../assets/${url}`, import.meta.url).href; 12 | } 13 | /** 14 | * 顺序获取多个assets静态资源 未完善 15 | * @param url 16 | * @param assets 17 | * @returns 18 | */ 19 | export function getAssetsFiles(url: string, assets?: string[]) { 20 | return []; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/iconList.ts: -------------------------------------------------------------------------------- 1 | import storage from './storage'; 2 | const keyName = 'iconList'; 3 | // 保存图标 4 | export function setIcon(iconList: string[]) { 5 | storage.set(keyName, iconList); 6 | } 7 | 8 | export function getIcon(): string[] { 9 | return storage.get(keyName); 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/isPhone.ts: -------------------------------------------------------------------------------- 1 | // 是否移动端 2 | export function isMobail() { 3 | const flag = 4 | navigator.userAgent.match( 5 | /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i, 6 | ) || document.body.clientWidth < 750; 7 | return flag; 8 | } 9 | 10 | //判断是否横屏 11 | export function isHorizontal() { 12 | const width = document.documentElement.clientWidth; //视窗宽 13 | const height = document.documentElement.clientHeight; //视窗高 14 | // return width <= height 15 | if (window.orientation === 180 || window.orientation === 0) { 16 | return false; 17 | } 18 | if (window.orientation === 90 || window.orientation === -90) { 19 | return true; 20 | } 21 | } 22 | 23 | // 判断是否微信打开 24 | export function ixWx() { 25 | const ua = window.navigator.userAgent.toLowerCase(); 26 | return /MicroMessenger/i.test(ua); 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/lang.ts: -------------------------------------------------------------------------------- 1 | // import offlineStore from 'store/dist/store.modern'; 2 | 3 | /** 4 | * 检测类型 5 | */ 6 | const { toString } = Object.prototype; 7 | const is = (type: any) => (obj: any) => 8 | toString.call(obj) === `[object ${type}]`; 9 | 10 | export const isRegExp = is('RegExp'); 11 | 12 | export const isString = is('String'); 13 | 14 | export const isFunction = is('Function'); 15 | 16 | export const isObject = is('Object'); 17 | 18 | export const isArray = is('Array'); 19 | 20 | export const isNumber = is('Number'); 21 | 22 | export const isDate = is('Date'); 23 | 24 | export const isError = is('Error'); 25 | 26 | export const isDigital = (n: any) => /^\d+$/.test(n); 27 | 28 | export const isDefined = (n: any) => typeof n !== 'undefined'; 29 | 30 | export const isUndefined = (n: any) => !isDefined(n); 31 | 32 | export const isHex = (n: any) => { 33 | n = String(n); 34 | const len = n.length; 35 | 36 | return (len === 3 || len === 6) && /^[0-9a-f]+$/g.test(n); 37 | }; 38 | 39 | export const hasOwnProperty = (obj: any, key: any) => 40 | Object.prototype.hasOwnProperty.call(obj, key); 41 | 42 | export const is32Hash = (value: any) => value.length === 32; 43 | 44 | // 某个 fn 的结果为 true,说明此 fn 成功执行完成,否则,认为其中有报错 45 | export const pipelineFns = async (...fns: any) => { 46 | fns = fns.filter((fn: any) => isFunction(fn)); 47 | for (let i = 0; i < fns.length; i += 1) { 48 | const res = await fns[i](); // eslint-disable-line no-await-in-loop 49 | if (!res) return; 50 | } 51 | }; 52 | 53 | // 不带四舍五入的fixed 54 | export const toFixedNoCarry = (num: any, len: any) => { 55 | num = `${num}`; 56 | const [integet, decimal] = num.split('.'); 57 | if (!decimal) return integet; 58 | return decimal ? `${integet}.${decimal.substr(0, len)}` : integet; 59 | }; 60 | -------------------------------------------------------------------------------- /src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * localStorage 一个有过期特性的localStorage类 3 | */ 4 | 5 | class Storage { 6 | /* 7 | * 获取缓存 8 | * @param { String } key 本地缓存中的指定的 key 9 | */ 10 | get(key: string) { 11 | const data = window.localStorage.getItem(key); 12 | if (!data) { 13 | return null; 14 | } 15 | const _data = JSON.parse(data); 16 | // 已过期 17 | if ( 18 | this.validExpire( 19 | new Date().getTime(), 20 | _data?.writeTime, 21 | _data.durationTime, 22 | ) 23 | ) { 24 | this.remove(key); 25 | return null; 26 | } else { 27 | return _data.data; 28 | } 29 | } 30 | 31 | /** 32 | * 将数据存储在本地缓存中指定的 key 中,会覆盖掉原来该 key 对应的内容 33 | * @param { String } key 本地缓存中的指定的 key 34 | * @param { Any } data 需要存储的内容,只支持原生类型、及能够通过 JSON.stringify 序列化的对象 35 | * @param { Number } duraction 有效时长,单位毫秒 36 | */ 37 | set(key: string, data: any, duraction?: number) { 38 | const obj = { 39 | data: data, // 存储数据 40 | writeTime: new Date().getTime(), // 写入时间 41 | durationTime: duraction, // 有效时长 单位毫秒 42 | }; 43 | window.localStorage.setItem(key, JSON.stringify(obj)); 44 | } 45 | 46 | /** 47 | * 从本地缓存中异步移除指定 key 48 | * @param { String } key 本地缓存中的指定的 key 49 | */ 50 | remove(key: string) { 51 | window.localStorage.removeItem(key); 52 | } 53 | 54 | /* 55 | * 清理本地数据缓存 56 | */ 57 | clear() { 58 | window.localStorage.clear(); 59 | } 60 | 61 | /** 62 | * 验证是否过期 63 | * @param { Number } nowTime 当前时间 64 | * @param { Number } writeTime 缓存写入时间 65 | * @param { Number } expireTime 缓存有效时长 66 | */ 67 | validExpire(nowTime: number, writeTime: number, durationTime: number) { 68 | if (!durationTime && durationTime !== 0) { 69 | return false; 70 | } 71 | return nowTime > writeTime + durationTime; 72 | } 73 | } 74 | 75 | export default new Storage(); 76 | -------------------------------------------------------------------------------- /src/utils/styles.ts: -------------------------------------------------------------------------------- 1 | import { isString, isObject } from './lang'; 2 | 3 | export const replaceLineBreaks = (str: any, tag = false) => { 4 | if (!isString(str)) return str || ''; 5 | if (!tag) return str.replace(/\r\n|\n|\\n|\\r\\n/g, '
'); 6 | const strExHtml = str 7 | .replace(/<[^>]+>/g, '') 8 | .replace(/\r\n|\n|\\n|\\r\\n/g, ''); 9 | return strExHtml; 10 | }; 11 | 12 | export const getProperties = () => { 13 | const div = document.createElement('div'); 14 | 15 | return { 16 | transform: 'transform' in div.style ? 'transform' : '-webkit-transform', 17 | transition: 'transition' in div.style ? 'transition' : '-webkit-transition', 18 | duration: 19 | 'transition-duration' in div.style 20 | ? 'transition-duration' 21 | : '-webkit-transition-duration', 22 | }; 23 | }; 24 | 25 | // fix error when value is null 26 | export const letPxGo = (value: any) => Number((value || '').replace('px', '')); 27 | 28 | export const getComputedProp = (el: any, prop: any) => 29 | el instanceof Element ? window.getComputedStyle(el, null)[prop] : null; 30 | 31 | export const getPropNumeric = (el: any, prop: any) => { 32 | if (!el) { 33 | return 0; 34 | } 35 | 36 | const value = letPxGo(getComputedProp(el, prop)); 37 | 38 | return isNaN(value) ? 0 : value; 39 | }; 40 | 41 | export const setStyle = (el: any, styles: any, keepStyle = false) => { 42 | if (isObject(styles)) { 43 | styles = Object.keys(styles).reduce( 44 | (style, key) => `${style} ${key}: ${styles[key]};`, 45 | '', 46 | ); 47 | } 48 | 49 | if (!isString(styles)) { 50 | return; 51 | } 52 | 53 | el.style.cssText = (keepStyle ? el.style.cssText : '') + styles; 54 | }; 55 | 56 | export const getAbsoultPosition = (el: any) => { 57 | const originalEle = el; 58 | let top = 0; 59 | let left = 0; 60 | 61 | while (el) { 62 | left += el.offsetLeft; 63 | top += el.offsetTop; 64 | el = el.offsetParent; 65 | } 66 | 67 | const zoom = getPropNumeric(originalEle, 'zoom'); 68 | top *= zoom; 69 | left *= zoom; 70 | 71 | return { left, top }; 72 | }; 73 | 74 | export const getPositionAgainstRoot = (el: any) => { 75 | const { top, left } = el.getBoundingClientRect(); 76 | const { scrollX, scrollY } = window; 77 | 78 | return { 79 | left: left + scrollX, 80 | top: top + scrollY, 81 | }; 82 | }; 83 | 84 | export const getScrollTop = () => 85 | Math.max( 86 | document.body.scrollTop, 87 | document.documentElement.scrollTop, 88 | Math.abs(getPropNumeric(document.body, 'top')), 89 | ); 90 | 91 | const to = (scrollTop: any) => { 92 | document.body.scrollTop = scrollTop; 93 | document.documentElement.scrollTop = scrollTop; 94 | }; 95 | const dialogOpenClass = 'dialog-open'; 96 | const scrollTop = null; 97 | 98 | export function rem2Px(rem: any) { 99 | return getPropNumeric(document.documentElement, 'fontSize') * rem; 100 | } 101 | -------------------------------------------------------------------------------- /src/views/error/404.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/error/500.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/error/reload.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/login.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/views/superAdmin/api/api.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /src/views/superAdmin/api/modules/addModal.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/views/superAdmin/api/modules/data.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/es/form'; 2 | import { ApiListRespType, ApiMethodEnum } from '@/types/api'; 3 | import { ref } from 'vue'; 4 | export const defaultData: ApiListRespType = { 5 | path: '', 6 | apiGroup: '', 7 | method: 'GET', 8 | description: '', 9 | ID: 0, 10 | }; 11 | 12 | // 是否选项 13 | export const selectMethods = ref([ 14 | { 15 | value: ApiMethodEnum.GET, 16 | label: ApiMethodEnum.GET, 17 | }, 18 | { 19 | value: ApiMethodEnum.POST, 20 | label: ApiMethodEnum.POST, 21 | }, 22 | { 23 | value: ApiMethodEnum.PUT, 24 | label: ApiMethodEnum.PUT, 25 | }, 26 | { 27 | value: ApiMethodEnum.DELETE, 28 | label: ApiMethodEnum.DELETE, 29 | }, 30 | ]); 31 | 32 | // 校验表单 33 | export const rules: Record = { 34 | path: [{ required: true, message: '请输入路径', trigger: 'change' }], 35 | method: [{ required: true, message: '请选择请求', trigger: 'change' }], 36 | apiGroup: [{ required: true, message: '请输入Api分组', trigger: 'change' }], 37 | description: [{ required: true, message: '请输入Api简介', trigger: 'change' }], 38 | }; 39 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/authority.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/modules/addModal.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/modules/data.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/es/form'; 2 | import { AuthorityType } from '@/types/authority'; 3 | 4 | export const defaultData: AuthorityType = { 5 | authorityId: 0, 6 | authorityName: '', 7 | parentId: 0, 8 | dataAuthorityId: [], 9 | children: [], 10 | menus: [], 11 | defaultRouter: '', 12 | }; 13 | 14 | // 校验表单 15 | export const rules: Record = { 16 | parentId: [{ required: true, message: '请选择父级角色', trigger: 'change' }], 17 | authorityId: [{ required: true, message: '请输入角色ID', trigger: 'change' }], 18 | authorityName: [{ required: true, message: '请输入角色姓名', trigger: 'change' }], 19 | }; 20 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/modules/editAuth/authApi.vue: -------------------------------------------------------------------------------- 1 | 10 | 95 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/modules/editAuth/authList.vue: -------------------------------------------------------------------------------- 1 | 28 | 128 | -------------------------------------------------------------------------------- /src/views/superAdmin/authority/modules/editAuth/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/dictionary.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/leftList/addModal.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/leftList/data.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/es/form'; 2 | import { DictionaryListResp } from '@/types/dictionary'; 3 | import { ref } from 'vue'; 4 | export const defaultData: DictionaryListResp = { 5 | name: '', 6 | type: '', 7 | status: 0, 8 | desc: '', 9 | sysDictionaryInfoList: [], 10 | }; 11 | 12 | // 校验表单 13 | export const rules: Record = { 14 | name: [{ required: true, message: '字典名 中文', trigger: 'blur' }], 15 | type: [{ required: true, message: '字典名 英文', trigger: 'blur' }], 16 | status: [{ required: true, message: '状态', trigger: 'change' }], 17 | desc: [{ required: true, message: '请输入描述', trigger: 'blur' }], 18 | }; 19 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/leftList/index.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/rightList/addModal.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/rightList/data.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/es/form'; 2 | import { DictionaryDetailListResp } from '@/types/dictionary'; 3 | export const defaultData: DictionaryDetailListResp = { 4 | label: '', 5 | value: 0, 6 | extend: '', 7 | status: 0, 8 | sort: 0, 9 | }; 10 | 11 | // 校验表单 12 | export const rules: Record = { 13 | label: [{ required: true, message: '展示值', trigger: 'blur' }], 14 | value: [{ required: true, message: '字典值', trigger: 'blur' }], 15 | status: [{ required: true, message: '状态', trigger: 'blur' }], 16 | sort: [{ required: true, message: '排序', trigger: 'blur' }], 17 | }; 18 | -------------------------------------------------------------------------------- /src/views/superAdmin/dictionary/modules/rightList/index.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /src/views/superAdmin/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/views/superAdmin/menu/menu.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/views/superAdmin/menu/modules/addMenuModal.vue: -------------------------------------------------------------------------------- 1 | 122 | 123 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/views/superAdmin/menu/modules/data.ts: -------------------------------------------------------------------------------- 1 | import type { SelectProps } from 'ant-design-vue'; 2 | import type { Rule } from 'ant-design-vue/es/form'; 3 | import { reactive, ref } from 'vue'; 4 | import { MenuDataType } from '@/types/menu'; 5 | 6 | export const defaultMenuData: MenuDataType = { 7 | parentId: 0, 8 | path: '', 9 | name: '', 10 | hidden: false, 11 | component: '', 12 | sort: 0, 13 | authoritys: [], 14 | children: null, 15 | parameters: [], 16 | menuBtn: [], 17 | meta: { 18 | activeName: '', 19 | keepAlive: false, 20 | defaultMenu: false, 21 | title: '', 22 | icon: '', 23 | closeTab: false, 24 | }, 25 | ID: 0, 26 | createdAt: '', 27 | UpdatedAt: '', 28 | DeletedAt: '', 29 | showMenuIds: [], 30 | }; 31 | 32 | // form 33 | 34 | export const selectParameters = ref([ 35 | { 36 | value: 'query', 37 | label: 'query', 38 | }, 39 | { 40 | value: 'params', 41 | label: 'params', 42 | }, 43 | ]); 44 | 45 | // 校验表单 46 | export const rules: Record = { 47 | name: [{ required: true, message: '请输入路由名字', trigger: 'change' }], 48 | path: [{ required: true, message: '请输入路由Path', trigger: 'change' }], 49 | component: [{ required: true, message: '请输入文件路径', trigger: 'change' }], 50 | }; 51 | 52 | // table 53 | export const columns = [ 54 | { title: '参数类型', slotName: 'type', width: 200 }, 55 | { title: '参数key', slotName: 'key', width: 200 }, 56 | { title: '参数值', slotName: 'value', width: 200 }, 57 | { title: '', slotName: 'edit', width: 200 }, 58 | ]; 59 | 60 | export const columnsBtn = [ 61 | { title: '按钮名称', slotName: 'name', width: 200 }, 62 | { title: '备注', slotName: 'desc', width: 200 }, 63 | { title: '', slotName: 'edit', width: 200 }, 64 | ]; 65 | -------------------------------------------------------------------------------- /src/views/superAdmin/user/modules/addModal.vue: -------------------------------------------------------------------------------- 1 | 60 | 61 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /src/views/superAdmin/user/modules/data.ts: -------------------------------------------------------------------------------- 1 | import type { Rule } from 'ant-design-vue/es/form'; 2 | import { UserListType } from '@/types/userList'; 3 | export const defaultData: UserListType = { 4 | userName: '', 5 | passWord: '', 6 | nickName: '', 7 | authorityId: 0, 8 | authorityIds: [], 9 | uuid: '', 10 | sideMode: '', 11 | headerImg: '', 12 | baseColor: '', 13 | activeColor: '', 14 | authority: { 15 | authorityId: 0, 16 | authorityName: '', 17 | parentId: 0, 18 | dataAuthorityId: [], 19 | showMenuIds: '', 20 | children: null, 21 | menus: [], 22 | defaultRouter: '', 23 | }, 24 | authorities: [], 25 | phone: '', 26 | email: '', 27 | enable: 0, 28 | ID: 0, 29 | }; 30 | 31 | // 校验表单 32 | export const rules: Record = { 33 | userName: [{ required: true, message: '请输入路径', trigger: 'change' }], 34 | passWord: [{ required: true, message: '请选择请求', trigger: 'change' }], 35 | nickName: [{ required: true, message: '请输入Api分组', trigger: 'change' }], 36 | selectIds: [{ required: true, message: '请输入Api简介', trigger: 'change' }], 37 | }; 38 | -------------------------------------------------------------------------------- /src/views/superAdmin/user/user.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /src/views/test/index.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 3 | darkMode: false, // or 'media' or 'class' 4 | theme: { 5 | extend: { 6 | colors: {}, 7 | }, 8 | }, 9 | variants: { 10 | extend: {}, 11 | }, 12 | plugins: [], 13 | }; 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "sourceMap": true, 10 | "resolveJsonModule": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "lib": ["ESNext", "DOM"], 14 | "skipLibCheck": true, 15 | "baseUrl": ".", 16 | "paths": { 17 | "@/*": ["./src/*"] 18 | } 19 | }, 20 | "include": [ 21 | "src/**/*.ts", 22 | "src/**/*.d.ts", 23 | "src/**/*.tsx", 24 | "src/**/*.vue", 25 | "types/**/*.d.ts" 26 | ], 27 | "references": [{ "path": "./tsconfig.node.json" }] 28 | } 29 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "ESNext", 5 | "moduleResolution": "Node", 6 | "allowSyntheticDefaultImports": true 7 | }, 8 | "include": ["vite.config.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /types/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | interface ImportMetaEnv { 4 | /** 网站标题 */ 5 | readonly VITE_APP_TITLE: string; 6 | /** 网站部署的目录 */ 7 | readonly VITE_BASE_URL: string; 8 | /** API 接口路径 */ 9 | readonly VITE_BASE_API: string; 10 | /** socket 请求路径前缀 */ 11 | readonly VITE_BASE_SOCKET_PATH: string; 12 | /** socket 命名空间 */ 13 | readonly VITE_BASE_SOCKET_NSP: string; 14 | /** mock API 路径 */ 15 | readonly VITE_MOCK_API: string; 16 | // 更多环境变量... 17 | } 18 | 19 | interface ImportMeta { 20 | readonly env: ImportMetaEnv; 21 | } 22 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | 3 | declare global { 4 | /** 5 | * 现在声明进入全局命名空间的类型,或者增加全局命名空间中的现有声明。 6 | */ 7 | 8 | declare type Nullable = T | null; 9 | declare type NonNullable = T extends null | undefined ? never : T; 10 | declare type Recordable = Record; 11 | declare type Key = string | number; 12 | } 13 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | declare interface Fn { 2 | (...arg: T[]): R; 3 | } 4 | 5 | declare interface PromiseFn { 6 | (...arg: T[]): Promise; 7 | } 8 | 9 | declare type RefType = T | null; 10 | 11 | declare type LabelValueOptions = { 12 | label: string; 13 | value: any; 14 | [key: string]: string | number | boolean; 15 | }[]; 16 | 17 | declare type EmitType = (event: string, ...args: any[]) => void; 18 | 19 | declare type TargetContext = '_self' | '_blank'; 20 | 21 | declare interface ComponentElRef { 22 | $el: T; 23 | } 24 | 25 | declare type ComponentRef = ComponentElRef | null; 26 | 27 | declare type ElRef = Nullable; 28 | 29 | // 分页请求参数 30 | declare interface IProgressReq { 31 | pageNo: number; 32 | pageSize: number; 33 | } 34 | // 分页结果返回 35 | declare interface IProgressResp { 36 | total: number; 37 | pageNo: number; 38 | pageSize: number; 39 | list: T[]; 40 | } 41 | declare interface BaseData { 42 | CreatedAt: string; 43 | UpdatedAt: string; 44 | DeletedAt: string; 45 | } 46 | // 选择类型 47 | declare interface SelectType { 48 | value: any; 49 | label: string; 50 | } 51 | // 请求返回 52 | declare interface RespType { 53 | code: number; 54 | message: string; 55 | result: T; 56 | returnData: string; 57 | success: boolean; 58 | timestamp: number; 59 | } 60 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfig, ConfigEnv, loadEnv } from 'vite'; 2 | import vue from '@vitejs/plugin-vue'; 3 | import { resolve } from 'path'; 4 | import { proxy } from './build/proxy'; 5 | import Components from 'unplugin-vue-components/vite'; 6 | import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers'; 7 | const root: string = process.cwd(); 8 | 9 | // https://vitejs.dev/config/ 10 | export default ({ command, mode }: ConfigEnv): UserConfig => { 11 | // 获取当前环境的配置 12 | const { 13 | VITE_PORT, 14 | VITE_PUBLIC_PATH, 15 | VITE_OUT_DIR, 16 | VITE_ASSET_DIR, 17 | VITE_BASEURL, 18 | }: any = loadEnv(mode, root); 19 | return { 20 | base: VITE_PUBLIC_PATH, 21 | plugins: [ 22 | vue(), 23 | Components({ 24 | resolvers: [ 25 | AntDesignVueResolver({ 26 | importStyle: false, // css in js 27 | }), 28 | ], 29 | }), 30 | ], 31 | // 解析相关 【别名】 32 | resolve: { 33 | alias: [ 34 | { 35 | find: '@', //字符串|正则 36 | replacement: resolve(__dirname, 'src'), 37 | }, 38 | ], 39 | }, 40 | // css相关 41 | css: { 42 | // 引入全局scss |less 指定传递给 CSS 预处理器的选项。 43 | preprocessorOptions: { 44 | scss: { 45 | additionalData: '@import "@/styles/mixin.scss";', 46 | }, 47 | less: { 48 | javascriptEnabled: true, 49 | }, 50 | }, 51 | }, 52 | // 服务相关 53 | server: { 54 | https: false, 55 | port: VITE_PORT, 56 | proxy: proxy(VITE_BASEURL), 57 | }, 58 | // .构建相关【打包相关】 59 | build: { 60 | outDir: VITE_OUT_DIR, 61 | assetsDir: VITE_ASSET_DIR, 62 | sourcemap: false, 63 | // 消除打包大小超过500kb警告 64 | chunkSizeWarningLimit: 4000, 65 | }, 66 | }; 67 | }; 68 | --------------------------------------------------------------------------------