mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-08 23:42:27 +08:00
Compare commits
191 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a469f1aca | ||
|
|
3bd0d28e05 | ||
|
|
447bdcc355 | ||
|
|
8bf849b803 | ||
|
|
02930e5208 | ||
|
|
ffca3ed61f | ||
|
|
0f92c953cb | ||
|
|
00247ee7b9 | ||
|
|
449761796c | ||
|
|
c96789f1ff | ||
|
|
301ca1a0df | ||
|
|
c5c28e958d | ||
|
|
2f97cbee06 | ||
|
|
764cb71f39 | ||
|
|
6d0aa46f20 | ||
|
|
f68ec16563 | ||
|
|
79c3cb5d4d | ||
|
|
7eb081ae87 | ||
|
|
cc2a911f2a | ||
|
|
b88c047643 | ||
|
|
01f9ba1046 | ||
|
|
f729a5b8ba | ||
|
|
7a62de39c2 | ||
|
|
1ae5372396 | ||
|
|
0bbc29023b | ||
|
|
84f55a8116 | ||
|
|
19df8d5f09 | ||
|
|
489f791caa | ||
|
|
2f72f34bbe | ||
|
|
3d4d554733 | ||
|
|
38fe255306 | ||
|
|
58e99c183b | ||
|
|
ee8aed2f62 | ||
|
|
260397f3ae | ||
|
|
fa983a9b64 | ||
|
|
4c49e28596 | ||
|
|
9a5fe5249c | ||
|
|
02235bf6fc | ||
|
|
0656035857 | ||
|
|
81e8222461 | ||
|
|
09411a927e | ||
|
|
347cd91735 | ||
|
|
9b2effbc55 | ||
|
|
d72f42dcd8 | ||
|
|
165c358ef5 | ||
|
|
fc181ce543 | ||
|
|
f04b9ba7f9 | ||
|
|
ee0e507e47 | ||
|
|
9d18715e90 | ||
|
|
74f0e4764e | ||
|
|
4cbdd9bc66 | ||
|
|
e03b1bdfca | ||
|
|
b6a350e1be | ||
|
|
503437a433 | ||
|
|
d4518849bf | ||
|
|
39178761b1 | ||
|
|
934de6b1e6 | ||
|
|
58d13a242d | ||
|
|
5d891c1f44 | ||
|
|
e1528823f7 | ||
|
|
1fa35db71b | ||
|
|
80729d925e | ||
|
|
e228184b72 | ||
|
|
2f541106f2 | ||
|
|
5f81f4cc77 | ||
|
|
b4f32f50c5 | ||
|
|
5d180b6fd5 | ||
|
|
3d6465346c | ||
|
|
dc98b96ce2 | ||
|
|
3f84af912f | ||
|
|
8dcd66b654 | ||
|
|
4bdc54a77f | ||
|
|
0ceb7437a4 | ||
|
|
b984828f33 | ||
|
|
1d10826760 | ||
|
|
74334de7e0 | ||
|
|
e153837497 | ||
|
|
d0ec44b48c | ||
|
|
e9efbb5e67 | ||
|
|
aa90e0a468 | ||
|
|
9049f743a8 | ||
|
|
1f858929d6 | ||
|
|
5a50bc0dc4 | ||
|
|
a35318f808 | ||
|
|
7a18f58f4a | ||
|
|
3fb8c85112 | ||
|
|
921dd91eda | ||
|
|
bcd2480f69 | ||
|
|
590d1615cb | ||
|
|
23dcfe000b | ||
|
|
47e47c8a59 | ||
|
|
719ad34a94 | ||
|
|
a0deba504a | ||
|
|
25d6134923 | ||
|
|
a646de72e2 | ||
|
|
e63963cf08 | ||
|
|
c43ec3ef1c | ||
|
|
4f0a7967f7 | ||
|
|
1c594b49c0 | ||
|
|
b68103455b | ||
|
|
724af94c08 | ||
|
|
af68d4bc67 | ||
|
|
2fb36695db | ||
|
|
32116be1f2 | ||
|
|
4a47fa2f7d | ||
|
|
89f68789ff | ||
|
|
4c0de9d5c2 | ||
|
|
1e72351dea | ||
|
|
da24659944 | ||
|
|
eb21a99df1 | ||
|
|
c598d3f7bc | ||
|
|
fa0163a385 | ||
|
|
57fef38bfe | ||
|
|
d62ca84328 | ||
|
|
c504e61d70 | ||
|
|
c0d3e11346 | ||
|
|
4c27623a0f | ||
|
|
0107c5f035 | ||
|
|
6134a4c125 | ||
|
|
00dd7409b1 | ||
|
|
a7ad4a3634 | ||
|
|
327f8de00b | ||
|
|
026e4c1acd | ||
|
|
9ba51e4a34 | ||
|
|
bc2d2b4e94 | ||
|
|
e2f90cbf88 | ||
|
|
2da9b8a465 | ||
|
|
f99ecea202 | ||
|
|
e3326a0f11 | ||
|
|
775f5b63af | ||
|
|
874fd3437d | ||
|
|
7644c59799 | ||
|
|
f8ed3d1607 | ||
|
|
0c8bb030de | ||
|
|
41a6f59617 | ||
|
|
1a7eaf7efb | ||
|
|
5714233d57 | ||
|
|
5e2452f2df | ||
|
|
e9d3e1affe | ||
|
|
f6d419b94f | ||
|
|
cc0d1824f2 | ||
|
|
299f8c89a5 | ||
|
|
9e40480b9b | ||
|
|
c20ee24cd8 | ||
|
|
eb27f19087 | ||
|
|
3cbb6061c5 | ||
|
|
e07e36ce5d | ||
|
|
0f8b0d3557 | ||
|
|
28d2696061 | ||
|
|
b2c22996ce | ||
|
|
f616852053 | ||
|
|
92316285bb | ||
|
|
1077a20cbd | ||
|
|
7b9b391adc | ||
|
|
88ada7ebee | ||
|
|
a7257568cf | ||
|
|
403a844668 | ||
|
|
197e63cb51 | ||
|
|
6c3273e214 | ||
|
|
71be58fc1f | ||
|
|
e3e14b8259 | ||
|
|
5f625aa305 | ||
|
|
8167d4165b | ||
|
|
873e0d3e43 | ||
|
|
f20f4209a3 | ||
|
|
363ed7ae9e | ||
|
|
a21cc8aec8 | ||
|
|
0f229aea13 | ||
|
|
89151afce3 | ||
|
|
ffac6c0d78 | ||
|
|
c257ca0948 | ||
|
|
9bcd6d9700 | ||
|
|
a66f56b9ce | ||
|
|
9e58578706 | ||
|
|
9e3ebd62b2 | ||
|
|
5b852506a6 | ||
|
|
f42857884f | ||
|
|
9ad5ba18a9 | ||
|
|
536e16f166 | ||
|
|
42f2256ea1 | ||
|
|
bf0d294322 | ||
|
|
51f5c64755 | ||
|
|
b49d9e8bd2 | ||
|
|
12e62d1179 | ||
|
|
6558e1597c | ||
|
|
7bf1e1265a | ||
|
|
1213de598e | ||
|
|
20c9dbbfe1 | ||
|
|
6dab2ab35b | ||
|
|
e6505c88b7 | ||
|
|
55ee389184 |
3
.env
3
.env
@@ -6,6 +6,3 @@ VITE_GLOB_APP_TITLE = AdminPro
|
|||||||
|
|
||||||
# spa shortname
|
# spa shortname
|
||||||
VITE_GLOB_APP_SHORT_NAME = AdminPro
|
VITE_GLOB_APP_SHORT_NAME = AdminPro
|
||||||
|
|
||||||
# 生产环境 开启mock
|
|
||||||
VITE_GLOB_PROD_MOCK = true
|
|
||||||
|
|||||||
@@ -4,27 +4,27 @@ VITE_PORT = 8001
|
|||||||
# 网站根目录
|
# 网站根目录
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
# 是否开启mock
|
# 是否开启 mock
|
||||||
VITE_USE_MOCK = true
|
VITE_USE_MOCK = true
|
||||||
|
|
||||||
# 网站前缀
|
# 是否开启控制台打印 mock 请求信息
|
||||||
VITE_BASE_URL = /
|
VITE_LOGGER_MOCK = true
|
||||||
|
|
||||||
# 是否删除console
|
# 是否删除console
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
# 跨域代理,可以配置多个,请注意不要换行
|
# 跨域代理,可以配置多个,请注意不要换行
|
||||||
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
|
#VITE_PROXY = [["/appApi","http://localhost:8001"],["/upload","http://localhost:8001/upload"]]
|
||||||
# VITE_PROXY=[["/api","https://naive-ui-admin"]]
|
#VITE_PROXY=[["/api","https://naive-ui-admin"]]
|
||||||
|
|
||||||
# API 接口地址
|
# API 接口地址
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
# 图片上传地址
|
|
||||||
VITE_GLOB_UPLOAD_URL=
|
|
||||||
|
|
||||||
# 图片前缀地址
|
|
||||||
VITE_GLOB_IMG_URL=
|
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
|
# 文件上传地址
|
||||||
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
|
# 文件前缀地址
|
||||||
|
VITE_GLOB_FILE_URL=
|
||||||
|
|||||||
@@ -4,24 +4,21 @@ VITE_USE_MOCK = true
|
|||||||
# 网站根目录
|
# 网站根目录
|
||||||
VITE_PUBLIC_PATH = /
|
VITE_PUBLIC_PATH = /
|
||||||
|
|
||||||
# 网站前缀
|
|
||||||
VITE_BASE_URL = /
|
|
||||||
|
|
||||||
# 是否删除console
|
# 是否删除console
|
||||||
VITE_DROP_CONSOLE = true
|
VITE_DROP_CONSOLE = true
|
||||||
|
|
||||||
# API
|
# API
|
||||||
VITE_GLOB_API_URL =
|
VITE_GLOB_API_URL =
|
||||||
|
|
||||||
|
# 接口前缀
|
||||||
|
VITE_GLOB_API_URL_PREFIX = /api
|
||||||
|
|
||||||
# 图片上传地址
|
# 图片上传地址
|
||||||
VITE_GLOB_UPLOAD_URL=
|
VITE_GLOB_UPLOAD_URL=
|
||||||
|
|
||||||
# 图片前缀地址
|
# 图片前缀地址
|
||||||
VITE_GLOB_IMG_URL=
|
VITE_GLOB_IMG_URL=
|
||||||
|
|
||||||
# 接口前缀
|
|
||||||
VITE_GLOB_API_URL_PREFIX = /api
|
|
||||||
|
|
||||||
# 是否启用gzip压缩或brotli压缩
|
# 是否启用gzip压缩或brotli压缩
|
||||||
# 可选: gzip | brotli | none
|
# 可选: gzip | brotli | none
|
||||||
# 如果你需要多种形式,你可以用','来分隔
|
# 如果你需要多种形式,你可以用','来分隔
|
||||||
|
|||||||
16
.eslintrc.js
16
.eslintrc.js
@@ -25,6 +25,7 @@ module.exports = defineConfig({
|
|||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
'vue/script-setup-uses-vars': 'error',
|
'vue/script-setup-uses-vars': 'error',
|
||||||
|
'vue/multi-word-component-names': 'off',
|
||||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
@@ -37,19 +38,12 @@ module.exports = defineConfig({
|
|||||||
'@typescript-eslint/ban-types': 'off',
|
'@typescript-eslint/ban-types': 'off',
|
||||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
'@typescript-eslint/no-unused-vars': [
|
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }],
|
||||||
'error',
|
|
||||||
{
|
|
||||||
argsIgnorePattern: '^_',
|
|
||||||
varsIgnorePattern: '^_',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'no-unused-vars': [
|
'no-unused-vars': [
|
||||||
'error',
|
'error',
|
||||||
{
|
// we are only using this rule to check for unused arguments since TS
|
||||||
argsIgnorePattern: '^_',
|
// catches unused variables but not args.
|
||||||
varsIgnorePattern: '^_',
|
{ varsIgnorePattern: '.*', args: 'none' },
|
||||||
},
|
|
||||||
],
|
],
|
||||||
'space-before-function-paren': 'off',
|
'space-before-function-paren': 'off',
|
||||||
|
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ pnpm-debug.log*
|
|||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
!.vscode/extensions.json
|
||||||
*.suo
|
*.suo
|
||||||
*.ntvs*
|
*.ntvs*
|
||||||
*.njsproj
|
*.njsproj
|
||||||
|
|||||||
5
.vscode/extensions.json
vendored
Normal file
5
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"recommendations": [
|
||||||
|
"tu6ge.naive-ui-intelligence",
|
||||||
|
]
|
||||||
|
}
|
||||||
78
CHANGELOG.md
78
CHANGELOG.md
@@ -1,5 +1,83 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 2.1.0
|
||||||
|
|
||||||
|
- 优化 `登录页面` 排版
|
||||||
|
- 新增 `构建分包策略`
|
||||||
|
- 新增 `useLocalSetting` hook
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
|
||||||
|
- 新增 `alova` 请求库
|
||||||
|
- 新增 `@faker-js/faker` 可配合 `mock` 数据模拟
|
||||||
|
- 新增 `VITE_USE_MOCK` 环境变量-开启 `mock`
|
||||||
|
- 新增 `demo` 实例,新增/编辑角色
|
||||||
|
- 移除 `axios` 请求封装,使用 `alova` 代替
|
||||||
|
- 移除 `vite-plugin-mock` 使用 `@alova/mock` 代替
|
||||||
|
- 移除 `VITE_GLOB_PROD_MOCK` 环境变量
|
||||||
|
- 变更 `VITE_GLOB_IMG_URL` 环境变量变更成 `VITE_GLOB_FILE_URL`
|
||||||
|
- 优化 `BasicTable` 组件相关样式
|
||||||
|
- 优化 `TS` 类型定义
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.9.2
|
||||||
|
|
||||||
|
- 升级 `vite` 到 `5.x` 版本
|
||||||
|
- 优化 `BasicTable` 组件,编辑样式
|
||||||
|
- 新增 `BasicTable` 组件,支持 `striped` 入参
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.9.1
|
||||||
|
|
||||||
|
- 优化 `typeSctipt` 类型定义
|
||||||
|
- 优化 `setup` 语法
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.9.0
|
||||||
|
|
||||||
|
- 新增 `BasicForm` 组件,支持 `setLoading`, `setSchema` 方法
|
||||||
|
- 新增 `countField` 总数字段名配置
|
||||||
|
- 优化 `yarn` 切换至 `pnpm`
|
||||||
|
- 优化 `BasicForm` 组件,验证返回值
|
||||||
|
- 优化 `BasicTable` 组件
|
||||||
|
- 修复 `TableAction组件,左右间隔不生效` 关闭[253](https://github.com/jekip/naive-ui-admin/issues/253)
|
||||||
|
- 修复 `BasicTable组件没有数据会一直请求接口` 关闭[#251](https://github.com/jekip/naive-ui-admin/issues/251)
|
||||||
|
- 修复 `useModal+useForm组件的bug` 关闭[#250](https://github.com/jekip/naive-ui-admin/issues/250)
|
||||||
|
- 修复 `手机端侧边导航风格不一致bug` 关闭[#247](https://github.com/jekip/naive-ui-admin/issues/247
|
||||||
|
- 移除 `yarn.lock` 文件
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.8.2
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `directive` 示例
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复 `样式异常` 图片
|
||||||
|
|
||||||
|
## 1.8.1
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `clean:cache` 删除缓存命令
|
||||||
|
- 新增 `clean:lib` 删除 `node_modules` 命令
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复 `开发环境` 运行控制台错误提示
|
||||||
|
|
||||||
|
## 1.8.0 (2022-04-01)
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `多页签` 支持配置 `affix` 固定属性
|
||||||
|
- 新增 `usePage` Hooks
|
||||||
|
- 表格列支持 `draggable` 配置拖拽 合并 [#114](https://github.com/jekip/naive-ui-admin/pull/114)
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复 `多页签` 关闭全部缺陷
|
||||||
|
- 修复 `多页签` 跳转缺陷(记得清空多页签缓存)
|
||||||
## 1.7.0 (2022-02-14)
|
## 1.7.0 (2022-02-14)
|
||||||
|
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
|
|||||||
106
README.md
106
README.md
@@ -1,53 +1,63 @@
|
|||||||
## 留步
|
## 🚀 简介
|
||||||
少侠留步,早知如此绊人心,何如当初莫相识,右上角免费的 `Star` 点一点,帮我们突破一下 `2k`,谢谢!O(∩_∩)O
|
|
||||||
|
|
||||||
## 简介
|
`Naive Ui Admin` 是一款 完全免费 且可商用的中后台解决方案,基于 🌟 `Vue3.x` 🌟、🚀 `Vite` 🚀、✨ [Naive UI](https://www.naiveui.com/) ✨ 和 🎉 `TypeScript` 🎉。
|
||||||
|
它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目
|
||||||
|
|
||||||
[Naive Ui Admin](https://github.com/jekip/naive-ui-admin) 完全免费,且可商用,基于 [Vue3.0](https://github.com/vuejs/vue-next)、[Vite](https://github.com/vitejs/vite)、 [Naive UI](https://www.naiveui.com/)、[TypeScript](https://www.typescriptlang.org/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目, 相信不管是从新技术使用还是其他方面,都能帮助到你。
|
## 🌈 特性
|
||||||
|
📦 二次封装的实用高扩展性组件
|
||||||
## 特性
|
🎨 响应式、多主题、多配置,快速集成,开箱即用
|
||||||
- 二次封装实用高扩展性组件
|
🚀 强大的鉴权系统,支持 三种鉴权模式,满足多样业务需求
|
||||||
- 响应式、多主题,多配置,快速集成,开箱即用
|
🌐 持续更新的实用性页面模板和交互设计,简化页面构建
|
||||||
- 最新技术栈,使用 `Vue3`、`Typescript`、`Pinia`、`Vite` 等前端前沿技术
|
|
||||||
- 强大的鉴权系统,对路由、菜单、功能点等支持`三种鉴权模式`,满足不同的业务鉴权需求
|
|
||||||
- 持续更新,实用性页面模板功能和交互,随意搭配组合,让构建页面变得简单化
|
|
||||||
|
|
||||||
|
|
||||||
## 预览
|
## 🎥 预览
|
||||||
- [naive-ui-admin](https://naive-ui-admin.vercel.app)
|
- [naive-ui-admin](https://gratis.naiveadmin.com)
|
||||||
|
|
||||||
账号:admin,密码:123456(随意)
|
账号:admin,密码:123456(随意)
|
||||||
|
|
||||||
## 提示
|
|
||||||
|
|
||||||
如果这个版本的功能和组件,并不能满足您的需求,不妨看看,我们全新 `NaiveAdmin v2` 他或许能让您眼前一亮O(∩_∩)O哈哈~
|
## 🚀 Naive Admin - 开箱即用的企业级前后端框架 `商业版本`
|
||||||
|
|
||||||
[NaiveAdmin 官网](https://www.naiveadmin.com)
|
> **✨ 多版本选择 · 四年持续迭代**
|
||||||
|
> 配套前后端支持 Java/Php 语言,支持单体和微服务多租户架构
|
||||||
|
> [详情→官网](https://www.naiveadmin.com) | [更新日志](https://www.yuque.com/u5825/zaqu0e)
|
||||||
|
|
||||||
[NaiveAdmin v2 预览](https://pro.naiveadmin.com)
|
---
|
||||||
|
|
||||||
[NaiveAdmin v2 变更日志](https://www.naiveadmin.com/guide/changelog)
|
## 🔥 为什么选择 NaiveAdmin 商业版?
|
||||||
|
- **省时间**:内置丰富扩展组件与业务模板,不写一行样板代码即可开始业务开发
|
||||||
|
- **经实战**:已落地电网、跨境 ERP、SaaS 等 30+ 场景
|
||||||
|
- **可扩展**:插件式菜单 / 按钮 / 数据权限,新增业务模块「0 侵入」
|
||||||
|
|
||||||
## 新品
|
---
|
||||||
|
|
||||||
### Antd vue
|
## 🖥️ 纯前端版本
|
||||||
|
|
||||||
千呼万唤 `Naive Admin Antd` 也迎来了第一个版本,同时具备 `Naive Ui Admin` 优点,如果您选的技术栈是 `Antd` 的话,不妨看看。
|
| 版本 | 技术栈 | 配套后端 | 预览地址 |
|
||||||
|
|-----|-------|---------|-------------|
|
||||||
|
| **🆕 Naive UI Max** | Vu3 + Ts + NaiveUI | 否 | [https://max.naiveadmin.com](https://max.naiveadmin.com) |
|
||||||
|
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | 支持Java/PHP | [https://plus.naiveadmin.com](https://plus.naiveadmin.com) |
|
||||||
|
|
||||||
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
## 🔌 前后端版本
|
||||||
|
|
||||||
### Arco vue
|
| 版本 | 技术栈 | 预览地址 |
|
||||||
|
|------|------------------|--------------------------------------------------------------|
|
||||||
|
| **🆕Naive UI Max** | Vu3 + Ts + NaiveUI | [https://max-full.naiveadmin.com](https://max-full.naiveadmin.com) |
|
||||||
|
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | [https://plus-full.naiveadmin.com](https://plus-full.naiveadmin.com) |
|
||||||
|
|
||||||
新产品,新生态,智能设计体系,连接轻盈体验,一如既往、开箱即用,欢迎前往查看。
|
## 🏢 多租户版本
|
||||||
|
|
||||||
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
|
| 版本 | 技术栈 | 适用场景 | 预览地址 |
|
||||||
|
|-----------------------------|-----------------------------|----------------|-------------------------------------------|
|
||||||
|
| **Vue3** | Vu3 + Ts + NaiveUI + Java | 构建企业级 Saas 化系统 | [https://tenant.naiveadmin.com](https://tenant.naiveadmin.com) |
|
||||||
|
| **React** | React + Ts + Ant + Java | 构建企业级 Saas 化系统 | [https://compose.warden.vip](https://compose.warden.vip) |
|
||||||
|
|
||||||
|
|
||||||
## 文档
|
## 📚 文档
|
||||||
|
|
||||||
[v1文档地址](https://naive-ui-admin-docs.vercel.app)
|
[开源版本文档](https://docs.naiveadmin.com)
|
||||||
|
|
||||||
## 准备
|
## 🛠 准备
|
||||||
|
|
||||||
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
|
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
|
||||||
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
|
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
|
||||||
@@ -55,10 +65,11 @@
|
|||||||
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
|
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
|
||||||
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
|
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
|
||||||
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
|
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
|
||||||
- [Naive-ui-admin](https://www.naiveui.com/) - ui 基本使用
|
- [NaiveUi](https://www.naiveui.com/) - ui 基本使用
|
||||||
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
|
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
|
||||||
|
|
||||||
## 使用
|
|
||||||
|
## 🏗️ 使用
|
||||||
|
|
||||||
- 获取项目代码
|
- 获取项目代码
|
||||||
|
|
||||||
@@ -71,30 +82,30 @@ git clone https://github.com/jekip/naive-ui-admin.git
|
|||||||
```bash
|
```bash
|
||||||
cd naive-ui-admin
|
cd naive-ui-admin
|
||||||
|
|
||||||
yarn install
|
pnpm install
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
- 运行
|
- 运行
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn dev
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
- 打包
|
- 打包
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
## 更新日志
|
## 📜 更新日志
|
||||||
|
|
||||||
[CHANGELOG](./CHANGELOG.md)
|
[CHANGELOG](./CHANGELOG.md)
|
||||||
|
|
||||||
|
|
||||||
## 如何贡献
|
## 🤝 如何贡献
|
||||||
|
|
||||||
非常欢迎你的加入 或者提交一个 Pull Request。
|
非常欢迎你的加入 或者提交一个 `Pull Request`
|
||||||
|
|
||||||
**Pull Request:**
|
**Pull Request:**
|
||||||
|
|
||||||
@@ -104,7 +115,7 @@ yarn build
|
|||||||
4. 推送您的分支: `git push origin feat/xxxx`
|
4. 推送您的分支: `git push origin feat/xxxx`
|
||||||
5. 提交`pull request`
|
5. 提交`pull request`
|
||||||
|
|
||||||
## Git 贡献提交规范
|
## 📋 Git 贡献提交规范
|
||||||
|
|
||||||
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||||
|
|
||||||
@@ -122,7 +133,7 @@ yarn build
|
|||||||
- `types` 类型定义文件更改
|
- `types` 类型定义文件更改
|
||||||
- `wip` 开发中
|
- `wip` 开发中
|
||||||
|
|
||||||
## 浏览器支持
|
## 🌐 浏览器支持
|
||||||
|
|
||||||
本地开发推荐使用`Chrome 80+` 浏览器
|
本地开发推荐使用`Chrome 80+` 浏览器
|
||||||
|
|
||||||
@@ -132,21 +143,18 @@ yarn build
|
|||||||
| :-: | :-: | :-: | :-: | :-: |
|
| :-: | :-: | :-: | :-: | :-: |
|
||||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||||
|
|
||||||
## 维护者
|
## 👥 维护者
|
||||||
[@Ah jung](https://github.com/jekip)
|
[@Ah jung](https://github.com/jekip)
|
||||||
|
|
||||||
## 交流
|
## 💬 交流
|
||||||
|
|
||||||
`Naive Ui Admin` 使用或者其他问题,都可以在群内讨论或提问。
|
有关 `Naive Ui Admin` 的使用或其他问题,可以加入讨论群交流问题
|
||||||
|
|
||||||
- QQ 群 `328347666`
|
QQ1群:328347666 (已满)
|
||||||
|
QQ2群:741353560
|
||||||
|
|
||||||

|
## 💖 赞助
|
||||||
|
#### 如果项目有帮到你,不妨请作者喝一杯咖啡吧!
|
||||||
|
|
||||||
## 赞助
|
|
||||||
#### 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 🍹。
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|

|
||||||
[Paypal Me](https://www.paypal.com/paypalme/majunping)
|
[Paypal Me](https://www.paypal.com/paypalme/majunping)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
* Plugin to minimize and use ejs template syntax in index.html.
|
* Plugin to minimize and use ejs template syntax in index.html.
|
||||||
* https://github.com/anncwb/vite-plugin-html
|
* https://github.com/anncwb/vite-plugin-html
|
||||||
*/
|
*/
|
||||||
import type { Plugin } from 'vite';
|
import type { PluginOption } from 'vite';
|
||||||
|
|
||||||
import html from 'vite-plugin-html';
|
import { createHtmlPlugin } from 'vite-plugin-html';
|
||||||
|
|
||||||
import pkg from '../../../package.json';
|
import pkg from '../../../package.json';
|
||||||
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
|
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
|
||||||
@@ -18,11 +18,11 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
|
|||||||
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
|
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const htmlPlugin: Plugin[] = html({
|
const htmlPlugin: PluginOption[] = createHtmlPlugin({
|
||||||
minify: isBuild,
|
minify: isBuild,
|
||||||
inject: {
|
inject: {
|
||||||
// Inject data into ejs template
|
// Inject data into ejs template
|
||||||
injectData: {
|
data: {
|
||||||
title: VITE_GLOB_APP_TITLE,
|
title: VITE_GLOB_APP_TITLE,
|
||||||
},
|
},
|
||||||
// Embed the generated app.config.js file
|
// Embed the generated app.config.js file
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Plugin } from 'vite';
|
import type { Plugin, PluginOption } from 'vite';
|
||||||
import Components from 'unplugin-vue-components/vite';
|
import Components from 'unplugin-vue-components/vite';
|
||||||
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
|
||||||
@@ -6,13 +6,12 @@ import vue from '@vitejs/plugin-vue';
|
|||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
|
|
||||||
import { configHtmlPlugin } from './html';
|
import { configHtmlPlugin } from './html';
|
||||||
import { configMockPlugin } from './mock';
|
|
||||||
import { configCompressPlugin } from './compress';
|
import { configCompressPlugin } from './compress';
|
||||||
|
|
||||||
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
|
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
|
||||||
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
|
||||||
|
|
||||||
const vitePlugins: (Plugin | Plugin[])[] = [
|
const vitePlugins: (Plugin | Plugin[] | PluginOption[])[] = [
|
||||||
// have to
|
// have to
|
||||||
vue(),
|
vue(),
|
||||||
// have to
|
// have to
|
||||||
@@ -28,9 +27,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock)
|
|||||||
// vite-plugin-html
|
// vite-plugin-html
|
||||||
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
|
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
|
||||||
|
|
||||||
// vite-plugin-mock
|
|
||||||
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
|
|
||||||
|
|
||||||
if (isBuild) {
|
if (isBuild) {
|
||||||
// rollup-plugin-gzip
|
// rollup-plugin-gzip
|
||||||
vitePlugins.push(
|
vitePlugins.push(
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
/**
|
|
||||||
* Mock plugin for development and production.
|
|
||||||
* https://github.com/anncwb/vite-plugin-mock
|
|
||||||
*/
|
|
||||||
import { viteMockServe } from 'vite-plugin-mock';
|
|
||||||
|
|
||||||
export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
|
|
||||||
return viteMockServe({
|
|
||||||
ignore: /^\_/,
|
|
||||||
mockPath: 'mock',
|
|
||||||
localEnabled: !isBuild,
|
|
||||||
prodEnabled: isBuild && prodMock,
|
|
||||||
injectCode: `
|
|
||||||
import { setupProdMockServer } from '../mock/_createProductionServer';
|
|
||||||
|
|
||||||
setupProdMockServer();
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Introduces component library styles on demand.
|
|
||||||
* https://github.com/anncwb/vite-plugin-style-import
|
|
||||||
*/
|
|
||||||
|
|
||||||
import styleImport from 'vite-plugin-style-import';
|
|
||||||
|
|
||||||
export function configStyleImportPlugin(isBuild: boolean) {
|
|
||||||
if (!isBuild) return [];
|
|
||||||
const styleImportPlugin = styleImport({
|
|
||||||
libs: [
|
|
||||||
{
|
|
||||||
libraryName: 'ant-design-vue',
|
|
||||||
esModule: true,
|
|
||||||
resolveStyle: (name) => {
|
|
||||||
return `ant-design-vue/es/${name}/style/index`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
return styleImportPlugin;
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
ignores: [(commit) => commit.includes('init')],
|
|
||||||
extends: ['@commitlint/config-conventional'],
|
|
||||||
parserPreset: {
|
|
||||||
parserOpts: {
|
|
||||||
headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\(](.*)[\)\)])?[\:\:] (.*)/,
|
|
||||||
headerCorrespondence: ['type', 'scope', 'subject'],
|
|
||||||
referenceActions: [
|
|
||||||
'close',
|
|
||||||
'closes',
|
|
||||||
'closed',
|
|
||||||
'fix',
|
|
||||||
'fixes',
|
|
||||||
'fixed',
|
|
||||||
'resolve',
|
|
||||||
'resolves',
|
|
||||||
'resolved',
|
|
||||||
],
|
|
||||||
issuePrefixes: ['#'],
|
|
||||||
noteKeywords: ['BREAKING CHANGE'],
|
|
||||||
fieldPattern: /^-(.*?)-$/,
|
|
||||||
revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./,
|
|
||||||
revertCorrespondence: ['header', 'hash'],
|
|
||||||
warn() {},
|
|
||||||
mergePattern: null,
|
|
||||||
mergeCorrespondence: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'body-leading-blank': [2, 'always'],
|
|
||||||
'footer-leading-blank': [1, 'always'],
|
|
||||||
'header-max-length': [2, 'always', 108],
|
|
||||||
'subject-empty': [2, 'never'],
|
|
||||||
'type-empty': [2, 'never'],
|
|
||||||
'type-enum': [
|
|
||||||
2,
|
|
||||||
'always',
|
|
||||||
[
|
|
||||||
'feat',
|
|
||||||
'fix',
|
|
||||||
'perf',
|
|
||||||
'style',
|
|
||||||
'docs',
|
|
||||||
'test',
|
|
||||||
'refactor',
|
|
||||||
'build',
|
|
||||||
'ci',
|
|
||||||
'chore',
|
|
||||||
'revert',
|
|
||||||
'wip',
|
|
||||||
'workflow',
|
|
||||||
'types',
|
|
||||||
'release',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
|
|
||||||
|
|
||||||
const modules = import.meta.globEager('./**/*.ts');
|
|
||||||
|
|
||||||
const mockModules: any[] = [];
|
|
||||||
Object.keys(modules).forEach((key) => {
|
|
||||||
if (key.includes('/_')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mockModules.push(...modules[key].default);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used in a production environment. Need to manually import all modules
|
|
||||||
*/
|
|
||||||
export function setupProdMockServer() {
|
|
||||||
createProdMockServer(mockModules);
|
|
||||||
}
|
|
||||||
@@ -1,44 +1,44 @@
|
|||||||
import { Random } from 'mockjs';
|
import { defineMock } from '@alova/mock';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const consoleInfo = {
|
function getRandom(options) {
|
||||||
|
return Number(faker.commerce.price(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {
|
||||||
//访问量
|
//访问量
|
||||||
visits: {
|
visits: {
|
||||||
dayVisits: Random.float(10000, 99999, 2, 2),
|
dayVisits: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 3, 5),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
//销售额
|
//销售额
|
||||||
saleroom: {
|
saleroom: {
|
||||||
weekSaleroom: Random.float(10000, 99999, 2, 2),
|
weekSaleroom: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
degree: Random.float(10, 99),
|
degree: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
},
|
},
|
||||||
//订单量
|
//订单量
|
||||||
orderLarge: {
|
orderLarge: {
|
||||||
weekLarge: Random.float(10000, 99999, 2, 2),
|
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
//成交额度
|
//成交额度
|
||||||
volume: {
|
volume: {
|
||||||
weekLarge: Random.float(10000, 99999, 2, 2),
|
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
rise: Random.float(10, 99),
|
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
decline: Random.float(10, 99),
|
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
|
||||||
amount: Random.float(99999, 999999, 2, 2),
|
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
//主控台 卡片数据
|
// 主控台数据
|
||||||
{
|
'/api/dashboard/console': () => {
|
||||||
url: '/api/dashboard/console',
|
return resultSuccess(result);
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess(consoleInfo);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
import type { ListDate } from '@/api/system/menu';
|
||||||
|
|
||||||
const menuList = () => {
|
const menuList = () => {
|
||||||
const result: any[] = [
|
const result: ListDate[] = [
|
||||||
{
|
{
|
||||||
label: 'Dashboard',
|
label: 'Dashboard',
|
||||||
key: 'dashboard',
|
key: 'dashboard',
|
||||||
@@ -74,16 +76,11 @@ const menuList = () => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/menu/list': () => {
|
||||||
url: '/api/menu/list',
|
const list = menuList();
|
||||||
timeout: 1000,
|
return resultSuccess({
|
||||||
method: 'get',
|
list,
|
||||||
response: () => {
|
});
|
||||||
const list = menuList();
|
|
||||||
return resultSuccess({
|
|
||||||
list,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
import { resultSuccess, doCustomTimes } from '../_util';
|
import { resultSuccess, doCustomTimes } from '../_util';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
function getMenuKeys() {
|
function getMenuKeys() {
|
||||||
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
|
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
|
||||||
const newKeys = [];
|
const newKeys = [];
|
||||||
doCustomTimes(parseInt(Math.random() * 6), () => {
|
doCustomTimes(parseInt(Math.random() * 6), () => {
|
||||||
const key = keys[Math.floor(Math.random() * keys.length)];
|
const key = keys[Math.floor(Math.random() * keys.length)];
|
||||||
newKeys.push(key);
|
newKeys.push(key as never);
|
||||||
});
|
});
|
||||||
return Array.from(new Set(newKeys));
|
return Array.from(new Set(newKeys));
|
||||||
}
|
}
|
||||||
@@ -14,32 +16,30 @@ const roleList = (pageSize) => {
|
|||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,100)',
|
id: faker.string.numeric(4),
|
||||||
name: '@cname()',
|
name: faker.person.firstName(),
|
||||||
explain: '@cname()',
|
explain: faker.lorem.sentence({ min: 2, max: 4 }),
|
||||||
isDefault: '@boolean()',
|
isDefault: faker.helpers.arrayElement([true, false]),
|
||||||
menu_keys: getMenuKeys(),
|
menu_keys: getMenuKeys(),
|
||||||
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
|
create_date: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
|
||||||
'status|1': ['normal', 'enable', 'disable'],
|
status: faker.helpers.arrayElement(['normal', 'enable', 'disable']),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/role/list': ({ query }) => {
|
||||||
url: '/api/role/list',
|
const { page = 1, pageSize = 10, name } = query;
|
||||||
timeout: 1000,
|
const list = roleList(Number(pageSize));
|
||||||
method: 'get',
|
// 并非真实,只是为了模拟搜索结果
|
||||||
response: ({ query }) => {
|
const count = name ? 30 : 60;
|
||||||
const { page = 1, pageSize = 10 } = query;
|
return resultSuccess({
|
||||||
const list = roleList(Number(pageSize));
|
page: Number(page),
|
||||||
return resultSuccess({
|
pageSize: Number(pageSize),
|
||||||
page: Number(page),
|
pageCount: count,
|
||||||
pageSize: Number(pageSize),
|
itemCount: count * Number(pageSize),
|
||||||
pageCount: 60,
|
list,
|
||||||
list,
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,40 +1,39 @@
|
|||||||
import { Random } from 'mockjs';
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess, doCustomTimes } from '../_util';
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { doCustomTimes, resultSuccess } from '../_util';
|
||||||
const tableList = (pageSize) => {
|
import dayjs from 'dayjs';
|
||||||
|
function tableList(pageSize: number) {
|
||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,999999)',
|
id: faker.string.numeric(4),
|
||||||
beginTime: '@datetime',
|
name: faker.person.firstName(),
|
||||||
endTime: '@datetime',
|
sex: faker.person.sexType(),
|
||||||
address: '@city()',
|
avatar: `https://picsum.photos/200/200?v=${faker.string.numeric(4)}`,
|
||||||
name: '@cname()',
|
email: faker.internet.email({ firstName: 'admin' }),
|
||||||
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
|
city: faker.location.city(),
|
||||||
date: `@date('yyyy-MM-dd')`,
|
status: faker.helpers.arrayElement(['close', 'refuse', 'pass']),
|
||||||
time: `@time('HH:mm')`,
|
type: faker.helpers.arrayElement(['person', 'company']),
|
||||||
'no|100000-10000000': 100000,
|
// createDate: faker.helpers.arrayElement(dateStrs),
|
||||||
'status|1': [true, false],
|
createDate: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
//表格数据列表
|
// 表格数据列表
|
||||||
{
|
'/api/table/list': ({ query }) => {
|
||||||
url: '/api/table/list',
|
const { page = 1, pageSize = 10, name } = query;
|
||||||
timeout: 1000,
|
const list = tableList(Number(pageSize));
|
||||||
method: 'get',
|
// 并非真实,只是为了模拟搜索结果
|
||||||
response: ({ query }) => {
|
const count = name ? 30 : 60;
|
||||||
const { page = 1, pageSize = 10 } = query;
|
return resultSuccess({
|
||||||
const list = tableList(Number(pageSize));
|
page: Number(page),
|
||||||
return resultSuccess({
|
pageSize: Number(pageSize),
|
||||||
page: Number(page),
|
pageCount: count,
|
||||||
pageSize: Number(pageSize),
|
itemCount: count * Number(pageSize),
|
||||||
pageCount: 60,
|
list,
|
||||||
list,
|
});
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
});
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Mock from 'mockjs';
|
import Mock from 'mockjs';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
import { defineMock } from '@alova/mock';
|
||||||
|
|
||||||
const Random = Mock.Random;
|
const Random = Mock.Random;
|
||||||
|
|
||||||
@@ -37,23 +38,7 @@ const adminInfo = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'[POST]/api/login': () => resultSuccess({ token }),
|
||||||
url: '/api/login',
|
'/api/admin_info': () => resultSuccess(adminInfo),
|
||||||
timeout: 1000,
|
});
|
||||||
method: 'post',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess({ token });
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
url: '/api/admin_info',
|
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
// const token = getRequestToken(request);
|
|
||||||
// if (!token) return resultError('Invalid token');
|
|
||||||
return resultSuccess(adminInfo);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { defineMock } from '@alova/mock';
|
||||||
import { resultSuccess } from '../_util';
|
import { resultSuccess } from '../_util';
|
||||||
|
|
||||||
const menusList = [
|
const menusList = [
|
||||||
@@ -40,13 +41,6 @@ const menusList = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default [
|
export default defineMock({
|
||||||
{
|
'/api/menus': () => resultSuccess(menusList),
|
||||||
url: '/api/menus',
|
});
|
||||||
timeout: 1000,
|
|
||||||
method: 'get',
|
|
||||||
response: () => {
|
|
||||||
return resultSuccess(menusList);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
148
package.json
148
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "naive-ui-admin",
|
"name": "naive-ui-admin",
|
||||||
"version": "1.7.0",
|
"version": "2.1.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ahjung",
|
"name": "Ahjung",
|
||||||
"email": "735878602@qq.com",
|
"email": "735878602@qq.com",
|
||||||
@@ -8,91 +8,93 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"bootstrap": "yarn install",
|
"bootstrap": "pnpm install",
|
||||||
"serve": "npm run dev",
|
"serve": "pnpm run dev",
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vite build && esno ./build/script/postBuild.ts",
|
"build": "vite build && esno ./build/script/postBuild.ts",
|
||||||
"build:no-cache": "yarn clean:cache && npm run build",
|
"build:no-cache": "pnpm clean:cache && pnpm run build",
|
||||||
"report": "cross-env REPORT=true npm run build",
|
"report": "cross-env REPORT=true pnpm run build",
|
||||||
"preview": "vite preview",
|
"preview": "pnpm run build && vite preview",
|
||||||
"build typecheck": "vuedx-typecheck . && vite build",
|
"preview:dist": "vite preview",
|
||||||
|
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
|
||||||
|
"clean:lib": "rimraf node_modules",
|
||||||
"deploy": "gh-pages -d dist",
|
"deploy": "gh-pages -d dist",
|
||||||
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||||
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||||
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
"lint:pretty": "pretty-quick --staged"
|
||||||
"lint:pretty": "pretty-quick --staged",
|
|
||||||
"test prod gzip": "http-server dist --cors --gzip -c-1"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vicons/antd": "^0.10.0",
|
"@alova/mock": "^2.0.17",
|
||||||
"@vicons/ionicons5": "^0.10.0",
|
"@vicons/antd": "^0.12.0",
|
||||||
"@vueup/vue-quill": "^1.0.0-beta.7",
|
"@vicons/ionicons5": "^0.12.0",
|
||||||
"@vueuse/core": "^5.3.0",
|
"@vueup/vue-quill": "^1.2.0",
|
||||||
"axios": "^0.21.4",
|
"@vueuse/core": "^9.13.0",
|
||||||
"blueimp-md5": "^2.19.0",
|
"alova": "^3.3.4",
|
||||||
"date-fns": "^2.28.0",
|
"date-fns": "^2.30.0",
|
||||||
"echarts": "^5.3.0",
|
"dayjs": "^1.11.18",
|
||||||
|
"echarts": "^5.6.0",
|
||||||
"element-resize-detector": "^1.2.4",
|
"element-resize-detector": "^1.2.4",
|
||||||
"lodash": "^4.17.21",
|
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mitt": "^2.1.0",
|
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.25.2",
|
"naive-ui": "^2.43.1",
|
||||||
"pinia": "^2.0.11",
|
"pinia": "^2.3.1",
|
||||||
"qs": "^6.10.3",
|
"qs": "^6.14.0",
|
||||||
"vfonts": "^0.1.0",
|
"vue": "^3.5.21",
|
||||||
"vue": "^3.2.31",
|
"vue-router": "^4.5.1",
|
||||||
"vue-router": "^4.0.12",
|
"vue-types": "^4.2.1"
|
||||||
"vue-types": "^4.1.1",
|
|
||||||
"vuedraggable": "^4.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^12.1.4",
|
"@commitlint/cli": "^17.8.1",
|
||||||
"@commitlint/config-conventional": "^12.1.4",
|
"@commitlint/config-conventional": "^17.8.1",
|
||||||
"@types/lodash": "^4.14.178",
|
"@faker-js/faker": "^9.9.0",
|
||||||
"@types/node": "^15.14.9",
|
"@types/lodash": "^4.17.20",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@typescript-eslint/parser": "^4.33.0",
|
"@types/node": "^18.19.126",
|
||||||
"@vitejs/plugin-vue": "^1.10.2",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.3.5",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
"@vue/compiler-sfc": "^3.2.31",
|
"@vitejs/plugin-vue": "^3.2.0",
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||||
"autoprefixer": "^10.4.2",
|
"@vue/compiler-sfc": "^3.5.21",
|
||||||
"commitizen": "^4.2.4",
|
"@vue/eslint-config-typescript": "^11.0.3",
|
||||||
"core-js": "^3.21.0",
|
"autoprefixer": "^10.4.21",
|
||||||
"dotenv": "^10.0.0",
|
"commitizen": "^4.3.1",
|
||||||
"eslint": "^7.32.0",
|
"core-js": "^3.45.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"cross-env": "^7.0.3",
|
||||||
"eslint-define-config": "1.0.9",
|
"dotenv": "^16.6.1",
|
||||||
"eslint-plugin-jest": "^24.7.0",
|
"eslint": "^8.57.1",
|
||||||
"eslint-plugin-prettier": "^3.4.1",
|
"eslint-config-prettier": "^8.10.2",
|
||||||
"eslint-plugin-vue": "^7.20.0",
|
"eslint-define-config": "1.12.0",
|
||||||
"esno": "^0.7.3",
|
"eslint-plugin-jest": "^27.9.0",
|
||||||
"gh-pages": "^3.2.3",
|
"eslint-plugin-prettier": "^4.2.5",
|
||||||
"husky": "^6.0.0",
|
"eslint-plugin-vue": "^9.33.0",
|
||||||
"jest": "^27.5.1",
|
"esno": "^4.8.0",
|
||||||
"less": "^4.1.2",
|
"gh-pages": "^4.0.0",
|
||||||
"less-loader": "^9.1.0",
|
"husky": "^8.0.3",
|
||||||
"lint-staged": "^11.2.6",
|
"jest": "^29.7.0",
|
||||||
"postcss": "^8.4.6",
|
"less": "^4.4.1",
|
||||||
"prettier": "^2.5.1",
|
"less-loader": "^11.1.4",
|
||||||
"pretty-quick": "^3.1.3",
|
"lint-staged": "^13.3.0",
|
||||||
|
"postcss": "^8.5.6",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"pretty-quick": "^3.3.1",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^14.16.1",
|
||||||
"stylelint-config-prettier": "^8.0.2",
|
"stylelint-config-prettier": "^9.0.5",
|
||||||
"stylelint-config-standard": "^22.0.0",
|
"stylelint-config-standard": "^29.0.0",
|
||||||
"stylelint-order": "^4.1.0",
|
"stylelint-order": "^5.0.0",
|
||||||
"stylelint-scss": "^3.21.0",
|
"stylelint-scss": "^4.7.0",
|
||||||
"tailwindcss": "^2.2.19",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^4.5.5",
|
"typescript": "^4.9.5",
|
||||||
"unplugin-vue-components": "^0.17.18",
|
"unplugin-vue-components": "^0.22.12",
|
||||||
"vite": "^2.8.1",
|
"vite": "^5.4.20",
|
||||||
"vite-plugin-compression": "^0.3.6",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-html": "^2.1.2",
|
"vite-plugin-html": "^3.2.2",
|
||||||
"vite-plugin-mock": "^2.9.6",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"vite-plugin-style-import": "^1.4.1",
|
"vue-demi": "^0.13.11",
|
||||||
"vue-eslint-parser": "^7.11.0"
|
"vue-draggable-next": "^2.3.0",
|
||||||
|
"vue-eslint-parser": "^9.4.3",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{vue,js,ts,tsx}": "eslint --fix"
|
"*.{vue,js,ts,tsx}": "eslint --fix"
|
||||||
@@ -122,6 +124,6 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/jekip/naive-ui-admin",
|
"homepage": "https://github.com/jekip/naive-ui-admin",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12 || >=14"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13281
pnpm-lock.yaml
generated
13281
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,37 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="renderer" content="webkit"/>
|
|
||||||
<meta name="force-rendering" content="webkit"/>
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
|
||||||
<meta name="referrer" content="no-referrer" />
|
|
||||||
<link rel="icon" href="/favicon.ico">
|
|
||||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
|
||||||
<script src="<%= BASE_URL %>iconfont.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>
|
|
||||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
|
||||||
Please enable it to continue.</strong>
|
|
||||||
</noscript>
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- built files will be auto injected -->
|
|
||||||
<!-- IE 浏览器跳转提示升级页面 start -->
|
|
||||||
<script>
|
|
||||||
if (/*@cc_on!@*/false || (!!window.MSInputMethodContext && !!document.documentMode)) {
|
|
||||||
window.open("https://support.dmeng.net/upgrade-your-browser.html?referrer="+encodeURIComponent(window.location.href));
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
<!-- IE 浏览器跳转提示升级页面 end -->
|
|
||||||
<script>
|
|
||||||
function isIE() {
|
|
||||||
if (!!window.ActiveXObject || "ActiveXObject" in window){
|
|
||||||
return true;
|
|
||||||
}else{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
25
src/App.vue
25
src/App.vue
@@ -18,19 +18,19 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, onUnmounted } from 'vue';
|
import { computed, onMounted, onUnmounted } from 'vue';
|
||||||
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui';
|
import { zhCN, dateZhCN, darkTheme } from 'naive-ui';
|
||||||
import { LockScreen } from '@/components/Lockscreen';
|
import { LockScreen } from '@/components/Lockscreen';
|
||||||
import { AppProvider } from '@/components/Application';
|
import { AppProvider } from '@/components/Application';
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
import { useScreenLockStore } from '@/store/modules/screenLock.js';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
import { lighten } from '@/utils/index';
|
import { lighten } from '@/utils/index';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const useLockscreen = useLockscreenStore();
|
const useScreenLock = useScreenLockStore();
|
||||||
const designStore = useDesignSettingStore();
|
const designStore = useDesignSettingStore();
|
||||||
const isLock = computed(() => useLockscreen.isLock);
|
const isLock = computed(() => useScreenLock.isLocked);
|
||||||
const lockTime = computed(() => useLockscreen.lockTime);
|
const lockTime = computed(() => useScreenLock.lockTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type import('naive-ui').GlobalThemeOverrides
|
* @type import('naive-ui').GlobalThemeOverrides
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
primaryColor: appTheme,
|
primaryColor: appTheme,
|
||||||
primaryColorHover: lightenStr,
|
primaryColorHover: lightenStr,
|
||||||
primaryColorPressed: lightenStr,
|
primaryColorPressed: lightenStr,
|
||||||
|
primaryColorSuppl: appTheme,
|
||||||
},
|
},
|
||||||
LoadingBar: {
|
LoadingBar: {
|
||||||
colorLoading: appTheme,
|
colorLoading: appTheme,
|
||||||
@@ -52,21 +53,21 @@
|
|||||||
|
|
||||||
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
||||||
|
|
||||||
let timer;
|
let timer: NodeJS.Timer;
|
||||||
|
|
||||||
const timekeeping = () => {
|
const timekeeping = () => {
|
||||||
clearInterval(timer);
|
clearInterval(timer);
|
||||||
if (route.name == 'login' || isLock.value) return;
|
if (route.name == 'login' || isLock.value) return;
|
||||||
// 设置不锁屏
|
// 设置不锁屏
|
||||||
useLockscreen.setLock(false);
|
useScreenLock.setLock(false);
|
||||||
// 重置锁屏时间
|
// 重置锁屏时间
|
||||||
useLockscreen.setLockTime();
|
useScreenLock.setLockTime();
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
// 锁屏倒计时递减
|
// 锁屏倒计时递减
|
||||||
useLockscreen.setLockTime(lockTime.value - 1);
|
useScreenLock.setLockTime(lockTime.value - 1);
|
||||||
if (lockTime.value <= 0) {
|
if (lockTime.value <= 0) {
|
||||||
// 设置锁屏
|
// 设置锁屏
|
||||||
useLockscreen.setLock(true);
|
useScreenLock.setLock(true);
|
||||||
return clearInterval(timer);
|
return clearInterval(timer);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
@@ -80,7 +81,3 @@
|
|||||||
document.removeEventListener('mousedown', timekeeping);
|
document.removeEventListener('mousedown', timekeeping);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
|
||||||
@import 'styles/index.less';
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,9 +1,35 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
|
export interface TypeVisits {
|
||||||
|
dayVisits: number;
|
||||||
|
rise: number;
|
||||||
|
decline: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
export interface TypeSaleroom {
|
||||||
|
weekSaleroom: number;
|
||||||
|
amount: number;
|
||||||
|
degree: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypeOrderLarge {
|
||||||
|
weekLarge: number;
|
||||||
|
rise: number;
|
||||||
|
decline: number;
|
||||||
|
amount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypeConsole {
|
||||||
|
visits: TypeVisits;
|
||||||
|
//销售额
|
||||||
|
saleroom: TypeSaleroom;
|
||||||
|
//订单量
|
||||||
|
orderLarge: TypeOrderLarge;
|
||||||
|
//成交额度
|
||||||
|
volume: TypeOrderLarge;
|
||||||
|
}
|
||||||
|
|
||||||
//获取主控台信息
|
//获取主控台信息
|
||||||
export function getConsoleInfo() {
|
export function getConsoleInfo() {
|
||||||
return http.request({
|
return Alova.Get<TypeConsole>('/dashboard/console');
|
||||||
url: '/dashboard/console',
|
|
||||||
method: 'get',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
export interface ListDate {
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
type: number;
|
||||||
|
subtitle: string;
|
||||||
|
openType: number;
|
||||||
|
auth: string;
|
||||||
|
path: string;
|
||||||
|
children?: ListDate[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 根据用户id获取用户菜单
|
* @description: 根据用户id获取用户菜单
|
||||||
*/
|
*/
|
||||||
export function adminMenus() {
|
export function adminMenus() {
|
||||||
return http.request({
|
return Alova.Get('/menus');
|
||||||
url: '/menus',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,9 +22,7 @@ export function adminMenus() {
|
|||||||
* @param params
|
* @param params
|
||||||
*/
|
*/
|
||||||
export function getMenuList(params?) {
|
export function getMenuList(params?) {
|
||||||
return http.request({
|
return Alova.Get<{ list: ListDate[] }>('/menu/list', {
|
||||||
url: '/menu/list',
|
|
||||||
method: 'GET',
|
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 角色列表
|
* @description: 角色列表
|
||||||
*/
|
*/
|
||||||
export function getRoleList() {
|
export function getRoleList(params) {
|
||||||
return http.request({
|
return Alova.Get('/role/list', { params });
|
||||||
url: '/role/list',
|
|
||||||
method: 'GET',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,13 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
export interface BasicResponseModel<T = any> {
|
|
||||||
code: number;
|
|
||||||
message: string;
|
|
||||||
result: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BasicPageParams {
|
|
||||||
pageNumber: number;
|
|
||||||
pageSize: number;
|
|
||||||
total: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 获取用户信息
|
* @description: 获取用户信息
|
||||||
*/
|
*/
|
||||||
export function getUserInfo() {
|
export function getUserInfo() {
|
||||||
return http.request({
|
return Alova.Get<InResult>('/admin_info', {
|
||||||
url: '/admin_info',
|
meta: {
|
||||||
method: 'get',
|
isReturnNativeResponse: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,14 +15,15 @@ export function getUserInfo() {
|
|||||||
* @description: 用户登录
|
* @description: 用户登录
|
||||||
*/
|
*/
|
||||||
export function login(params) {
|
export function login(params) {
|
||||||
return http.request<BasicResponseModel>(
|
return Alova.Post<InResult>(
|
||||||
|
'/login',
|
||||||
{
|
{
|
||||||
url: '/login',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
isTransformResponse: false,
|
meta: {
|
||||||
|
isReturnNativeResponse: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -42,25 +32,14 @@ export function login(params) {
|
|||||||
* @description: 用户修改密码
|
* @description: 用户修改密码
|
||||||
*/
|
*/
|
||||||
export function changePassword(params, uid) {
|
export function changePassword(params, uid) {
|
||||||
return http.request(
|
return Alova.Post(`/user/u${uid}/changepw`, { params });
|
||||||
{
|
|
||||||
url: `/user/u${uid}/changepw`,
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
isTransformResponse: false,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 用户登出
|
* @description: 用户登出
|
||||||
*/
|
*/
|
||||||
export function logout(params) {
|
export function logout(params) {
|
||||||
return http.request({
|
return Alova.Post('/login/logout', {
|
||||||
url: '/login/logout',
|
|
||||||
method: 'POST',
|
|
||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import { http } from '@/utils/http/axios';
|
import { Alova } from '@/utils/http/alova/index';
|
||||||
|
|
||||||
//获取table
|
//获取table
|
||||||
export function getTableList(params) {
|
export function getTableList(params) {
|
||||||
return http.request({
|
return Alova.Get('/table/list', { params });
|
||||||
url: '/table/list',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-loading-bar-provider>
|
<n-dialog-provider>
|
||||||
<n-dialog-provider>
|
<n-notification-provider>
|
||||||
<DialogContent />
|
<n-message-provider>
|
||||||
<n-notification-provider>
|
<slot name="default"></slot>
|
||||||
<n-message-provider>
|
</n-message-provider>
|
||||||
<MessageContent />
|
</n-notification-provider>
|
||||||
<slot slot="default"></slot>
|
</n-dialog-provider>
|
||||||
</n-message-provider>
|
|
||||||
</n-notification-provider>
|
|
||||||
</n-dialog-provider>
|
|
||||||
</n-loading-bar-provider>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import {
|
import { NDialogProvider, NNotificationProvider, NMessageProvider } from 'naive-ui';
|
||||||
NDialogProvider,
|
|
||||||
NNotificationProvider,
|
|
||||||
NMessageProvider,
|
|
||||||
NLoadingBarProvider,
|
|
||||||
} from 'naive-ui';
|
|
||||||
import { MessageContent } from '@/components/MessageContent';
|
|
||||||
import { DialogContent } from '@/components/DialogContent';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Application',
|
name: 'Application',
|
||||||
@@ -29,9 +18,6 @@
|
|||||||
NDialogProvider,
|
NDialogProvider,
|
||||||
NNotificationProvider,
|
NNotificationProvider,
|
||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
NLoadingBarProvider,
|
|
||||||
MessageContent,
|
|
||||||
DialogContent,
|
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
return {};
|
return {};
|
||||||
|
|||||||
@@ -3,12 +3,14 @@
|
|||||||
{{ value }}
|
{{ value }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
import { ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
|
||||||
import { useTransition, TransitionPresets } from '@vueuse/core';
|
import { useTransition, TransitionPresets } from '@vueuse/core';
|
||||||
import { isNumber } from '@/utils/is';
|
import { isNumber } from '@/utils/is';
|
||||||
|
|
||||||
const props = {
|
defineOptions({ name: 'CountTo' });
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
startVal: { type: Number, default: 0 },
|
startVal: { type: Number, default: 0 },
|
||||||
endVal: { type: Number, default: 2021 },
|
endVal: { type: Number, default: 2021 },
|
||||||
duration: { type: Number, default: 1500 },
|
duration: { type: Number, default: 1500 },
|
||||||
@@ -36,75 +38,72 @@
|
|||||||
* Digital animation
|
* Digital animation
|
||||||
*/
|
*/
|
||||||
transition: { type: String, default: 'linear' },
|
transition: { type: String, default: 'linear' },
|
||||||
};
|
});
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits(['onStarted', 'onFinished']);
|
||||||
name: 'CountTo',
|
|
||||||
props,
|
|
||||||
emits: ['onStarted', 'onFinished'],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const source = ref(props.startVal);
|
|
||||||
const disabled = ref(false);
|
|
||||||
let outputValue = useTransition(source);
|
|
||||||
|
|
||||||
const value = computed(() => formatNumber(unref(outputValue)));
|
const source = ref(props.startVal);
|
||||||
|
const disabled = ref(false);
|
||||||
|
let outputValue = useTransition(source);
|
||||||
|
|
||||||
watchEffect(() => {
|
const value = computed(() => formatNumber(unref(outputValue)));
|
||||||
source.value = props.startVal;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch([() => props.startVal, () => props.endVal], () => {
|
watchEffect(() => {
|
||||||
if (props.autoplay) {
|
source.value = props.startVal;
|
||||||
start();
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
watch([() => props.startVal, () => props.endVal], () => {
|
||||||
props.autoplay && start();
|
if (props.autoplay) {
|
||||||
});
|
start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function start() {
|
onMounted(() => {
|
||||||
run();
|
props.autoplay && start();
|
||||||
source.value = props.endVal;
|
});
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
run();
|
||||||
|
source.value = props.endVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
source.value = props.startVal;
|
||||||
|
run();
|
||||||
|
}
|
||||||
|
|
||||||
|
function run() {
|
||||||
|
outputValue = useTransition(source, {
|
||||||
|
disabled,
|
||||||
|
duration: props.duration,
|
||||||
|
onFinished: () => emit('onFinished'),
|
||||||
|
onStarted: () => emit('onStarted'),
|
||||||
|
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatNumber(num: number | string) {
|
||||||
|
if (!num && num !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
const { decimals, decimal, separator, suffix, prefix } = props;
|
||||||
|
num = Number(num).toFixed(decimals);
|
||||||
|
num += '';
|
||||||
|
|
||||||
|
const x = num.split('.');
|
||||||
|
let x1 = x[0];
|
||||||
|
const x2 = x.length > 1 ? decimal + x[1] : '';
|
||||||
|
|
||||||
|
const rgx = /(\d+)(\d{3})/;
|
||||||
|
if (separator && !isNumber(separator)) {
|
||||||
|
while (rgx.test(x1)) {
|
||||||
|
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return prefix + x1 + x2 + suffix;
|
||||||
|
}
|
||||||
|
|
||||||
function reset() {
|
defineExpose({
|
||||||
source.value = props.startVal;
|
reset,
|
||||||
run();
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
outputValue = useTransition(source, {
|
|
||||||
disabled,
|
|
||||||
duration: props.duration,
|
|
||||||
onFinished: () => emit('onFinished'),
|
|
||||||
onStarted: () => emit('onStarted'),
|
|
||||||
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatNumber(num: number | string) {
|
|
||||||
if (!num) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
const { decimals, decimal, separator, suffix, prefix } = props;
|
|
||||||
num = Number(num).toFixed(decimals);
|
|
||||||
num += '';
|
|
||||||
|
|
||||||
const x = num.split('.');
|
|
||||||
let x1 = x[0];
|
|
||||||
const x2 = x.length > 1 ? decimal + x[1] : '';
|
|
||||||
|
|
||||||
const rgx = /(\d+)(\d{3})/;
|
|
||||||
if (separator && !isNumber(separator)) {
|
|
||||||
while (rgx.test(x1)) {
|
|
||||||
x1 = x1.replace(rgx, '$1' + separator + '$2');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prefix + x1 + x2 + suffix;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { value, start, reset };
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import DialogContent from './index.vue';
|
|
||||||
|
|
||||||
export { DialogContent };
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template></template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { useDialog } from 'naive-ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'DialogContent',
|
|
||||||
setup() {
|
|
||||||
//挂载在 window 方便与在js中使用
|
|
||||||
window['$dialog'] = useDialog();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
{{ schema.label }}
|
{{ schema.label }}
|
||||||
<n-tooltip trigger="hover" :style="schema.labelMessageStyle">
|
<n-tooltip trigger="hover" :style="schema.labelMessageStyle">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" class="cursor-pointer text-gray-400">
|
<n-icon size="18" class="text-gray-400 cursor-pointer">
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -90,6 +90,7 @@
|
|||||||
v-bind="getSubmitBtnOptions"
|
v-bind="getSubmitBtnOptions"
|
||||||
@click="handleSubmit"
|
@click="handleSubmit"
|
||||||
:loading="loadingSub"
|
:loading="loadingSub"
|
||||||
|
attr-type="submit"
|
||||||
>{{ getProps.submitButtonText }}</n-button
|
>{{ getProps.submitButtonText }}</n-button
|
||||||
>
|
>
|
||||||
<n-button
|
<n-button
|
||||||
@@ -138,7 +139,7 @@
|
|||||||
import { deepMerge } from '@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'BasicUpload',
|
name: 'BasicForm',
|
||||||
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
|
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
|
||||||
props: {
|
props: {
|
||||||
...basicProps,
|
...basicProps,
|
||||||
@@ -230,7 +231,6 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { handleFormValues, initDefault } = useFormValues({
|
const { handleFormValues, initDefault } = useFormValues({
|
||||||
getProps,
|
|
||||||
defaultFormModel,
|
defaultFormModel,
|
||||||
getSchema,
|
getSchema,
|
||||||
formModel,
|
formModel,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ComponentType } from '/types/index';
|
import { ComponentType } from './types/index';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 生成placeholder
|
* @description: 生成placeholder
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ export function useForm(props?: Props): UseFormReturnType {
|
|||||||
const form = await getForm();
|
const form = await getForm();
|
||||||
return form.validate(nameList);
|
return form.validate(nameList);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setLoading: (value: boolean) => {
|
||||||
|
loadedRef.value = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
setSchema: async (values) => {
|
||||||
|
const form = await getForm();
|
||||||
|
form.setSchema(values);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return [register, methods];
|
return [register, methods];
|
||||||
|
|||||||
@@ -32,23 +32,27 @@ export function useFormEvents({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 提交
|
// 提交
|
||||||
async function handleSubmit(e?: Event): Promise<void> {
|
async function handleSubmit(e?: Event): Promise<object | boolean> {
|
||||||
e && e.preventDefault();
|
e && e.preventDefault();
|
||||||
loadingSub.value = true;
|
loadingSub.value = true;
|
||||||
const { submitFunc } = unref(getProps);
|
const { submitFunc } = unref(getProps);
|
||||||
if (submitFunc && isFunction(submitFunc)) {
|
if (submitFunc && isFunction(submitFunc)) {
|
||||||
await submitFunc();
|
await submitFunc();
|
||||||
return;
|
loadingSub.value = false;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
const formEl = unref(formElRef);
|
const formEl = unref(formElRef);
|
||||||
if (!formEl) return;
|
if (!formEl) return false;
|
||||||
try {
|
try {
|
||||||
await validate();
|
await validate();
|
||||||
|
const values = getFieldsValue();
|
||||||
loadingSub.value = false;
|
loadingSub.value = false;
|
||||||
emit('submit', formModel);
|
emit('submit', values);
|
||||||
return true;
|
return values;
|
||||||
} catch (error) {
|
} catch (error: any) {
|
||||||
|
emit('submit', false);
|
||||||
loadingSub.value = false;
|
loadingSub.value = false;
|
||||||
|
console.error(error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,6 +100,10 @@ export function useFormEvents({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLoading(value: boolean): void {
|
||||||
|
loadingSub.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
validate,
|
validate,
|
||||||
@@ -103,5 +111,6 @@ export function useFormEvents({
|
|||||||
getFieldsValue,
|
getFieldsValue,
|
||||||
clearValidate,
|
clearValidate,
|
||||||
setFieldsValue,
|
setFieldsValue,
|
||||||
|
setLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,16 +41,19 @@ export interface FormProps {
|
|||||||
submitFunc?: () => Promise<void>;
|
submitFunc?: () => Promise<void>;
|
||||||
submitOnReset?: boolean;
|
submitOnReset?: boolean;
|
||||||
baseGridStyle?: CSSProperties;
|
baseGridStyle?: CSSProperties;
|
||||||
|
collapsedRows?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FormActionType {
|
export interface FormActionType {
|
||||||
submit: () => Promise<any>;
|
submit: () => Promise<any>;
|
||||||
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||||
setFieldsValue: <T>(values: T) => Promise<void>;
|
setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
|
||||||
|
setFieldsValue: (values: Recordable) => void;
|
||||||
clearValidate: (name?: string | string[]) => Promise<void>;
|
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||||
getFieldsValue: () => Recordable;
|
getFieldsValue: () => Recordable;
|
||||||
resetFields: () => Promise<void>;
|
resetFields: () => Promise<void>;
|
||||||
validate: (nameList?: any[]) => Promise<any>;
|
validate: (nameList?: any[]) => Promise<any>;
|
||||||
|
setLoading: (status: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RegisterFn = (formInstance: FormActionType) => void;
|
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import LoadingContent from './index.vue';
|
|
||||||
|
|
||||||
export { LoadingContent };
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template></template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { useLoadingBar } from 'naive-ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'LoadingContent',
|
|
||||||
setup() {
|
|
||||||
//挂载在 window 方便与在js中使用
|
|
||||||
window['$loading'] = useLoadingBar();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-input>
|
</n-input>
|
||||||
|
|
||||||
<div class="w-full flex" v-if="isLoginError">
|
<div class="flex w-full" v-if="isLoginError">
|
||||||
<span class="text-red-500">{{ errorMsg }}</span>
|
<span class="text-red-500">{{ errorMsg }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-full mt-1 flex justify-around">
|
<div class="flex justify-around w-full mt-1">
|
||||||
<div><a @click="showLogin = false">返回</a></div>
|
<div><a @click="showLogin = false">返回</a></div>
|
||||||
<div><a @click="goLogin">重新登录</a></div>
|
<div><a @click="goLogin">重新登录</a></div>
|
||||||
<div><a @click="onLogin">进入系统</a></div>
|
<div><a @click="onLogin">进入系统</a></div>
|
||||||
@@ -91,11 +91,11 @@
|
|||||||
import { useOnline } from '@/hooks/useOnline';
|
import { useOnline } from '@/hooks/useOnline';
|
||||||
import { useTime } from '@/hooks/useTime';
|
import { useTime } from '@/hooks/useTime';
|
||||||
import { useBattery } from '@/hooks/useBattery';
|
import { useBattery } from '@/hooks/useBattery';
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
import { useScreenLockStore } from '@/store/modules/screenLock';
|
||||||
import { useUserStore } from '@/store/modules/user';
|
import { UserInfoType, useUserStore } from '@/store/modules/user';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Lockscreen',
|
name: 'ScreenLock',
|
||||||
components: {
|
components: {
|
||||||
LockOutlined,
|
LockOutlined,
|
||||||
LoadingOutlined,
|
LoadingOutlined,
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
recharge,
|
recharge,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const useLockscreen = useLockscreenStore();
|
const useScreenLock = useScreenLockStore();
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 获取时间
|
// 获取时间
|
||||||
@@ -117,7 +117,7 @@
|
|||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } = useBattery();
|
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } = useBattery();
|
||||||
const userInfo: object = userStore.getUserInfo || {};
|
const userInfo: UserInfoType = userStore.getUserInfo || {};
|
||||||
const username = userInfo['username'] || '';
|
const username = userInfo['username'] || '';
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
showLogin: false,
|
showLogin: false,
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
const { code, message } = await userStore.login(params);
|
const { code, message } = await userStore.login(params);
|
||||||
if (code === ResultEnum.SUCCESS) {
|
if (code === ResultEnum.SUCCESS) {
|
||||||
onLockLogin(false);
|
onLockLogin(false);
|
||||||
useLockscreen.setLock(false);
|
useScreenLock.setLock(false);
|
||||||
} else {
|
} else {
|
||||||
state.errorMsg = message;
|
state.errorMsg = message;
|
||||||
state.isLoginError = true;
|
state.isLoginError = true;
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
//重新登录
|
//重新登录
|
||||||
const goLogin = () => {
|
const goLogin = () => {
|
||||||
onLockLogin(false);
|
onLockLogin(false);
|
||||||
useLockscreen.setLock(false);
|
useScreenLock.setLock(false);
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
query: {
|
query: {
|
||||||
|
|||||||
@@ -135,22 +135,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@width: ~`Math.round(Math.random() * 100) ` px;
|
|
||||||
@left: calc(15px + `Math.round(Math.random(70)) `);
|
|
||||||
each(range(15), {
|
|
||||||
.xiaoma-@{value} {
|
|
||||||
height: (@value * 50px);
|
|
||||||
}
|
|
||||||
li:nth-child(@{index}) {
|
|
||||||
top: 50%;
|
|
||||||
left: @left;
|
|
||||||
width: @width;
|
|
||||||
height: @width;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
/*animation: moveToTop (Math.random(6) + 3s) ease-in-out -(Math.random(5000) / 1000s) infinite;*/
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
50% {
|
50% {
|
||||||
border-radius: 45% / 42% 38% 58% 49%;
|
border-radius: 45% / 42% 38% 58% 49%;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import MessageContent from './index.vue';
|
|
||||||
|
|
||||||
export { MessageContent };
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<template></template>
|
|
||||||
<script lang="ts">
|
|
||||||
import { useMessage } from 'naive-ui';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MessageContent',
|
|
||||||
setup() {
|
|
||||||
//挂载在 window 方便与在js中使用
|
|
||||||
window['$message'] = useMessage();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -21,16 +21,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {
|
import { getCurrentInstance, ref, nextTick, unref, computed, useAttrs } from 'vue';
|
||||||
getCurrentInstance,
|
|
||||||
ref,
|
|
||||||
nextTick,
|
|
||||||
unref,
|
|
||||||
computed,
|
|
||||||
useAttrs,
|
|
||||||
defineEmits,
|
|
||||||
defineProps,
|
|
||||||
} from 'vue';
|
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import startDrag from '@/utils/Drag';
|
import startDrag from '@/utils/Drag';
|
||||||
import { deepMerge } from '@/utils';
|
import { deepMerge } from '@/utils';
|
||||||
@@ -41,7 +32,7 @@
|
|||||||
const props = defineProps({ ...basicProps });
|
const props = defineProps({ ...basicProps });
|
||||||
const emit = defineEmits(['on-close', 'on-ok', 'register']);
|
const emit = defineEmits(['on-close', 'on-ok', 'register']);
|
||||||
|
|
||||||
const propsRef = ref(<Partial<ModalProps> | null>null);
|
const propsRef = ref<Partial<ModalProps> | null>(null);
|
||||||
|
|
||||||
const isModal = ref(false);
|
const isModal = ref(false);
|
||||||
const subLoading = ref(false);
|
const subLoading = ref(false);
|
||||||
@@ -93,7 +84,6 @@
|
|||||||
|
|
||||||
function handleSubmit() {
|
function handleSubmit() {
|
||||||
subLoading.value = true;
|
subLoading.value = true;
|
||||||
console.log(subLoading.value)
|
|
||||||
emit('on-ok');
|
emit('on-ok');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,6 +98,8 @@
|
|||||||
if (instance) {
|
if (instance) {
|
||||||
emit('register', modalMethods);
|
emit('register', modalMethods);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defineExpose(modalMethods);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ref, onUnmounted, unref, getCurrentInstance, watch, nextTick } from 'vue';
|
import { ref, unref, getCurrentInstance, watch } from 'vue';
|
||||||
import { isProdMode } from '@/utils/env';
|
import { isProdMode } from '@/utils/env';
|
||||||
import { ModalMethods, UseModalReturnType } from '../type';
|
import { ModalMethods, UseModalReturnType } from '../type';
|
||||||
import { getDynamicProps } from '@/utils';
|
import { getDynamicProps } from '@/utils';
|
||||||
import { tryOnUnmounted } from '@vueuse/core';
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
export function useModal(props): UseModalReturnType {
|
export function useModal(props): UseModalReturnType {
|
||||||
|
|
||||||
const modalRef = ref<Nullable<ModalMethods>>(null);
|
const modalRef = ref<Nullable<ModalMethods>>(null);
|
||||||
const currentInstance = getCurrentInstance();
|
const currentInstance = getCurrentInstance();
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export interface ModalMethods {
|
|||||||
/**
|
/**
|
||||||
* 支持修改,DialogOptions 參數
|
* 支持修改,DialogOptions 參數
|
||||||
*/
|
*/
|
||||||
export interface ModalProps extends DialogOptions { }
|
export type ModalProps = DialogOptions;
|
||||||
|
|
||||||
export type RegisterFn = (ModalInstance: ModalMethods) => void;
|
export type RegisterFn = (ModalInstance: ModalMethods) => void;
|
||||||
|
|
||||||
export type UseModalReturnType = [RegisterFn, ModalMethods];
|
export type UseModalReturnType = [RegisterFn, ModalMethods];
|
||||||
|
|||||||
@@ -2,26 +2,37 @@
|
|||||||
<div class="table-toolbar">
|
<div class="table-toolbar">
|
||||||
<!--顶部左侧区域-->
|
<!--顶部左侧区域-->
|
||||||
<div class="flex items-center table-toolbar-left">
|
<div class="flex items-center table-toolbar-left">
|
||||||
<template v-if="title">
|
<template v-if="props.title">
|
||||||
<div class="table-toolbar-left-title">
|
<div class="table-toolbar-left-title">
|
||||||
{{ title }}
|
{{ props.title }}
|
||||||
<n-tooltip trigger="hover" v-if="titleTooltip">
|
<n-tooltip trigger="hover" v-if="props.titleTooltip">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
|
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
{{ titleTooltip }}
|
{{ props.titleTooltip }}
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<slot name="tableTitle"></slot>
|
<slot name="tableTitle"></slot>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center table-toolbar-right">
|
<div class="flex items-center leading-none table-toolbar-right">
|
||||||
<!--顶部右侧区域-->
|
<!--顶部右侧区域-->
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
|
|
||||||
|
<!--斑马纹-->
|
||||||
|
<n-tooltip trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<div class="mr-2 table-toolbar-right-icon">
|
||||||
|
<n-switch v-model:value="isStriped" @update:value="setStriped" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<span>表格斑马纹</span>
|
||||||
|
</n-tooltip>
|
||||||
|
<n-divider vertical />
|
||||||
|
|
||||||
<!--刷新-->
|
<!--刷新-->
|
||||||
<n-tooltip trigger="hover">
|
<n-tooltip trigger="hover">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -61,6 +72,7 @@
|
|||||||
<n-data-table
|
<n-data-table
|
||||||
ref="tableElRef"
|
ref="tableElRef"
|
||||||
v-bind="getBindValues"
|
v-bind="getBindValues"
|
||||||
|
:striped="isStriped"
|
||||||
:pagination="pagination"
|
:pagination="pagination"
|
||||||
@update:page="updatePage"
|
@update:page="updatePage"
|
||||||
@update:page-size="updatePageSize"
|
@update:page-size="updatePageSize"
|
||||||
@@ -72,18 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import {
|
import { ref, unref, toRaw, computed, onMounted, nextTick } from 'vue';
|
||||||
ref,
|
|
||||||
defineComponent,
|
|
||||||
reactive,
|
|
||||||
unref,
|
|
||||||
toRaw,
|
|
||||||
computed,
|
|
||||||
toRefs,
|
|
||||||
onMounted,
|
|
||||||
nextTick,
|
|
||||||
} from 'vue';
|
|
||||||
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||||
import { createTableContext } from './hooks/useTableContext';
|
import { createTableContext } from './hooks/useTableContext';
|
||||||
|
|
||||||
@@ -120,177 +122,150 @@
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export default defineComponent({
|
const emit = defineEmits([
|
||||||
components: {
|
'fetch-success',
|
||||||
ReloadOutlined,
|
'fetch-error',
|
||||||
ColumnHeightOutlined,
|
'update:checked-row-keys',
|
||||||
ColumnSetting,
|
'edit-end',
|
||||||
QuestionCircleOutlined,
|
'edit-cancel',
|
||||||
},
|
'edit-row-end',
|
||||||
props: {
|
'edit-change',
|
||||||
...basicProps,
|
]);
|
||||||
},
|
|
||||||
emits: [
|
|
||||||
'fetch-success',
|
|
||||||
'fetch-error',
|
|
||||||
'update:checked-row-keys',
|
|
||||||
'edit-end',
|
|
||||||
'edit-cancel',
|
|
||||||
'edit-row-end',
|
|
||||||
'edit-change',
|
|
||||||
],
|
|
||||||
setup(props, { emit }) {
|
|
||||||
const deviceHeight = ref(150);
|
|
||||||
const tableElRef = ref<ComponentRef>(null);
|
|
||||||
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
|
||||||
let paginationEl: HTMLElement | null;
|
|
||||||
|
|
||||||
const tableData = ref<Recordable[]>([]);
|
const props = defineProps({ ...basicProps });
|
||||||
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
const deviceHeight = ref(150);
|
||||||
|
const tableElRef = ref<ComponentRef>(null);
|
||||||
|
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
|
||||||
|
let paginationEl: HTMLElement | null;
|
||||||
|
const isStriped = ref(props.striped || false);
|
||||||
|
const tableData = ref<Recordable[]>([]);
|
||||||
|
const innerPropsRef = ref<Partial<BasicTableProps>>();
|
||||||
|
|
||||||
const getProps = computed(() => {
|
const getProps = computed(() => {
|
||||||
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
|
||||||
});
|
|
||||||
|
|
||||||
const { getLoading, setLoading } = useLoading(getProps);
|
|
||||||
|
|
||||||
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
|
||||||
|
|
||||||
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
|
|
||||||
getProps,
|
|
||||||
{
|
|
||||||
getPaginationInfo,
|
|
||||||
setPagination,
|
|
||||||
tableData,
|
|
||||||
setLoading,
|
|
||||||
},
|
|
||||||
emit
|
|
||||||
);
|
|
||||||
|
|
||||||
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
|
||||||
useColumns(getProps);
|
|
||||||
|
|
||||||
const state = reactive({
|
|
||||||
tableSize: unref(getProps as any).size || 'medium',
|
|
||||||
isColumnSetting: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
//页码切换
|
|
||||||
function updatePage(page) {
|
|
||||||
setPagination({ page: page });
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
//分页数量切换
|
|
||||||
function updatePageSize(size) {
|
|
||||||
setPagination({ page: 1, pageSize: size });
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
//密度切换
|
|
||||||
function densitySelect(e) {
|
|
||||||
state.tableSize = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
//选中行
|
|
||||||
function updateCheckedRowKeys(rowKeys) {
|
|
||||||
emit('update:checked-row-keys', rowKeys);
|
|
||||||
}
|
|
||||||
|
|
||||||
//获取表格大小
|
|
||||||
const getTableSize = computed(() => state.tableSize);
|
|
||||||
|
|
||||||
//组装表格信息
|
|
||||||
const getBindValues = computed(() => {
|
|
||||||
const tableData = unref(getDataSourceRef);
|
|
||||||
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
|
||||||
return {
|
|
||||||
...unref(getProps),
|
|
||||||
loading: unref(getLoading),
|
|
||||||
columns: toRaw(unref(getPageColumns)),
|
|
||||||
rowKey: unref(getRowKey),
|
|
||||||
data: tableData,
|
|
||||||
size: unref(getTableSize),
|
|
||||||
remote: true,
|
|
||||||
'max-height': maxHeight,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
//获取分页信息
|
|
||||||
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
|
||||||
|
|
||||||
function setProps(props: Partial<BasicTableProps>) {
|
|
||||||
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableAction = {
|
|
||||||
reload,
|
|
||||||
setColumns,
|
|
||||||
setLoading,
|
|
||||||
setProps,
|
|
||||||
getColumns,
|
|
||||||
getPageColumns,
|
|
||||||
getCacheColumns,
|
|
||||||
setCacheColumnsField,
|
|
||||||
emit,
|
|
||||||
};
|
|
||||||
|
|
||||||
const getCanResize = computed(() => {
|
|
||||||
const { canResize } = unref(getProps);
|
|
||||||
return canResize;
|
|
||||||
});
|
|
||||||
|
|
||||||
async function computeTableHeight() {
|
|
||||||
const table = unref(tableElRef);
|
|
||||||
if (!table) return;
|
|
||||||
if (!unref(getCanResize)) return;
|
|
||||||
const tableEl: any = table?.$el;
|
|
||||||
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
|
||||||
const { bottomIncludeBody } = getViewportOffset(headEl);
|
|
||||||
const headerH = 64;
|
|
||||||
let paginationH = 2;
|
|
||||||
let marginH = 24;
|
|
||||||
if (!isBoolean(pagination)) {
|
|
||||||
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
|
||||||
if (paginationEl) {
|
|
||||||
const offsetHeight = paginationEl.offsetHeight;
|
|
||||||
paginationH += offsetHeight || 0;
|
|
||||||
} else {
|
|
||||||
paginationH += 28;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let height =
|
|
||||||
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
|
||||||
const maxHeight = props.maxHeight;
|
|
||||||
height = maxHeight && maxHeight < height ? maxHeight : height;
|
|
||||||
deviceHeight.value = height;
|
|
||||||
}
|
|
||||||
|
|
||||||
useWindowSizeFn(computeTableHeight, 280);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
nextTick(() => {
|
|
||||||
computeTableHeight();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
|
||||||
|
|
||||||
return {
|
|
||||||
...toRefs(state),
|
|
||||||
tableElRef,
|
|
||||||
getBindValues,
|
|
||||||
getDataSource,
|
|
||||||
densityOptions,
|
|
||||||
reload,
|
|
||||||
densitySelect,
|
|
||||||
updatePage,
|
|
||||||
updatePageSize,
|
|
||||||
pagination,
|
|
||||||
tableAction,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tableSize = ref(unref(getProps as any).size || 'medium');
|
||||||
|
|
||||||
|
const { getLoading, setLoading } = useLoading(getProps);
|
||||||
|
|
||||||
|
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
||||||
|
|
||||||
|
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
|
||||||
|
getProps,
|
||||||
|
{
|
||||||
|
getPaginationInfo,
|
||||||
|
setPagination,
|
||||||
|
tableData,
|
||||||
|
setLoading,
|
||||||
|
},
|
||||||
|
emit
|
||||||
|
);
|
||||||
|
|
||||||
|
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
|
||||||
|
useColumns(getProps);
|
||||||
|
|
||||||
|
//页码切换
|
||||||
|
function updatePage(page) {
|
||||||
|
setPagination({ page: page });
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
//分页数量切换
|
||||||
|
function updatePageSize(size) {
|
||||||
|
setPagination({ page: 1, pageSize: size });
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
//密度切换
|
||||||
|
function densitySelect(e) {
|
||||||
|
tableSize.value = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取表格大小
|
||||||
|
const getTableSize = computed(() => tableSize.value);
|
||||||
|
|
||||||
|
//组装表格信息
|
||||||
|
const getBindValues = computed(() => {
|
||||||
|
const tableData = unref(getDataSourceRef);
|
||||||
|
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
|
||||||
|
return {
|
||||||
|
...unref(getProps),
|
||||||
|
loading: unref(getLoading),
|
||||||
|
columns: toRaw(unref(getPageColumns)),
|
||||||
|
rowKey: unref(getRowKey),
|
||||||
|
data: tableData,
|
||||||
|
size: unref(getTableSize),
|
||||||
|
remote: true,
|
||||||
|
'max-height': maxHeight,
|
||||||
|
title: '', // 重置为空 避免绑定到 table 上面
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
//获取分页信息
|
||||||
|
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
|
||||||
|
|
||||||
|
function setProps(props: Partial<BasicTableProps>) {
|
||||||
|
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
|
||||||
|
}
|
||||||
|
|
||||||
|
const setStriped = (value: boolean) => (isStriped.value = value);
|
||||||
|
|
||||||
|
const tableAction = {
|
||||||
|
reload,
|
||||||
|
setColumns,
|
||||||
|
setLoading,
|
||||||
|
setProps,
|
||||||
|
getColumns,
|
||||||
|
getDataSource,
|
||||||
|
getPageColumns,
|
||||||
|
getCacheColumns,
|
||||||
|
setCacheColumnsField,
|
||||||
|
emit,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getCanResize = computed(() => {
|
||||||
|
const { canResize } = unref(getProps);
|
||||||
|
return canResize;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function computeTableHeight() {
|
||||||
|
const table = unref(tableElRef);
|
||||||
|
if (!table) return;
|
||||||
|
if (!unref(getCanResize)) return;
|
||||||
|
const tableEl: any = table?.$el;
|
||||||
|
const headEl = tableEl.querySelector('.n-data-table-thead ');
|
||||||
|
const { bottomIncludeBody } = getViewportOffset(headEl);
|
||||||
|
const headerH = 64;
|
||||||
|
let paginationH = 2;
|
||||||
|
let marginH = 24;
|
||||||
|
if (!isBoolean(unref(pagination))) {
|
||||||
|
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
|
||||||
|
if (paginationEl) {
|
||||||
|
const offsetHeight = paginationEl.offsetHeight;
|
||||||
|
paginationH += offsetHeight || 0;
|
||||||
|
} else {
|
||||||
|
paginationH += 28;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let height =
|
||||||
|
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
|
||||||
|
const maxHeight = props.maxHeight;
|
||||||
|
height = maxHeight && maxHeight < height ? maxHeight : height;
|
||||||
|
deviceHeight.value = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
useWindowSizeFn(computeTableHeight, 280);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
computeTableHeight();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
createTableContext({ ...tableAction, wrapRef, getBindValues });
|
||||||
|
|
||||||
|
defineExpose(tableAction);
|
||||||
</script>
|
</script>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.table-toolbar {
|
.table-toolbar {
|
||||||
|
|||||||
@@ -2,7 +2,12 @@
|
|||||||
<div class="tableAction">
|
<div class="tableAction">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
|
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
|
||||||
<n-button v-bind="action" class="mx-2">{{ action.label }}</n-button>
|
<n-button v-bind="action" class="mx-1">
|
||||||
|
{{ action.label }}
|
||||||
|
<template #icon v-if="action.hasOwnProperty('icon')">
|
||||||
|
<n-icon :component="action.icon" />
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
</template>
|
</template>
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
v-if="dropDownActions && getDropdownList.length"
|
v-if="dropDownActions && getDropdownList.length"
|
||||||
@@ -11,7 +16,7 @@
|
|||||||
@select="select"
|
@select="select"
|
||||||
>
|
>
|
||||||
<slot name="more"></slot>
|
<slot name="more"></slot>
|
||||||
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
|
<n-button v-bind="getMoreProps" class="mx-1" v-if="!$slots.more" icon-placement="right">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<span>更多</span>
|
<span>更多</span>
|
||||||
<n-icon size="14" class="ml-1">
|
<n-icon size="14" class="ml-1">
|
||||||
@@ -75,7 +80,7 @@
|
|||||||
const getDropdownList = computed(() => {
|
const getDropdownList = computed(() => {
|
||||||
return (toRaw(props.dropDownActions) || [])
|
return (toRaw(props.dropDownActions) || [])
|
||||||
.filter((action) => {
|
.filter((action) => {
|
||||||
return hasPermission(action.auth) && isIfShow(action);
|
return hasPermission(action.auth as string[]) && isIfShow(action);
|
||||||
})
|
})
|
||||||
.map((action) => {
|
.map((action) => {
|
||||||
const { popConfirm } = action;
|
const { popConfirm } = action;
|
||||||
@@ -108,7 +113,7 @@
|
|||||||
const getActions = computed(() => {
|
const getActions = computed(() => {
|
||||||
return (toRaw(props.actions) || [])
|
return (toRaw(props.actions) || [])
|
||||||
.filter((action) => {
|
.filter((action) => {
|
||||||
return hasPermission(action.auth) && isIfShow(action);
|
return hasPermission(action.auth as string[]) && isIfShow(action);
|
||||||
})
|
})
|
||||||
.map((action) => {
|
.map((action) => {
|
||||||
const { popConfirm } = action;
|
const { popConfirm } = action;
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editable-cell">
|
<div class="editable-cell">
|
||||||
<div v-show="!isEdit" class="editable-cell-content" @click="handleEdit">
|
<div class="flex editable-cell-content" v-if="isEdit" v-click-outside="onClickOutside">
|
||||||
{{ getValues }}
|
|
||||||
<n-icon class="edit-icon" v-if="!column.editRow">
|
|
||||||
<FormOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
|
||||||
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
|
||||||
<div class="editable-cell-content-comp">
|
<div class="editable-cell-content-comp">
|
||||||
<CellComponent
|
<CellComponent
|
||||||
v-bind="getComponentProps"
|
v-bind="getComponentProps"
|
||||||
@@ -17,18 +11,24 @@
|
|||||||
:class="getWrapperClass"
|
:class="getWrapperClass"
|
||||||
ref="elRef"
|
ref="elRef"
|
||||||
@options-change="handleOptionsChange"
|
@options-change="handleOptionsChange"
|
||||||
@pressEnter="handleEnter"
|
@press-enter="handleEnter"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||||
<n-icon class="mx-2 cursor-pointer">
|
<n-icon class="mx-2 cursor-pointer" title="保存">
|
||||||
<CheckOutlined @click="handleSubmit" />
|
<CheckOutlined @click="handleSubmit" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-icon class="mx-2 cursor-pointer">
|
<n-icon class="mx-2 cursor-pointer" title="取消">
|
||||||
<CloseOutlined @click="handleCancel" />
|
<CloseOutlined @click="handleCancel" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="flex items-center editable-cell-content" @click="handleEdit">
|
||||||
|
{{ getValues }}
|
||||||
|
<n-icon class="ml-1 edit-icon" v-if="!column.editRow">
|
||||||
|
<FormOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
@@ -51,7 +51,6 @@
|
|||||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||||
|
|
||||||
import { parseISO, format } from 'date-fns';
|
import { parseISO, format } from 'date-fns';
|
||||||
import { Fn, LabelValueOptions } from '/#/index';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'EditableCell',
|
name: 'EditableCell',
|
||||||
@@ -317,103 +316,103 @@
|
|||||||
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
function initCbs(cbs: 'submitCbs' | 'validCbs' | 'cancelCbs', handle: Fn) {
|
||||||
if (props.record) {
|
if (props.record) {
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
isArray(props.record[cbs])
|
isArray(props.record[cbs])
|
||||||
? props.record[cbs]?.push(handle)
|
? props.record[cbs]?.push(handle)
|
||||||
: (props.record[cbs] = [handle]);
|
: (props.record[cbs] = [handle]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (props.record) {
|
|
||||||
initCbs('submitCbs', handleSubmit);
|
|
||||||
initCbs('validCbs', handleSubmiRule);
|
|
||||||
initCbs('cancelCbs', handleCancel);
|
|
||||||
|
|
||||||
if (props.column.key) {
|
|
||||||
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
|
||||||
props.record.editValueRefs[props.column.key] = currentValueRef;
|
|
||||||
}
|
|
||||||
/* eslint-disable */
|
|
||||||
props.record.onCancelEdit = () => {
|
|
||||||
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
|
||||||
};
|
|
||||||
/* eslint-disable */
|
|
||||||
props.record.onSubmitEdit = async() => {
|
|
||||||
if (isArray(props.record?.submitCbs)) {
|
|
||||||
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
|
||||||
|
|
||||||
const res = await Promise.all(validFns);
|
|
||||||
|
|
||||||
const pass = res.every((item) => !!item);
|
|
||||||
|
|
||||||
if (!pass) return;
|
|
||||||
const submitFns = props.record?.submitCbs || [];
|
|
||||||
submitFns.forEach((fn) => fn(false, false));
|
|
||||||
table.emit?.('edit-row-end');
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
if (props.record) {
|
||||||
isEdit,
|
initCbs('submitCbs', handleSubmit);
|
||||||
handleEdit,
|
initCbs('validCbs', handleSubmiRule);
|
||||||
currentValueRef,
|
initCbs('cancelCbs', handleCancel);
|
||||||
handleSubmit,
|
|
||||||
handleChange,
|
if (props.column.key) {
|
||||||
handleCancel,
|
if (!props.record.editValueRefs) props.record.editValueRefs = {};
|
||||||
elRef,
|
props.record.editValueRefs[props.column.key] = currentValueRef;
|
||||||
getComponent,
|
}
|
||||||
getRule,
|
/* eslint-disable */
|
||||||
onClickOutside,
|
props.record.onCancelEdit = () => {
|
||||||
ruleMessage,
|
isArray(props.record?.cancelCbs) && props.record?.cancelCbs.forEach((fn) => fn());
|
||||||
getRuleVisible,
|
};
|
||||||
getComponentProps,
|
/* eslint-disable */
|
||||||
handleOptionsChange,
|
props.record.onSubmitEdit = async () => {
|
||||||
getWrapperClass,
|
if (isArray(props.record?.submitCbs)) {
|
||||||
getRowEditable,
|
const validFns = (props.record?.validCbs || []).map((fn) => fn());
|
||||||
getValues,
|
|
||||||
handleEnter,
|
const res = await Promise.all(validFns);
|
||||||
// getSize,
|
|
||||||
};
|
const pass = res.every((item) => !!item);
|
||||||
},
|
|
||||||
|
if (!pass) return;
|
||||||
|
const submitFns = props.record?.submitCbs || [];
|
||||||
|
submitFns.forEach((fn) => fn(false, false));
|
||||||
|
table.emit?.('edit-row-end');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isEdit,
|
||||||
|
handleEdit,
|
||||||
|
currentValueRef,
|
||||||
|
handleSubmit,
|
||||||
|
handleChange,
|
||||||
|
handleCancel,
|
||||||
|
elRef,
|
||||||
|
getComponent,
|
||||||
|
getRule,
|
||||||
|
onClickOutside,
|
||||||
|
ruleMessage,
|
||||||
|
getRuleVisible,
|
||||||
|
getComponentProps,
|
||||||
|
handleOptionsChange,
|
||||||
|
getWrapperClass,
|
||||||
|
getRowEditable,
|
||||||
|
getValues,
|
||||||
|
handleEnter,
|
||||||
|
// getSize,
|
||||||
|
};
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.editable-cell {
|
.editable-cell {
|
||||||
&-content {
|
&-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
&-comp{
|
&-comp {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
//position: absolute;
|
||||||
|
//top: 4px;
|
||||||
|
//right: 0;
|
||||||
|
display: none;
|
||||||
|
width: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.edit-icon {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
//position: absolute;
|
|
||||||
//top: 4px;
|
|
||||||
//right: 0;
|
|
||||||
display: none;
|
|
||||||
width: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.edit-icon {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-action {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -25,13 +25,26 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="table-toolbar-inner">
|
<div class="table-toolbar-inner">
|
||||||
<n-checkbox-group v-model:value="checkList" @update:value="onChange">
|
<n-checkbox-group v-model:value="checkList" @update:value="onChange">
|
||||||
<Draggable v-model="columnsList" animation="300" item-key="key" @end="draggableEnd">
|
<Draggable
|
||||||
|
v-model="columnsList"
|
||||||
|
animation="300"
|
||||||
|
item-key="key"
|
||||||
|
filter=".no-draggable"
|
||||||
|
:move="onMove"
|
||||||
|
@end="draggableEnd"
|
||||||
|
>
|
||||||
<template #item="{ element }">
|
<template #item="{ element }">
|
||||||
<div
|
<div
|
||||||
class="table-toolbar-inner-checkbox"
|
class="table-toolbar-inner-checkbox"
|
||||||
:class="{ 'table-toolbar-inner-checkbox-dark': getDarkTheme === true }"
|
:class="{
|
||||||
|
'table-toolbar-inner-checkbox-dark': getDarkTheme === true,
|
||||||
|
'no-draggable': element.draggable === false,
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
<span class="drag-icon">
|
<span
|
||||||
|
class="drag-icon"
|
||||||
|
:class="{ 'drag-icon-hidden': element.draggable === false }"
|
||||||
|
>
|
||||||
<n-icon size="18">
|
<n-icon size="18">
|
||||||
<DragOutlined />
|
<DragOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -88,7 +101,7 @@
|
|||||||
VerticalRightOutlined,
|
VerticalRightOutlined,
|
||||||
VerticalLeftOutlined,
|
VerticalLeftOutlined,
|
||||||
} from '@vicons/antd';
|
} from '@vicons/antd';
|
||||||
import Draggable from 'vuedraggable/src/vuedraggable';
|
import Draggable from 'vuedraggable';
|
||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
@@ -211,6 +224,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onMove(e) {
|
||||||
|
if (e.draggedContext.element.draggable === false) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
//固定
|
//固定
|
||||||
function fixedColumn(item, fixed) {
|
function fixedColumn(item, fixed) {
|
||||||
if (!state.checkList.includes(item.key)) return;
|
if (!state.checkList.includes(item.key)) return;
|
||||||
@@ -232,6 +250,7 @@
|
|||||||
onChange,
|
onChange,
|
||||||
onCheckAll,
|
onCheckAll,
|
||||||
onSelection,
|
onSelection,
|
||||||
|
onMove,
|
||||||
resetColumns,
|
resetColumns,
|
||||||
fixedColumn,
|
fixedColumn,
|
||||||
draggableEnd,
|
draggableEnd,
|
||||||
@@ -275,6 +294,10 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
&-hidden {
|
||||||
|
visibility: hidden;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fixed-item {
|
.fixed-item {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
const title: any = column.title;
|
const title: any = column.title;
|
||||||
column.title = () => {
|
column.title = () => {
|
||||||
return renderTooltip(
|
return renderTooltip(
|
||||||
h('span', {}, [
|
h('div', { class: 'flex items-center' }, [
|
||||||
h('span', { style: { 'margin-right': '5px' } }, title),
|
h('span', { style: { 'margin-right': '5px' } }, title),
|
||||||
h(
|
h(
|
||||||
NIcon,
|
NIcon,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ref, ComputedRef, unref, computed, onMounted, watchEffect, watch } from 'vue';
|
import { ref, ComputedRef, unref, computed, onMounted, watchEffect, watch } from 'vue';
|
||||||
import type { BasicTableProps } from '../types/table';
|
import type { BasicTableProps } from '../types/table';
|
||||||
import type { PaginationProps } from '../types/pagination';
|
import type { PaginationProps } from '../types/pagination';
|
||||||
import { isBoolean } from '@/utils/is';
|
import { isBoolean, isFunction, isArray } from '@/utils/is';
|
||||||
import { APISETTING } from '../const';
|
import { APISETTING } from '../const';
|
||||||
|
|
||||||
export function useDataSource(
|
export function useDataSource(
|
||||||
@@ -46,14 +46,14 @@ export function useDataSource(
|
|||||||
async function fetch(opt?) {
|
async function fetch(opt?) {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { request, pagination }: any = unref(propsRef);
|
const { request, pagination, beforeRequest, afterRequest }: any = unref(propsRef);
|
||||||
if (!request) return;
|
if (!request) return;
|
||||||
//组装分页信息
|
//组装分页信息
|
||||||
const pageField = APISETTING.pageField;
|
const pageField = APISETTING.pageField;
|
||||||
const sizeField = APISETTING.sizeField;
|
const sizeField = APISETTING.sizeField;
|
||||||
const totalField = APISETTING.totalField;
|
const totalField = APISETTING.totalField;
|
||||||
const listField = APISETTING.listField;
|
const listField = APISETTING.listField;
|
||||||
|
const itemCount = APISETTING.countField;
|
||||||
let pageParams = {};
|
let pageParams = {};
|
||||||
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
|
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
|
||||||
|
|
||||||
@@ -64,32 +64,45 @@ export function useDataSource(
|
|||||||
pageParams[sizeField] = pageSize;
|
pageParams[sizeField] = pageSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = {
|
let params = {
|
||||||
...pageParams,
|
...pageParams,
|
||||||
|
...opt,
|
||||||
};
|
};
|
||||||
|
if (beforeRequest && isFunction(beforeRequest)) {
|
||||||
|
// The params parameter can be modified by outsiders
|
||||||
|
params = (await beforeRequest(params)) || params;
|
||||||
|
}
|
||||||
const res = await request(params);
|
const res = await request(params);
|
||||||
|
const resultTotal = res[totalField];
|
||||||
const resultTotal = res[totalField] || 0;
|
|
||||||
const currentPage = res[pageField];
|
const currentPage = res[pageField];
|
||||||
|
const total = res[itemCount];
|
||||||
|
const results = res[listField] ? res[listField] : [];
|
||||||
|
|
||||||
// 如果数据异常,需获取正确的页码再次执行
|
// 如果数据异常,需获取正确的页码再次执行
|
||||||
if (resultTotal) {
|
if (resultTotal) {
|
||||||
if (page > resultTotal) {
|
const currentTotalPage = Math.ceil(total / pageSize);
|
||||||
|
if (page > currentTotalPage) {
|
||||||
setPagination({
|
setPagination({
|
||||||
[pageField]: resultTotal,
|
page: currentTotalPage,
|
||||||
|
itemCount: total,
|
||||||
});
|
});
|
||||||
fetch(opt);
|
return await fetch(opt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const resultInfo = res[listField] ? res[listField] : [];
|
let resultInfo = res[listField] ? res[listField] : [];
|
||||||
|
if (afterRequest && isFunction(afterRequest)) {
|
||||||
|
// can modify the data returned by the interface for processing
|
||||||
|
resultInfo = (await afterRequest(resultInfo)) || resultInfo;
|
||||||
|
}
|
||||||
dataSourceRef.value = resultInfo;
|
dataSourceRef.value = resultInfo;
|
||||||
setPagination({
|
setPagination({
|
||||||
[pageField]: currentPage,
|
page: currentPage,
|
||||||
[totalField]: resultTotal,
|
pageCount: resultTotal,
|
||||||
|
itemCount: total,
|
||||||
});
|
});
|
||||||
if (opt && opt[pageField]) {
|
if (opt && opt[pageField]) {
|
||||||
setPagination({
|
setPagination({
|
||||||
[pageField]: opt[pageField] || 1,
|
page: opt[pageField] || 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
emit('fetch-success', {
|
emit('fetch-success', {
|
||||||
@@ -100,9 +113,9 @@ export function useDataSource(
|
|||||||
console.error(error);
|
console.error(error);
|
||||||
emit('fetch-error', error);
|
emit('fetch-error', error);
|
||||||
dataSourceRef.value = [];
|
dataSourceRef.value = [];
|
||||||
// setPagination({
|
setPagination({
|
||||||
// pageCount: 0,
|
pageCount: 0,
|
||||||
// });
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,40 @@
|
|||||||
import type { PaginationProps } from '../types/pagination';
|
import type { PaginationProps } from '../types/pagination';
|
||||||
import type { BasicTableProps } from '../types/table';
|
import type { BasicTableProps } from '../types/table';
|
||||||
import { computed, unref, ref, ComputedRef } from 'vue';
|
import { computed, unref, ref, ComputedRef, watch } from 'vue';
|
||||||
|
|
||||||
import { isBoolean } from '@/utils/is';
|
import { isBoolean } from '@/utils/is';
|
||||||
import { APISETTING, DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
||||||
|
|
||||||
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
||||||
const configRef = ref<PaginationProps>({});
|
const configRef = ref<PaginationProps>({});
|
||||||
const show = ref(true);
|
const show = ref(true);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => unref(refProps).pagination,
|
||||||
|
(pagination) => {
|
||||||
|
if (!isBoolean(pagination) && pagination) {
|
||||||
|
configRef.value = {
|
||||||
|
...unref(configRef),
|
||||||
|
...(pagination ?? {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const getPaginationInfo = computed((): PaginationProps | boolean => {
|
const getPaginationInfo = computed((): PaginationProps | boolean => {
|
||||||
const { pagination } = unref(refProps);
|
const { pagination } = unref(refProps);
|
||||||
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const { totalField } = APISETTING;
|
|
||||||
return {
|
return {
|
||||||
pageSize: DEFAULTPAGESIZE,
|
page: 1, //当前页
|
||||||
pageSizes: PAGESIZES,
|
pageSize: DEFAULTPAGESIZE, //分页大小
|
||||||
|
pageSizes: PAGESIZES, // 每页条数
|
||||||
showSizePicker: true,
|
showSizePicker: true,
|
||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
|
prefix: (pagingInfo) => `共 ${pagingInfo.itemCount} 条`, // 不需要可以通过 pagination 重置或者删除
|
||||||
...(isBoolean(pagination) ? {} : pagination),
|
...(isBoolean(pagination) ? {} : pagination),
|
||||||
...unref(configRef),
|
...unref(configRef),
|
||||||
pageCount: unref(configRef)[totalField],
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,18 @@ export const basicProps = {
|
|||||||
default: () => [],
|
default: () => [],
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
beforeRequest: {
|
||||||
|
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
request: {
|
request: {
|
||||||
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
afterRequest: {
|
||||||
|
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
rowKey: {
|
rowKey: {
|
||||||
type: [String, Function] as PropType<string | ((record) => string)>,
|
type: [String, Function] as PropType<string | ((record) => string)>,
|
||||||
default: undefined,
|
default: undefined,
|
||||||
@@ -48,4 +56,5 @@ export const basicProps = {
|
|||||||
},
|
},
|
||||||
canResize: propTypes.bool.def(true),
|
canResize: propTypes.bool.def(true),
|
||||||
resizeHeightOffset: propTypes.number.def(0),
|
resizeHeightOffset: propTypes.number.def(0),
|
||||||
|
striped: propTypes.bool.def(false),
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,4 +5,5 @@ export type ComponentType =
|
|||||||
| 'NCheckbox'
|
| 'NCheckbox'
|
||||||
| 'NSwitch'
|
| 'NSwitch'
|
||||||
| 'NDatePicker'
|
| 'NDatePicker'
|
||||||
| 'NTimePicker';
|
| 'NTimePicker'
|
||||||
|
| 'NCascader';
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
export interface PaginationProps {
|
export interface PaginationProps {
|
||||||
page?: number;
|
page?: number; //受控模式下的当前页
|
||||||
pageCount?: number;
|
itemCount?: number; //总条数
|
||||||
pageSize?: number;
|
pageCount?: number; //总页数
|
||||||
pageSizes?: number[];
|
pageSize?: number; //受控模式下的分页大小
|
||||||
showSizePicker?: boolean;
|
pageSizes?: number[]; //每页条数, 可自定义
|
||||||
showQuickJumper?: boolean;
|
showSizePicker?: boolean; //是否显示每页条数的选择器
|
||||||
|
showQuickJumper?: boolean; //是否显示快速跳转
|
||||||
|
prefix?: any; //分页前缀
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
import type { InternalRowData, TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
|
||||||
import { ComponentType } from './componentType';
|
import { ComponentType } from './componentType';
|
||||||
export interface BasicColumn extends TableBaseColumn {
|
export interface BasicColumn<T = InternalRowData> extends TableBaseColumn<T> {
|
||||||
//编辑表格
|
//编辑表格
|
||||||
edit?: boolean;
|
edit?: boolean;
|
||||||
editRow?: boolean;
|
editRow?: boolean;
|
||||||
@@ -14,6 +14,8 @@ export interface BasicColumn extends TableBaseColumn {
|
|||||||
auth?: string[];
|
auth?: string[];
|
||||||
// 业务控制是否显示
|
// 业务控制是否显示
|
||||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||||
|
// 控制是否支持拖拽,默认支持
|
||||||
|
draggable?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TableActionType {
|
export interface TableActionType {
|
||||||
@@ -32,4 +34,5 @@ export interface BasicTableProps {
|
|||||||
actionColumn: any[];
|
actionColumn: any[];
|
||||||
canResize: boolean;
|
canResize: boolean;
|
||||||
resizeHeightOffset: number;
|
resizeHeightOffset: number;
|
||||||
|
loading: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { NButton } from 'naive-ui';
|
import { NButton } from 'naive-ui';
|
||||||
|
import type { Component } from 'vue';
|
||||||
import { PermissionsEnum } from '@/enums/permissionsEnum';
|
import { PermissionsEnum } from '@/enums/permissionsEnum';
|
||||||
export interface ActionItem extends NButton.props {
|
export interface ActionItem extends Partial<InstanceType<typeof NButton>> {
|
||||||
onClick?: Fn;
|
onClick?: Fn;
|
||||||
label?: string;
|
label?: string;
|
||||||
color?: 'success' | 'error' | 'warning';
|
type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default';
|
||||||
icon?: string;
|
// 设定 color 后会覆盖 type 的样式
|
||||||
|
color?: string;
|
||||||
|
icon?: Component;
|
||||||
popConfirm?: PopConfirm;
|
popConfirm?: PopConfirm;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
@@ -20,5 +23,5 @@ export interface PopConfirm {
|
|||||||
cancelText?: string;
|
cancelText?: string;
|
||||||
confirm: Fn;
|
confirm: Fn;
|
||||||
cancel?: Fn;
|
cancel?: Fn;
|
||||||
icon?: string;
|
icon?: Component;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
v-if="imgList.length < maxNumber"
|
v-if="imgList.length < maxNumber"
|
||||||
>
|
>
|
||||||
<n-upload
|
<n-upload
|
||||||
|
class="w-auto"
|
||||||
v-bind="$props"
|
v-bind="$props"
|
||||||
:file-list-style="{ display: 'none' }"
|
:file-list-style="{ display: 'none' }"
|
||||||
@before-upload="beforeUpload"
|
@before-upload="beforeUpload"
|
||||||
@@ -109,10 +110,11 @@
|
|||||||
watch(
|
watch(
|
||||||
() => props.value,
|
() => props.value,
|
||||||
() => {
|
() => {
|
||||||
imgList.value = props.value.map((item) => {
|
state.imgList = props.value.map((item) => {
|
||||||
return getImgUrl(item);
|
return getImgUrl(item);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
//预览
|
//预览
|
||||||
@@ -236,6 +238,7 @@
|
|||||||
&-info {
|
&-info {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
|||||||
9
src/config/website.config.ts
Normal file
9
src/config/website.config.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import logoImage from '@/assets/images/logo.png';
|
||||||
|
import loginImage from '@/assets/images/account-logo.png';
|
||||||
|
|
||||||
|
export const websiteConfig = Object.freeze({
|
||||||
|
title: 'NaiveUiAdmin',
|
||||||
|
logo: logoImage,
|
||||||
|
loginImage: loginImage,
|
||||||
|
loginDesc: 'Naive Ui Admin 中后台前端/设计解决方案',
|
||||||
|
});
|
||||||
34
src/directives/copy.ts
Normal file
34
src/directives/copy.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* v-copy
|
||||||
|
* 复制某个值至剪贴板
|
||||||
|
* 接收参数:string类型/Ref<string>类型/Reactive<string>类型
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
copyData: string | number;
|
||||||
|
__handleClick__: any;
|
||||||
|
}
|
||||||
|
const copy: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
el.copyData = binding.value;
|
||||||
|
el.addEventListener('click', handleClick);
|
||||||
|
},
|
||||||
|
updated(el: ElType, binding: DirectiveBinding) {
|
||||||
|
el.copyData = binding.value;
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener('click', el.__handleClick__);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
function handleClick(this: any) {
|
||||||
|
const input = document.createElement('input');
|
||||||
|
input.value = this.copyData.toLocaleString();
|
||||||
|
document.body.appendChild(input);
|
||||||
|
input.select();
|
||||||
|
document.execCommand('Copy');
|
||||||
|
document.body.removeChild(input);
|
||||||
|
console.log('复制成功', this.copyData);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default copy;
|
||||||
31
src/directives/debounce.ts
Normal file
31
src/directives/debounce.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* v-debounce
|
||||||
|
* 按钮防抖指令,可自行扩展至input
|
||||||
|
* 接收参数:function类型
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue';
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
__handleClick__: () => any;
|
||||||
|
}
|
||||||
|
const debounce: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== 'function') {
|
||||||
|
throw 'callback must be a function';
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
if (timer) {
|
||||||
|
clearInterval(timer);
|
||||||
|
}
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
binding.value();
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
el.addEventListener('click', el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener('click', el.__handleClick__);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default debounce;
|
||||||
49
src/directives/draggable.ts
Normal file
49
src/directives/draggable.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、设置需要拖拽的元素为absolute,其父元素为relative。
|
||||||
|
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
|
||||||
|
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
|
||||||
|
4、鼠标松开(onmouseup)时完成一次拖拽
|
||||||
|
|
||||||
|
使用:在 Dom 上加上 v-draggable 即可
|
||||||
|
<div class="dialog-model" v-draggable></div>
|
||||||
|
*/
|
||||||
|
import type { Directive } from 'vue';
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
parentNode: any;
|
||||||
|
}
|
||||||
|
const draggable: Directive = {
|
||||||
|
mounted: function (el: ElType) {
|
||||||
|
el.style.cursor = 'move';
|
||||||
|
el.style.position = 'absolute';
|
||||||
|
el.onmousedown = function (e) {
|
||||||
|
const disX = e.pageX - el.offsetLeft;
|
||||||
|
const disY = e.pageY - el.offsetTop;
|
||||||
|
document.onmousemove = function (e) {
|
||||||
|
let x = e.pageX - disX;
|
||||||
|
let y = e.pageY - disY;
|
||||||
|
const maxX = el.parentNode.offsetWidth - el.offsetWidth;
|
||||||
|
const maxY = el.parentNode.offsetHeight - el.offsetHeight;
|
||||||
|
if (x < 0) {
|
||||||
|
x = 0;
|
||||||
|
} else if (x > maxX) {
|
||||||
|
x = maxX;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y < 0) {
|
||||||
|
y = 0;
|
||||||
|
} else if (y > maxY) {
|
||||||
|
y = maxY;
|
||||||
|
}
|
||||||
|
el.style.left = x + 'px';
|
||||||
|
el.style.top = y + 'px';
|
||||||
|
};
|
||||||
|
document.onmouseup = function () {
|
||||||
|
document.onmousemove = document.onmouseup = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
export default draggable;
|
||||||
49
src/directives/longpress.ts
Normal file
49
src/directives/longpress.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* v-longpress
|
||||||
|
* 长按指令,长按时触发事件
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue';
|
||||||
|
|
||||||
|
const directive: Directive = {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== 'function') {
|
||||||
|
throw 'callback must be a function';
|
||||||
|
}
|
||||||
|
// 定义变量
|
||||||
|
let pressTimer: any = null;
|
||||||
|
// 创建计时器( 2秒后执行函数 )
|
||||||
|
const start = (e: any) => {
|
||||||
|
if (e.button) {
|
||||||
|
if (e.type === 'click' && e.button !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pressTimer === null) {
|
||||||
|
pressTimer = setTimeout(() => {
|
||||||
|
handler(e);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 取消计时器
|
||||||
|
const cancel = () => {
|
||||||
|
if (pressTimer !== null) {
|
||||||
|
clearTimeout(pressTimer);
|
||||||
|
pressTimer = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 运行函数
|
||||||
|
const handler = (e: MouseEvent | TouchEvent) => {
|
||||||
|
binding.value(e);
|
||||||
|
};
|
||||||
|
// 添加事件监听器
|
||||||
|
el.addEventListener('mousedown', start);
|
||||||
|
el.addEventListener('touchstart', start);
|
||||||
|
// 取消计时器
|
||||||
|
el.addEventListener('click', cancel);
|
||||||
|
el.addEventListener('mouseout', cancel);
|
||||||
|
el.addEventListener('touchend', cancel);
|
||||||
|
el.addEventListener('touchcancel', cancel);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default directive;
|
||||||
41
src/directives/throttle.ts
Normal file
41
src/directives/throttle.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
|
||||||
|
|
||||||
|
思路:
|
||||||
|
1、第一次点击,立即调用方法并禁用按钮,等延迟结束再次激活按钮
|
||||||
|
2、将需要触发的方法绑定在指令上
|
||||||
|
|
||||||
|
使用:给 Dom 加上 v-throttle 及回调函数即可
|
||||||
|
<button v-throttle="debounceClick">节流提交</button>
|
||||||
|
*/
|
||||||
|
import type { Directive, DirectiveBinding } from 'vue';
|
||||||
|
interface ElType extends HTMLElement {
|
||||||
|
__handleClick__: () => any;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
const throttle: Directive = {
|
||||||
|
mounted(el: ElType, binding: DirectiveBinding) {
|
||||||
|
if (typeof binding.value !== 'function') {
|
||||||
|
throw 'callback must be a function';
|
||||||
|
}
|
||||||
|
let timer: NodeJS.Timeout | null = null;
|
||||||
|
el.__handleClick__ = function () {
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
}
|
||||||
|
if (!el.disabled) {
|
||||||
|
el.disabled = true;
|
||||||
|
binding.value();
|
||||||
|
timer = setTimeout(() => {
|
||||||
|
el.disabled = false;
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
el.addEventListener('click', el.__handleClick__);
|
||||||
|
},
|
||||||
|
beforeUnmount(el: ElType) {
|
||||||
|
el.removeEventListener('click', el.__handleClick__);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default throttle;
|
||||||
4
src/enums/permissionsEnum.ts
Normal file
4
src/enums/permissionsEnum.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface PermissionsEnum {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
import { useAsync } from './use-async';
|
import { useAsync } from './useAsync';
|
||||||
|
|
||||||
export { useAsync };
|
export { useAsync };
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { GlobConfig } from '/#/config';
|
import type { GlobConfig, LocalConfig } from '/#/config';
|
||||||
|
|
||||||
import { warn } from '@/utils/log';
|
|
||||||
import { getAppEnvConfig } from '@/utils/env';
|
import { getAppEnvConfig } from '@/utils/env';
|
||||||
|
import { warn } from '@/utils/log';
|
||||||
|
|
||||||
|
// 这里的 useGlobSetting 用于获取全局配置,以下环境变量 带 VITE_GLOB_开头 会打包到 app.config 中去
|
||||||
export const useGlobSetting = (): Readonly<GlobConfig> => {
|
export const useGlobSetting = (): Readonly<GlobConfig> => {
|
||||||
const {
|
const {
|
||||||
VITE_GLOB_APP_TITLE,
|
VITE_GLOB_APP_TITLE,
|
||||||
@@ -10,8 +11,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
VITE_GLOB_APP_SHORT_NAME,
|
VITE_GLOB_APP_SHORT_NAME,
|
||||||
VITE_GLOB_API_URL_PREFIX,
|
VITE_GLOB_API_URL_PREFIX,
|
||||||
VITE_GLOB_UPLOAD_URL,
|
VITE_GLOB_UPLOAD_URL,
|
||||||
VITE_GLOB_PROD_MOCK,
|
VITE_GLOB_FILE_URL,
|
||||||
VITE_GLOB_IMG_URL,
|
|
||||||
} = getAppEnvConfig();
|
} = getAppEnvConfig();
|
||||||
|
|
||||||
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
|
||||||
@@ -21,14 +21,25 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Take global configuration
|
// Take global configuration
|
||||||
const glob: Readonly<GlobConfig> = {
|
return {
|
||||||
title: VITE_GLOB_APP_TITLE,
|
title: VITE_GLOB_APP_TITLE,
|
||||||
apiUrl: VITE_GLOB_API_URL,
|
apiUrl: VITE_GLOB_API_URL,
|
||||||
shortName: VITE_GLOB_APP_SHORT_NAME,
|
shortName: VITE_GLOB_APP_SHORT_NAME,
|
||||||
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
urlPrefix: VITE_GLOB_API_URL_PREFIX,
|
||||||
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
uploadUrl: VITE_GLOB_UPLOAD_URL,
|
||||||
prodMock: VITE_GLOB_PROD_MOCK,
|
fileUrl: VITE_GLOB_FILE_URL,
|
||||||
imgUrl: VITE_GLOB_IMG_URL,
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 这里的 useLocalSetting 用于获取本地配置,以下环境变量不会打包到 app.config 中去
|
||||||
|
export const useLocalSetting = (): Readonly<LocalConfig> => {
|
||||||
|
const { VITE_USE_MOCK, VITE_LOGGER_MOCK } = import.meta.env;
|
||||||
|
|
||||||
|
function strToBoolean(val): boolean {
|
||||||
|
return val === 'true';
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
useMock: strToBoolean(VITE_USE_MOCK),
|
||||||
|
loggerMock: strToBoolean(VITE_LOGGER_MOCK),
|
||||||
};
|
};
|
||||||
return glob as Readonly<GlobConfig>;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,36 +4,39 @@ import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
|||||||
export function useProjectSetting() {
|
export function useProjectSetting() {
|
||||||
const projectStore = useProjectSettingStore();
|
const projectStore = useProjectSettingStore();
|
||||||
|
|
||||||
const getNavMode = computed(() => projectStore.navMode);
|
const navMode = computed(() => projectStore.navMode);
|
||||||
|
|
||||||
const getNavTheme = computed(() => projectStore.navTheme);
|
const navTheme = computed(() => projectStore.navTheme);
|
||||||
|
|
||||||
const getHeaderSetting = computed(() => projectStore.headerSetting);
|
const isMobile = computed(() => projectStore.isMobile);
|
||||||
|
|
||||||
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
const headerSetting = computed(() => projectStore.headerSetting);
|
||||||
|
|
||||||
const getMenuSetting = computed(() => projectStore.menuSetting);
|
const multiTabsSetting = computed(() => projectStore.multiTabsSetting);
|
||||||
|
|
||||||
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
|
const menuSetting = computed(() => projectStore.menuSetting);
|
||||||
|
|
||||||
const getPermissionMode = computed(() => projectStore.permissionMode);
|
const crumbsSetting = computed(() => projectStore.crumbsSetting);
|
||||||
|
|
||||||
const getShowFooter = computed(() => projectStore.showFooter);
|
const permissionMode = computed(() => projectStore.permissionMode);
|
||||||
|
|
||||||
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
|
const showFooter = computed(() => projectStore.showFooter);
|
||||||
|
|
||||||
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
|
const isPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||||
|
|
||||||
|
const pageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getNavMode,
|
navMode,
|
||||||
getNavTheme,
|
navTheme,
|
||||||
getHeaderSetting,
|
isMobile,
|
||||||
getMultiTabsSetting,
|
headerSetting,
|
||||||
getMenuSetting,
|
multiTabsSetting,
|
||||||
getCrumbsSetting,
|
menuSetting,
|
||||||
getPermissionMode,
|
crumbsSetting,
|
||||||
getShowFooter,
|
permissionMode,
|
||||||
getIsPageAnimate,
|
showFooter,
|
||||||
getPageAnimateType,
|
isPageAnimate,
|
||||||
|
pageAnimateType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash-es';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* description: 获取页面宽度
|
* description: 获取页面宽度
|
||||||
|
|||||||
@@ -10,23 +10,27 @@ import { useBreakpoint } from '@/hooks/event/useBreakpoint';
|
|||||||
|
|
||||||
import echarts from '@/utils/lib/echarts';
|
import echarts from '@/utils/lib/echarts';
|
||||||
|
|
||||||
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
|
|
||||||
export function useECharts(
|
export function useECharts(
|
||||||
elRef: Ref<HTMLDivElement>,
|
elRef: Ref<HTMLDivElement>,
|
||||||
theme: 'light' | 'dark' | 'default' = 'light'
|
theme: 'light' | 'dark' | 'default' = 'default'
|
||||||
) {
|
) {
|
||||||
// const { getDarkMode } = useRootSetting();
|
const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
|
||||||
const getDarkMode = 'light';
|
|
||||||
|
const getDarkTheme = computed(() => {
|
||||||
|
const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
|
||||||
|
return theme === 'default' ? sysTheme : theme;
|
||||||
|
});
|
||||||
|
|
||||||
let chartInstance: echarts.ECharts | null = null;
|
let chartInstance: echarts.ECharts | null = null;
|
||||||
let resizeFn: Fn = resize;
|
let resizeFn: Fn = resize;
|
||||||
const cacheOptions = ref<EChartsOption>({});
|
const cacheOptions = ref({});
|
||||||
let removeResizeFn: Fn = () => {};
|
let removeResizeFn: Fn = () => {};
|
||||||
|
|
||||||
resizeFn = useDebounceFn(resize, 200);
|
resizeFn = useDebounceFn(resize, 200);
|
||||||
|
|
||||||
const getOptions = computed((): EChartsOption => {
|
const getOptions = computed((): EChartsOption => {
|
||||||
if (getDarkMode !== 'dark') {
|
if (getDarkTheme.value !== 'dark') {
|
||||||
return cacheOptions.value;
|
return cacheOptions.value;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@@ -67,7 +71,7 @@ export function useECharts(
|
|||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
useTimeoutFn(() => {
|
useTimeoutFn(() => {
|
||||||
if (!chartInstance) {
|
if (!chartInstance) {
|
||||||
initCharts(getDarkMode.value as 'default');
|
initCharts(getDarkTheme.value as 'default');
|
||||||
|
|
||||||
if (!chartInstance) return;
|
if (!chartInstance) return;
|
||||||
}
|
}
|
||||||
@@ -83,7 +87,7 @@ export function useECharts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => getDarkMode.value,
|
() => getDarkTheme.value,
|
||||||
(theme) => {
|
(theme) => {
|
||||||
if (chartInstance) {
|
if (chartInstance) {
|
||||||
chartInstance.dispose();
|
chartInstance.dispose();
|
||||||
@@ -93,18 +97,20 @@ export function useECharts(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
tryOnUnmounted(() => {
|
tryOnUnmounted(disposeInstance);
|
||||||
|
|
||||||
|
function getInstance(): echarts.ECharts | null {
|
||||||
|
if (!chartInstance) {
|
||||||
|
initCharts(getDarkTheme.value as 'default');
|
||||||
|
}
|
||||||
|
return chartInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
function disposeInstance() {
|
||||||
if (!chartInstance) return;
|
if (!chartInstance) return;
|
||||||
removeResizeFn();
|
removeResizeFn();
|
||||||
chartInstance.dispose();
|
chartInstance.dispose();
|
||||||
chartInstance = null;
|
chartInstance = null;
|
||||||
});
|
|
||||||
|
|
||||||
function getInstance(): echarts.ECharts | null {
|
|
||||||
if (!chartInstance) {
|
|
||||||
initCharts(getDarkMode.value as 'default');
|
|
||||||
}
|
|
||||||
return chartInstance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -112,5 +118,6 @@ export function useECharts(
|
|||||||
resize,
|
resize,
|
||||||
echarts,
|
echarts,
|
||||||
getInstance,
|
getInstance,
|
||||||
|
disposeInstance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
62
src/hooks/web/usePage.ts
Normal file
62
src/hooks/web/usePage.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import type { RouteLocationRaw, Router } from 'vue-router';
|
||||||
|
|
||||||
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import { RedirectName } from '@/router/constant';
|
||||||
|
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import { isString } from '@/utils/is';
|
||||||
|
import { unref } from 'vue';
|
||||||
|
|
||||||
|
export type RouteLocationRawEx = Omit<RouteLocationRaw, 'path'> & { path: PageEnum };
|
||||||
|
|
||||||
|
function handleError(e: Error) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 页面切换
|
||||||
|
*/
|
||||||
|
export function useGo(_router?: Router) {
|
||||||
|
let router;
|
||||||
|
if (!_router) {
|
||||||
|
router = useRouter();
|
||||||
|
}
|
||||||
|
const { push, replace } = _router || router;
|
||||||
|
function go(opt: PageEnum | RouteLocationRawEx | string = PageEnum.BASE_HOME, isReplace = false) {
|
||||||
|
if (!opt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isString(opt)) {
|
||||||
|
isReplace ? replace(opt).catch(handleError) : push(opt).catch(handleError);
|
||||||
|
} else {
|
||||||
|
const o = opt as RouteLocationRaw;
|
||||||
|
isReplace ? replace(o).catch(handleError) : push(o).catch(handleError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return go;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重做当前页面
|
||||||
|
*/
|
||||||
|
export const useRedo = (_router?: Router) => {
|
||||||
|
const { push, currentRoute } = _router || useRouter();
|
||||||
|
const { query, params = {}, name, fullPath } = unref(currentRoute.value);
|
||||||
|
function redo(): Promise<boolean> {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
if (name === RedirectName) {
|
||||||
|
resolve(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (name && Object.keys(params).length > 0) {
|
||||||
|
params['_redirect_type'] = 'name';
|
||||||
|
params['path'] = String(name);
|
||||||
|
} else {
|
||||||
|
params['_redirect_type'] = 'path';
|
||||||
|
params['path'] = fullPath;
|
||||||
|
}
|
||||||
|
push({ name: RedirectName, params, query }).then(() => resolve(true));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return redo;
|
||||||
|
};
|
||||||
@@ -206,7 +206,7 @@
|
|||||||
<n-divider title-placement="center">动画</n-divider>
|
<n-divider title-placement="center">动画</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<div class="drawer-setting-item-title"> 禁用动画 </div>
|
<div class="drawer-setting-item-title"> 启用动画 </div>
|
||||||
<div class="drawer-setting-item-action">
|
<div class="drawer-setting-item-action">
|
||||||
<n-switch v-model:value="settingStore.isPageAnimate" />
|
<n-switch v-model:value="settingStore.isPageAnimate" />
|
||||||
</div>
|
</div>
|
||||||
@@ -259,8 +259,7 @@
|
|||||||
title: props.title,
|
title: props.title,
|
||||||
isDrawer: false,
|
isDrawer: false,
|
||||||
placement: 'right',
|
placement: 'right',
|
||||||
alertText:
|
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置',
|
||||||
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
|
|
||||||
appThemeList: designStore.appThemeList,
|
appThemeList: designStore.appThemeList,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -359,6 +358,7 @@
|
|||||||
margin: 0 5px 5px 0;
|
margin: 0 5px 5px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 14px;
|
line-height: 14px;
|
||||||
|
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@
|
|||||||
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
||||||
>
|
>
|
||||||
<div class="logo" v-if="navMode === 'horizontal'">
|
<div class="logo" v-if="navMode === 'horizontal'">
|
||||||
<img src="~@/assets/images/logo.png" alt="" />
|
<img :src="websiteConfig.logo" alt="" />
|
||||||
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<AsideMenu
|
<AsideMenu
|
||||||
v-model:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
v-model:location="getMenuLocation"
|
v-model:location="getMenuLocation"
|
||||||
:inverted="getInverted"
|
:inverted="getInverted"
|
||||||
mode="horizontal"
|
mode="horizontal"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
<!-- 菜单收起 -->
|
<!-- 菜单收起 -->
|
||||||
<div
|
<div
|
||||||
class="ml-1 layout-header-trigger layout-header-trigger-min"
|
class="ml-1 layout-header-trigger layout-header-trigger-min"
|
||||||
@click="() => $emit('update:collapsed', !collapsed)"
|
@click="handleMenuCollapsed"
|
||||||
>
|
>
|
||||||
<n-icon size="18" v-if="collapsed">
|
<n-icon size="18" v-if="collapsed">
|
||||||
<MenuUnfoldOutlined />
|
<MenuUnfoldOutlined />
|
||||||
@@ -42,8 +42,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 面包屑 -->
|
<!-- 面包屑 -->
|
||||||
<n-breadcrumb v-if="crumbsSetting.show">
|
<n-breadcrumb v-if="crumbsSetting.show">
|
||||||
<template v-for="routeItem in breadcrumbList" :key="routeItem.name">
|
<template
|
||||||
<n-breadcrumb-item>
|
v-for="routeItem in breadcrumbList"
|
||||||
|
:key="routeItem.name === RedirectName ? void 0 : routeItem.name"
|
||||||
|
>
|
||||||
|
<n-breadcrumb-item v-if="routeItem.meta.title">
|
||||||
<n-dropdown
|
<n-dropdown
|
||||||
v-if="routeItem.children.length"
|
v-if="routeItem.children.length"
|
||||||
:options="routeItem.children"
|
:options="routeItem.children"
|
||||||
@@ -72,7 +75,7 @@
|
|||||||
<div
|
<div
|
||||||
class="layout-header-trigger layout-header-trigger-min"
|
class="layout-header-trigger layout-header-trigger-min"
|
||||||
v-for="item in iconList"
|
v-for="item in iconList"
|
||||||
:key="item.icon.name"
|
:key="item.icon"
|
||||||
>
|
>
|
||||||
<n-tooltip placement="bottom">
|
<n-tooltip placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
@@ -98,12 +101,13 @@
|
|||||||
<div class="layout-header-trigger layout-header-trigger-min">
|
<div class="layout-header-trigger layout-header-trigger-min">
|
||||||
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
|
||||||
<div class="avatar">
|
<div class="avatar">
|
||||||
<n-avatar round>
|
<n-avatar :src="websiteConfig.logo">
|
||||||
{{ username }}
|
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<UserOutlined />
|
<UserOutlined />
|
||||||
</template>
|
</template>
|
||||||
</n-avatar>
|
</n-avatar>
|
||||||
|
<n-divider vertical />
|
||||||
|
<span>{{ username }}</span>
|
||||||
</div>
|
</div>
|
||||||
</n-dropdown>
|
</n-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -125,16 +129,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
|
import { websiteConfig } from '@/config/website.config';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import components from './components';
|
|
||||||
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
|
||||||
import { TABS_ROUTES } from '@/store/mutation-types';
|
|
||||||
import { useUserStore } from '@/store/modules/user';
|
|
||||||
import { useLockscreenStore } from '@/store/modules/lockscreen';
|
|
||||||
import ProjectSetting from './ProjectSetting.vue';
|
|
||||||
import { AsideMenu } from '@/layout/components/Menu';
|
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
import { AsideMenu } from '@/layout/components/Menu';
|
||||||
|
import { RedirectName } from '@/router/constant';
|
||||||
|
import { useScreenLockStore } from '@/store/modules/screenLock';
|
||||||
|
import { useUserStore } from '@/store/modules/user';
|
||||||
|
import { TABS_ROUTES } from '@/store/mutation-types';
|
||||||
|
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
|
||||||
|
import { computed, defineComponent, reactive, ref, toRefs, unref } from 'vue';
|
||||||
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
|
import components from './components';
|
||||||
|
import ProjectSetting from './ProjectSetting.vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PageHeader',
|
name: 'PageHeader',
|
||||||
@@ -147,39 +153,38 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
emits: ['update:collapsed'],
|
||||||
|
setup(props, { emit }) {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const useLockscreen = useLockscreenStore();
|
const useLockscreen = useScreenLockStore();
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
|
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
|
||||||
useProjectSetting();
|
|
||||||
|
|
||||||
const { username } = userStore?.info || {};
|
|
||||||
|
|
||||||
const drawerSetting = ref();
|
const drawerSetting = ref();
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
username: username || '',
|
username: userStore?.info?.username ?? '',
|
||||||
fullscreenIcon: 'FullscreenOutlined',
|
fullscreenIcon: 'FullscreenOutlined',
|
||||||
navMode: getNavMode,
|
navMode,
|
||||||
navTheme: getNavTheme,
|
navTheme,
|
||||||
headerSetting: getHeaderSetting,
|
headerSetting,
|
||||||
crumbsSetting: getCrumbsSetting,
|
crumbsSetting,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getInverted = computed(() => {
|
const getInverted = computed(() => {
|
||||||
const navTheme = unref(getNavTheme);
|
return ['light', 'header-dark'].includes(unref(navTheme))
|
||||||
return ['light', 'header-dark'].includes(navTheme) ? props.inverted : !props.inverted;
|
? props.inverted
|
||||||
|
: !props.inverted;
|
||||||
});
|
});
|
||||||
|
|
||||||
const mixMenu = computed(() => {
|
const mixMenu = computed(() => {
|
||||||
return unref(getMenuSetting).mixMenu;
|
return unref(menuSetting).mixMenu;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props;
|
const { collapsed } = props;
|
||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
const { minMenuWidth, menuWidth } = unref(menuSetting);
|
||||||
return {
|
return {
|
||||||
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
|
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||||
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
|
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
|
||||||
@@ -319,6 +324,10 @@
|
|||||||
openDrawer();
|
openDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleMenuCollapsed() {
|
||||||
|
emit('update:collapsed', !props.collapsed);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
iconList,
|
iconList,
|
||||||
@@ -336,6 +345,9 @@
|
|||||||
getInverted,
|
getInverted,
|
||||||
getMenuLocation,
|
getMenuLocation,
|
||||||
mixMenu,
|
mixMenu,
|
||||||
|
websiteConfig,
|
||||||
|
handleMenuCollapsed,
|
||||||
|
RedirectName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -347,7 +359,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: @header-height;
|
height: 64px;
|
||||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
|
<img :src="websiteConfig.logo" alt="" :class="{ 'mr-2': !collapsed }" />
|
||||||
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="ts">
|
||||||
|
import { websiteConfig } from '@/config/website.config';
|
||||||
export default {
|
export default {
|
||||||
name: 'Index',
|
name: 'Index',
|
||||||
props: {
|
props: {
|
||||||
@@ -13,6 +14,11 @@
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
websiteConfig,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -32,7 +38,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
margin-bottom: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<RouterView>
|
<RouterView>
|
||||||
<template #default="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<transition :name="getTransitionName" mode="out-in" appear>
|
<template v-if="mode === 'production'">
|
||||||
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
|
<transition :name="getTransitionName" mode="out-in" appear>
|
||||||
|
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
|
||||||
|
<component :is="Component" :key="route.fullPath" />
|
||||||
|
</keep-alive>
|
||||||
|
<component v-else :is="Component" :key="route.fullPath" />
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
<component v-else :is="Component" :key="route.fullPath" />
|
<component v-else :is="Component" :key="route.fullPath" />
|
||||||
</transition>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</RouterView>
|
</RouterView>
|
||||||
</template>
|
</template>
|
||||||
@@ -30,18 +38,20 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { getIsPageAnimate, getPageAnimateType } = useProjectSetting();
|
const { isPageAnimate, pageAnimateType } = useProjectSetting();
|
||||||
const asyncRouteStore = useAsyncRouteStore();
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
// 需要缓存的路由组件
|
// 需要缓存的路由组件
|
||||||
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
||||||
|
|
||||||
const getTransitionName = computed(() => {
|
const getTransitionName = computed(() => {
|
||||||
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : '';
|
return unref(isPageAnimate) ? unref(pageAnimateType) : '';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const mode = import.meta.env.MODE;
|
||||||
return {
|
return {
|
||||||
keepAliveComponents,
|
keepAliveComponents,
|
||||||
getTransitionName,
|
getTransitionName,
|
||||||
|
mode,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Menu',
|
name: 'AppMenu',
|
||||||
components: {},
|
components: {},
|
||||||
props: {
|
props: {
|
||||||
mode: {
|
mode: {
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
default: 'left',
|
default: 'left',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emits: ['update:collapsed'],
|
emits: ['update:collapsed', 'clickMenuItem'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
// 当前路由
|
// 当前路由
|
||||||
const currentRoute = useRoute();
|
const currentRoute = useRoute();
|
||||||
@@ -52,9 +52,7 @@
|
|||||||
const selectedKeys = ref<string>(currentRoute.name as string);
|
const selectedKeys = ref<string>(currentRoute.name as string);
|
||||||
const headerMenuSelectKey = ref<string>('');
|
const headerMenuSelectKey = ref<string>('');
|
||||||
|
|
||||||
const { getNavMode } = useProjectSetting();
|
const { navMode } = useProjectSetting();
|
||||||
|
|
||||||
const navMode = getNavMode;
|
|
||||||
|
|
||||||
// 获取当前打开的子菜单
|
// 获取当前打开的子菜单
|
||||||
const matched = currentRoute.matched;
|
const matched = currentRoute.matched;
|
||||||
@@ -99,13 +97,16 @@
|
|||||||
() => currentRoute.fullPath,
|
() => currentRoute.fullPath,
|
||||||
() => {
|
() => {
|
||||||
updateMenu();
|
updateMenu();
|
||||||
const matched = currentRoute.matched;
|
|
||||||
state.openKeys = matched.map((item) => item.name);
|
|
||||||
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
|
|
||||||
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function updateSelectedKeys() {
|
||||||
|
const matched = currentRoute.matched;
|
||||||
|
state.openKeys = matched.map((item) => item.name);
|
||||||
|
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
|
||||||
|
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
|
||||||
|
}
|
||||||
|
|
||||||
function updateMenu() {
|
function updateMenu() {
|
||||||
if (!settingStore.menuSetting.mixMenu) {
|
if (!settingStore.menuSetting.mixMenu) {
|
||||||
menus.value = generatorMenu(asyncRouteStore.getMenus);
|
menus.value = generatorMenu(asyncRouteStore.getMenus);
|
||||||
@@ -116,6 +117,7 @@
|
|||||||
const activeMenu: string = currentRoute?.matched[0].meta?.activeMenu as string;
|
const activeMenu: string = currentRoute?.matched[0].meta?.activeMenu as string;
|
||||||
headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || '';
|
headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || '';
|
||||||
}
|
}
|
||||||
|
updateSelectedKeys();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点击菜单
|
// 点击菜单
|
||||||
@@ -125,6 +127,7 @@
|
|||||||
} else {
|
} else {
|
||||||
router.push({ name: key });
|
router.push({ name: key });
|
||||||
}
|
}
|
||||||
|
emit('clickMenuItem' as any, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
//展开菜单
|
//展开菜单
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
import {
|
|
||||||
DownOutlined,
|
|
||||||
ReloadOutlined,
|
|
||||||
CloseOutlined,
|
|
||||||
VerticalRightOutlined,
|
|
||||||
VerticalLeftOutlined,
|
|
||||||
ColumnWidthOutlined,
|
|
||||||
MinusOutlined,
|
|
||||||
} from '@ant-design/icons-vue';
|
|
||||||
import { Dropdown, Tabs, Card } from 'ant-design-vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
[Tabs.name]: Tabs,
|
|
||||||
[Tabs.TabPane.name]: Tabs.TabPane,
|
|
||||||
[Dropdown.name]: Dropdown,
|
|
||||||
[Card.name]: Card,
|
|
||||||
MinusOutlined,
|
|
||||||
DownOutlined,
|
|
||||||
ReloadOutlined,
|
|
||||||
CloseOutlined,
|
|
||||||
VerticalRightOutlined,
|
|
||||||
VerticalLeftOutlined,
|
|
||||||
ColumnWidthOutlined,
|
|
||||||
};
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="tabs-view"
|
class="box-border tabs-view"
|
||||||
:class="{
|
:class="{
|
||||||
'tabs-view-fix': multiTabsSetting.fixed,
|
'tabs-view-fix': multiTabsSetting.fixed,
|
||||||
'tabs-view-fixed-header': isMultiHeaderFixed,
|
'tabs-view-fixed-header': isMultiHeaderFixed,
|
||||||
@@ -35,16 +35,12 @@
|
|||||||
<div
|
<div
|
||||||
:id="`tag${element.fullPath.split('/').join('\/')}`"
|
:id="`tag${element.fullPath.split('/').join('\/')}`"
|
||||||
class="tabs-card-scroll-item"
|
class="tabs-card-scroll-item"
|
||||||
:class="{ 'active-item': activeKey === element.path }"
|
:class="{ 'active-item': activeKey === element.fullPath }"
|
||||||
@click.stop="goPage(element)"
|
@click.stop="goPage(element)"
|
||||||
@contextmenu="handleContextMenu($event, element)"
|
@contextmenu="handleContextMenu($event, element)"
|
||||||
>
|
>
|
||||||
<span>{{ element.meta.title }}</span>
|
<span>{{ element.meta.title }}</span>
|
||||||
<n-icon
|
<n-icon size="14" @click.stop="closeTabItem(element)" v-if="!element.meta.affix">
|
||||||
size="14"
|
|
||||||
@click.stop="closeTabItem(element)"
|
|
||||||
v-if="element.path !== baseHome"
|
|
||||||
>
|
|
||||||
<CloseOutlined />
|
<CloseOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -86,7 +82,6 @@
|
|||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
toRefs,
|
toRefs,
|
||||||
unref,
|
|
||||||
provide,
|
provide,
|
||||||
watch,
|
watch,
|
||||||
onMounted,
|
onMounted,
|
||||||
@@ -116,6 +111,7 @@
|
|||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
import { useThemeVars } from 'naive-ui';
|
import { useThemeVars } from 'naive-ui';
|
||||||
|
import { useGo } from '@/hooks/web/usePage';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TabsView',
|
name: 'TabsView',
|
||||||
@@ -133,7 +129,7 @@
|
|||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { getDarkTheme, getAppTheme } = useDesignSetting();
|
const { getDarkTheme, getAppTheme } = useDesignSetting();
|
||||||
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
|
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
|
||||||
useProjectSetting();
|
useProjectSetting();
|
||||||
const settingStore = useProjectSettingStore();
|
const settingStore = useProjectSettingStore();
|
||||||
|
|
||||||
@@ -145,6 +141,7 @@
|
|||||||
const navScroll: any = ref(null);
|
const navScroll: any = ref(null);
|
||||||
const navWrap: any = ref(null);
|
const navWrap: any = ref(null);
|
||||||
const isCurrent = ref(false);
|
const isCurrent = ref(false);
|
||||||
|
const go = useGo();
|
||||||
|
|
||||||
const themeVars = useThemeVars();
|
const themeVars = useThemeVars();
|
||||||
|
|
||||||
@@ -163,7 +160,7 @@
|
|||||||
dropdownY: 0,
|
dropdownY: 0,
|
||||||
showDropdown: false,
|
showDropdown: false,
|
||||||
isMultiHeaderFixed: false,
|
isMultiHeaderFixed: false,
|
||||||
multiTabsSetting: getMultiTabsSetting,
|
multiTabsSetting: multiTabsSetting,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取简易的路由对象
|
// 获取简易的路由对象
|
||||||
@@ -175,23 +172,28 @@
|
|||||||
const isMixMenuNoneSub = computed(() => {
|
const isMixMenuNoneSub = computed(() => {
|
||||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||||
const currentRoute = useRoute();
|
const currentRoute = useRoute();
|
||||||
const navMode = unref(getNavMode);
|
if (navMode.value != 'horizontal-mix') return true;
|
||||||
if (unref(navMode) != 'horizontal-mix') return true;
|
return !(navMode.value === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
||||||
return !(unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//动态组装样式 菜单缩进
|
//动态组装样式 菜单缩进
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props;
|
const { collapsed } = props;
|
||||||
const navMode = unref(getNavMode);
|
const { minMenuWidth, menuWidth }: any = menuSetting.value;
|
||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
const { fixed }: any = multiTabsSetting.value;
|
||||||
const { fixed }: any = unref(getMultiTabsSetting);
|
|
||||||
let lenNum =
|
let lenNum =
|
||||||
navMode === 'horizontal' || !isMixMenuNoneSub.value
|
navMode.value === 'horizontal' || !isMixMenuNoneSub.value
|
||||||
? '0px'
|
? '0px'
|
||||||
: collapsed
|
: collapsed
|
||||||
? `${minMenuWidth}px`
|
? `${minMenuWidth}px`
|
||||||
: `${menuWidth}px`;
|
: `${menuWidth}px`;
|
||||||
|
|
||||||
|
if (isMobile.value) {
|
||||||
|
return {
|
||||||
|
left: '0px',
|
||||||
|
width: '100%',
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
left: lenNum,
|
left: lenNum,
|
||||||
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
||||||
@@ -200,7 +202,7 @@
|
|||||||
|
|
||||||
//tags 右侧下拉菜单
|
//tags 右侧下拉菜单
|
||||||
const TabsMenuOptions = computed(() => {
|
const TabsMenuOptions = computed(() => {
|
||||||
const isDisabled = unref(tabsList).length <= 1;
|
const isDisabled = tabsList.value.length <= 1;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '刷新当前',
|
label: '刷新当前',
|
||||||
@@ -210,7 +212,7 @@
|
|||||||
{
|
{
|
||||||
label: `关闭当前`,
|
label: `关闭当前`,
|
||||||
key: '2',
|
key: '2',
|
||||||
disabled: unref(isCurrent) || isDisabled,
|
disabled: isCurrent.value || isDisabled,
|
||||||
icon: renderIcon(CloseOutlined),
|
icon: renderIcon(CloseOutlined),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -258,8 +260,8 @@
|
|||||||
window.pageYOffset ||
|
window.pageYOffset ||
|
||||||
document.body.scrollTop; // 滚动条偏移量
|
document.body.scrollTop; // 滚动条偏移量
|
||||||
state.isMultiHeaderFixed = !!(
|
state.isMultiHeaderFixed = !!(
|
||||||
!getHeaderSetting.value.fixed &&
|
!headerSetting.value.fixed &&
|
||||||
getMultiTabsSetting.value.fixed &&
|
multiTabsSetting.value.fixed &&
|
||||||
scrollTop >= 64
|
scrollTop >= 64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -292,7 +294,7 @@
|
|||||||
(to) => {
|
(to) => {
|
||||||
if (whiteList.includes(route.name as string)) return;
|
if (whiteList.includes(route.name as string)) return;
|
||||||
state.activeKey = to;
|
state.activeKey = to;
|
||||||
tabsViewStore.addTabs(getSimpleRoute(route));
|
tabsViewStore.addTab(getSimpleRoute(route));
|
||||||
updateNavScroll(true);
|
updateNavScroll(true);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
@@ -323,7 +325,7 @@
|
|||||||
const reloadPage = () => {
|
const reloadPage = () => {
|
||||||
delKeepAliveCompName();
|
delKeepAliveCompName();
|
||||||
router.push({
|
router.push({
|
||||||
path: '/redirect' + unref(route).fullPath,
|
path: '/redirect' + route.fullPath,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -356,7 +358,6 @@
|
|||||||
|
|
||||||
// 关闭全部
|
// 关闭全部
|
||||||
const closeAll = () => {
|
const closeAll = () => {
|
||||||
localStorage.removeItem('routes');
|
|
||||||
tabsViewStore.closeAllTabs();
|
tabsViewStore.closeAllTabs();
|
||||||
router.replace(PageEnum.BASE_HOME);
|
router.replace(PageEnum.BASE_HOME);
|
||||||
updateNavScroll();
|
updateNavScroll();
|
||||||
@@ -473,7 +474,7 @@
|
|||||||
const { fullPath } = e;
|
const { fullPath } = e;
|
||||||
if (fullPath === route.fullPath) return;
|
if (fullPath === route.fullPath) return;
|
||||||
state.activeKey = fullPath;
|
state.activeKey = fullPath;
|
||||||
router.push({ path: fullPath });
|
go(e, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//删除tab
|
//删除tab
|
||||||
@@ -499,7 +500,6 @@
|
|||||||
navScroll,
|
navScroll,
|
||||||
route,
|
route,
|
||||||
tabsList,
|
tabsList,
|
||||||
baseHome: PageEnum.BASE_HOME_REDIRECT,
|
|
||||||
goPage,
|
goPage,
|
||||||
closeTabItem,
|
closeTabItem,
|
||||||
closeLeft,
|
closeLeft,
|
||||||
@@ -639,7 +639,6 @@
|
|||||||
background: var(--color);
|
background: var(--color);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
//margin-right: 10px;
|
|
||||||
|
|
||||||
&-btn {
|
&-btn {
|
||||||
color: var(--color);
|
color: var(--color);
|
||||||
@@ -662,7 +661,7 @@
|
|||||||
.tabs-view-fix {
|
.tabs-view-fix {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
padding: 6px 19px 6px 10px;
|
padding: 6px 10px 6px 10px;
|
||||||
left: 200px;
|
left: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-layout class="layout" :position="fixedMenu" has-sider>
|
<n-layout class="layout" :position="fixedMenu" has-sider>
|
||||||
<n-layout-sider
|
<n-layout-sider
|
||||||
v-if="isMixMenuNoneSub && (navMode === 'vertical' || navMode === 'horizontal-mix')"
|
v-if="
|
||||||
|
!isMobile && isMixMenuNoneSub && (navMode === 'vertical' || navMode === 'horizontal-mix')
|
||||||
|
"
|
||||||
show-trigger="bar"
|
show-trigger="bar"
|
||||||
@collapse="collapsed = true"
|
@collapse="collapsed = true"
|
||||||
:position="fixedMenu"
|
:position="fixedMenu"
|
||||||
@@ -18,6 +20,25 @@
|
|||||||
<AsideMenu v-model:collapsed="collapsed" v-model:location="getMenuLocation" />
|
<AsideMenu v-model:collapsed="collapsed" v-model:location="getMenuLocation" />
|
||||||
</n-layout-sider>
|
</n-layout-sider>
|
||||||
|
|
||||||
|
<n-drawer
|
||||||
|
v-model:show="showSideDrawer"
|
||||||
|
:width="menuWidth"
|
||||||
|
:placement="'left'"
|
||||||
|
class="layout-side-drawer"
|
||||||
|
>
|
||||||
|
<n-layout-sider
|
||||||
|
:position="fixedMenu"
|
||||||
|
:collapsed="false"
|
||||||
|
:width="menuWidth"
|
||||||
|
:native-scrollbar="false"
|
||||||
|
:inverted="inverted"
|
||||||
|
class="layout-sider"
|
||||||
|
>
|
||||||
|
<Logo :collapsed="collapsed" />
|
||||||
|
<AsideMenu v-model:location="getMenuLocation" />
|
||||||
|
</n-layout-sider>
|
||||||
|
</n-drawer>
|
||||||
|
|
||||||
<n-layout :inverted="inverted">
|
<n-layout :inverted="inverted">
|
||||||
<n-layout-header :inverted="getHeaderInverted" :position="fixedHeader">
|
<n-layout-header :inverted="getHeaderInverted" :position="fixedHeader">
|
||||||
<PageHeader v-model:collapsed="collapsed" :inverted="inverted" />
|
<PageHeader v-model:collapsed="collapsed" :inverted="inverted" />
|
||||||
@@ -65,33 +86,37 @@
|
|||||||
import { PageHeader } from './components/Header';
|
import { PageHeader } from './components/Header';
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
import { useLoadingBar } from 'naive-ui';
|
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
|
||||||
const { getDarkTheme } = useDesignSetting();
|
const { getDarkTheme } = useDesignSetting();
|
||||||
const {
|
const {
|
||||||
getShowFooter,
|
// showFooter,
|
||||||
getNavMode,
|
navMode,
|
||||||
getNavTheme,
|
navTheme,
|
||||||
getHeaderSetting,
|
headerSetting,
|
||||||
getMenuSetting,
|
menuSetting,
|
||||||
getMultiTabsSetting,
|
multiTabsSetting,
|
||||||
} = useProjectSetting();
|
} = useProjectSetting();
|
||||||
|
|
||||||
const settingStore = useProjectSettingStore();
|
const settingStore = useProjectSettingStore();
|
||||||
|
|
||||||
const navMode = getNavMode;
|
|
||||||
|
|
||||||
const collapsed = ref<boolean>(false);
|
const collapsed = ref<boolean>(false);
|
||||||
|
|
||||||
|
const { mobileWidth, menuWidth } = unref(menuSetting);
|
||||||
|
|
||||||
|
const isMobile = computed<boolean>({
|
||||||
|
get: () => settingStore.getIsMobile,
|
||||||
|
set: (val) => settingStore.setIsMobile(val),
|
||||||
|
});
|
||||||
|
|
||||||
const fixedHeader = computed(() => {
|
const fixedHeader = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting);
|
const { fixed } = unref(headerSetting);
|
||||||
return fixed ? 'absolute' : 'static';
|
return fixed ? 'absolute' : 'static';
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMixMenuNoneSub = computed(() => {
|
const isMixMenuNoneSub = computed(() => {
|
||||||
const mixMenu = settingStore.menuSetting.mixMenu;
|
const mixMenu = unref(menuSetting).mixMenu;
|
||||||
const currentRoute = useRoute();
|
const currentRoute = useRoute();
|
||||||
if (unref(navMode) != 'horizontal-mix') return true;
|
if (unref(navMode) != 'horizontal-mix') return true;
|
||||||
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
||||||
@@ -101,58 +126,79 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const fixedMenu = computed(() => {
|
const fixedMenu = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting);
|
const { fixed } = unref(headerSetting);
|
||||||
return fixed ? 'absolute' : 'static';
|
return fixed ? 'absolute' : 'static';
|
||||||
});
|
});
|
||||||
|
|
||||||
const isMultiTabs = computed(() => {
|
const isMultiTabs = computed(() => {
|
||||||
return unref(getMultiTabsSetting).show;
|
return unref(multiTabsSetting).show;
|
||||||
});
|
});
|
||||||
|
|
||||||
const fixedMulti = computed(() => {
|
const fixedMulti = computed(() => {
|
||||||
return unref(getMultiTabsSetting).fixed;
|
return unref(multiTabsSetting).fixed;
|
||||||
});
|
});
|
||||||
|
|
||||||
const inverted = computed(() => {
|
const inverted = computed(() => {
|
||||||
return ['dark', 'header-dark'].includes(unref(getNavTheme));
|
return ['dark', 'header-dark'].includes(unref(navTheme));
|
||||||
});
|
});
|
||||||
|
|
||||||
const getHeaderInverted = computed(() => {
|
const getHeaderInverted = computed(() => {
|
||||||
const navTheme = unref(getNavTheme);
|
return ['light', 'header-dark'].includes(unref(navTheme)) ? unref(inverted) : !unref(inverted);
|
||||||
return ['light', 'header-dark'].includes(navTheme) ? unref(inverted) : !unref(inverted);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftMenuWidth = computed(() => {
|
const leftMenuWidth = computed(() => {
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
const { minMenuWidth, menuWidth } = unref(menuSetting);
|
||||||
return collapsed.value ? minMenuWidth : menuWidth;
|
return collapsed.value ? minMenuWidth : menuWidth;
|
||||||
});
|
});
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
|
||||||
return {
|
|
||||||
'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const getMenuLocation = computed(() => {
|
const getMenuLocation = computed(() => {
|
||||||
return 'left';
|
return 'left';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 控制显示或隐藏移动端侧边栏
|
||||||
|
const showSideDrawer = computed({
|
||||||
|
get: () => isMobile.value && collapsed.value,
|
||||||
|
set: (val) => (collapsed.value = val),
|
||||||
|
});
|
||||||
|
|
||||||
|
//判断是否触发移动端模式
|
||||||
|
const checkMobileMode = () => {
|
||||||
|
if (document.body.clientWidth <= mobileWidth) {
|
||||||
|
isMobile.value = true;
|
||||||
|
} else {
|
||||||
|
isMobile.value = false;
|
||||||
|
}
|
||||||
|
collapsed.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
const watchWidth = () => {
|
const watchWidth = () => {
|
||||||
const Width = document.body.clientWidth;
|
const Width = document.body.clientWidth;
|
||||||
if (Width <= 950) {
|
if (Width <= 950) {
|
||||||
collapsed.value = true;
|
collapsed.value = true;
|
||||||
} else collapsed.value = false;
|
} else collapsed.value = false;
|
||||||
|
|
||||||
|
checkMobileMode();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
checkMobileMode();
|
||||||
window.addEventListener('resize', watchWidth);
|
window.addEventListener('resize', watchWidth);
|
||||||
//挂载在 window 方便与在js中使用
|
|
||||||
window['$loading'] = useLoadingBar();
|
|
||||||
window['$loading'].finish();
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.layout-side-drawer {
|
||||||
|
background-color: rgb(0, 20, 40);
|
||||||
|
|
||||||
|
.layout-sider {
|
||||||
|
min-height: 100vh;
|
||||||
|
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
|
||||||
|
position: relative;
|
||||||
|
z-index: 13;
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.layout {
|
.layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -213,7 +259,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.fluid-header {
|
.fluid-header {
|
||||||
padding-top: 0px;
|
padding-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-view-fix {
|
.main-view-fix {
|
||||||
|
|||||||
28
src/main.ts
28
src/main.ts
@@ -1,19 +1,23 @@
|
|||||||
import './styles/tailwind.css';
|
import './styles/tailwind.css';
|
||||||
|
import './styles/index.less';
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
|
import { setupNaiveDiscreteApi, setupNaive, setupDirectives } from '@/plugins';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
import router, { setupRouter } from './router';
|
import router, { setupRouter } from './router';
|
||||||
import { setupStore } from '@/store';
|
import { setupStore } from '@/store';
|
||||||
import { setupNaive, setupDirectives } from '@/plugins';
|
|
||||||
import { AppProvider } from '@/components/Application';
|
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const appProvider = createApp(AppProvider);
|
|
||||||
|
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
|
// 挂载状态管理
|
||||||
|
setupStore(app);
|
||||||
|
|
||||||
// 注册全局常用的 naive-ui 组件
|
// 注册全局常用的 naive-ui 组件
|
||||||
setupNaive(app);
|
setupNaive(app);
|
||||||
|
|
||||||
|
// 挂载 naive-ui 脱离上下文的 Api
|
||||||
|
setupNaiveDiscreteApi();
|
||||||
|
|
||||||
// 注册全局自定义组件
|
// 注册全局自定义组件
|
||||||
//setupCustomComponents();
|
//setupCustomComponents();
|
||||||
|
|
||||||
@@ -23,18 +27,18 @@ async function bootstrap() {
|
|||||||
// 注册全局方法,如:app.config.globalProperties.$message = message
|
// 注册全局方法,如:app.config.globalProperties.$message = message
|
||||||
//setupGlobalMethods(app);
|
//setupGlobalMethods(app);
|
||||||
|
|
||||||
// 挂载状态管理
|
|
||||||
setupStore(app);
|
|
||||||
|
|
||||||
//优先挂载一下 Provider 解决路由守卫,Axios中可使用,Dialog,Message 等之类组件
|
|
||||||
appProvider.mount('#appProvider', true);
|
|
||||||
|
|
||||||
// 挂载路由
|
// 挂载路由
|
||||||
await setupRouter(app);
|
setupRouter(app);
|
||||||
|
|
||||||
// 路由准备就绪后挂载APP实例
|
// 路由准备就绪后挂载 APP 实例
|
||||||
|
// https://router.vuejs.org/api/interfaces/router.html#isready
|
||||||
await router.isReady();
|
await router.isReady();
|
||||||
|
|
||||||
|
// https://www.naiveui.com/en-US/os-theme/docs/style-conflict#About-Tailwind's-Preflight-Style-Override
|
||||||
|
const meta = document.createElement('meta');
|
||||||
|
meta.name = 'naive-ui-style';
|
||||||
|
document.head.appendChild(meta);
|
||||||
|
|
||||||
app.mount('#app', true);
|
app.mount('#app', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
|
||||||
import { permission } from '@/directives/permission';
|
import { permission } from '@/directives/permission';
|
||||||
|
import copy from '@/directives/copy';
|
||||||
|
import debounce from '@/directives/debounce';
|
||||||
|
import throttle from '@/directives/throttle';
|
||||||
|
import draggable from '@/directives/draggable';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 注册全局自定义指令
|
* 注册全局自定义指令
|
||||||
@@ -9,4 +13,12 @@ import { permission } from '@/directives/permission';
|
|||||||
export function setupDirectives(app: App) {
|
export function setupDirectives(app: App) {
|
||||||
// 权限控制指令(演示)
|
// 权限控制指令(演示)
|
||||||
app.directive('permission', permission);
|
app.directive('permission', permission);
|
||||||
|
// 复制指令
|
||||||
|
app.directive('copy', copy);
|
||||||
|
// 防抖指令
|
||||||
|
app.directive('debounce', debounce);
|
||||||
|
// 节流指令
|
||||||
|
app.directive('throttle', throttle);
|
||||||
|
// 拖拽指令
|
||||||
|
app.directive('draggable', draggable);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export { setupNaive } from '@/plugins/naive';
|
export { setupNaive } from '@/plugins/naive';
|
||||||
|
export { setupNaiveDiscreteApi } from '@/plugins/naiveDiscreteApi';
|
||||||
export { setupDirectives } from '@/plugins/directives';
|
export { setupDirectives } from '@/plugins/directives';
|
||||||
export { setupCustomComponents } from '@/plugins/customComponents';
|
export { setupCustomComponents } from '@/plugins/customComponents';
|
||||||
export { setupGlobalMethods } from '@/plugins/globalMethods';
|
export { setupGlobalMethods } from '@/plugins/globalMethods';
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { App } from 'vue';
|
import type { App } from 'vue';
|
||||||
import {
|
import {
|
||||||
create,
|
create,
|
||||||
NConfigProvider,
|
|
||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
NDialogProvider,
|
NDialogProvider,
|
||||||
|
NConfigProvider,
|
||||||
NInput,
|
NInput,
|
||||||
NButton,
|
NButton,
|
||||||
NForm,
|
NForm,
|
||||||
@@ -66,8 +66,10 @@ import {
|
|||||||
NTimePicker,
|
NTimePicker,
|
||||||
NBackTop,
|
NBackTop,
|
||||||
NSkeleton,
|
NSkeleton,
|
||||||
|
NCascader,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
|
|
||||||
|
// https://www.naiveui.com/en-US/os-theme/docs/import-on-demand
|
||||||
const naive = create({
|
const naive = create({
|
||||||
components: [
|
components: [
|
||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
@@ -135,6 +137,7 @@ const naive = create({
|
|||||||
NTimePicker,
|
NTimePicker,
|
||||||
NBackTop,
|
NBackTop,
|
||||||
NSkeleton,
|
NSkeleton,
|
||||||
|
NCascader,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
39
src/plugins/naiveDiscreteApi.ts
Normal file
39
src/plugins/naiveDiscreteApi.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import * as NaiveUI from 'naive-ui';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
import { useDesignSetting } from '@/store/modules/designSetting';
|
||||||
|
import { lighten } from '@/utils/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 挂载 Naive-ui 脱离上下文的 API
|
||||||
|
* 如果你想在 setup 外使用 useDialog、useMessage、useNotification、useLoadingBar,可以通过 createDiscreteApi 来构建对应的 API。
|
||||||
|
* https://www.naiveui.com/zh-CN/dark/components/discrete
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function setupNaiveDiscreteApi() {
|
||||||
|
const designStore = useDesignSetting();
|
||||||
|
|
||||||
|
const configProviderPropsRef = computed(() => ({
|
||||||
|
theme: designStore.darkTheme ? NaiveUI.darkTheme : undefined,
|
||||||
|
themeOverrides: {
|
||||||
|
common: {
|
||||||
|
primaryColor: designStore.appTheme,
|
||||||
|
primaryColorHover: lighten(designStore.appTheme, 6),
|
||||||
|
primaryColorPressed: lighten(designStore.appTheme, 6),
|
||||||
|
},
|
||||||
|
LoadingBar: {
|
||||||
|
colorLoading: designStore.appTheme,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
|
||||||
|
['message', 'dialog', 'notification', 'loadingBar'],
|
||||||
|
{
|
||||||
|
configProviderProps: configProviderPropsRef,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
window['$message'] = message;
|
||||||
|
window['$dialog'] = dialog;
|
||||||
|
window['$notification'] = notification;
|
||||||
|
window['$loading'] = loadingBar;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { AppRouteRecordRaw } from '@/router/types';
|
|
||||||
import { ErrorPage, RedirectName, Layout } from '@/router/constant';
|
import { ErrorPage, RedirectName, Layout } from '@/router/constant';
|
||||||
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
// 404 on a page
|
// 404 on a page
|
||||||
export const ErrorPageRoute: AppRouteRecordRaw = {
|
export const ErrorPageRoute: RouteRecordRaw = {
|
||||||
path: '/:path(.*)*',
|
path: '/:path(.*)*',
|
||||||
name: 'ErrorPage',
|
name: 'ErrorPage',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -23,7 +23,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RedirectRoute: AppRouteRecordRaw = {
|
export const RedirectRoute: RouteRecordRaw = {
|
||||||
path: '/redirect',
|
path: '/redirect',
|
||||||
name: RedirectName,
|
name: RedirectName,
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -34,7 +34,7 @@ export const RedirectRoute: AppRouteRecordRaw = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/redirect/:path(.*)',
|
path: '/redirect/:path(.*)',
|
||||||
name: RedirectName,
|
name: `${RedirectName}Son`,
|
||||||
component: () => import('@/views/redirect/index.vue'),
|
component: () => import('@/views/redirect/index.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: RedirectName,
|
title: RedirectName,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { adminMenus } from '@/api/system/menu';
|
import { adminMenus } from '@/api/system/menu';
|
||||||
import { constantRouterIcon } from './router-icons';
|
import { constantRouterIcon } from './icons';
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import { Layout, ParentLayout } from '@/router/constant';
|
import { Layout, ParentLayout } from '@/router/constant';
|
||||||
import type { AppRouteRecordRaw } from '@/router/types';
|
import type { AppRouteRecordRaw } from '@/router/types';
|
||||||
@@ -16,13 +16,13 @@ LayoutMap.set('IFRAME', Iframe);
|
|||||||
* @param parent
|
* @param parent
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
*/
|
*/
|
||||||
export const routerGenerator = (routerMap, parent?): any[] => {
|
export const generateRoutes = (routerMap, parent?): any[] => {
|
||||||
return routerMap.map((item) => {
|
return routerMap.map((item) => {
|
||||||
const currentRouter: any = {
|
const currentRoute: any = {
|
||||||
// 路由地址 动态拼接生成如 /dashboard/workplace
|
// 路由地址 动态拼接生成如 /dashboard/workplace
|
||||||
path: `${(parent && parent.path) || ''}/${item.path}`,
|
path: `${(parent && parent.path) ?? ''}/${item.path}`,
|
||||||
// 路由名称,建议唯一
|
// 路由名称,建议唯一
|
||||||
name: item.name || '',
|
name: item.name ?? '',
|
||||||
// 该路由对应页面的 组件
|
// 该路由对应页面的 组件
|
||||||
component: item.component,
|
component: item.component,
|
||||||
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
|
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
|
||||||
@@ -35,17 +35,17 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
|
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
|
||||||
currentRouter.path = currentRouter.path.replace('//', '/');
|
currentRoute.path = currentRoute.path.replace('//', '/');
|
||||||
// 重定向
|
// 重定向
|
||||||
item.redirect && (currentRouter.redirect = item.redirect);
|
item.redirect && (currentRoute.redirect = item.redirect);
|
||||||
// 是否有子菜单,并递归处理
|
// 是否有子菜单,并递归处理
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
//如果未定义 redirect 默认第一个子路由为 redirect
|
//如果未定义 redirect 默认第一个子路由为 redirect
|
||||||
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
|
!item.redirect && (currentRoute.redirect = `${item.path}/${item.children[0].path}`);
|
||||||
// Recursion
|
// Recursion
|
||||||
currentRouter.children = routerGenerator(item.children, currentRouter);
|
currentRoute.children = generateRoutes(item.children, currentRoute);
|
||||||
}
|
}
|
||||||
return currentRouter;
|
return currentRoute;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,19 +53,11 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
|||||||
* 动态生成菜单
|
* 动态生成菜单
|
||||||
* @returns {Promise<Router>}
|
* @returns {Promise<Router>}
|
||||||
*/
|
*/
|
||||||
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
export const generateDynamicRoutes = async (): Promise<RouteRecordRaw[]> => {
|
||||||
return new Promise((resolve, reject) => {
|
const result = await adminMenus();
|
||||||
adminMenus()
|
const router = generateRoutes(result);
|
||||||
.then((result) => {
|
asyncImportRoute(router);
|
||||||
const routeList = routerGenerator(result);
|
return router;
|
||||||
asyncImportRoute(routeList);
|
|
||||||
|
|
||||||
resolve(routeList);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
reject(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import type { RouteRecordRaw } from 'vue-router';
|
|
||||||
import { isNavigationFailure, Router } from 'vue-router';
|
|
||||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
|
||||||
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
|
|
||||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
|
||||||
import { storage } from '@/utils/Storage';
|
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import { ErrorPageRoute } from '@/router/base';
|
import { ErrorPageRoute } from '@/router/base';
|
||||||
|
import { useAsyncRoute } from '@/store/modules/asyncRoute';
|
||||||
|
import { useUser } from '@/store/modules/user';
|
||||||
|
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||||
|
import { storage } from '@/utils/Storage';
|
||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { isNavigationFailure, Router } from 'vue-router';
|
||||||
|
import { RedirectName } from './constant';
|
||||||
|
|
||||||
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||||
|
|
||||||
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
||||||
|
|
||||||
export function createRouterGuards(router: Router) {
|
export function createRouterGuards(router: Router) {
|
||||||
const userStore = useUserStoreWidthOut();
|
const userStore = useUser();
|
||||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
const asyncRouteStore = useAsyncRoute();
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const Loading = window['$loading'] || null;
|
const Loading = window['$loading'] || null;
|
||||||
Loading && Loading.start();
|
Loading && Loading.start();
|
||||||
@@ -51,12 +52,12 @@ export function createRouterGuards(router: Router) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asyncRouteStore.getIsDynamicAddedRoute) {
|
if (asyncRouteStore.getIsDynamicRouteAdded) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInfo = await userStore.GetInfo();
|
const userInfo = await userStore.getInfo();
|
||||||
|
|
||||||
const routes = await asyncRouteStore.generateRoutes(userInfo);
|
const routes = await asyncRouteStore.generateRoutes(userInfo);
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ export function createRouterGuards(router: Router) {
|
|||||||
const redirectPath = (from.query.redirect || to.path) as string;
|
const redirectPath = (from.query.redirect || to.path) as string;
|
||||||
const redirect = decodeURIComponent(redirectPath);
|
const redirect = decodeURIComponent(redirectPath);
|
||||||
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
|
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
|
||||||
asyncRouteStore.setDynamicAddedRoute(true);
|
asyncRouteStore.setDynamicRouteAdded(true);
|
||||||
next(nextData);
|
next(nextData);
|
||||||
Loading && Loading.finish();
|
Loading && Loading.finish();
|
||||||
});
|
});
|
||||||
@@ -84,14 +85,14 @@ export function createRouterGuards(router: Router) {
|
|||||||
if (isNavigationFailure(failure)) {
|
if (isNavigationFailure(failure)) {
|
||||||
//console.log('failed navigation', failure)
|
//console.log('failed navigation', failure)
|
||||||
}
|
}
|
||||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
const asyncRouteStore = useAsyncRoute();
|
||||||
// 在这里设置需要缓存的组件名称
|
// 在这里设置需要缓存的组件名称
|
||||||
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
|
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
|
||||||
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
|
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
|
||||||
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
|
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
|
||||||
// 需要缓存的组件
|
// 需要缓存的组件
|
||||||
keepAliveComponents.push(currentComName);
|
keepAliveComponents.push(currentComName);
|
||||||
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
|
} else if (!to.meta?.keepAlive || to.name == RedirectName) {
|
||||||
// 不需要缓存的组件
|
// 不需要缓存的组件
|
||||||
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
|
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { RedirectRoute } from '@/router/base';
|
import { RedirectRoute } from '@/router/base';
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import { createRouterGuards } from './router-guards';
|
import { createRouterGuards } from './guards';
|
||||||
|
import type { IModuleType } from './types';
|
||||||
|
|
||||||
const modules = import.meta.globEager('./modules/**/*.ts');
|
const modules = import.meta.glob<IModuleType>('./modules/**/*.ts', { eager: true });
|
||||||
|
|
||||||
const routeModuleList: RouteRecordRaw[] = [];
|
const routeModuleList: RouteRecordRaw[] = Object.keys(modules).reduce((list, key) => {
|
||||||
|
const mod = modules[key].default ?? {};
|
||||||
Object.keys(modules).forEach((key) => {
|
|
||||||
const mod = modules[key].default || {};
|
|
||||||
const modList = Array.isArray(mod) ? [...mod] : [mod];
|
const modList = Array.isArray(mod) ? [...mod] : [mod];
|
||||||
routeModuleList.push(...modList);
|
return [...list, ...modList];
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
function sortRoute(a, b) {
|
function sortRoute(a, b) {
|
||||||
return (a.meta?.sort || 0) - (b.meta?.sort || 0);
|
return (a.meta?.sort ?? 0) - (b.meta?.sort ?? 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
routeModuleList.sort(sortRoute);
|
routeModuleList.sort(sortRoute);
|
||||||
@@ -42,10 +41,10 @@ export const LoginRoute: RouteRecordRaw = {
|
|||||||
export const asyncRoutes = [...routeModuleList];
|
export const asyncRoutes = [...routeModuleList];
|
||||||
|
|
||||||
//普通路由 无需验证权限
|
//普通路由 无需验证权限
|
||||||
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
|
export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, RedirectRoute];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHashHistory(''),
|
history: createWebHistory(),
|
||||||
routes: constantRouter,
|
routes: constantRouter,
|
||||||
strict: true,
|
strict: true,
|
||||||
scrollBehavior: () => ({ left: 0, top: 0 }),
|
scrollBehavior: () => ({ left: 0, top: 0 }),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import { Layout } from '@/router/constant';
|
import { Layout } from '@/router/constant';
|
||||||
import { ProjectOutlined } from '@vicons/antd';
|
import { ProjectOutlined } from '@vicons/antd';
|
||||||
import { renderIcon, renderNew } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
@@ -19,8 +19,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
path: 'index',
|
path: 'index',
|
||||||
name: `about_index`,
|
name: `about_index`,
|
||||||
meta: {
|
meta: {
|
||||||
title: '关于',
|
title: '关于项目',
|
||||||
extra: renderNew(),
|
|
||||||
activeMenu: 'about_index',
|
activeMenu: 'about_index',
|
||||||
},
|
},
|
||||||
component: () => import('@/views/about/index.vue'),
|
component: () => import('@/views/about/index.vue'),
|
||||||
|
|||||||
@@ -5,17 +5,6 @@ import { renderIcon, renderNew } from '@/utils';
|
|||||||
|
|
||||||
const routeName = 'comp';
|
const routeName = 'comp';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/comp',
|
path: '/comp',
|
||||||
|
|||||||
@@ -5,16 +5,6 @@ import { renderIcon } from '@/utils/index';
|
|||||||
|
|
||||||
const routeName = 'dashboard';
|
const routeName = 'dashboard';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
@@ -34,6 +24,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '主控台',
|
title: '主控台',
|
||||||
permissions: ['dashboard_console'],
|
permissions: ['dashboard_console'],
|
||||||
|
affix: true,
|
||||||
},
|
},
|
||||||
component: () => import('@/views/dashboard/console/console.vue'),
|
component: () => import('@/views/dashboard/console/console.vue'),
|
||||||
},
|
},
|
||||||
|
|||||||
31
src/router/modules/directive.ts
Normal file
31
src/router/modules/directive.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { Layout } from '@/router/constant';
|
||||||
|
import { BorderOuterOutlined } from '@vicons/antd';
|
||||||
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: '/directive',
|
||||||
|
name: 'directive',
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
sort: 9,
|
||||||
|
isRoot: true,
|
||||||
|
activeMenu: 'directive_index',
|
||||||
|
icon: renderIcon(BorderOuterOutlined),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
name: `directive_index`,
|
||||||
|
meta: {
|
||||||
|
title: '指令示例',
|
||||||
|
activeMenu: 'directive_index',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/directive/index.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -6,12 +6,12 @@ import { renderIcon } from '@/utils/index';
|
|||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/external',
|
path: '/external',
|
||||||
name: 'https://naive-ui-admin-docs.vercel.app',
|
name: 'https://docs.naiveadmin.com',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
meta: {
|
meta: {
|
||||||
title: '项目文档',
|
title: '项目文档',
|
||||||
icon: renderIcon(DocumentTextOutline),
|
icon: renderIcon(DocumentTextOutline),
|
||||||
sort: 9,
|
sort: 11,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { ExclamationCircleOutlined } from '@vicons/antd';
|
import { ExclamationCircleOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/exception',
|
path: '/exception',
|
||||||
|
|||||||
@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
|
|||||||
import { ProfileOutlined } from '@vicons/antd';
|
import { ProfileOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
/**
|
|
||||||
* @param name 路由名称, 必须设置,且不能重名
|
|
||||||
* @param meta 路由元信息(路由附带扩展信息)
|
|
||||||
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
|
|
||||||
* @param meta.disabled 禁用整个菜单
|
|
||||||
* @param meta.title 菜单名称
|
|
||||||
* @param meta.icon 菜单图标
|
|
||||||
* @param meta.keepAlive 缓存该路由
|
|
||||||
* @param meta.sort 排序越小越排前
|
|
||||||
*
|
|
||||||
* */
|
|
||||||
const routes: Array<RouteRecordRaw> = [
|
const routes: Array<RouteRecordRaw> = [
|
||||||
{
|
{
|
||||||
path: '/form',
|
path: '/form',
|
||||||
|
|||||||
@@ -17,12 +17,21 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
icon: renderIcon(DesktopOutline),
|
icon: renderIcon(DesktopOutline),
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
path: 'naive-admin',
|
||||||
|
name: 'naive-admin',
|
||||||
|
meta: {
|
||||||
|
title: 'NaiveAdmin',
|
||||||
|
frameSrc: 'https://www.naiveadmin.com',
|
||||||
|
},
|
||||||
|
component: IFrame,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'docs',
|
path: 'docs',
|
||||||
name: 'frame-docs',
|
name: 'frame-docs',
|
||||||
meta: {
|
meta: {
|
||||||
title: '项目文档(内嵌)',
|
title: '项目文档(内嵌)',
|
||||||
frameSrc: 'https://naive-ui-admin-docs.vercel.app',
|
frameSrc: 'https://jekip.github.io/docs',
|
||||||
},
|
},
|
||||||
component: IFrame,
|
component: IFrame,
|
||||||
},
|
},
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user