├── .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 |
4 |
5 |
6 |
7 |
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 |
--------------------------------------------------------------------------------
/src/components/Layout/content/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
11 |
12 |
13 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/components/Layout/footer/index.vue:
--------------------------------------------------------------------------------
1 |
2 | Footer
5 |
6 |
7 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/components/Layout/header/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
11 |
12 |
13 |
19 |
24 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/breadcrumb.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ route.meta.title }}
14 |
15 |
16 |
17 |
18 |
19 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/sysTools/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/sysTools/utils/fullScreem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/sysTools/utils/refresh.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
39 |
40 |
66 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/tabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
21 |
22 | {{ tab.meta.title }}
23 |
24 |
25 |
26 | 关闭当前
27 | 关闭左侧
28 | 关闭右侧
29 | 关闭其他
30 | 关闭所有
31 |
32 |
33 |
34 |
35 | {{ tab.meta.title }}
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
202 |
203 |
204 |
--------------------------------------------------------------------------------
/src/components/Layout/header/modules/userTools.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
{{ userStore.loginResp.userInfo.nickName }}
7 |
8 |
9 |
10 |
11 |
12 | {{ item.authorityName }}
13 |
14 | 个人信息
15 | 登出
16 |
17 |
18 |
19 |
20 |
21 |
22 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/src/components/Layout/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/components/Layout/sysMenu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
38 |
39 |
42 |
--------------------------------------------------------------------------------
/src/components/Layout/sysMenu/menuItem/menuItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ item.meta?.title }}
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ item.meta?.title }}
14 |
15 |
16 |
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 |
2 |
3 |
4 |
5 |
6 |
7 |
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 |
2 |
3 |
4 |
5 |
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 |
2 |
3 |
4 |
5 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/sysDict/modules/dictRadio.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/components/sysDict/modules/dictSelect.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/components/sysDict/modules/dictText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ text }}
5 |
6 |
7 |
8 |
9 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/components/sysDict/sysDict.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 字典不存在
19 |
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 |
23 |
24 |
25 |
26 |
27 |
28 |
36 |
37 |
46 |
47 | select: {{ selectVal }}
48 | ========================================
49 |
50 |
51 | checkbox: {{ checkboxVal }}
52 | ========================================
53 |
54 |
55 |
56 | radio: {{ radioVal }}
57 | ========================================
58 |
59 |
60 |
61 |
62 |
63 | {{ text }}
64 | {{ option }}
65 |
66 |
67 |
68 |
69 |
77 |
78 |
79 |
80 | ```
--------------------------------------------------------------------------------
/src/components/sysRemoveBtn/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 删除
7 |
8 |
9 |
10 |
11 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/components/sysSearch/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | {{ isFolded ? '收起' : '展开' }}
26 |
27 |
搜索
28 |
重置
29 |
30 |
31 |
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 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ```
--------------------------------------------------------------------------------
/src/components/sysTable/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
106 |
116 |
--------------------------------------------------------------------------------
/src/components/sysTable/使用方式.md:
--------------------------------------------------------------------------------
1 | ## 使用方法 1 除插槽外 其他方法函数均可与 a-table用法一致 ,请求方法 数据处理均需要自己实现
2 | 插槽使用方式
3 | ```vue
4 |
5 |
6 |
7 |
8 | {{text, record, index, column}}
9 |
10 |
11 |
12 |
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 |
35 |
41 |
42 |
43 |
44 |
60 |
61 | ```
62 |
63 | ## 不使用分页 :pagination=false 即可
64 | ```js
65 |
66 |
67 | ```
--------------------------------------------------------------------------------
/src/components/sysUpload/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | {{ btnText }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
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 |
13 |
14 |
15 |
19 |
20 | ```
--------------------------------------------------------------------------------
/src/components/widget/Buoy.vue:
--------------------------------------------------------------------------------
1 |
2 |
26 |
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 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/error/500.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/error/reload.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/views/login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
Go-Zero-Admin
7 |
8 |
9 |
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 | 确定
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/views/superAdmin/api/api.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
10 |
13 |
20 |
21 |
添加
24 |
25 |
26 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
50 |
57 |
58 |
59 |
60 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/src/views/superAdmin/api/modules/addModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
!
5 |
新增API,需要在角色管理内配置权限才可使用
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
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 |
2 |
3 |
4 |
5 |
6 |
7 | 新增根角色
9 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 设置权限
31 |
32 |
33 |
34 |
35 | 添加子角色
37 |
38 |
39 | 复制
41 |
编辑
44 |
45 |
46 |
47 |
48 |
49 |
50 |
58 |
59 |
60 |
61 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/src/views/superAdmin/authority/modules/addModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
22 |
23 | {{ authorityName }}
24 |
25 |
26 |
27 |
28 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
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 |
2 |
9 |
10 |
95 |
--------------------------------------------------------------------------------
/src/views/superAdmin/authority/modules/editAuth/authList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
18 |
19 | {{ meta.title }}
20 | 首页
21 | 设置首页
24 |
25 |
26 |
27 |
28 |
128 |
--------------------------------------------------------------------------------
/src/views/superAdmin/authority/modules/editAuth/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/src/views/superAdmin/dictionary/dictionary.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
!
5 |
取字典且缓存方法已在前端utils/dictionary 已经封装完成 不必自己书写 使用方法查看文件内注释
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/views/superAdmin/dictionary/modules/leftList/addModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
2 |
6 |
7 |
14 |
{{ item.name }}
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/views/superAdmin/dictionary/modules/rightList/addModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
2 |
3 |
字典详情内容
4 |
5 |
6 | 新增字典项
8 |
9 |
10 |
19 |
20 |
21 |
22 |
23 |
24 |
30 |
31 |
32 |
33 |
34 |
42 |
43 |
44 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/src/views/superAdmin/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/views/superAdmin/menu/menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 新增根菜单
9 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | 添加子菜单
31 |
编辑
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/src/views/superAdmin/menu/modules/addMenuModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
!
6 |
新增菜单,需要在角色管理内配置权限才可使用
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
如果菜单包含子菜单,请创建router-view二级路由页面或者
27 |
点我设置
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | {{ icon.value }}
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
66 |
67 |
68 |
69 |
70 |
71 | 新增菜单参数
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | 删除
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | 新增可控按钮
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 | 删除
115 |
116 |
117 |
118 |
119 |
120 |
121 |
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 |
2 |
3 |
4 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
40 |
41 | {{ authorityName }}
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
55 |
56 |
57 |
58 |
59 |
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 |
2 |
3 |
新增用户
6 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {selectChange(val,record,extra)}"
26 | :fieldNames="{
27 | label: 'authorityName',
28 | value: 'authorityId',
29 | }"
30 | style="width: 100%"
31 | tree-checkable
32 | :allowClear="false"
33 | tree-default-expand-all
34 | :height="233"
35 | :tree-data="authorityListTree"
36 | :max-tag-count="1"
37 | tree-node-filter-prop="title"
38 | >
39 |
40 | {{ authorityName }}
41 |
42 |
43 |
44 |
45 |
46 | {enableUser(checked,record.ID)}"
48 | :checkedValue="1"
49 | :unCheckedValue="-1"
50 | v-model:checked="record.enable"
51 | />
52 |
53 |
54 |
55 |
56 |
编辑
59 |
重置密码
62 |
63 |
64 |
65 |
66 |
67 |
75 |
76 |
77 |
78 |
170 |
171 |
172 |
--------------------------------------------------------------------------------
/src/views/test/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
16 |
25 |
26 | select: {{ selectVal }}
27 | ========================================
28 |
29 |
30 | checkbox: {{ checkboxVal }}
31 | ========================================
32 |
33 |
34 |
35 | radio: {{ radioVal }}
36 | ========================================
37 |
38 |
39 |
40 |
41 |
42 | {{ text }}
43 | {{ option }}
44 |
45 |
46 | {{ data }}
47 |
48 |
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 |
--------------------------------------------------------------------------------