mirror of
https://github.com/jekip/naive-ui-admin.git
synced 2026-02-10 16:32:25 +08:00
Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8288f0a84b | ||
|
|
c0ff8985ea | ||
|
|
a8400ac475 | ||
|
|
d4b173e3c8 | ||
|
|
1ab8b5b221 | ||
|
|
fc5e138ed8 | ||
|
|
b9b2c6a07e | ||
|
|
3503569dfe | ||
|
|
125b7f44e3 | ||
|
|
3307b927a4 | ||
|
|
97de86eacb | ||
|
|
cb92f6e87b | ||
|
|
4f81743f90 | ||
|
|
7837c87392 | ||
|
|
17a5d08d94 | ||
|
|
19e5e5fc4a | ||
|
|
822e69deec | ||
|
|
3bf1e941d4 | ||
|
|
ef4912636e | ||
|
|
7929a74d20 | ||
|
|
45862d4f9d | ||
|
|
8e6471060c | ||
|
|
30c0cd5c95 | ||
|
|
259e73c056 | ||
|
|
8eaa889399 | ||
|
|
d1635add5f | ||
|
|
531a31ee5d | ||
|
|
3b64fc1563 | ||
|
|
229fc72cda | ||
|
|
900954d757 | ||
|
|
fca4dddaf6 | ||
|
|
4add3c3eaa | ||
|
|
21e80f8041 | ||
|
|
a63c34dbb5 | ||
|
|
e02e96d4e0 | ||
|
|
70c2b75f17 | ||
|
|
b186e045bc | ||
|
|
742749a838 | ||
|
|
82dc7d2589 | ||
|
|
f11af4fc76 | ||
|
|
85f1dbd5e9 | ||
|
|
ae001fc7bd | ||
|
|
be770016bf | ||
|
|
1bf722bed0 | ||
|
|
8c7dd14004 | ||
|
|
a0490e3b97 | ||
|
|
905984367c | ||
|
|
d3f7fa0f9e | ||
|
|
9c512002d2 | ||
|
|
737f967aab | ||
|
|
1cdb02c9d7 | ||
|
|
bc8dd21405 | ||
|
|
2dba60405e | ||
|
|
eba3047be2 | ||
|
|
0979b5af5d | ||
|
|
ade138997d | ||
|
|
d388ae5656 | ||
|
|
8f05b20ffa | ||
|
|
d973b2a543 | ||
|
|
1d5113a663 | ||
|
|
f331d9c4c7 | ||
|
|
c647e19d06 | ||
|
|
5c5c52d9fa | ||
|
|
3e0b8efe7e | ||
|
|
450234e7ea | ||
|
|
5116c387d5 | ||
|
|
8a5f237630 | ||
|
|
1e3ccaa6dc | ||
|
|
98e1bf0227 | ||
|
|
6a290b314a | ||
|
|
58f0997fb6 | ||
|
|
e602fc50c0 | ||
|
|
81a3e6d970 | ||
|
|
0c709871f3 | ||
|
|
9d9cac8064 | ||
|
|
b8f8334539 | ||
|
|
361a2a14c7 | ||
|
|
dd4e6c1670 | ||
|
|
85d39add87 | ||
|
|
20da92aeab | ||
|
|
57245d21ee | ||
|
|
f97a94e74c | ||
|
|
fd6fd723d7 | ||
|
|
da5231b384 | ||
|
|
97ae37efd0 | ||
|
|
b19430170f | ||
|
|
b642d28815 |
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
*.sh
|
*.sh
|
||||||
node_modules
|
node_modules
|
||||||
*.md
|
*.md
|
||||||
@@ -13,3 +12,5 @@ dist
|
|||||||
.local
|
.local
|
||||||
/bin
|
/bin
|
||||||
Dockerfile
|
Dockerfile
|
||||||
|
components.d.ts
|
||||||
|
components.d.ts
|
||||||
|
|||||||
@@ -22,9 +22,9 @@ module.exports = defineConfig({
|
|||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'prettier',
|
'prettier',
|
||||||
'plugin:prettier/recommended',
|
'plugin:prettier/recommended',
|
||||||
'plugin:jest/recommended',
|
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
|
'vue/script-setup-uses-vars': 'error',
|
||||||
'@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',
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -23,3 +23,5 @@ pnpm-debug.log*
|
|||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
/components.d.ts
|
||||||
|
/components.d.ts
|
||||||
|
|||||||
110
CHANGELOG.md
110
CHANGELOG.md
@@ -1,4 +1,102 @@
|
|||||||
# 1.5 (2021-07-30)
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 1.6.1 (2022-01-06)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复 `项目配置` 打开空白
|
||||||
|
- 修复 `多标签` 背景和字体色变量丢失
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.6.0 (2021-12-24)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复 `低版本浏览器` 报 globalThis 未定义
|
||||||
|
- 修复 `Axios` api地址拼接异常
|
||||||
|
- 修复 `createStorage存在prefixKey` 会出bug
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 破坏 `Axios` 取消默认导出 `http` 可支持多个请求导出
|
||||||
|
- 搜索 `import http from '@/utils/http/axios'` 替换为 `import { http } from '@/utils/http/axios`
|
||||||
|
- 新增 `Axios` 多项配置 `urlPrefix`,`joinTime`,`ignoreCancelToken`,`withToken`,`uploadFile方法`
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
## 1.5.5 (2021-08-14)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复路由只存在一个子路由,图标不显示问题
|
||||||
|
- UI样式美化
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 支持 Vue 3.2.x
|
||||||
|
- 代码全部按 `script setup` 语法重写(完成80%)
|
||||||
|
- 新增 `回到顶部` 功能
|
||||||
|
- 新增 `拖拽` 示例页面
|
||||||
|
- 新增 `富文本` 组件
|
||||||
|
- 新增 `路由切换动画` 可在项目设置切换
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
# CHANGELOG
|
||||||
|
|
||||||
|
## 1.5.4 (2021-08-10)
|
||||||
|
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
|
||||||
|
- `暗色模式下多页签背景问题 ` 合并 [#23](https://github.com/jekip/naive-ui-admin/pull/23) 感谢 [@Dishone](https://github.com/Dishone)
|
||||||
|
- `表格设置列,重复添加action列样式错乱问题` 合并 [#24](https://github.com/jekip/naive-ui-admin/pull/24) 感谢 [@CasbaL](https://github.com/CasbaL)
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `路由支持(内联外部地址)`配置
|
||||||
|
- 新增 `顶部菜单` logo展示
|
||||||
|
-(破坏性更新)
|
||||||
|
- 优化 `动态路由配置` 取消`constantRouterComponents.ts`,中组件映射配置,更名为 `router-icons.ts`
|
||||||
|
- 优化 `admin_info接口结构`,roles 更名为:permissions,roles.roleName,更名为:label
|
||||||
|
- 优化 多级路由,当没有配置`redirect`时,默认为第一个子路由,配置则优先按配置
|
||||||
|
- 依赖升级
|
||||||
|
|
||||||
|
# 1.5.3 (2021-08-09)
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复顶部菜单,选中联动
|
||||||
|
- 修复混合菜单模式,切换其他模式菜单未重置
|
||||||
|
- 实例基础列表,和表格组件实例,开启横向滚动特性
|
||||||
|
- `naiveui` 升级成最新版
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- table组件,默认开启 `ellipsis` 特性
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 1.5.2 (2021-08-06)
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复已知bug
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `混合菜单模式`
|
||||||
|
- 新增 `根路由`
|
||||||
|
- 新增 `关于` 根路由示例页面
|
||||||
|
- 文档同步更新,组件和示例
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 1.5.1 (2021-08-05)
|
||||||
|
### 🐛 Bug Fixes
|
||||||
|
- 修复windows系统获取项目换行符问题
|
||||||
|
- 修复表格分页计算问题 [@Chika99](https://github.com/Chika99)
|
||||||
|
- 修复锁屏样式自适应问题 [@Chika99](https://github.com/Chika99)
|
||||||
|
- 依赖 dayjs 移除,用date-fns,和UI框架底层保持一致
|
||||||
|
- 修复已知bug
|
||||||
|
|
||||||
|
- ### ✨ Features
|
||||||
|
- 新增 `baseForm` 组件,和`基础`,`useForm`使用方式
|
||||||
|
- 新增 `baseModal`,组件,和 `useForm`使用方式
|
||||||
|
- 新增`子菜单` new Tag标签
|
||||||
|
- 菜单支持 `根路由`配置
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 1.5.0 (2021-07-30)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- 修复表格列配置,拖拽时最后的操作列重复增加
|
- 修复表格列配置,拖拽时最后的操作列重复增加
|
||||||
- 多标签页交互优化
|
- 多标签页交互优化
|
||||||
@@ -15,7 +113,7 @@
|
|||||||
- 本次更新,有破坏性更新,涉及文件重命名,增删调整
|
- 本次更新,有破坏性更新,涉及文件重命名,增删调整
|
||||||
|
|
||||||
|
|
||||||
# 1.4 (2021-07-21)
|
# 1.4.0 (2021-07-21)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- vite降至2.3.6
|
- vite降至2.3.6
|
||||||
- 多标签页交互优化
|
- 多标签页交互优化
|
||||||
@@ -27,7 +125,7 @@
|
|||||||
- 持续更新更多实用组件及示例,感谢Star
|
- 持续更新更多实用组件及示例,感谢Star
|
||||||
|
|
||||||
|
|
||||||
# 1.3 (2021-07-19)
|
# 1.3.0 (2021-07-19)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- 修复多标签页左右切换按钮自适应展示
|
- 修复多标签页左右切换按钮自适应展示
|
||||||
- 修复登录页面出现多标签页
|
- 修复登录页面出现多标签页
|
||||||
@@ -40,7 +138,7 @@
|
|||||||
- 持续更新更多实用组件及示例,感谢Star
|
- 持续更新更多实用组件及示例,感谢Star
|
||||||
|
|
||||||
|
|
||||||
# 1.2 (2021-07-16)
|
# 1.2.0 (2021-07-16)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- 修复面包屑显示登录页面
|
- 修复面包屑显示登录页面
|
||||||
- 菜单支持只展开当前父级菜单
|
- 菜单支持只展开当前父级菜单
|
||||||
@@ -54,7 +152,7 @@
|
|||||||
- 持续更新更多实用示例,同时也演示`Naive UI`使用方法
|
- 持续更新更多实用示例,同时也演示`Naive UI`使用方法
|
||||||
|
|
||||||
|
|
||||||
# 1.1 (2021-07-15)
|
# 1.1.0 (2021-07-15)
|
||||||
- ### ✨ Features
|
- ### ✨ Features
|
||||||
- 新增 `基础表单` 示例页面
|
- 新增 `基础表单` 示例页面
|
||||||
- 新增 `分步表单` 示例页面
|
- 新增 `分步表单` 示例页面
|
||||||
@@ -62,7 +160,7 @@
|
|||||||
- 持续更新更多实用示例,同时也演示`Naive UI`使用方法
|
- 持续更新更多实用示例,同时也演示`Naive UI`使用方法
|
||||||
|
|
||||||
|
|
||||||
# 1.0 (2021-07-12)
|
# 1.0.0 (2021-07-12)
|
||||||
### 🐛 Bug Fixes
|
### 🐛 Bug Fixes
|
||||||
- 修复页面切换面包屑未及时更新
|
- 修复页面切换面包屑未及时更新
|
||||||
|
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,24 +1,51 @@
|
|||||||
|
## 留步
|
||||||
|
少侠留步,早知如此绊人心,何如当初莫相识,右上角免费的 `Star` 点一点,帮我们突破一下 `2k`,谢谢!O(∩_∩)O
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
|
|
||||||
[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/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目,该项目使用最新的前端技术栈,相信不管是从新技术使用还是其他方面,都能帮助到你。
|
[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/vite2 等前端前沿技术开发
|
- 二次封装实用高扩展性组件
|
||||||
- **TypeScript**: 应用程序级 JavaScript 的语言
|
- 响应式、多主题,多配置,快速集成,开箱即用
|
||||||
- **主题**:可配置的主题
|
- 最新技术栈,使用 `Vue3`、`Typescript`、`Pinia`、`Vite` 等前端前沿技术
|
||||||
- **Mock 数据** 内置 Mock 数据方案
|
- 强大的鉴权系统,对路由、菜单、功能点等支持`三种鉴权模式`,满足不同的业务鉴权需求
|
||||||
- **权限** 内置完善的动态路由权限生成方案
|
- 持续更新,实用性页面模板功能和交互,随意搭配组合,让构建页面变得简单化
|
||||||
- **组件** 二次封装了多个常用的组件
|
|
||||||
|
|
||||||
|
|
||||||
## 在线预览
|
## 预览
|
||||||
- [naive-ui-admin](https://jekip.github.io)
|
- [naive-ui-admin](https://naive-ui-admin.vercel.app)
|
||||||
|
|
||||||
账号:admin,密码:123456(随意)
|
账号:admin,密码:123456(随意)
|
||||||
|
|
||||||
|
## 提示
|
||||||
|
|
||||||
|
如果这个版本的功能和组件,并不能满足您的需求,不妨看看,我们全新 `NaiveAdmin v2` 他或许能让您眼前一亮O(∩_∩)O哈哈~
|
||||||
|
|
||||||
|
[NaiveAdmin 官网](https://www.naiveadmin.com)
|
||||||
|
|
||||||
|
[NaiveAdmin v2 预览](https://pro.naiveadmin.com)
|
||||||
|
|
||||||
|
[NaiveAdmin v2 变更日志](https://www.naiveadmin.com/guide/changelog)
|
||||||
|
|
||||||
|
## 新品
|
||||||
|
|
||||||
|
### Antd vue
|
||||||
|
|
||||||
|
千呼万唤 `Naive Admin Antd` 也迎来了第一个版本,同时具备 `Naive Ui Admin` 优点,如果您选的技术栈是 `Antd` 的话,不妨看看。
|
||||||
|
|
||||||
|
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
|
||||||
|
|
||||||
|
### Arco vue
|
||||||
|
|
||||||
|
新产品,新生态,智能设计体系,连接轻盈体验,一如既往、开箱即用,欢迎前往查看。
|
||||||
|
|
||||||
|
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
|
||||||
|
|
||||||
|
|
||||||
## 文档
|
## 文档
|
||||||
|
|
||||||
[文档地址](https://jekip.github.io/docs/)
|
[v1文档地址](https://naive-ui-admin-docs.vercel.app)
|
||||||
|
|
||||||
## 准备
|
## 准备
|
||||||
|
|
||||||
@@ -31,7 +58,7 @@
|
|||||||
- [Naive-ui-admin](https://www.naiveui.com/) - ui 基本使用
|
- [Naive-ui-admin](https://www.naiveui.com/) - ui 基本使用
|
||||||
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
|
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
|
||||||
|
|
||||||
## 安装使用
|
## 使用
|
||||||
|
|
||||||
- 获取项目代码
|
- 获取项目代码
|
||||||
|
|
||||||
@@ -64,9 +91,6 @@ yarn build
|
|||||||
|
|
||||||
[CHANGELOG](./CHANGELOG.md)
|
[CHANGELOG](./CHANGELOG.md)
|
||||||
|
|
||||||
## 感谢
|
|
||||||
[@Vben](https://github.com/anncwb/vue-vben-admin) 借鉴 vue-vben-admin 实现的骨架,同时也使用作者开发的 vite 插件,再次感谢作者。
|
|
||||||
|
|
||||||
|
|
||||||
## 如何贡献
|
## 如何贡献
|
||||||
|
|
||||||
@@ -113,7 +137,11 @@ yarn build
|
|||||||
|
|
||||||
## 交流
|
## 交流
|
||||||
|
|
||||||
`Naive Ui Admin` 是完全开源免费的项目,在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
|
`Naive Ui Admin` 在帮助开发者更方便地进行中大型管理系统开发,同时也提供 QQ 交流群使用问题欢迎在群内提问。
|
||||||
|
|
||||||
- QQ 群 `328347666`
|
- QQ 群 `328347666`
|
||||||
|
|
||||||
|
## 赞助
|
||||||
|
#### 如果你觉得这个项目帮助到了你,你可以帮作者买一杯果汁表示鼓励 🍹。
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
* @param env
|
* @param env
|
||||||
*/
|
*/
|
||||||
export const getConfigFileName = (env: Record<string, any>) => {
|
export const getConfigFileName = (env: Record<string, any>) => {
|
||||||
return `__PRODUCTION__${ env.VITE_GLOB_APP_SHORT_NAME || '__APP' }__CONF__`
|
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
|
||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.replace(/\s/g, '');
|
.replace(/\s/g, '');
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,19 +18,19 @@ function createConfig(
|
|||||||
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
|
}: { configName: string; config: any; configFileName?: string } = { configName: '', config: {} }
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const windowConf = `window.${ configName }`;
|
const windowConf = `window.${configName}`;
|
||||||
// Ensure that the variable will not be modified
|
// Ensure that the variable will not be modified
|
||||||
const configStr = `${ windowConf }=${ JSON.stringify(config) };
|
const configStr = `${windowConf}=${JSON.stringify(config)};
|
||||||
Object.freeze(${ windowConf });
|
Object.freeze(${windowConf});
|
||||||
Object.defineProperty(window, "${ configName }", {
|
Object.defineProperty(window, "${configName}", {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
writable: false,
|
writable: false,
|
||||||
});
|
});
|
||||||
`.replace(/\s/g, '');
|
`.replace(/\s/g, '');
|
||||||
fs.mkdirp(getRootPath(OUTPUT_DIR));
|
fs.mkdirp(getRootPath(OUTPUT_DIR));
|
||||||
writeFileSync(getRootPath(`${ OUTPUT_DIR }/${ configFileName }`), configStr);
|
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);
|
||||||
|
|
||||||
console.log(chalk.cyan(`✨ [${ pkg.name }]`) + ` - configuration file is build successfully:`);
|
console.log(chalk.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
|
||||||
console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n');
|
console.log(chalk.gray(OUTPUT_DIR + '/' + chalk.green(configFileName)) + '\n');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('configuration file configuration file failed to package:\n' + error));
|
console.log(chalk.red('configuration file configuration file failed to package:\n' + error));
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export const runBuild = async () => {
|
|||||||
await runBuildConfig();
|
await runBuildConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✨ ${ chalk.cyan(`[${ pkg.name }]`) }` + ' - build successfully!');
|
console.log(`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(chalk.red('vite build error:\n' + error));
|
console.log(chalk.red('vite build error:\n' + error));
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import { GLOB_CONFIG_FILE_NAME } from '../../constant';
|
|||||||
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
|
export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
|
||||||
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
|
const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env;
|
||||||
|
|
||||||
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${ VITE_PUBLIC_PATH }/`;
|
const path = VITE_PUBLIC_PATH.endsWith('/') ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`;
|
||||||
|
|
||||||
const getAppConfigSrc = () => {
|
const getAppConfigSrc = () => {
|
||||||
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: Plugin[] = html({
|
||||||
@@ -28,13 +28,13 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
|
|||||||
// Embed the generated app.config.js file
|
// Embed the generated app.config.js file
|
||||||
tags: isBuild
|
tags: isBuild
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
tag: 'script',
|
tag: 'script',
|
||||||
attrs: {
|
attrs: {
|
||||||
src: getAppConfigSrc(),
|
src: getAppConfigSrc(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
]
|
||||||
]
|
|
||||||
: [],
|
: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import type { Plugin } from 'vite';
|
import type { Plugin } from 'vite';
|
||||||
|
import Components from 'unplugin-vue-components/vite';
|
||||||
|
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||||
|
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||||
@@ -15,6 +17,12 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock)
|
|||||||
vue(),
|
vue(),
|
||||||
// have to
|
// have to
|
||||||
vueJsx(),
|
vueJsx(),
|
||||||
|
|
||||||
|
// 按需引入NaiveUi且自动创建组件声明
|
||||||
|
Components({
|
||||||
|
dts: true,
|
||||||
|
resolvers: [NaiveUiResolver()],
|
||||||
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// vite-plugin-html
|
// vite-plugin-html
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export function configStyleImportPlugin(isBuild: boolean) {
|
|||||||
libraryName: 'ant-design-vue',
|
libraryName: 'ant-design-vue',
|
||||||
esModule: true,
|
esModule: true,
|
||||||
resolveStyle: (name) => {
|
resolveStyle: (name) => {
|
||||||
return `ant-design-vue/es/${ name }/style/index`;
|
return `ant-design-vue/es/${name}/style/index`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module.exports = {
|
|||||||
'fixed',
|
'fixed',
|
||||||
'resolve',
|
'resolve',
|
||||||
'resolves',
|
'resolves',
|
||||||
'resolved'
|
'resolved',
|
||||||
],
|
],
|
||||||
issuePrefixes: ['#'],
|
issuePrefixes: ['#'],
|
||||||
noteKeywords: ['BREAKING CHANGE'],
|
noteKeywords: ['BREAKING CHANGE'],
|
||||||
@@ -23,8 +23,8 @@ module.exports = {
|
|||||||
revertCorrespondence: ['header', 'hash'],
|
revertCorrespondence: ['header', 'hash'],
|
||||||
warn() {},
|
warn() {},
|
||||||
mergePattern: null,
|
mergePattern: null,
|
||||||
mergeCorrespondence: null
|
mergeCorrespondence: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'body-leading-blank': [2, 'always'],
|
'body-leading-blank': [2, 'always'],
|
||||||
@@ -50,8 +50,8 @@ module.exports = {
|
|||||||
'wip',
|
'wip',
|
||||||
'workflow',
|
'workflow',
|
||||||
'types',
|
'types',
|
||||||
'release'
|
'release',
|
||||||
]
|
],
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
116
index.html
116
index.html
@@ -1,26 +1,122 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="zh-cmn-Hans">
|
<html lang="zh-cmn-Hans" id="htmlRoot" data-theme="light">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
|
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"/>
|
||||||
<meta name="renderer" content="webkit"/>
|
<meta content="webkit" name="renderer"/>
|
||||||
<meta
|
<meta
|
||||||
name="viewport"
|
|
||||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||||
|
name="viewport"
|
||||||
/>
|
/>
|
||||||
<link rel="icon" href="/favicon.ico"/>
|
<link href="/favicon.ico" rel="icon"/>
|
||||||
<title><%= title %></title>
|
<title><%= title %></title>
|
||||||
<style>.first-loading-wrp{display:flex;justify-content:center;align-items:center;flex-direction:column;min-height:420px;height:100%}.first-loading-wrp>h1{font-size:128px}.first-loading-wrp .loading-wrp{padding:98px;display:flex;justify-content:center;align-items:center}.dot{animation:antRotate 1.2s infinite linear;transform:rotate(45deg);position:relative;display:inline-block;font-size:32px;width:32px;height:32px;box-sizing:border-box}.dot i{width:14px;height:14px;position:absolute;display:block;background-color:#1890ff;border-radius:100%;transform:scale(.75);transform-origin:50% 50%;opacity:.3;animation:antSpinMove 1s infinite linear alternate}.dot i:nth-child(1){top:0;left:0}.dot i:nth-child(2){top:0;right:0;-webkit-animation-delay:.4s;animation-delay:.4s}.dot i:nth-child(3){right:0;bottom:0;-webkit-animation-delay:.8s;animation-delay:.8s}.dot i:nth-child(4){bottom:0;left:0;-webkit-animation-delay:1.2s;animation-delay:1.2s}@keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@-webkit-keyframes antRotate{to{-webkit-transform:rotate(405deg);transform:rotate(405deg)}}@keyframes antSpinMove{to{opacity:1}}@-webkit-keyframes antSpinMove{to{opacity:1}}</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="appProvider" style="display: none"></div>
|
<div id="appProvider" style="display: none"></div>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div class="first-loading-wrp">
|
<style>
|
||||||
<div class="loading-wrp">
|
.first-loading-wrap {
|
||||||
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-loading-wrap > h1 {
|
||||||
|
font-size: 128px
|
||||||
|
}
|
||||||
|
|
||||||
|
.first-loading-wrap .loading-wrap {
|
||||||
|
padding: 98px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
animation: antRotate 1.2s infinite linear;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 32px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
box-sizing: border-box
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot i {
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
background-color: #1890ff;
|
||||||
|
border-radius: 100%;
|
||||||
|
transform: scale(.75);
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
opacity: .3;
|
||||||
|
animation: antSpinMove 1s infinite linear alternate
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot i:nth-child(1) {
|
||||||
|
top: 0;
|
||||||
|
left: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot i:nth-child(2) {
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
-webkit-animation-delay: .4s;
|
||||||
|
animation-delay: .4s
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot i:nth-child(3) {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
-webkit-animation-delay: .8s;
|
||||||
|
animation-delay: .8s
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot i:nth-child(4) {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
-webkit-animation-delay: 1.2s;
|
||||||
|
animation-delay: 1.2s
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antRotate {
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(405deg);
|
||||||
|
transform: rotate(405deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes antRotate {
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(405deg);
|
||||||
|
transform: rotate(405deg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes antSpinMove {
|
||||||
|
to {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes antSpinMove {
|
||||||
|
to {
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
}</style>
|
||||||
|
<div class="first-loading-wrap">
|
||||||
|
<div class="loading-wrap">
|
||||||
|
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script>var globalThis = window;</script>
|
||||||
|
<script src="/src/main.ts" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const tableList = (pageSize) => {
|
|||||||
const result: any[] = [];
|
const result: any[] = [];
|
||||||
doCustomTimes(pageSize, () => {
|
doCustomTimes(pageSize, () => {
|
||||||
result.push({
|
result.push({
|
||||||
id: '@integer(10,100)',
|
id: '@integer(10,999999)',
|
||||||
beginTime: '@datetime',
|
beginTime: '@datetime',
|
||||||
endTime: '@datetime',
|
endTime: '@datetime',
|
||||||
address: '@city()',
|
address: '@city()',
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const menusList = [
|
|||||||
{
|
{
|
||||||
path: '/dashboard',
|
path: '/dashboard',
|
||||||
name: 'Dashboard',
|
name: 'Dashboard',
|
||||||
component: 'Layout',
|
component: 'LAYOUT',
|
||||||
redirect: '/dashboard/console',
|
redirect: '/dashboard/console',
|
||||||
meta: {
|
meta: {
|
||||||
icon: 'DashboardOutlined',
|
icon: 'DashboardOutlined',
|
||||||
@@ -14,7 +14,7 @@ const menusList = [
|
|||||||
{
|
{
|
||||||
path: 'console',
|
path: 'console',
|
||||||
name: 'dashboard_console',
|
name: 'dashboard_console',
|
||||||
component: 'DashboardConsole',
|
component: '/dashboard/console/console',
|
||||||
meta: {
|
meta: {
|
||||||
title: '主控台',
|
title: '主控台',
|
||||||
},
|
},
|
||||||
@@ -22,7 +22,7 @@ const menusList = [
|
|||||||
{
|
{
|
||||||
path: 'monitor',
|
path: 'monitor',
|
||||||
name: 'dashboard_monitor',
|
name: 'dashboard_monitor',
|
||||||
component: 'DashboardMonitor',
|
component: '/dashboard/monitor/monitor',
|
||||||
meta: {
|
meta: {
|
||||||
title: '监控页',
|
title: '监控页',
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@ const menusList = [
|
|||||||
{
|
{
|
||||||
path: 'workplace',
|
path: 'workplace',
|
||||||
name: 'dashboard_workplace',
|
name: 'dashboard_workplace',
|
||||||
component: 'DashboardWorkplace',
|
component: '/dashboard/workplace/workplace',
|
||||||
meta: {
|
meta: {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
title: '工作台',
|
title: '工作台',
|
||||||
|
|||||||
@@ -13,25 +13,25 @@ const adminInfo = {
|
|||||||
desc: 'manager',
|
desc: 'manager',
|
||||||
password: Random.string('upper', 4, 16),
|
password: Random.string('upper', 4, 16),
|
||||||
token,
|
token,
|
||||||
roles: [
|
permissions: [
|
||||||
{
|
{
|
||||||
roleName: '主控台',
|
label: '主控台',
|
||||||
value: 'dashboard_console',
|
value: 'dashboard_console',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
roleName: '监控页',
|
label: '监控页',
|
||||||
value: 'dashboard_monitor',
|
value: 'dashboard_monitor',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
roleName: '工作台',
|
label: '工作台',
|
||||||
value: 'dashboard_workplace',
|
value: 'dashboard_workplace',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
roleName: '基础列表',
|
label: '基础列表',
|
||||||
value: 'basic_list',
|
value: 'basic_list',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
roleName: '基础列表删除',
|
label: '基础列表删除',
|
||||||
value: 'basic_list_delete',
|
value: 'basic_list_delete',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
39
package.json
39
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "naive-ui-admin",
|
"name": "naive-ui-admin",
|
||||||
"version": "1.5",
|
"version": "1.6.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Ahjung",
|
"name": "Ahjung",
|
||||||
"email": "735878602@qq.com",
|
"email": "735878602@qq.com",
|
||||||
@@ -27,10 +27,11 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vicons/antd": "^0.10.0",
|
"@vicons/antd": "^0.10.0",
|
||||||
"@vicons/ionicons5": "^0.10.0",
|
"@vicons/ionicons5": "^0.10.0",
|
||||||
|
"@vueup/vue-quill": "^1.0.0-beta.7",
|
||||||
"@vueuse/core": "^5.0.3",
|
"@vueuse/core": "^5.0.3",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"blueimp-md5": "^2.18.0",
|
"blueimp-md5": "^2.18.0",
|
||||||
"dayjs": "^1.10.5",
|
"date-fns": "^2.23.0",
|
||||||
"echarts": "^5.1.2",
|
"echarts": "^5.1.2",
|
||||||
"element-resize-detector": "^1.2.3",
|
"element-resize-detector": "^1.2.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@@ -38,37 +39,36 @@
|
|||||||
"makeit-captcha": "^1.2.5",
|
"makeit-captcha": "^1.2.5",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"mockjs": "^1.1.0",
|
"mockjs": "^1.1.0",
|
||||||
"naive-ui": "^2.15.11",
|
"naive-ui": "^2.23.2",
|
||||||
"pinia": "^2.0.0-beta.3",
|
"pinia": "^2.0.0-rc.4",
|
||||||
"qs": "^6.10.1",
|
"qs": "^6.10.1",
|
||||||
"vfonts": "^0.1.0",
|
"vfonts": "^0.1.0",
|
||||||
"vue": "^3.1.2",
|
"vue": "^3.2.16",
|
||||||
"vue-router": "^4.0.10",
|
"vue-router": "^4.0.11",
|
||||||
"vue-types": "^4.0.0",
|
"vue-types": "^4.1.0",
|
||||||
"vuedraggable": "^4.0.3",
|
"vuedraggable": "^4.0.3"
|
||||||
"vuex": "^4.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "^12.1.4",
|
"@commitlint/cli": "^12.1.4",
|
||||||
"@commitlint/config-conventional": "^12.1.4",
|
"@commitlint/config-conventional": "^12.1.4",
|
||||||
"@types/lodash": "^4.14.170",
|
"@types/lodash": "^4.14.170",
|
||||||
"@types/node": "^15.12.2",
|
"@types/node": "^15.12.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.26.1",
|
"@typescript-eslint/eslint-plugin": "^4.31.2",
|
||||||
"@typescript-eslint/parser": "^4.26.1",
|
"@typescript-eslint/parser": "^4.31.2",
|
||||||
"@vitejs/plugin-vue": "^1.2.3",
|
"@vitejs/plugin-vue": "^1.9.1",
|
||||||
"@vitejs/plugin-vue-jsx": "^1.1.5",
|
"@vitejs/plugin-vue-jsx": "^1.1.8",
|
||||||
"@vue/compiler-sfc": "3.1.1",
|
"@vue/compiler-sfc": "^3.2.16",
|
||||||
"@vue/eslint-config-typescript": "^7.0.0",
|
"@vue/eslint-config-typescript": "^7.0.0",
|
||||||
"autoprefixer": "^10.3.1",
|
"autoprefixer": "^10.3.1",
|
||||||
"commitizen": "^4.2.4",
|
"commitizen": "^4.2.4",
|
||||||
"core-js": "^3.14.0",
|
"core-js": "^3.14.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"eslint": "^7.28.0",
|
"eslint": "^7.32.0",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-define-config": "^1.0.9",
|
"eslint-define-config": "^1.0.9",
|
||||||
"eslint-plugin-jest": "^24.4.0",
|
"eslint-plugin-jest": "^24.4.0",
|
||||||
"eslint-plugin-prettier": "^3.4.0",
|
"eslint-plugin-prettier": "^3.4.0",
|
||||||
"eslint-plugin-vue": "^7.11.1",
|
"eslint-plugin-vue": "^7.18.0",
|
||||||
"esno": "^0.7.3",
|
"esno": "^0.7.3",
|
||||||
"gh-pages": "^3.2.0",
|
"gh-pages": "^3.2.0",
|
||||||
"husky": "^6.0.0",
|
"husky": "^6.0.0",
|
||||||
@@ -86,13 +86,14 @@
|
|||||||
"stylelint-order": "^4.1.0",
|
"stylelint-order": "^4.1.0",
|
||||||
"stylelint-scss": "^3.19.0",
|
"stylelint-scss": "^3.19.0",
|
||||||
"tailwindcss": "^2.2.7",
|
"tailwindcss": "^2.2.7",
|
||||||
"typescript": "^4.3.5",
|
"typescript": "^4.4.3",
|
||||||
"vite": "2.3.6",
|
"unplugin-vue-components": "^0.17.2",
|
||||||
|
"vite": "^2.5.10",
|
||||||
"vite-plugin-compression": "^0.3.1",
|
"vite-plugin-compression": "^0.3.1",
|
||||||
"vite-plugin-html": "^2.0.7",
|
"vite-plugin-html": "^2.0.7",
|
||||||
"vite-plugin-mock": "^2.9.3",
|
"vite-plugin-mock": "^2.9.3",
|
||||||
"vite-plugin-style-import": "^1.0.1",
|
"vite-plugin-style-import": "^1.0.1",
|
||||||
"vue-eslint-parser": "^7.8.0"
|
"vue-eslint-parser": "^7.11.0"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{vue,js,ts,tsx}": "eslint --fix"
|
"*.{vue,js,ts,tsx}": "eslint --fix"
|
||||||
|
|||||||
@@ -3,4 +3,4 @@ module.exports = {
|
|||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -15,6 +15,6 @@ module.exports = {
|
|||||||
requirePragma: false,
|
requirePragma: false,
|
||||||
proseWrap: 'never',
|
proseWrap: 'never',
|
||||||
htmlWhitespaceSensitivity: 'strict',
|
htmlWhitespaceSensitivity: 'strict',
|
||||||
endOfLine: 'lf',
|
endOfLine: 'auto',
|
||||||
rangeStart: 0,
|
rangeStart: 0,
|
||||||
};
|
};
|
||||||
|
|||||||
115
src/App.vue
115
src/App.vue
@@ -16,86 +16,71 @@
|
|||||||
</transition>
|
</transition>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, computed, onMounted, onUnmounted } from 'vue';
|
import { computed, onMounted, onUnmounted } from 'vue';
|
||||||
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, darkTheme } from 'naive-ui';
|
import { zhCN, dateZhCN, createTheme, inputDark, datePickerDark, 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 { useLockscreenStore } from '@/store/modules/lockscreen';
|
||||||
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';
|
||||||
|
|
||||||
export default defineComponent({
|
const route = useRoute();
|
||||||
name: 'App',
|
const useLockscreen = useLockscreenStore();
|
||||||
components: { LockScreen, AppProvider },
|
const designStore = useDesignSettingStore();
|
||||||
setup() {
|
const isLock = computed(() => useLockscreen.isLock);
|
||||||
const route = useRoute();
|
const lockTime = computed(() => useLockscreen.lockTime);
|
||||||
const useLockscreen = useLockscreenStore();
|
|
||||||
const designStore = useDesignSettingStore();
|
|
||||||
const isLock = computed(() => useLockscreen.isLock);
|
|
||||||
const lockTime = computed(() => useLockscreen.lockTime);
|
|
||||||
|
|
||||||
const getThemeOverrides = computed(() => {
|
/**
|
||||||
return {
|
* @type import('naive-ui').GlobalThemeOverrides
|
||||||
common: {
|
*/
|
||||||
primaryColor: designStore.appTheme,
|
const getThemeOverrides = computed(() => {
|
||||||
primaryColorHover: '#57a3f3',
|
const appTheme = designStore.appTheme;
|
||||||
},
|
const lightenStr = lighten(designStore.appTheme, 6);
|
||||||
};
|
return {
|
||||||
});
|
common: {
|
||||||
|
primaryColor: appTheme,
|
||||||
|
primaryColorHover: lightenStr,
|
||||||
|
primaryColorPressed: lightenStr,
|
||||||
|
},
|
||||||
|
LoadingBar: {
|
||||||
|
colorLoading: appTheme,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
|
||||||
|
|
||||||
let timer;
|
let 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);
|
useLockscreen.setLock(false);
|
||||||
// 重置锁屏时间
|
// 重置锁屏时间
|
||||||
useLockscreen.setLockTime();
|
useLockscreen.setLockTime();
|
||||||
timer = setInterval(() => {
|
timer = setInterval(() => {
|
||||||
// 锁屏倒计时递减
|
// 锁屏倒计时递减
|
||||||
useLockscreen.setLockTime(lockTime.value - 1);
|
useLockscreen.setLockTime(lockTime.value - 1);
|
||||||
if (lockTime.value <= 0) {
|
if (lockTime.value <= 0) {
|
||||||
// 设置锁屏
|
// 设置锁屏
|
||||||
useLockscreen.setLock(true);
|
useLockscreen.setLock(true);
|
||||||
return clearInterval(timer);
|
return clearInterval(timer);
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.addEventListener('mousedown', timekeeping);
|
document.addEventListener('mousedown', timekeeping);
|
||||||
});
|
});
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
document.removeEventListener('mousedown', timekeeping);
|
document.removeEventListener('mousedown', timekeeping);
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
darkTheme: createTheme([inputDark, datePickerDark]),
|
|
||||||
getDarkTheme,
|
|
||||||
zhCN,
|
|
||||||
dateZhCN,
|
|
||||||
isLock,
|
|
||||||
getThemeOverrides,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
@import 'styles/common.less';
|
@import 'styles/index.less';
|
||||||
|
|
||||||
.slide-up-enter-active,
|
|
||||||
.slide-up-leave-active {
|
|
||||||
transition: transform 0.35s ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-up-enter-form,
|
|
||||||
.slide-up-leave-to {
|
|
||||||
transform: translateY(-100%);
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import http from '@/utils/http/axios';
|
import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
//获取主控台信息
|
//获取主控台信息
|
||||||
export function getConsoleInfo() {
|
export function getConsoleInfo() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import http from '@/utils/http/axios';
|
import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 根据用户id获取用户菜单
|
* @description: 根据用户id获取用户菜单
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import http from '@/utils/http/axios';
|
import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 角色列表
|
* @description: 角色列表
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import http from '@/utils/http/axios';
|
import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
export interface BasicResponseModel<T = any> {
|
export interface BasicResponseModel<T = any> {
|
||||||
code: number;
|
code: number;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import http from '@/utils/http/axios';
|
import { http } from '@/utils/http/axios';
|
||||||
|
|
||||||
//获取table
|
//获取table
|
||||||
export function getTableList(params) {
|
export function getTableList(params) {
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.3 KiB |
15
src/assets/images/nav-horizontal-mix.svg
Normal file
15
src/assets/images/nav-horizontal-mix.svg
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="52px" height="45px" viewBox="0 0 52 45" enable-background="new 0 0 52 45" xml:space="preserve"> <image id="image0" width="52" height="45" x="0" y="0"
|
||||||
|
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADQAAAAtCAMAAADWf7iKAAAABGdBTUEAALGPC/xhBQAAACBjSFJN
|
||||||
|
AAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAdVBMVEX///8AAABkbnc9RVY7
|
||||||
|
QE9fY3GIiJZjbHk9QFOCi5QAAAA9QlZfZHMAAAA4QFEAAADt7/Lf5OTt7/KChoYAAADu8PTf3+Nw
|
||||||
|
c3MAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyNUkyOEn////w8vWhURXFAAAAI3RS
|
||||||
|
TlMAAE/2/uZJUP45AvfkBP4F9LrwPwP0vUkOAREsNjk0JwYHCLrjEiIAAAABYktHRACIBR1IAAAA
|
||||||
|
CXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH5QgGAhE5kB5L+gAAAIZJREFUSMft1rsSgjAQhWEW
|
||||||
|
FTUYIopKlPui7/+IZqFhbMxmhm7//qvPiaKgAOIN+rbdJQCE9qO3cR2OhFTKMYgn5ZDmGcy0Q4aJ
|
||||||
|
0AhaoPdPnz8JEiRIkKCV0DkE5YQ0D12uBQ01B93uj9LSJdDPV1V71rRlMf0Iq7p+8Kw3aj4fAJYR
|
||||||
|
TCigL0lMJ5P4y7LRAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTA4LTA2VDAyOjE3OjU2KzAwOjAw
|
||||||
|
Kbo8/wAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wOC0wNlQwMjoxNzo1NiswMDowMFjnhEMAAAAA
|
||||||
|
SUVORK5CYII=" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -1,6 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-loading-bar-provider>
|
<n-loading-bar-provider>
|
||||||
<LoadingContent />
|
|
||||||
<n-dialog-provider>
|
<n-dialog-provider>
|
||||||
<DialogContent />
|
<DialogContent />
|
||||||
<n-notification-provider>
|
<n-notification-provider>
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
NLoadingBarProvider,
|
NLoadingBarProvider,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
import { LoadingContent } from '@/components/LoadingContent';
|
|
||||||
import { MessageContent } from '@/components/MessageContent';
|
import { MessageContent } from '@/components/MessageContent';
|
||||||
import { DialogContent } from '@/components/DialogContent';
|
import { DialogContent } from '@/components/DialogContent';
|
||||||
|
|
||||||
@@ -32,7 +30,6 @@
|
|||||||
NNotificationProvider,
|
NNotificationProvider,
|
||||||
NMessageProvider,
|
NMessageProvider,
|
||||||
NLoadingBarProvider,
|
NLoadingBarProvider,
|
||||||
LoadingContent,
|
|
||||||
MessageContent,
|
MessageContent,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
},
|
},
|
||||||
|
|||||||
4
src/components/Form/index.ts
Normal file
4
src/components/Form/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export { default as BasicForm } from './src/BasicForm.vue';
|
||||||
|
export { useForm } from './src/hooks/useForm';
|
||||||
|
export * from './src/types/form';
|
||||||
|
export * from './src/types/index';
|
||||||
319
src/components/Form/src/BasicForm.vue
Normal file
319
src/components/Form/src/BasicForm.vue
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
<template>
|
||||||
|
<n-form v-bind="getBindValue" :model="formModel" ref="formElRef">
|
||||||
|
<n-grid v-bind="getGrid">
|
||||||
|
<n-gi v-bind="schema.giProps" v-for="schema in getSchema" :key="schema.field">
|
||||||
|
<n-form-item :label="schema.label" :path="schema.field">
|
||||||
|
<!--标签名右侧温馨提示-->
|
||||||
|
<template #label v-if="schema.labelMessage">
|
||||||
|
{{ schema.label }}
|
||||||
|
<n-tooltip trigger="hover" :style="schema.labelMessageStyle">
|
||||||
|
<template #trigger>
|
||||||
|
<n-icon size="18" class="cursor-pointer text-gray-400">
|
||||||
|
<QuestionCircleOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ schema.labelMessage }}
|
||||||
|
</n-tooltip>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!--判断插槽-->
|
||||||
|
<template v-if="schema.slot">
|
||||||
|
<slot
|
||||||
|
:name="schema.slot"
|
||||||
|
:model="formModel"
|
||||||
|
:field="schema.field"
|
||||||
|
:value="formModel[schema.field]"
|
||||||
|
></slot>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!--NCheckbox-->
|
||||||
|
<template v-else-if="schema.component === 'NCheckbox'">
|
||||||
|
<n-checkbox-group v-model:value="formModel[schema.field]">
|
||||||
|
<n-space>
|
||||||
|
<n-checkbox
|
||||||
|
v-for="item in schema.componentProps.options"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
/>
|
||||||
|
</n-space>
|
||||||
|
</n-checkbox-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!--NRadioGroup-->
|
||||||
|
<template v-else-if="schema.component === 'NRadioGroup'">
|
||||||
|
<n-radio-group v-model:value="formModel[schema.field]">
|
||||||
|
<n-space>
|
||||||
|
<n-radio
|
||||||
|
v-for="item in schema.componentProps.options"
|
||||||
|
:key="item.value"
|
||||||
|
:value="item.value"
|
||||||
|
>
|
||||||
|
{{ item.label }}
|
||||||
|
</n-radio>
|
||||||
|
</n-space>
|
||||||
|
</n-radio-group>
|
||||||
|
</template>
|
||||||
|
<!--动态渲染表单组件-->
|
||||||
|
<component
|
||||||
|
v-else
|
||||||
|
v-bind="getComponentProps(schema)"
|
||||||
|
:is="schema.component"
|
||||||
|
v-model:value="formModel[schema.field]"
|
||||||
|
:class="{ isFull: schema.isFull != false && getProps.isFull }"
|
||||||
|
/>
|
||||||
|
<!--组件后面的内容-->
|
||||||
|
<template v-if="schema.suffix">
|
||||||
|
<slot
|
||||||
|
:name="schema.suffix"
|
||||||
|
:model="formModel"
|
||||||
|
:field="schema.field"
|
||||||
|
:value="formModel[schema.field]"
|
||||||
|
></slot>
|
||||||
|
</template>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<!--提交 重置 展开 收起 按钮-->
|
||||||
|
<n-gi
|
||||||
|
:span="isInline ? '' : 24"
|
||||||
|
:suffix="isInline ? true : false"
|
||||||
|
#="{ overflow }"
|
||||||
|
v-if="getProps.showActionButtonGroup"
|
||||||
|
>
|
||||||
|
<n-space
|
||||||
|
align="center"
|
||||||
|
:justify="isInline ? 'end' : 'start'"
|
||||||
|
:style="{ 'margin-left': `${isInline ? 12 : getProps.labelWidth}px` }"
|
||||||
|
>
|
||||||
|
<n-button
|
||||||
|
v-if="getProps.showSubmitButton"
|
||||||
|
v-bind="getSubmitBtnOptions"
|
||||||
|
@click="handleSubmit"
|
||||||
|
:loading="loadingSub"
|
||||||
|
>{{ getProps.submitButtonText }}</n-button
|
||||||
|
>
|
||||||
|
<n-button
|
||||||
|
v-if="getProps.showResetButton"
|
||||||
|
v-bind="getResetBtnOptions"
|
||||||
|
@click="resetFields"
|
||||||
|
>{{ getProps.resetButtonText }}</n-button
|
||||||
|
>
|
||||||
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
icon-placement="right"
|
||||||
|
v-if="isInline && getProps.showAdvancedButton"
|
||||||
|
@click="unfoldToggle"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon size="14" class="unfold-icon" v-if="overflow">
|
||||||
|
<DownOutlined />
|
||||||
|
</n-icon>
|
||||||
|
<n-icon size="14" class="unfold-icon" v-else>
|
||||||
|
<UpOutlined />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
{{ overflow ? '展开' : '收起' }}
|
||||||
|
</n-button>
|
||||||
|
</n-space>
|
||||||
|
</n-gi>
|
||||||
|
</n-grid>
|
||||||
|
</n-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, ref, computed, unref, onMounted, watch } from 'vue';
|
||||||
|
import { createPlaceholderMessage } from './helper';
|
||||||
|
import { useFormEvents } from './hooks/useFormEvents';
|
||||||
|
import { useFormValues } from './hooks/useFormValues';
|
||||||
|
|
||||||
|
import { basicProps } from './props';
|
||||||
|
import { DownOutlined, UpOutlined, QuestionCircleOutlined } from '@vicons/antd';
|
||||||
|
|
||||||
|
import type { Ref } from 'vue';
|
||||||
|
import type { GridProps } from 'naive-ui/lib/grid';
|
||||||
|
import type { FormSchema, FormProps, FormActionType } from './types/form';
|
||||||
|
|
||||||
|
import { isArray } from '@/utils/is/index';
|
||||||
|
import { deepMerge } from '@/utils';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'BasicUpload',
|
||||||
|
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
|
||||||
|
props: {
|
||||||
|
...basicProps,
|
||||||
|
},
|
||||||
|
emits: ['reset', 'submit', 'register'],
|
||||||
|
setup(props, { emit, attrs }) {
|
||||||
|
const defaultFormModel = ref<Recordable>({});
|
||||||
|
const formModel = reactive<Recordable>({});
|
||||||
|
const propsRef = ref<Partial<FormProps>>({});
|
||||||
|
const schemaRef = ref<Nullable<FormSchema[]>>(null);
|
||||||
|
const formElRef = ref<Nullable<FormActionType>>(null);
|
||||||
|
const gridCollapsed = ref(true);
|
||||||
|
const loadingSub = ref(false);
|
||||||
|
const isUpdateDefaultRef = ref(false);
|
||||||
|
|
||||||
|
const getSubmitBtnOptions = computed(() => {
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
size: props.size,
|
||||||
|
type: 'primary',
|
||||||
|
},
|
||||||
|
props.submitButtonOptions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getResetBtnOptions = computed(() => {
|
||||||
|
return Object.assign(
|
||||||
|
{
|
||||||
|
size: props.size,
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
props.resetButtonOptions
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getComponentProps(schema) {
|
||||||
|
const compProps = schema.componentProps ?? {};
|
||||||
|
const component = schema.component;
|
||||||
|
return {
|
||||||
|
clearable: true,
|
||||||
|
placeholder: createPlaceholderMessage(unref(component)),
|
||||||
|
...compProps,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProps = computed((): FormProps => {
|
||||||
|
const formProps = { ...props, ...unref(propsRef) } as FormProps;
|
||||||
|
const rulesObj: any = {
|
||||||
|
rules: {},
|
||||||
|
};
|
||||||
|
const schemas: any = formProps.schemas || [];
|
||||||
|
schemas.forEach((item) => {
|
||||||
|
if (item.rules && isArray(item.rules)) {
|
||||||
|
rulesObj.rules[item.field] = item.rules;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return { ...formProps, ...unref(rulesObj) };
|
||||||
|
});
|
||||||
|
|
||||||
|
const isInline = computed(() => {
|
||||||
|
const { layout } = unref(getProps);
|
||||||
|
return layout === 'inline';
|
||||||
|
});
|
||||||
|
|
||||||
|
const getGrid = computed((): GridProps => {
|
||||||
|
const { gridProps } = unref(getProps);
|
||||||
|
return {
|
||||||
|
...gridProps,
|
||||||
|
collapsed: isInline.value ? gridCollapsed.value : false,
|
||||||
|
responsive: 'screen',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const getBindValue = computed(
|
||||||
|
() => ({ ...attrs, ...props, ...unref(getProps) } as Recordable)
|
||||||
|
);
|
||||||
|
|
||||||
|
const getSchema = computed((): FormSchema[] => {
|
||||||
|
const schemas: FormSchema[] = unref(schemaRef) || (unref(getProps).schemas as any);
|
||||||
|
for (const schema of schemas) {
|
||||||
|
const { defaultValue } = schema;
|
||||||
|
// handle date type
|
||||||
|
// dateItemType.includes(component as string)
|
||||||
|
if (defaultValue) {
|
||||||
|
schema.defaultValue = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return schemas as FormSchema[];
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleFormValues, initDefault } = useFormValues({
|
||||||
|
getProps,
|
||||||
|
defaultFormModel,
|
||||||
|
getSchema,
|
||||||
|
formModel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleSubmit, validate, resetFields, getFieldsValue, clearValidate, setFieldsValue } =
|
||||||
|
useFormEvents({
|
||||||
|
emit,
|
||||||
|
getProps,
|
||||||
|
formModel,
|
||||||
|
getSchema,
|
||||||
|
formElRef: formElRef as Ref<FormActionType>,
|
||||||
|
defaultFormModel,
|
||||||
|
loadingSub,
|
||||||
|
handleFormValues,
|
||||||
|
});
|
||||||
|
|
||||||
|
function unfoldToggle() {
|
||||||
|
gridCollapsed.value = !gridCollapsed.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setProps(formProps: Partial<FormProps>): Promise<void> {
|
||||||
|
propsRef.value = deepMerge(unref(propsRef) || {}, formProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const formActionType: Partial<FormActionType> = {
|
||||||
|
getFieldsValue,
|
||||||
|
setFieldsValue,
|
||||||
|
resetFields,
|
||||||
|
validate,
|
||||||
|
clearValidate,
|
||||||
|
setProps,
|
||||||
|
submit: handleSubmit,
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => getSchema.value,
|
||||||
|
(schema) => {
|
||||||
|
if (unref(isUpdateDefaultRef)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (schema?.length) {
|
||||||
|
initDefault();
|
||||||
|
isUpdateDefaultRef.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initDefault();
|
||||||
|
emit('register', formActionType);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
formElRef,
|
||||||
|
formModel,
|
||||||
|
getGrid,
|
||||||
|
getProps,
|
||||||
|
getBindValue,
|
||||||
|
getSchema,
|
||||||
|
getSubmitBtnOptions,
|
||||||
|
getResetBtnOptions,
|
||||||
|
handleSubmit,
|
||||||
|
resetFields,
|
||||||
|
loadingSub,
|
||||||
|
isInline,
|
||||||
|
getComponentProps,
|
||||||
|
unfoldToggle,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.isFull {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unfold-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 100%;
|
||||||
|
margin-left: -3px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
42
src/components/Form/src/helper.ts
Normal file
42
src/components/Form/src/helper.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { ComponentType } from '/types/index';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 生成placeholder
|
||||||
|
*/
|
||||||
|
export function createPlaceholderMessage(component: ComponentType) {
|
||||||
|
if (component === 'NInput') return '请输入';
|
||||||
|
if (
|
||||||
|
['NPicker', 'NSelect', 'NCheckbox', 'NRadio', 'NSwitch', 'NDatePicker', 'NTimePicker'].includes(
|
||||||
|
component
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return '请选择';
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const DATE_TYPE = ['DatePicker', 'MonthPicker', 'WeekPicker', 'TimePicker'];
|
||||||
|
|
||||||
|
function genType() {
|
||||||
|
return [...DATE_TYPE, 'RangePicker'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 时间字段
|
||||||
|
*/
|
||||||
|
export const dateItemType = genType();
|
||||||
|
|
||||||
|
export function defaultType(component) {
|
||||||
|
if (component === 'NInput') return '';
|
||||||
|
if (component === 'NInputNumber') return null;
|
||||||
|
return [
|
||||||
|
'NPicker',
|
||||||
|
'NSelect',
|
||||||
|
'NCheckbox',
|
||||||
|
'NRadio',
|
||||||
|
'NSwitch',
|
||||||
|
'NDatePicker',
|
||||||
|
'NTimePicker',
|
||||||
|
].includes(component)
|
||||||
|
? ''
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
86
src/components/Form/src/hooks/useForm.ts
Normal file
86
src/components/Form/src/hooks/useForm.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import type { FormProps, FormActionType, UseFormReturnType } from '../types/form';
|
||||||
|
import type { DynamicProps } from '/#/utils';
|
||||||
|
|
||||||
|
import { ref, onUnmounted, unref, nextTick, watch } from 'vue';
|
||||||
|
import { isProdMode } from '@/utils/env';
|
||||||
|
import { getDynamicProps } from '@/utils';
|
||||||
|
|
||||||
|
type Props = Partial<DynamicProps<FormProps>>;
|
||||||
|
|
||||||
|
export function useForm(props?: Props): UseFormReturnType {
|
||||||
|
const formRef = ref<Nullable<FormActionType>>(null);
|
||||||
|
const loadedRef = ref<Nullable<boolean>>(false);
|
||||||
|
|
||||||
|
async function getForm() {
|
||||||
|
const form = unref(formRef);
|
||||||
|
if (!form) {
|
||||||
|
console.error(
|
||||||
|
'The form instance has not been obtained, please make sure that the form has been rendered when performing the form operation!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await nextTick();
|
||||||
|
return form as FormActionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
function register(instance: FormActionType) {
|
||||||
|
isProdMode() &&
|
||||||
|
onUnmounted(() => {
|
||||||
|
formRef.value = null;
|
||||||
|
loadedRef.value = null;
|
||||||
|
});
|
||||||
|
if (unref(loadedRef) && isProdMode() && instance === unref(formRef)) return;
|
||||||
|
|
||||||
|
formRef.value = instance;
|
||||||
|
loadedRef.value = true;
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props,
|
||||||
|
() => {
|
||||||
|
props && instance.setProps(getDynamicProps(props));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const methods: FormActionType = {
|
||||||
|
setProps: async (formProps: Partial<FormProps>) => {
|
||||||
|
const form = await getForm();
|
||||||
|
await form.setProps(formProps);
|
||||||
|
},
|
||||||
|
|
||||||
|
resetFields: async () => {
|
||||||
|
getForm().then(async (form) => {
|
||||||
|
await form.resetFields();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
clearValidate: async (name?: string | string[]) => {
|
||||||
|
const form = await getForm();
|
||||||
|
await form.clearValidate(name);
|
||||||
|
},
|
||||||
|
|
||||||
|
getFieldsValue: <T>() => {
|
||||||
|
return unref(formRef)?.getFieldsValue() as T;
|
||||||
|
},
|
||||||
|
|
||||||
|
setFieldsValue: async <T>(values: T) => {
|
||||||
|
const form = await getForm();
|
||||||
|
await form.setFieldsValue<T>(values);
|
||||||
|
},
|
||||||
|
|
||||||
|
submit: async (): Promise<any> => {
|
||||||
|
const form = await getForm();
|
||||||
|
return form.submit();
|
||||||
|
},
|
||||||
|
|
||||||
|
validate: async (nameList?: any[]): Promise<Recordable> => {
|
||||||
|
const form = await getForm();
|
||||||
|
return form.validate(nameList);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [register, methods];
|
||||||
|
}
|
||||||
11
src/components/Form/src/hooks/useFormContext.ts
Normal file
11
src/components/Form/src/hooks/useFormContext.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { provide, inject } from 'vue';
|
||||||
|
|
||||||
|
const key = Symbol('formElRef');
|
||||||
|
|
||||||
|
export function createFormContext(instance) {
|
||||||
|
provide(key, instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFormContext() {
|
||||||
|
return inject(key);
|
||||||
|
}
|
||||||
107
src/components/Form/src/hooks/useFormEvents.ts
Normal file
107
src/components/Form/src/hooks/useFormEvents.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import type { ComputedRef, Ref } from 'vue';
|
||||||
|
import type { FormProps, FormSchema, FormActionType } from '../types/form';
|
||||||
|
import { unref, toRaw } from 'vue';
|
||||||
|
import { isFunction } from '@/utils/is';
|
||||||
|
|
||||||
|
declare type EmitType = (event: string, ...args: any[]) => void;
|
||||||
|
|
||||||
|
interface UseFormActionContext {
|
||||||
|
emit: EmitType;
|
||||||
|
getProps: ComputedRef<FormProps>;
|
||||||
|
getSchema: ComputedRef<FormSchema[]>;
|
||||||
|
formModel: Recordable;
|
||||||
|
formElRef: Ref<FormActionType>;
|
||||||
|
defaultFormModel: Recordable;
|
||||||
|
loadingSub: Ref<boolean>;
|
||||||
|
handleFormValues: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useFormEvents({
|
||||||
|
emit,
|
||||||
|
getProps,
|
||||||
|
formModel,
|
||||||
|
getSchema,
|
||||||
|
formElRef,
|
||||||
|
defaultFormModel,
|
||||||
|
loadingSub,
|
||||||
|
handleFormValues,
|
||||||
|
}: UseFormActionContext) {
|
||||||
|
// 验证
|
||||||
|
async function validate() {
|
||||||
|
return unref(formElRef)?.validate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
async function handleSubmit(e?: Event): Promise<void> {
|
||||||
|
e && e.preventDefault();
|
||||||
|
loadingSub.value = true;
|
||||||
|
const { submitFunc } = unref(getProps);
|
||||||
|
if (submitFunc && isFunction(submitFunc)) {
|
||||||
|
await submitFunc();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const formEl = unref(formElRef);
|
||||||
|
if (!formEl) return;
|
||||||
|
try {
|
||||||
|
await validate();
|
||||||
|
loadingSub.value = false;
|
||||||
|
emit('submit', formModel);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
loadingSub.value = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//清空校验
|
||||||
|
async function clearValidate() {
|
||||||
|
// @ts-ignore
|
||||||
|
await unref(formElRef)?.restoreValidation();
|
||||||
|
}
|
||||||
|
|
||||||
|
//重置
|
||||||
|
async function resetFields(): Promise<void> {
|
||||||
|
const { resetFunc, submitOnReset } = unref(getProps);
|
||||||
|
resetFunc && isFunction(resetFunc) && (await resetFunc());
|
||||||
|
|
||||||
|
const formEl = unref(formElRef);
|
||||||
|
if (!formEl) return;
|
||||||
|
Object.keys(formModel).forEach((key) => {
|
||||||
|
formModel[key] = unref(defaultFormModel)[key] || null;
|
||||||
|
});
|
||||||
|
await clearValidate();
|
||||||
|
const fromValues = handleFormValues(toRaw(unref(formModel)));
|
||||||
|
emit('reset', fromValues);
|
||||||
|
submitOnReset && (await handleSubmit());
|
||||||
|
}
|
||||||
|
|
||||||
|
//获取表单值
|
||||||
|
function getFieldsValue(): Recordable {
|
||||||
|
const formEl = unref(formElRef);
|
||||||
|
if (!formEl) return {};
|
||||||
|
return handleFormValues(toRaw(unref(formModel)));
|
||||||
|
}
|
||||||
|
|
||||||
|
//设置表单字段值
|
||||||
|
async function setFieldsValue(values: Recordable): Promise<void> {
|
||||||
|
const fields = unref(getSchema)
|
||||||
|
.map((item) => item.field)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
Object.keys(values).forEach((key) => {
|
||||||
|
const value = values[key];
|
||||||
|
if (fields.includes(key)) {
|
||||||
|
formModel[key] = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleSubmit,
|
||||||
|
validate,
|
||||||
|
resetFields,
|
||||||
|
getFieldsValue,
|
||||||
|
clearValidate,
|
||||||
|
setFieldsValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
54
src/components/Form/src/hooks/useFormValues.ts
Normal file
54
src/components/Form/src/hooks/useFormValues.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { isArray, isFunction, isObject, isString, isNullOrUnDef } from '@/utils/is';
|
||||||
|
import { unref } from 'vue';
|
||||||
|
import type { Ref, ComputedRef } from 'vue';
|
||||||
|
import type { FormSchema } from '../types/form';
|
||||||
|
import { set } from 'lodash-es';
|
||||||
|
|
||||||
|
interface UseFormValuesContext {
|
||||||
|
defaultFormModel: Ref<any>;
|
||||||
|
getSchema: ComputedRef<FormSchema[]>;
|
||||||
|
formModel: Recordable;
|
||||||
|
}
|
||||||
|
export function useFormValues({ defaultFormModel, getSchema, formModel }: UseFormValuesContext) {
|
||||||
|
// 加工 form values
|
||||||
|
function handleFormValues(values: Recordable) {
|
||||||
|
if (!isObject(values)) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
const res: Recordable = {};
|
||||||
|
for (const item of Object.entries(values)) {
|
||||||
|
let [, value] = item;
|
||||||
|
const [key] = item;
|
||||||
|
if (
|
||||||
|
!key ||
|
||||||
|
(isArray(value) && value.length === 0) ||
|
||||||
|
isFunction(value) ||
|
||||||
|
isNullOrUnDef(value)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// 删除空格
|
||||||
|
if (isString(value)) {
|
||||||
|
value = value.trim();
|
||||||
|
}
|
||||||
|
set(res, key, value);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
//初始化默认值
|
||||||
|
function initDefault() {
|
||||||
|
const schemas = unref(getSchema);
|
||||||
|
const obj: Recordable = {};
|
||||||
|
schemas.forEach((item) => {
|
||||||
|
const { defaultValue } = item;
|
||||||
|
if (!isNullOrUnDef(defaultValue)) {
|
||||||
|
obj[item.field] = defaultValue;
|
||||||
|
formModel[item.field] = defaultValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
defaultFormModel.value = obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleFormValues, initDefault };
|
||||||
|
}
|
||||||
82
src/components/Form/src/props.ts
Normal file
82
src/components/Form/src/props.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import type { CSSProperties, PropType } from 'vue';
|
||||||
|
import { FormSchema } from './types/form';
|
||||||
|
import type { GridProps, GridItemProps } from 'naive-ui/lib/grid';
|
||||||
|
import type { ButtonProps } from 'naive-ui/lib/button';
|
||||||
|
import { propTypes } from '@/utils/propTypes';
|
||||||
|
export const basicProps = {
|
||||||
|
// 标签宽度 固定宽度
|
||||||
|
labelWidth: {
|
||||||
|
type: [Number, String] as PropType<number | string>,
|
||||||
|
default: 80,
|
||||||
|
},
|
||||||
|
// 表单配置规则
|
||||||
|
schemas: {
|
||||||
|
type: [Array] as PropType<FormSchema[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
//布局方式
|
||||||
|
layout: {
|
||||||
|
type: String,
|
||||||
|
default: 'inline',
|
||||||
|
},
|
||||||
|
//是否展示为行内表单
|
||||||
|
inline: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
//大小
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: 'medium',
|
||||||
|
},
|
||||||
|
//标签位置
|
||||||
|
labelPlacement: {
|
||||||
|
type: String,
|
||||||
|
default: 'left',
|
||||||
|
},
|
||||||
|
//组件是否width 100%
|
||||||
|
isFull: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
//是否显示操作按钮(查询/重置)
|
||||||
|
showActionButtonGroup: propTypes.bool.def(true),
|
||||||
|
// 显示重置按钮
|
||||||
|
showResetButton: propTypes.bool.def(true),
|
||||||
|
//重置按钮配置
|
||||||
|
resetButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||||
|
// 显示确认按钮
|
||||||
|
showSubmitButton: propTypes.bool.def(true),
|
||||||
|
// 确认按钮配置
|
||||||
|
submitButtonOptions: Object as PropType<Partial<ButtonProps>>,
|
||||||
|
//展开收起按钮
|
||||||
|
showAdvancedButton: propTypes.bool.def(true),
|
||||||
|
// 确认按钮文字
|
||||||
|
submitButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: '查询',
|
||||||
|
},
|
||||||
|
//重置按钮文字
|
||||||
|
resetButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: '重置',
|
||||||
|
},
|
||||||
|
//grid 配置
|
||||||
|
gridProps: Object as PropType<GridProps>,
|
||||||
|
//gi配置
|
||||||
|
giProps: Object as PropType<GridItemProps>,
|
||||||
|
//grid 样式
|
||||||
|
baseGridStyle: {
|
||||||
|
type: Object as PropType<CSSProperties>,
|
||||||
|
},
|
||||||
|
//是否折叠
|
||||||
|
collapsed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
//默认展示的行数
|
||||||
|
collapsedRows: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
58
src/components/Form/src/types/form.ts
Normal file
58
src/components/Form/src/types/form.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { ComponentType } from './index';
|
||||||
|
import type { CSSProperties } from 'vue';
|
||||||
|
import type { GridProps, GridItemProps } from 'naive-ui/lib/grid';
|
||||||
|
import type { ButtonProps } from 'naive-ui/lib/button';
|
||||||
|
|
||||||
|
export interface FormSchema {
|
||||||
|
field: string;
|
||||||
|
label: string;
|
||||||
|
labelMessage?: string;
|
||||||
|
labelMessageStyle?: object | string;
|
||||||
|
defaultValue?: any;
|
||||||
|
component?: ComponentType;
|
||||||
|
componentProps?: object;
|
||||||
|
slot?: string;
|
||||||
|
rules?: object | object[];
|
||||||
|
giProps?: GridItemProps;
|
||||||
|
isFull?: boolean;
|
||||||
|
suffix?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormProps {
|
||||||
|
model?: Recordable;
|
||||||
|
labelWidth?: number | string;
|
||||||
|
schemas?: FormSchema[];
|
||||||
|
inline: boolean;
|
||||||
|
layout?: string;
|
||||||
|
size: string;
|
||||||
|
labelPlacement: string;
|
||||||
|
isFull: boolean;
|
||||||
|
showActionButtonGroup?: boolean;
|
||||||
|
showResetButton?: boolean;
|
||||||
|
resetButtonOptions?: Partial<ButtonProps>;
|
||||||
|
showSubmitButton?: boolean;
|
||||||
|
showAdvancedButton?: boolean;
|
||||||
|
submitButtonOptions?: Partial<ButtonProps>;
|
||||||
|
submitButtonText?: string;
|
||||||
|
resetButtonText?: string;
|
||||||
|
gridProps?: GridProps;
|
||||||
|
giProps?: GridItemProps;
|
||||||
|
resetFunc?: () => Promise<void>;
|
||||||
|
submitFunc?: () => Promise<void>;
|
||||||
|
submitOnReset?: boolean;
|
||||||
|
baseGridStyle?: CSSProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FormActionType {
|
||||||
|
submit: () => Promise<any>;
|
||||||
|
setProps: (formProps: Partial<FormProps>) => Promise<void>;
|
||||||
|
setFieldsValue: <T>(values: T) => Promise<void>;
|
||||||
|
clearValidate: (name?: string | string[]) => Promise<void>;
|
||||||
|
getFieldsValue: () => Recordable;
|
||||||
|
resetFields: () => Promise<void>;
|
||||||
|
validate: (nameList?: any[]) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RegisterFn = (formInstance: FormActionType) => void;
|
||||||
|
|
||||||
|
export type UseFormReturnType = [RegisterFn, FormActionType];
|
||||||
28
src/components/Form/src/types/index.ts
Normal file
28
src/components/Form/src/types/index.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export type ComponentType =
|
||||||
|
| 'NInput'
|
||||||
|
| 'NInputGroup'
|
||||||
|
| 'NInputPassword'
|
||||||
|
| 'NInputSearch'
|
||||||
|
| 'NInputTextArea'
|
||||||
|
| 'NInputNumber'
|
||||||
|
| 'NInputCountDown'
|
||||||
|
| 'NSelect'
|
||||||
|
| 'NTreeSelect'
|
||||||
|
| 'NRadioButtonGroup'
|
||||||
|
| 'NRadioGroup'
|
||||||
|
| 'NCheckbox'
|
||||||
|
| 'NCheckboxGroup'
|
||||||
|
| 'NAutoComplete'
|
||||||
|
| 'NCascader'
|
||||||
|
| 'NDatePicker'
|
||||||
|
| 'NMonthPicker'
|
||||||
|
| 'NRangePicker'
|
||||||
|
| 'NWeekPicker'
|
||||||
|
| 'NTimePicker'
|
||||||
|
| 'NSwitch'
|
||||||
|
| 'NStrengthMeter'
|
||||||
|
| 'NUpload'
|
||||||
|
| 'NIconPicker'
|
||||||
|
| 'NRender'
|
||||||
|
| 'NSlider'
|
||||||
|
| 'NRate';
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
:battery="battery"
|
:battery="battery"
|
||||||
:battery-status="batteryStatus"
|
:battery-status="batteryStatus"
|
||||||
:calc-discharging-time="calcDischargingTime"
|
:calc-discharging-time="calcDischargingTime"
|
||||||
|
:calc-charging-time="calcChargingTime"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="local-time">
|
<div class="local-time">
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
type="password"
|
type="password"
|
||||||
autofocus
|
autofocus
|
||||||
v-model:value="loginParams.password"
|
v-model:value="loginParams.password"
|
||||||
|
@keyup.enter="onLogin"
|
||||||
placeholder="请输入登录密码"
|
placeholder="请输入登录密码"
|
||||||
>
|
>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
@@ -114,7 +116,7 @@
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
const { battery, batteryStatus, calcDischargingTime } = useBattery();
|
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } = useBattery();
|
||||||
const userInfo: object = userStore.getUserInfo || {};
|
const userInfo: object = userStore.getUserInfo || {};
|
||||||
const username = userInfo['username'] || '';
|
const username = userInfo['username'] || '';
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
@@ -176,6 +178,7 @@
|
|||||||
battery,
|
battery,
|
||||||
batteryStatus,
|
batteryStatus,
|
||||||
calcDischargingTime,
|
calcDischargingTime,
|
||||||
|
calcChargingTime,
|
||||||
onLockLogin,
|
onLockLogin,
|
||||||
onLogin,
|
onLogin,
|
||||||
goLogin,
|
goLogin,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
剩余可使用时间:{{ calcDischargingTime }}
|
剩余可使用时间:{{ calcDischargingTime }}
|
||||||
</div>
|
</div>
|
||||||
<span v-show="Number.isFinite(battery.chargingTime) && battery.chargingTime != 0">
|
<span v-show="Number.isFinite(battery.chargingTime) && battery.chargingTime != 0">
|
||||||
距离电池充满需要:{{ calcDischargingTime }}
|
距离电池充满需要:{{ calcChargingTime }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
calcChargingTime: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
batteryStatus: {
|
batteryStatus: {
|
||||||
// 电池状态
|
// 电池状态
|
||||||
type: String,
|
type: String,
|
||||||
@@ -51,12 +55,12 @@
|
|||||||
bottom: 20vh;
|
bottom: 20vh;
|
||||||
left: 50vw;
|
left: 50vw;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 400px;
|
height: 500px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 27%;
|
top: 20%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
|
|||||||
3
src/components/Modal/index.ts
Normal file
3
src/components/Modal/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as basicModal } from './src/basicModal.vue';
|
||||||
|
export { useModal } from './src/hooks/useModal';
|
||||||
|
export * from './src/type';
|
||||||
117
src/components/Modal/src/basicModal.vue
Normal file
117
src/components/Modal/src/basicModal.vue
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal id="basic-modal" v-bind="getBindValue" v-model:show="isModal" @close="onCloseModal">
|
||||||
|
<template #header>
|
||||||
|
<div class="w-full cursor-move" id="basic-modal-bar">{{ getBindValue.title }}</div>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<slot name="default"></slot>
|
||||||
|
</template>
|
||||||
|
<template #action v-if="!$slots.action">
|
||||||
|
<n-space>
|
||||||
|
<n-button @click="closeModal">取消</n-button>
|
||||||
|
<n-button type="primary" :loading="subLoading" @click="handleSubmit">{{
|
||||||
|
subBtuText
|
||||||
|
}}</n-button>
|
||||||
|
</n-space>
|
||||||
|
</template>
|
||||||
|
<template v-else #action>
|
||||||
|
<slot name="action"></slot>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {
|
||||||
|
getCurrentInstance,
|
||||||
|
ref,
|
||||||
|
nextTick,
|
||||||
|
unref,
|
||||||
|
computed,
|
||||||
|
useAttrs,
|
||||||
|
defineEmits,
|
||||||
|
defineProps,
|
||||||
|
} from 'vue';
|
||||||
|
import { basicProps } from './props';
|
||||||
|
import startDrag from '@/utils/Drag';
|
||||||
|
import { deepMerge } from '@/utils';
|
||||||
|
import { FormProps } from '@/components/Form';
|
||||||
|
import { ModalProps, ModalMethods } from './type';
|
||||||
|
|
||||||
|
const attrs = useAttrs();
|
||||||
|
const props = defineProps({ ...basicProps });
|
||||||
|
const emit = defineEmits(['on-close', 'on-ok', 'register']);
|
||||||
|
|
||||||
|
const propsRef = ref(<Partial<ModalProps> | null>null);
|
||||||
|
|
||||||
|
const isModal = ref(false);
|
||||||
|
const subLoading = ref(false);
|
||||||
|
|
||||||
|
const getProps = computed((): FormProps => {
|
||||||
|
return { ...props, ...(unref(propsRef) as any) };
|
||||||
|
});
|
||||||
|
|
||||||
|
const subBtuText = computed(() => {
|
||||||
|
const { subBtuText } = propsRef.value as any;
|
||||||
|
return subBtuText || props.subBtuText;
|
||||||
|
});
|
||||||
|
|
||||||
|
async function setProps(modalProps: Partial<ModalProps>): Promise<void> {
|
||||||
|
propsRef.value = deepMerge(unref(propsRef) || ({} as any), modalProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBindValue = computed(() => {
|
||||||
|
return {
|
||||||
|
...attrs,
|
||||||
|
...unref(getProps),
|
||||||
|
...unref(propsRef),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
function setSubLoading(status: boolean) {
|
||||||
|
subLoading.value = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
isModal.value = true;
|
||||||
|
nextTick(() => {
|
||||||
|
const oBox = document.getElementById('basic-modal');
|
||||||
|
const oBar = document.getElementById('basic-modal-bar');
|
||||||
|
startDrag(oBar, oBox);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
isModal.value = false;
|
||||||
|
subLoading.value = false;
|
||||||
|
emit('on-close');
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCloseModal() {
|
||||||
|
isModal.value = false;
|
||||||
|
emit('on-close');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSubmit() {
|
||||||
|
subLoading.value = true;
|
||||||
|
console.log(subLoading.value)
|
||||||
|
emit('on-ok');
|
||||||
|
}
|
||||||
|
|
||||||
|
const modalMethods: ModalMethods = {
|
||||||
|
setProps,
|
||||||
|
openModal,
|
||||||
|
closeModal,
|
||||||
|
setSubLoading,
|
||||||
|
};
|
||||||
|
|
||||||
|
const instance = getCurrentInstance();
|
||||||
|
if (instance) {
|
||||||
|
emit('register', modalMethods);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
.cursor-move {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
55
src/components/Modal/src/hooks/useModal.ts
Normal file
55
src/components/Modal/src/hooks/useModal.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { ref, onUnmounted, unref, getCurrentInstance, watch, nextTick } from 'vue';
|
||||||
|
import { isProdMode } from '@/utils/env';
|
||||||
|
import { ModalMethods, UseModalReturnType } from '../type';
|
||||||
|
import { getDynamicProps } from '@/utils';
|
||||||
|
import { tryOnUnmounted } from '@vueuse/core';
|
||||||
|
export function useModal(props): UseModalReturnType {
|
||||||
|
|
||||||
|
const modalRef = ref<Nullable<ModalMethods>>(null);
|
||||||
|
const currentInstance = getCurrentInstance();
|
||||||
|
|
||||||
|
const getInstance = () => {
|
||||||
|
const instance = unref(modalRef.value);
|
||||||
|
if (!instance) {
|
||||||
|
console.error('useModal instance is undefined!');
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
const register = (modalInstance: ModalMethods) => {
|
||||||
|
isProdMode() &&
|
||||||
|
tryOnUnmounted(() => {
|
||||||
|
modalRef.value = null;
|
||||||
|
});
|
||||||
|
modalRef.value = modalInstance;
|
||||||
|
currentInstance?.emit('register', modalInstance);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props,
|
||||||
|
() => {
|
||||||
|
props && modalInstance.setProps(getDynamicProps(props));
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const methods: ModalMethods = {
|
||||||
|
setProps: (props): void => {
|
||||||
|
getInstance()?.setProps(props);
|
||||||
|
},
|
||||||
|
openModal: () => {
|
||||||
|
getInstance()?.openModal();
|
||||||
|
},
|
||||||
|
closeModal: () => {
|
||||||
|
getInstance()?.closeModal();
|
||||||
|
},
|
||||||
|
setSubLoading: (status) => {
|
||||||
|
getInstance()?.setSubLoading(status);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return [register, methods];
|
||||||
|
}
|
||||||
30
src/components/Modal/src/props.ts
Normal file
30
src/components/Modal/src/props.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { NModal } from 'naive-ui';
|
||||||
|
|
||||||
|
export const basicProps = {
|
||||||
|
...NModal.props,
|
||||||
|
// 确认按钮文字
|
||||||
|
subBtuText: {
|
||||||
|
type: String,
|
||||||
|
default: '确认',
|
||||||
|
},
|
||||||
|
showIcon: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
width: {
|
||||||
|
type: Number,
|
||||||
|
default: 446,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
maskClosable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
preset: {
|
||||||
|
type: String,
|
||||||
|
default: 'dialog',
|
||||||
|
},
|
||||||
|
};
|
||||||
19
src/components/Modal/src/type/index.ts
Normal file
19
src/components/Modal/src/type/index.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import type { DialogOptions } from 'naive-ui/lib/dialog';
|
||||||
|
/**
|
||||||
|
* @description: 弹窗对外暴露的方法
|
||||||
|
*/
|
||||||
|
export interface ModalMethods {
|
||||||
|
setProps: (props) => void;
|
||||||
|
openModal: () => void;
|
||||||
|
closeModal: () => void;
|
||||||
|
setSubLoading: (status) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持修改,DialogOptions 參數
|
||||||
|
*/
|
||||||
|
export interface ModalProps extends DialogOptions { }
|
||||||
|
|
||||||
|
export type RegisterFn = (ModalInstance: ModalMethods) => void;
|
||||||
|
|
||||||
|
export type UseModalReturnType = [RegisterFn, ModalMethods];
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
{{ title }}
|
{{ title }}
|
||||||
<n-tooltip trigger="hover" v-if="titleTooltip">
|
<n-tooltip trigger="hover" v-if="titleTooltip">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-icon size="18" class="ml-1 cursor-pointer text-gray-400">
|
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
|
||||||
<QuestionCircleOutlined />
|
<QuestionCircleOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</template>
|
</template>
|
||||||
@@ -73,7 +73,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { NDataTable } from 'naive-ui';
|
|
||||||
import {
|
import {
|
||||||
ref,
|
ref,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
@@ -129,7 +128,6 @@
|
|||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
...NDataTable.props, // 这里继承原 UI 组件的 props
|
|
||||||
...basicProps,
|
...basicProps,
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
@@ -158,7 +156,7 @@
|
|||||||
|
|
||||||
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
const { getPaginationInfo, setPagination } = usePagination(getProps);
|
||||||
|
|
||||||
const { getDataSourceRef, getRowKey, reload } = useDataSource(
|
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
|
||||||
getProps,
|
getProps,
|
||||||
{
|
{
|
||||||
getPaginationInfo,
|
getPaginationInfo,
|
||||||
@@ -173,7 +171,7 @@
|
|||||||
useColumns(getProps);
|
useColumns(getProps);
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
tableSize: 'medium',
|
tableSize: unref(getProps as any).size || 'medium',
|
||||||
isColumnSetting: false,
|
isColumnSetting: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -235,9 +233,6 @@
|
|||||||
getCacheColumns,
|
getCacheColumns,
|
||||||
setCacheColumnsField,
|
setCacheColumnsField,
|
||||||
emit,
|
emit,
|
||||||
getSize: () => {
|
|
||||||
return unref(getBindValues).size;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCanResize = computed(() => {
|
const getCanResize = computed(() => {
|
||||||
@@ -285,12 +280,12 @@
|
|||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
tableElRef,
|
tableElRef,
|
||||||
getBindValues,
|
getBindValues,
|
||||||
|
getDataSource,
|
||||||
densityOptions,
|
densityOptions,
|
||||||
reload,
|
reload,
|
||||||
densitySelect,
|
densitySelect,
|
||||||
updatePage,
|
updatePage,
|
||||||
updatePageSize,
|
updatePageSize,
|
||||||
updateCheckedRowKeys,
|
|
||||||
pagination,
|
pagination,
|
||||||
tableAction,
|
tableAction,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,23 +7,25 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
|
||||||
<CellComponent
|
<div class="editable-cell-content-comp">
|
||||||
v-bind="getComponentProps"
|
<CellComponent
|
||||||
:component="getComponent"
|
v-bind="getComponentProps"
|
||||||
:style="getWrapperStyle"
|
:component="getComponent"
|
||||||
:popoverVisible="getRuleVisible"
|
:style="getWrapperStyle"
|
||||||
:ruleMessage="ruleMessage"
|
:popoverVisible="getRuleVisible"
|
||||||
:rule="getRule"
|
:ruleMessage="ruleMessage"
|
||||||
:class="getWrapperClass"
|
:rule="getRule"
|
||||||
ref="elRef"
|
:class="getWrapperClass"
|
||||||
@options-change="handleOptionsChange"
|
ref="elRef"
|
||||||
@pressEnter="handleEnter"
|
@options-change="handleOptionsChange"
|
||||||
/>
|
@pressEnter="handleEnter"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div class="editable-cell-action" v-if="!getRowEditable">
|
<div class="editable-cell-action" v-if="!getRowEditable">
|
||||||
<n-icon class="cursor-pointer mx-2">
|
<n-icon class="mx-2 cursor-pointer">
|
||||||
<CheckOutlined @click="handleSubmit" />
|
<CheckOutlined @click="handleSubmit" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
<n-icon class="cursor-pointer mx-2">
|
<n-icon class="mx-2 cursor-pointer">
|
||||||
<CloseOutlined @click="handleCancel" />
|
<CloseOutlined @click="handleCancel" />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -49,7 +51,7 @@
|
|||||||
import { set, omit } from 'lodash-es';
|
import { set, omit } from 'lodash-es';
|
||||||
import { EventEnum } from '@/components/Table/src/componentMap';
|
import { EventEnum } from '@/components/Table/src/componentMap';
|
||||||
|
|
||||||
import dayjs from 'dayjs';
|
import { milliseconds, format } from 'date-fns';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'EditableCell',
|
name: 'EditableCell',
|
||||||
@@ -92,7 +94,7 @@
|
|||||||
|
|
||||||
const getIsCheckComp = computed(() => {
|
const getIsCheckComp = computed(() => {
|
||||||
const component = unref(getComponent);
|
const component = unref(getComponent);
|
||||||
return ['NCheckbox', 'NSwitch'].includes(component);
|
return ['NCheckbox', 'NRadio'].includes(component);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getComponentProps = computed(() => {
|
const getComponentProps = computed(() => {
|
||||||
@@ -108,10 +110,11 @@
|
|||||||
|
|
||||||
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
let value = isCheckValue ? (isNumber(val) && isBoolean(val) ? val : !!val) : val;
|
||||||
|
|
||||||
if (component === 'NDatePicker') {
|
if (isString(value) && component === 'NDatePicker') {
|
||||||
value = dayjs(value).valueOf();
|
value = milliseconds(value as Duration);
|
||||||
|
} else if (isArray(value) && component === 'NDatePicker') {
|
||||||
|
value = value.map((item) => milliseconds(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
const onEvent: any = editComponent ? EventEnum[editComponent] : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -195,13 +198,10 @@
|
|||||||
currentValueRef.value = e;
|
currentValueRef.value = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO 这里组件参数格式,和dayjs格式不一致
|
//TODO 根据组件格式化值
|
||||||
if (component === 'NDatePicker') {
|
// if (component === 'NDatePicker') {
|
||||||
let format = (props.column.editComponentProps?.format)
|
// currentValueRef.value = format(currentValueRef.value,'yyyy-MM-dd HH:mm:ss');
|
||||||
.replace(/yyyy/g, 'YYYY')
|
// }
|
||||||
.replace(/dd/g, 'DD');
|
|
||||||
currentValueRef.value = dayjs(currentValueRef.value).format(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
const onChange = props.column?.editComponentProps?.onChange;
|
const onChange = props.column?.editComponentProps?.onChange;
|
||||||
if (onChange && isFunction(onChange)) onChange(...arguments);
|
if (onChange && isFunction(onChange)) onChange(...arguments);
|
||||||
@@ -376,6 +376,10 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
&-comp{
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.edit-icon {
|
.edit-icon {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
//position: absolute;
|
//position: absolute;
|
||||||
|
|||||||
@@ -81,6 +81,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
|
import { ref, defineComponent, reactive, unref, toRaw, computed, toRefs, watchEffect } from 'vue';
|
||||||
import { useTableContext } from '../../hooks/useTableContext';
|
import { useTableContext } from '../../hooks/useTableContext';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
import {
|
import {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
DragOutlined,
|
DragOutlined,
|
||||||
@@ -107,7 +108,7 @@
|
|||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
const { getDarkTheme } = useDesignSetting();
|
const { getDarkTheme } = useDesignSetting();
|
||||||
const table = useTableContext();
|
const table: any = useTableContext();
|
||||||
const columnsList = ref<Options[]>([]);
|
const columnsList = ref<Options[]>([]);
|
||||||
const cacheColumnsList = ref<Options[]>([]);
|
const cacheColumnsList = ref<Options[]>([]);
|
||||||
|
|
||||||
@@ -135,8 +136,11 @@
|
|||||||
const checkList: any = columns.map((item) => item.key);
|
const checkList: any = columns.map((item) => item.key);
|
||||||
state.checkList = checkList;
|
state.checkList = checkList;
|
||||||
state.defaultCheckList = checkList;
|
state.defaultCheckList = checkList;
|
||||||
columnsList.value = columns;
|
const newColumns = columns.filter((item) => item.key != 'action' && item.title != '操作');
|
||||||
cacheColumnsList.value = columns;
|
if (!columnsList.value.length) {
|
||||||
|
columnsList.value = cloneDeep(newColumns);
|
||||||
|
cacheColumnsList.value = cloneDeep(newColumns);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//切换
|
//切换
|
||||||
@@ -154,11 +158,11 @@
|
|||||||
|
|
||||||
//获取
|
//获取
|
||||||
function getColumns() {
|
function getColumns() {
|
||||||
let newRet = [];
|
let newRet: any[] = [];
|
||||||
table.getColumns().forEach((item) => {
|
table.getColumns().forEach((item) => {
|
||||||
newRet.push({ ...item });
|
newRet.push({ ...item });
|
||||||
});
|
});
|
||||||
return newRet.filter((item) => item.key != 'action' && item.title != '操作');
|
return newRet;
|
||||||
}
|
}
|
||||||
|
|
||||||
//重置
|
//重置
|
||||||
|
|||||||
@@ -48,11 +48,12 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
const columns = cloneDeep(pageColumns);
|
const columns = cloneDeep(pageColumns);
|
||||||
return columns
|
return columns
|
||||||
.filter((column) => {
|
.filter((column) => {
|
||||||
// @ts-ignore
|
return hasPermission(column.auth as string[]) && isIfShow(column);
|
||||||
return hasPermission(column.auth) && isIfShow(column);
|
|
||||||
})
|
})
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
const { edit, editRow } = column;
|
//默认 ellipsis 为true
|
||||||
|
column.ellipsis = typeof column.ellipsis === 'undefined' ? { tooltip: true } : false;
|
||||||
|
const { edit } = column;
|
||||||
if (edit) {
|
if (edit) {
|
||||||
column.render = renderEditCell(column);
|
column.render = renderEditCell(column);
|
||||||
if (edit) {
|
if (edit) {
|
||||||
@@ -91,10 +92,10 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
|
function handleActionColumn(propsRef: ComputedRef<BasicTableProps>, columns: BasicColumn[]) {
|
||||||
const { actionColumn } = unref(propsRef);
|
const { actionColumn } = unref(propsRef);
|
||||||
if (!actionColumn) return;
|
if (!actionColumn) return;
|
||||||
// @ts-ignore
|
!columns.find((col) => col.key === 'action') &&
|
||||||
columns.push({
|
columns.push({
|
||||||
...actionColumn,
|
...(actionColumn as any),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//设置
|
//设置
|
||||||
@@ -127,7 +128,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//获取
|
//获取
|
||||||
function getColumns() {
|
function getColumns(): BasicColumn[] {
|
||||||
const columns = toRaw(unref(getColumnsRef));
|
const columns = toRaw(unref(getColumnsRef));
|
||||||
return columns.map((item) => {
|
return columns.map((item) => {
|
||||||
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };
|
return { ...item, title: item.title, key: item.key, fixed: item.fixed || undefined };
|
||||||
@@ -140,12 +141,12 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//更新原始数据单个字段
|
//更新原始数据单个字段
|
||||||
function setCacheColumnsField(dataIndex: string | undefined, value: Partial<BasicColumn>) {
|
function setCacheColumnsField(key: string | undefined, value: Partial<BasicColumn>) {
|
||||||
if (!dataIndex || !value) {
|
if (!key || !value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cacheColumns.forEach((item) => {
|
cacheColumns.forEach((item) => {
|
||||||
if (item.key === dataIndex) {
|
if (item.key === key) {
|
||||||
Object.assign(item, value);
|
Object.assign(item, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function useDataSource(
|
|||||||
{ getPaginationInfo, setPagination, setLoading, tableData },
|
{ getPaginationInfo, setPagination, setLoading, tableData },
|
||||||
emit
|
emit
|
||||||
) {
|
) {
|
||||||
const dataSourceRef = ref([]);
|
const dataSourceRef = ref<Recordable[]>([]);
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
tableData.value = unref(dataSourceRef);
|
tableData.value = unref(dataSourceRef);
|
||||||
@@ -47,7 +47,7 @@ export function useDataSource(
|
|||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const { request, pagination }: any = unref(propsRef);
|
const { request, pagination }: any = unref(propsRef);
|
||||||
|
if (!request) return;
|
||||||
//组装分页信息
|
//组装分页信息
|
||||||
const pageField = APISETTING.pageField;
|
const pageField = APISETTING.pageField;
|
||||||
const sizeField = APISETTING.sizeField;
|
const sizeField = APISETTING.sizeField;
|
||||||
@@ -74,10 +74,9 @@ export function useDataSource(
|
|||||||
|
|
||||||
// 如果数据异常,需获取正确的页码再次执行
|
// 如果数据异常,需获取正确的页码再次执行
|
||||||
if (resultTotal) {
|
if (resultTotal) {
|
||||||
const currentTotalPage = Math.ceil(resultTotal / pageSize);
|
if (page > resultTotal) {
|
||||||
if (page > currentTotalPage) {
|
|
||||||
setPagination({
|
setPagination({
|
||||||
[pageField]: currentTotalPage,
|
[pageField]: resultTotal,
|
||||||
});
|
});
|
||||||
fetch(opt);
|
fetch(opt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { BasicTableProps } from '../types/table';
|
|||||||
import { computed, unref, ref, ComputedRef } from 'vue';
|
import { computed, unref, ref, ComputedRef } from 'vue';
|
||||||
|
|
||||||
import { isBoolean } from '@/utils/is';
|
import { isBoolean } from '@/utils/is';
|
||||||
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
import { APISETTING, DEFAULTPAGESIZE, PAGESIZES } from '../const';
|
||||||
|
|
||||||
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
||||||
const configRef = ref<PaginationProps>({});
|
const configRef = ref<PaginationProps>({});
|
||||||
@@ -14,6 +14,7 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
|||||||
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const { totalField } = APISETTING;
|
||||||
return {
|
return {
|
||||||
pageSize: DEFAULTPAGESIZE,
|
pageSize: DEFAULTPAGESIZE,
|
||||||
pageSizes: PAGESIZES,
|
pageSizes: PAGESIZES,
|
||||||
@@ -21,6 +22,7 @@ export function usePagination(refProps: ComputedRef<BasicTableProps>) {
|
|||||||
showQuickJumper: true,
|
showQuickJumper: true,
|
||||||
...(isBoolean(pagination) ? {} : pagination),
|
...(isBoolean(pagination) ? {} : pagination),
|
||||||
...unref(configRef),
|
...unref(configRef),
|
||||||
|
pageCount: unref(configRef)[totalField],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import type { PropType } from 'vue';
|
import type { PropType } from 'vue';
|
||||||
import { propTypes } from '@/utils/propTypes';
|
import { propTypes } from '@/utils/propTypes';
|
||||||
import { BasicColumn } from './types/table';
|
import { BasicColumn } from './types/table';
|
||||||
|
import { NDataTable } from 'naive-ui';
|
||||||
export const basicProps = {
|
export const basicProps = {
|
||||||
|
...NDataTable.props, // 这里继承原 UI 组件的 props
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: null,
|
default: null,
|
||||||
@@ -15,7 +16,7 @@ export const basicProps = {
|
|||||||
type: String,
|
type: String,
|
||||||
default: 'medium',
|
default: 'medium',
|
||||||
},
|
},
|
||||||
tableData: {
|
dataSource: {
|
||||||
type: [Object],
|
type: [Object],
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
@@ -27,7 +28,6 @@ export const basicProps = {
|
|||||||
request: {
|
request: {
|
||||||
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
type: Function as PropType<(...arg: any[]) => Promise<any>>,
|
||||||
default: null,
|
default: null,
|
||||||
required: true,
|
|
||||||
},
|
},
|
||||||
rowKey: {
|
rowKey: {
|
||||||
type: [String, Function] as PropType<string | ((record) => string)>,
|
type: [String, Function] as PropType<string | ((record) => string)>,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface BasicColumn extends TableBaseColumn {
|
|||||||
editValueMap?: (value: any) => string;
|
editValueMap?: (value: any) => string;
|
||||||
onEditRow?: () => void;
|
onEditRow?: () => void;
|
||||||
// 权限编码控制是否显示
|
// 权限编码控制是否显示
|
||||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
auth?: string[];
|
||||||
// 业务控制是否显示
|
// 业务控制是否显示
|
||||||
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
ifShow?: boolean | ((column: BasicColumn) => boolean);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// @ts-ignore
|
|
||||||
import { NButton } from 'naive-ui';
|
import { NButton } from 'naive-ui';
|
||||||
import { RoleEnum } from '@/enums/roleEnum';
|
import { PermissionsEnum } from '@/enums/permissionsEnum';
|
||||||
// @ts-ignore
|
|
||||||
export interface ActionItem extends NButton.props {
|
export interface ActionItem extends NButton.props {
|
||||||
onClick?: Fn;
|
onClick?: Fn;
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -11,7 +9,7 @@ export interface ActionItem extends NButton.props {
|
|||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
// 权限编码控制是否显示
|
// 权限编码控制是否显示
|
||||||
auth?: RoleEnum | RoleEnum[] | string | string[];
|
auth?: PermissionsEnum | PermissionsEnum[] | string | string[];
|
||||||
// 业务控制是否显示
|
// 业务控制是否显示
|
||||||
ifShow?: boolean | ((action: ActionItem) => boolean);
|
ifShow?: boolean | ((action: ActionItem) => boolean);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, toRefs, reactive, computed } from 'vue';
|
import { defineComponent, toRefs, reactive, computed } from 'vue';
|
||||||
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
import { EyeOutlined, DeleteOutlined, PlusOutlined } from '@vicons/antd';
|
||||||
import { NUpload } from 'naive-ui';
|
|
||||||
import { basicProps } from './props';
|
import { basicProps } from './props';
|
||||||
import { useMessage, useDialog } from 'naive-ui';
|
import { useMessage, useDialog } from 'naive-ui';
|
||||||
import { ResultEnum } from '@/enums/httpEnum';
|
import { ResultEnum } from '@/enums/httpEnum';
|
||||||
@@ -85,7 +84,6 @@
|
|||||||
|
|
||||||
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
components: { EyeOutlined, DeleteOutlined, PlusOutlined },
|
||||||
props: {
|
props: {
|
||||||
...NUpload.props, // 这里继承原 UI 组件的 props
|
|
||||||
...basicProps,
|
...basicProps,
|
||||||
},
|
},
|
||||||
emits: ['uploadChange', 'delete'],
|
emits: ['uploadChange', 'delete'],
|
||||||
@@ -103,8 +101,8 @@
|
|||||||
const state = reactive({
|
const state = reactive({
|
||||||
showModal: false,
|
showModal: false,
|
||||||
previewUrl: '',
|
previewUrl: '',
|
||||||
originalImgList: [],
|
originalImgList: [] as string[],
|
||||||
imgList: [],
|
imgList: [] as string[],
|
||||||
});
|
});
|
||||||
|
|
||||||
//赋值默认图片显示
|
//赋值默认图片显示
|
||||||
@@ -178,7 +176,7 @@
|
|||||||
const result = res[infoField];
|
const result = res[infoField];
|
||||||
//成功
|
//成功
|
||||||
if (code === ResultEnum.SUCCESS) {
|
if (code === ResultEnum.SUCCESS) {
|
||||||
let imgUrl = getImgUrl(result.photo);
|
let imgUrl: string = getImgUrl(result.photo);
|
||||||
state.imgList.push(imgUrl);
|
state.imgList.push(imgUrl);
|
||||||
state.originalImgList.push(result.photo);
|
state.originalImgList.push(result.photo);
|
||||||
emit('uploadChange', state.originalImgList);
|
emit('uploadChange', state.originalImgList);
|
||||||
@@ -222,6 +220,7 @@
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: 0 0;
|
background: 0 0;
|
||||||
|
|
||||||
.upload-card-item-info::before {
|
.upload-card-item-info::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export function useProjectSetting() {
|
|||||||
|
|
||||||
const getShowFooter = computed(() => projectStore.showFooter);
|
const getShowFooter = computed(() => projectStore.showFooter);
|
||||||
|
|
||||||
|
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
|
||||||
|
|
||||||
|
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getNavMode,
|
getNavMode,
|
||||||
getNavTheme,
|
getNavTheme,
|
||||||
@@ -29,5 +33,7 @@ export function useProjectSetting() {
|
|||||||
getCrumbsSetting,
|
getCrumbsSetting,
|
||||||
getPermissionMode,
|
getPermissionMode,
|
||||||
getShowFooter,
|
getShowFooter,
|
||||||
|
getIsPageAnimate,
|
||||||
|
getPageAnimateType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,14 @@ export const useBattery = () => {
|
|||||||
return `${~~hour}小时${~~minute}分钟`;
|
return `${~~hour}小时${~~minute}分钟`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 计算电池充满剩余时间
|
||||||
|
const calcChargingTime = computed(() => {
|
||||||
|
console.log(state.battery);
|
||||||
|
const hour = state.battery.chargingTime / 3600;
|
||||||
|
const minute = (state.battery.chargingTime / 60) % 60;
|
||||||
|
return `${~~hour}小时${~~minute}分钟`;
|
||||||
|
});
|
||||||
|
|
||||||
// 电池状态
|
// 电池状态
|
||||||
const batteryStatus = computed(() => {
|
const batteryStatus = computed(() => {
|
||||||
if (state.battery.charging && state.battery.level >= 100) {
|
if (state.battery.charging && state.battery.level >= 100) {
|
||||||
@@ -80,5 +88,6 @@ export const useBattery = () => {
|
|||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
batteryStatus,
|
batteryStatus,
|
||||||
calcDischargingTime,
|
calcDischargingTime,
|
||||||
|
calcChargingTime,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ export function usePermission() {
|
|||||||
* 检查权限
|
* 检查权限
|
||||||
* @param accesses
|
* @param accesses
|
||||||
*/
|
*/
|
||||||
function _someRoles(accesses: string[]) {
|
function _somePermissions(accesses: string[]) {
|
||||||
return userStore.getRoles.some((item) => {
|
return userStore.getPermissions.some((item) => {
|
||||||
const { value }: any = item;
|
const { value }: any = item;
|
||||||
return accesses.includes(value);
|
return accesses.includes(value);
|
||||||
});
|
});
|
||||||
@@ -20,7 +20,7 @@ export function usePermission() {
|
|||||||
* */
|
* */
|
||||||
function hasPermission(accesses: string[]): boolean {
|
function hasPermission(accesses: string[]): boolean {
|
||||||
if (!accesses || !accesses.length) return true;
|
if (!accesses || !accesses.length) return true;
|
||||||
return _someRoles(accesses);
|
return _somePermissions(accesses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -28,9 +28,9 @@ export function usePermission() {
|
|||||||
* @param accesses
|
* @param accesses
|
||||||
*/
|
*/
|
||||||
function hasEveryPermission(accesses: string[]): boolean {
|
function hasEveryPermission(accesses: string[]): boolean {
|
||||||
const rolesList = userStore.getRoles;
|
const permissionsList = userStore.getPermissions;
|
||||||
if (Array.isArray(accesses)) {
|
if (Array.isArray(accesses)) {
|
||||||
return accesses.every((access) => !!rolesList[access]);
|
return permissionsList.every((access: any) => accesses.includes(access.value));
|
||||||
}
|
}
|
||||||
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
|
throw new Error(`[hasEveryPermission]: ${accesses} should be a array !`);
|
||||||
}
|
}
|
||||||
@@ -41,9 +41,9 @@ export function usePermission() {
|
|||||||
* @param accessMap
|
* @param accessMap
|
||||||
*/
|
*/
|
||||||
function hasSomePermission(accesses: string[]): boolean {
|
function hasSomePermission(accesses: string[]): boolean {
|
||||||
const rolesList = userStore.getRoles;
|
const permissionsList = userStore.getPermissions;
|
||||||
if (Array.isArray(accesses)) {
|
if (Array.isArray(accesses)) {
|
||||||
return accesses.some((access) => !!rolesList[access]);
|
return permissionsList.some((access: any) => accesses.includes(access.value));
|
||||||
}
|
}
|
||||||
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
|
throw new Error(`[hasSomePermission]: ${accesses} should be a array !`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement" :native-scrollbar="false">
|
<n-drawer v-model:show="isDrawer" :width="width" :placement="placement">
|
||||||
<n-drawer-content :title="title">
|
<n-drawer-content :title="title" :native-scrollbar="false">
|
||||||
<div class="drawer">
|
<div class="drawer">
|
||||||
<n-divider title-placement="center">主题</n-divider>
|
<n-divider title-placement="center">主题</n-divider>
|
||||||
|
|
||||||
<div class="drawer-setting-item justify-center dark-switch">
|
<div class="justify-center drawer-setting-item dark-switch">
|
||||||
<n-tooltip placement="bottom">
|
<n-tooltip placement="bottom">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-switch v-model:value="designStore.darkTheme" class="dark-theme-switch">
|
<n-switch v-model:value="designStore.darkTheme" class="dark-theme-switch">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</n-switch>
|
</n-switch>
|
||||||
</template>
|
</template>
|
||||||
<span>深色主题</span>
|
<span>{{ designStore.darkTheme ? '深' : '浅' }}色主题</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -46,7 +46,11 @@
|
|||||||
<div class="drawer-setting-item-style align-items-top">
|
<div class="drawer-setting-item-style align-items-top">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavMode('vertical')" />
|
<img
|
||||||
|
src="~@/assets/images/nav-theme-dark.svg"
|
||||||
|
@click="togNavMode('vertical')"
|
||||||
|
alt="左侧菜单模式"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>左侧菜单模式</span>
|
<span>左侧菜单模式</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
@@ -56,12 +60,30 @@
|
|||||||
<div class="drawer-setting-item-style">
|
<div class="drawer-setting-item-style">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img src="~@/assets/images/nav-horizontal.svg" @click="togNavMode('horizontal')" />
|
<img
|
||||||
|
src="~@/assets/images/nav-horizontal.svg"
|
||||||
|
alt="顶部菜单模式"
|
||||||
|
@click="togNavMode('horizontal')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>顶部菜单模式</span>
|
<span>顶部菜单模式</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'horizontal'" />
|
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'horizontal'" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="drawer-setting-item-style">
|
||||||
|
<n-tooltip placement="top">
|
||||||
|
<template #trigger>
|
||||||
|
<img
|
||||||
|
src="~@/assets/images/nav-horizontal-mix.svg"
|
||||||
|
@click="togNavMode('horizontal-mix')"
|
||||||
|
alt="顶部菜单混合模式"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<span>顶部菜单混合模式</span>
|
||||||
|
</n-tooltip>
|
||||||
|
<n-badge dot color="#19be6b" v-show="settingStore.navMode === 'horizontal-mix'" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-divider title-placement="center">导航栏风格</n-divider>
|
<n-divider title-placement="center">导航栏风格</n-divider>
|
||||||
@@ -70,7 +92,11 @@
|
|||||||
<div class="drawer-setting-item-style align-items-top">
|
<div class="drawer-setting-item-style align-items-top">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img src="~@/assets/images/nav-theme-dark.svg" @click="togNavTheme('dark')" />
|
<img
|
||||||
|
src="~@/assets/images/nav-theme-dark.svg"
|
||||||
|
alt="暗色侧边栏"
|
||||||
|
@click="togNavTheme('dark')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>暗色侧边栏</span>
|
<span>暗色侧边栏</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
@@ -80,21 +106,24 @@
|
|||||||
<div class="drawer-setting-item-style">
|
<div class="drawer-setting-item-style">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img src="~@/assets/images/nav-theme-light.svg" @click="togNavTheme('light')" />
|
<img
|
||||||
|
src="~@/assets/images/nav-theme-light.svg"
|
||||||
|
alt="白色侧边栏"
|
||||||
|
@click="togNavTheme('light')"
|
||||||
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>白色侧边栏</span>
|
<span>白色侧边栏</span>
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'light'" />
|
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'light'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="drawer-setting-item align-items-top">
|
|
||||||
<div class="drawer-setting-item-style">
|
<div class="drawer-setting-item-style">
|
||||||
<n-tooltip placement="top">
|
<n-tooltip placement="top">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<img
|
<img
|
||||||
src="~@/assets/images/header-theme-dark.svg"
|
src="~@/assets/images/header-theme-dark.svg"
|
||||||
@click="togNavTheme('header-dark')"
|
@click="togNavTheme('header-dark')"
|
||||||
|
alt="暗色顶栏"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<span>暗色顶栏</span>
|
<span>暗色顶栏</span>
|
||||||
@@ -102,9 +131,18 @@
|
|||||||
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'header-dark'" />
|
<n-badge dot color="#19be6b" v-if="settingStore.navTheme === 'header-dark'" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<n-divider title-placement="center">界面功能</n-divider>
|
<n-divider title-placement="center">界面功能</n-divider>
|
||||||
|
|
||||||
|
<div class="drawer-setting-item">
|
||||||
|
<div class="drawer-setting-item-title"> 分割菜单 </div>
|
||||||
|
<div class="drawer-setting-item-action">
|
||||||
|
<n-switch
|
||||||
|
:disabled="settingStore.navMode !== 'horizontal-mix'"
|
||||||
|
v-model:value="settingStore.menuSetting.mixMenu"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<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">
|
||||||
@@ -165,6 +203,22 @@
|
|||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
<!-- </div>-->
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<n-divider title-placement="center">动画</n-divider>
|
||||||
|
|
||||||
|
<div class="drawer-setting-item">
|
||||||
|
<div class="drawer-setting-item-title"> 禁用动画 </div>
|
||||||
|
<div class="drawer-setting-item-action">
|
||||||
|
<n-switch v-model:value="settingStore.isPageAnimate" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="drawer-setting-item">
|
||||||
|
<div class="drawer-setting-item-title"> 动画类型 </div>
|
||||||
|
<div class="drawer-setting-item-select">
|
||||||
|
<n-select v-model:value="settingStore.pageAnimateType" :options="animateOptions" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="drawer-setting-item">
|
<div class="drawer-setting-item">
|
||||||
<n-alert type="warning" :showIcon="false">
|
<n-alert type="warning" :showIcon="false">
|
||||||
<p>{{ alertText }}</p>
|
<p>{{ alertText }}</p>
|
||||||
@@ -176,12 +230,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, toRefs, watch } from 'vue';
|
import { defineComponent, reactive, toRefs, unref, watch, computed } from 'vue';
|
||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
import { useDesignSettingStore } from '@/store/modules/designSetting';
|
||||||
import { CheckOutlined } from '@vicons/antd';
|
import { CheckOutlined } from '@vicons/antd';
|
||||||
import { Moon, SunnySharp } from '@vicons/ionicons5';
|
import { Moon, SunnySharp } from '@vicons/ionicons5';
|
||||||
import { darkTheme } from 'naive-ui';
|
import { darkTheme } from 'naive-ui';
|
||||||
|
import { animates as animateOptions } from '@/settings/animateSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'ProjectSetting',
|
name: 'ProjectSetting',
|
||||||
@@ -216,6 +271,10 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const directionsOptions = computed(() => {
|
||||||
|
return animateOptions.find((item) => item.value == unref(settingStore.pageAnimateType));
|
||||||
|
});
|
||||||
|
|
||||||
function openDrawer() {
|
function openDrawer() {
|
||||||
state.isDrawer = true;
|
state.isDrawer = true;
|
||||||
}
|
}
|
||||||
@@ -226,7 +285,7 @@
|
|||||||
|
|
||||||
function togNavTheme(theme) {
|
function togNavTheme(theme) {
|
||||||
settingStore.navTheme = theme;
|
settingStore.navTheme = theme;
|
||||||
if (settingStore.navMode === 'horizontal' && theme === 'light') {
|
if (settingStore.navMode === 'horizontal' && ['light'].includes(theme)) {
|
||||||
settingStore.navTheme = 'dark';
|
settingStore.navTheme = 'dark';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -237,11 +296,7 @@
|
|||||||
|
|
||||||
function togNavMode(mode) {
|
function togNavMode(mode) {
|
||||||
settingStore.navMode = mode;
|
settingStore.navMode = mode;
|
||||||
if (mode === 'horizontal') {
|
settingStore.menuSetting.mixMenu = false;
|
||||||
settingStore.setNavTheme('light');
|
|
||||||
} else {
|
|
||||||
settingStore.setNavTheme('dark');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -254,6 +309,8 @@
|
|||||||
darkTheme,
|
darkTheme,
|
||||||
openDrawer,
|
openDrawer,
|
||||||
closeDrawer,
|
closeDrawer,
|
||||||
|
animateOptions,
|
||||||
|
directionsOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -288,6 +345,10 @@
|
|||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-select {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.theme-item {
|
.theme-item {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
min-width: 20px;
|
min-width: 20px;
|
||||||
@@ -297,7 +358,7 @@
|
|||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin: 0 5px 5px 0;
|
margin: 0 5px 5px 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
line-height: 14px;
|
||||||
.n-icon {
|
.n-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
@@ -312,6 +373,7 @@
|
|||||||
.justify-center {
|
.justify-center {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark-switch .n-switch {
|
.dark-switch .n-switch {
|
||||||
::v-deep(.n-switch__rail) {
|
::v-deep(.n-switch__rail) {
|
||||||
background-color: #000e1c;
|
background-color: #000e1c;
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="layout-header" :class="{ 'layout-header-light': !(navTheme == 'header-dark') }">
|
<div class="layout-header">
|
||||||
<!--顶部菜单-->
|
<!--顶部菜单-->
|
||||||
<div class="layout-header-left" v-if="navMode === 'horizontal'">
|
<div
|
||||||
<AsideMenu v-model:collapsed="collapsed" mode="horizontal" class="n-menu-horizontal-light" />
|
class="layout-header-left"
|
||||||
|
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
|
||||||
|
>
|
||||||
|
<div class="logo" v-if="navMode === 'horizontal'">
|
||||||
|
<img src="~@/assets/images/logo.png" alt="" />
|
||||||
|
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
||||||
|
</div>
|
||||||
|
<AsideMenu
|
||||||
|
v-model:collapsed="collapsed"
|
||||||
|
v-model:location="getMenuLocation"
|
||||||
|
:inverted="getInverted"
|
||||||
|
mode="horizontal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<!--左侧菜单-->
|
<!--左侧菜单-->
|
||||||
<div class="layout-header-left" v-else>
|
<div class="layout-header-left" v-else>
|
||||||
@@ -131,6 +143,9 @@
|
|||||||
collapsed: {
|
collapsed: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
inverted: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
@@ -153,6 +168,15 @@
|
|||||||
crumbsSetting: getCrumbsSetting,
|
crumbsSetting: getCrumbsSetting,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getInverted = computed(() => {
|
||||||
|
const navTheme = unref(getNavTheme);
|
||||||
|
return ['light', 'header-dark'].includes(navTheme) ? props.inverted : !props.inverted;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mixMenu = computed(() => {
|
||||||
|
return unref(getMenuSetting).mixMenu;
|
||||||
|
});
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props;
|
const { collapsed } = props;
|
||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||||
@@ -162,6 +186,10 @@
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getMenuLocation = computed(() => {
|
||||||
|
return 'header';
|
||||||
|
});
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
@@ -305,6 +333,9 @@
|
|||||||
reloadPage,
|
reloadPage,
|
||||||
drawerSetting,
|
drawerSetting,
|
||||||
openSetting,
|
openSetting,
|
||||||
|
getInverted,
|
||||||
|
getMenuLocation,
|
||||||
|
mixMenu,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -321,27 +352,43 @@
|
|||||||
transition: all 0.2s ease-in-out;
|
transition: all 0.2s ease-in-out;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
//color: #fff;
|
|
||||||
|
|
||||||
//.n-icon {
|
|
||||||
// color: #fff
|
|
||||||
//}
|
|
||||||
|
|
||||||
&-left {
|
&-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 64px;
|
||||||
|
line-height: 64px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: auto;
|
||||||
|
height: 32px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
::v-deep(.ant-breadcrumb span:last-child .link-text) {
|
::v-deep(.ant-breadcrumb span:last-child .link-text) {
|
||||||
color: #515a6e;
|
color: #515a6e;
|
||||||
}
|
}
|
||||||
|
|
||||||
::v-deep(.n-breadcrumb .n-breadcrumb-item:last-child .n-breadcrumb-item__link) {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.n-breadcrumb {
|
.n-breadcrumb {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-menu {
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-right {
|
&-right {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="~@/assets/images/logo.png" alt="" />
|
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
|
||||||
<h2 v-show="!collapsed" class="title"> NaiveUiAdmin</h2>
|
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,7 +32,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: white;
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<RouterView>
|
<RouterView>
|
||||||
<template #default="{ Component, route }">
|
<template #default="{ Component, route }">
|
||||||
<transition name="zoom-fade" mode="out-in" appear>
|
<transition :name="getTransitionName" mode="out-in" appear>
|
||||||
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
|
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
|
||||||
<component :is="Component" :key="route.fullPath" />
|
<component :is="Component" :key="route.fullPath" />
|
||||||
</keep-alive>
|
</keep-alive>
|
||||||
@@ -12,8 +12,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { defineComponent, computed } from 'vue';
|
import { defineComponent, computed, unref } from 'vue';
|
||||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||||
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MainView',
|
name: 'MainView',
|
||||||
@@ -29,11 +30,18 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
|
const { getIsPageAnimate, getPageAnimateType } = useProjectSetting();
|
||||||
const asyncRouteStore = useAsyncRouteStore();
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
// 需要缓存的路由组件
|
// 需要缓存的路由组件
|
||||||
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
|
||||||
|
|
||||||
|
const getTransitionName = computed(() => {
|
||||||
|
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : '';
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
keepAliveComponents,
|
keepAliveComponents,
|
||||||
|
getTransitionName,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,20 +6,21 @@
|
|||||||
:collapsed="collapsed"
|
:collapsed="collapsed"
|
||||||
:collapsed-width="64"
|
:collapsed-width="64"
|
||||||
:collapsed-icon-size="20"
|
:collapsed-icon-size="20"
|
||||||
:indent="28"
|
:indent="24"
|
||||||
:expanded-keys="openKeys"
|
:expanded-keys="openKeys"
|
||||||
v-model:value="selectedKeys"
|
:value="getSelectedKeys"
|
||||||
@update:value="clickMenuItem"
|
@update:value="clickMenuItem"
|
||||||
@update:expanded-keys="menuExpanded"
|
@update:expanded-keys="menuExpanded"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, computed, watch, toRefs, unref } from 'vue';
|
import { defineComponent, ref, onMounted, reactive, computed, watch, toRefs, unref } from 'vue';
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
import { useAsyncRouteStore } from '@/store/modules/asyncRoute';
|
||||||
import { generatorMenu } from '@/utils/index';
|
import { generatorMenu, generatorMenuMix } from '@/utils';
|
||||||
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Menu',
|
name: 'Menu',
|
||||||
@@ -34,13 +35,26 @@
|
|||||||
// 侧边栏菜单是否收起
|
// 侧边栏菜单是否收起
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
},
|
},
|
||||||
|
//位置
|
||||||
|
location: {
|
||||||
|
type: String,
|
||||||
|
default: 'left',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
emits: ['update:collapsed'],
|
||||||
|
setup(props, { emit }) {
|
||||||
// 当前路由
|
// 当前路由
|
||||||
const currentRoute = useRoute();
|
const currentRoute = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const asyncRouteStore = useAsyncRouteStore();
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
const settingStore = useProjectSettingStore();
|
const settingStore = useProjectSettingStore();
|
||||||
|
const menus = ref<any[]>([]);
|
||||||
|
const selectedKeys = ref<string>(currentRoute.name as string);
|
||||||
|
const headerMenuSelectKey = ref<string>('');
|
||||||
|
|
||||||
|
const { getNavMode } = useProjectSetting();
|
||||||
|
|
||||||
|
const navMode = getNavMode;
|
||||||
|
|
||||||
// 获取当前打开的子菜单
|
// 获取当前打开的子菜单
|
||||||
const matched = currentRoute.matched;
|
const matched = currentRoute.matched;
|
||||||
@@ -49,36 +63,61 @@
|
|||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
openKeys: getOpenKeys,
|
openKeys: getOpenKeys,
|
||||||
selectedKeys: currentRoute.name,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const inverted = computed(() => {
|
const inverted = computed(() => {
|
||||||
return ['dark', 'header-dark'].includes(settingStore.navTheme);
|
return ['dark', 'header-dark'].includes(settingStore.navTheme);
|
||||||
});
|
});
|
||||||
|
|
||||||
const menus = computed(() => {
|
const getSelectedKeys = computed(() => {
|
||||||
return generatorMenu(asyncRouteStore.getMenus);
|
let location = props.location;
|
||||||
|
return location === 'left' || (location === 'header' && unref(navMode) === 'horizontal')
|
||||||
|
? unref(selectedKeys)
|
||||||
|
: unref(headerMenuSelectKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听菜单收缩状态
|
// 监听分割菜单
|
||||||
watch(
|
watch(
|
||||||
() => props.collapsed,
|
() => settingStore.menuSetting.mixMenu,
|
||||||
(newVal) => {
|
() => {
|
||||||
state.openKeys = newVal ? [] : getOpenKeys;
|
updateMenu();
|
||||||
state.selectedKeys = currentRoute.name;
|
if (props.collapsed) {
|
||||||
|
emit('update:collapsed', !props.collapsed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 监听菜单收缩状态
|
||||||
|
// watch(
|
||||||
|
// () => props.collapsed,
|
||||||
|
// (newVal) => {
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
|
||||||
// 跟随页面路由变化,切换菜单选中状态
|
// 跟随页面路由变化,切换菜单选中状态
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.fullPath,
|
() => currentRoute.fullPath,
|
||||||
() => {
|
() => {
|
||||||
|
updateMenu();
|
||||||
const matched = currentRoute.matched;
|
const matched = currentRoute.matched;
|
||||||
state.openKeys = matched.map((item) => item.name);
|
state.openKeys = matched.map((item) => item.name);
|
||||||
state.selectedKeys = currentRoute.name;
|
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
|
||||||
|
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function updateMenu() {
|
||||||
|
if (!settingStore.menuSetting.mixMenu) {
|
||||||
|
menus.value = generatorMenu(asyncRouteStore.getMenus);
|
||||||
|
} else {
|
||||||
|
//混合菜单
|
||||||
|
const firstRouteName: string = (currentRoute.matched[0].name as string) || '';
|
||||||
|
menus.value = generatorMenuMix(asyncRouteStore.getMenus, firstRouteName, props.location);
|
||||||
|
const activeMenu: string = currentRoute?.matched[0].meta?.activeMenu as string;
|
||||||
|
headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 点击菜单
|
// 点击菜单
|
||||||
function clickMenuItem(key: string) {
|
function clickMenuItem(key: string) {
|
||||||
if (/http(s)?:/.test(key)) {
|
if (/http(s)?:/.test(key)) {
|
||||||
@@ -101,17 +140,24 @@
|
|||||||
if (!key) return false;
|
if (!key) return false;
|
||||||
const subRouteChildren: string[] = [];
|
const subRouteChildren: string[] = [];
|
||||||
for (const { children, key } of unref(menus)) {
|
for (const { children, key } of unref(menus)) {
|
||||||
if (children && children.length > 0) {
|
if (children && children.length) {
|
||||||
subRouteChildren.push(key as string);
|
subRouteChildren.push(key as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return subRouteChildren.includes(key);
|
return subRouteChildren.includes(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
updateMenu();
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
inverted,
|
inverted,
|
||||||
menus,
|
menus,
|
||||||
|
selectedKeys,
|
||||||
|
headerMenuSelectKey,
|
||||||
|
getSelectedKeys,
|
||||||
clickMenuItem,
|
clickMenuItem,
|
||||||
menuExpanded,
|
menuExpanded,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
'tabs-view-fix': multiTabsSetting.fixed,
|
'tabs-view-fix': multiTabsSetting.fixed,
|
||||||
'tabs-view-fixed-header': isMultiHeaderFixed,
|
'tabs-view-fixed-header': isMultiHeaderFixed,
|
||||||
'tabs-view-default-background': getDarkTheme === false,
|
'tabs-view-default-background': getDarkTheme === false,
|
||||||
|
'tabs-view-dark-background': getDarkTheme === true,
|
||||||
}"
|
}"
|
||||||
:style="getChangeStyle"
|
:style="getChangeStyle"
|
||||||
>
|
>
|
||||||
@@ -29,27 +30,26 @@
|
|||||||
</n-icon>
|
</n-icon>
|
||||||
</span>
|
</span>
|
||||||
<div ref="navScroll" class="tabs-card-scroll">
|
<div ref="navScroll" class="tabs-card-scroll">
|
||||||
<div ref="navRef" class="tabs-card-nav" :style="getNavStyle">
|
<Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex">
|
||||||
<Draggable :list="tabsList" animation="300" item-key="fullPath" class="flex">
|
<template #item="{ element }">
|
||||||
<template #item="{ element }">
|
<div
|
||||||
<div
|
: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.path }"
|
||||||
@click.stop="goPage(element)"
|
@click.stop="goPage(element)"
|
||||||
@contextmenu="handleContextMenu($event, element)"
|
@contextmenu="handleContextMenu($event, element)"
|
||||||
|
>
|
||||||
|
<span>{{ element.meta.title }}</span>
|
||||||
|
<n-icon
|
||||||
|
size="14"
|
||||||
|
@click.stop="closeTabItem(element)"
|
||||||
|
v-if="element.path !== baseHome"
|
||||||
>
|
>
|
||||||
<span>{{ element.meta.title }}</span>
|
<CloseOutlined />
|
||||||
<n-icon
|
</n-icon>
|
||||||
size="14"
|
</div>
|
||||||
@click.stop="closeTabItem(element)"
|
</template>
|
||||||
v-if="element.path != baseHome"
|
</Draggable>
|
||||||
>
|
|
||||||
<CloseOutlined />
|
|
||||||
</n-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Draggable>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs-close">
|
<div class="tabs-close">
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
placement="bottom-end"
|
placement="bottom-end"
|
||||||
:options="TabsMenuOptions"
|
:options="TabsMenuOptions"
|
||||||
>
|
>
|
||||||
<div class="tabs-close-btn" @click.prevent>
|
<div class="tabs-close-btn">
|
||||||
<n-icon size="16" color="#515a6e">
|
<n-icon size="16" color="#515a6e">
|
||||||
<DownOutlined />
|
<DownOutlined />
|
||||||
</n-icon>
|
</n-icon>
|
||||||
@@ -86,7 +86,6 @@
|
|||||||
computed,
|
computed,
|
||||||
ref,
|
ref,
|
||||||
toRefs,
|
toRefs,
|
||||||
toRaw,
|
|
||||||
unref,
|
unref,
|
||||||
provide,
|
provide,
|
||||||
watch,
|
watch,
|
||||||
@@ -101,8 +100,7 @@
|
|||||||
import { RouteItem } from '@/store/modules/tabsView';
|
import { RouteItem } from '@/store/modules/tabsView';
|
||||||
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
|
||||||
import { useMessage } from 'naive-ui';
|
import { useMessage } from 'naive-ui';
|
||||||
// @ts-ignore
|
import Draggable from 'vuedraggable';
|
||||||
import Draggable from 'vuedraggable/src/vuedraggable';
|
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
import {
|
import {
|
||||||
DownOutlined,
|
DownOutlined,
|
||||||
@@ -113,9 +111,11 @@
|
|||||||
LeftOutlined,
|
LeftOutlined,
|
||||||
RightOutlined,
|
RightOutlined,
|
||||||
} from '@vicons/antd';
|
} from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon } from '@/utils';
|
||||||
import elementResizeDetectorMaker from 'element-resize-detector';
|
import elementResizeDetectorMaker from 'element-resize-detector';
|
||||||
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
|
||||||
|
import { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
import { useThemeVars } from 'naive-ui';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TabsView',
|
name: 'TabsView',
|
||||||
@@ -132,26 +132,33 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const { getDarkTheme } = useDesignSetting();
|
const { getDarkTheme, getAppTheme } = useDesignSetting();
|
||||||
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
|
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting } =
|
||||||
useProjectSetting();
|
useProjectSetting();
|
||||||
|
const settingStore = useProjectSettingStore();
|
||||||
|
|
||||||
const message = useMessage();
|
const message = useMessage();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const tabsViewStore = useTabsViewStore();
|
const tabsViewStore = useTabsViewStore();
|
||||||
const asyncRouteStore = useAsyncRouteStore();
|
const asyncRouteStore = useAsyncRouteStore();
|
||||||
const navRef: any = ref(null);
|
|
||||||
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 themeVars = useThemeVars();
|
||||||
|
|
||||||
|
const getCardColor = computed(() => {
|
||||||
|
return themeVars.value.cardColor;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getBaseColor = computed(() => {
|
||||||
|
return themeVars.value.textColor1;
|
||||||
|
});
|
||||||
|
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
activeKey: route.fullPath,
|
activeKey: route.fullPath,
|
||||||
scrollable: false,
|
scrollable: false,
|
||||||
navStyle: {
|
|
||||||
transform: '',
|
|
||||||
},
|
|
||||||
dropdownX: 0,
|
dropdownX: 0,
|
||||||
dropdownY: 0,
|
dropdownY: 0,
|
||||||
showDropdown: false,
|
showDropdown: false,
|
||||||
@@ -165,6 +172,14 @@
|
|||||||
return { fullPath, hash, meta, name, params, path, query };
|
return { fullPath, hash, meta, name, params, path, query };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMixMenuNoneSub = computed(() => {
|
||||||
|
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||||
|
const currentRoute = useRoute();
|
||||||
|
const navMode = unref(getNavMode);
|
||||||
|
if (unref(navMode) != 'horizontal-mix') return true;
|
||||||
|
return !(unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
|
||||||
|
});
|
||||||
|
|
||||||
//动态组装样式 菜单缩进
|
//动态组装样式 菜单缩进
|
||||||
const getChangeStyle = computed(() => {
|
const getChangeStyle = computed(() => {
|
||||||
const { collapsed } = props;
|
const { collapsed } = props;
|
||||||
@@ -172,7 +187,11 @@
|
|||||||
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
|
||||||
const { fixed }: any = unref(getMultiTabsSetting);
|
const { fixed }: any = unref(getMultiTabsSetting);
|
||||||
let lenNum =
|
let lenNum =
|
||||||
navMode === 'horizontal' ? '0px' : collapsed ? `${minMenuWidth}px` : `${menuWidth}px`;
|
navMode === 'horizontal' || !isMixMenuNoneSub.value
|
||||||
|
? '0px'
|
||||||
|
: collapsed
|
||||||
|
? `${minMenuWidth}px`
|
||||||
|
: `${menuWidth}px`;
|
||||||
return {
|
return {
|
||||||
left: lenNum,
|
left: lenNum,
|
||||||
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
width: `calc(100% - ${!fixed ? '0px' : lenNum})`,
|
||||||
@@ -181,7 +200,7 @@
|
|||||||
|
|
||||||
//tags 右侧下拉菜单
|
//tags 右侧下拉菜单
|
||||||
const TabsMenuOptions = computed(() => {
|
const TabsMenuOptions = computed(() => {
|
||||||
const isDisabled = unref(tabsList).length <= 1 ? true : false;
|
const isDisabled = unref(tabsList).length <= 1;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '刷新当前',
|
label: '刷新当前',
|
||||||
@@ -209,17 +228,27 @@
|
|||||||
];
|
];
|
||||||
});
|
});
|
||||||
|
|
||||||
let routes: RouteItem[] = [];
|
let cacheRoutes: RouteItem[] = [];
|
||||||
|
const simpleRoute = getSimpleRoute(route);
|
||||||
try {
|
try {
|
||||||
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
|
const routesStr = storage.get(TABS_ROUTES) as string | null | undefined;
|
||||||
routes = routesStr ? JSON.parse(routesStr) : [getSimpleRoute(route)];
|
cacheRoutes = routesStr ? JSON.parse(routesStr) : [simpleRoute];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
routes = [getSimpleRoute(route)];
|
cacheRoutes = [simpleRoute];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将最新的路由信息同步到 localStorage 中
|
||||||
|
const routes = router.getRoutes();
|
||||||
|
cacheRoutes.forEach((cacheRoute) => {
|
||||||
|
const route = routes.find((route) => route.path === cacheRoute.path);
|
||||||
|
if (route) {
|
||||||
|
cacheRoute.meta = route.meta || cacheRoute.meta;
|
||||||
|
cacheRoute.name = (route.name || cacheRoute.name) as string;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 初始化标签页
|
// 初始化标签页
|
||||||
tabsViewStore.initTabs(routes);
|
tabsViewStore.initTabs(cacheRoutes);
|
||||||
|
|
||||||
//监听滚动条
|
//监听滚动条
|
||||||
function onScroll(e) {
|
function onScroll(e) {
|
||||||
@@ -228,11 +257,11 @@
|
|||||||
document.documentElement.scrollTop ||
|
document.documentElement.scrollTop ||
|
||||||
window.pageYOffset ||
|
window.pageYOffset ||
|
||||||
document.body.scrollTop; // 滚动条偏移量
|
document.body.scrollTop; // 滚动条偏移量
|
||||||
if (!getHeaderSetting.fixed && getMultiTabsSetting.fixed && scrollTop >= 64) {
|
state.isMultiHeaderFixed = !!(
|
||||||
state.isMultiHeaderFixed = true;
|
!getHeaderSetting.fixed &&
|
||||||
} else {
|
getMultiTabsSetting.fixed &&
|
||||||
state.isMultiHeaderFixed = false;
|
scrollTop >= 64
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('scroll', onScroll, true);
|
window.addEventListener('scroll', onScroll, true);
|
||||||
@@ -264,7 +293,7 @@
|
|||||||
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.addTabs(getSimpleRoute(route));
|
||||||
updateNavScroll();
|
updateNavScroll(true);
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
);
|
);
|
||||||
@@ -354,66 +383,76 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
updateNavScroll();
|
updateNavScroll();
|
||||||
|
state.showDropdown = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCurrentScrollOffset() {
|
/**
|
||||||
const { navStyle } = state;
|
* @param value 要滚动到的位置
|
||||||
const transform: any = toRaw(navStyle.transform);
|
* @param amplitude 每次滚动的长度
|
||||||
return transform ? Number(transform.match(/translateX\(-(\d+(\.\d+)*)px\)/)[1]) : 0;
|
*/
|
||||||
}
|
function scrollTo(value: number, amplitude: number) {
|
||||||
|
const currentScroll = navScroll.value.scrollLeft;
|
||||||
function setOffset(value) {
|
const scrollWidth =
|
||||||
state.navStyle.transform = `translateX(-${value}px)`;
|
(amplitude > 0 && currentScroll + amplitude >= value) ||
|
||||||
|
(amplitude < 0 && currentScroll + amplitude <= value)
|
||||||
|
? value
|
||||||
|
: currentScroll + amplitude;
|
||||||
|
navScroll.value && navScroll.value.scrollTo(scrollWidth, 0);
|
||||||
|
if (scrollWidth === value) return;
|
||||||
|
return window.requestAnimationFrame(() => scrollTo(value, amplitude));
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollPrev() {
|
function scrollPrev() {
|
||||||
const containerWidth = navScroll.value.offsetWidth;
|
const containerWidth = navScroll.value.offsetWidth;
|
||||||
const currentOffset = getCurrentScrollOffset();
|
const currentScroll = navScroll.value.scrollLeft;
|
||||||
if (!currentOffset) return;
|
|
||||||
let newOffset = currentOffset > containerWidth ? currentOffset - containerWidth : 0;
|
if (!currentScroll) return;
|
||||||
setOffset(newOffset);
|
const scrollLeft = currentScroll > containerWidth ? currentScroll - containerWidth : 0;
|
||||||
|
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollNext() {
|
function scrollNext() {
|
||||||
const navWidth = navRef.value.scrollWidth;
|
|
||||||
const containerWidth = navScroll.value.offsetWidth;
|
const containerWidth = navScroll.value.offsetWidth;
|
||||||
const currentOffset = getCurrentScrollOffset();
|
const navWidth = navScroll.value.scrollWidth;
|
||||||
if (navWidth - currentOffset <= containerWidth) return;
|
const currentScroll = navScroll.value.scrollLeft;
|
||||||
|
|
||||||
let newOffset =
|
if (navWidth - currentScroll <= containerWidth) return;
|
||||||
navWidth - currentOffset > containerWidth * 2
|
const scrollLeft =
|
||||||
? currentOffset + containerWidth
|
navWidth - currentScroll > containerWidth * 2
|
||||||
|
? currentScroll + containerWidth
|
||||||
: navWidth - containerWidth;
|
: navWidth - containerWidth;
|
||||||
|
scrollTo(scrollLeft, (scrollLeft - currentScroll) / 20);
|
||||||
setOffset(newOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateNavScroll() {
|
/**
|
||||||
if (!navRef.value) return;
|
* @param autoScroll 是否开启自动滚动功能
|
||||||
let navWidth = navRef.value.scrollWidth;
|
*/
|
||||||
let containerWidth = navScroll.value.offsetWidth;
|
async function updateNavScroll(autoScroll?: boolean) {
|
||||||
const currentOffset = getCurrentScrollOffset();
|
await nextTick();
|
||||||
|
if (!navScroll.value) return;
|
||||||
|
const containerWidth = navScroll.value.offsetWidth;
|
||||||
|
const navWidth = navScroll.value.scrollWidth;
|
||||||
|
|
||||||
if (containerWidth < navWidth) {
|
if (containerWidth < navWidth) {
|
||||||
state.scrollable = true;
|
state.scrollable = true;
|
||||||
if (navWidth - currentOffset < containerWidth) {
|
if (autoScroll) {
|
||||||
setOffset(navWidth - containerWidth);
|
let tagList = navScroll.value.querySelectorAll('.tabs-card-scroll-item') || [];
|
||||||
|
[...tagList].forEach((tag: HTMLElement) => {
|
||||||
|
// fix SyntaxError
|
||||||
|
if (tag.id === `tag${state.activeKey.split('/').join('\/')}`) {
|
||||||
|
tag.scrollIntoView && tag.scrollIntoView();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.scrollable = false;
|
state.scrollable = false;
|
||||||
if (currentOffset > 0) {
|
|
||||||
setOffset(0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
updateNavScroll();
|
updateNavScroll(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const getNavStyle = computed(() => {
|
|
||||||
return state.navStyle;
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleContextMenu(e, item) {
|
function handleContextMenu(e, item) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path;
|
isCurrent.value = PageEnum.BASE_HOME_REDIRECT === item.path;
|
||||||
@@ -457,7 +496,6 @@
|
|||||||
return {
|
return {
|
||||||
...toRefs(state),
|
...toRefs(state),
|
||||||
navWrap,
|
navWrap,
|
||||||
navRef,
|
|
||||||
navScroll,
|
navScroll,
|
||||||
route,
|
route,
|
||||||
tabsList,
|
tabsList,
|
||||||
@@ -474,10 +512,12 @@
|
|||||||
closeHandleSelect,
|
closeHandleSelect,
|
||||||
scrollNext,
|
scrollNext,
|
||||||
scrollPrev,
|
scrollPrev,
|
||||||
getNavStyle,
|
|
||||||
handleContextMenu,
|
handleContextMenu,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
getDarkTheme,
|
getDarkTheme,
|
||||||
|
getAppTheme,
|
||||||
|
getCardColor,
|
||||||
|
getBaseColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -534,22 +574,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-scroll {
|
&-scroll {
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
.tabs-card-nav {
|
|
||||||
padding-left: 0;
|
|
||||||
margin: 0;
|
|
||||||
float: left;
|
|
||||||
list-style: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
transition: transform 0.5s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-item {
|
&-item {
|
||||||
background: var(--color);
|
background: v-bind(getCardColor);
|
||||||
color: var(--text-color);
|
color: v-bind(getBaseColor);
|
||||||
height: 32px;
|
height: 32px;
|
||||||
padding: 6px 16px 4px;
|
padding: 6px 16px 4px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@@ -557,6 +587,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
span {
|
span {
|
||||||
float: left;
|
float: left;
|
||||||
@@ -588,7 +619,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.active-item {
|
.active-item {
|
||||||
color: #2d8cf0;
|
color: v-bind(getAppTheme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -624,6 +655,10 @@
|
|||||||
background: #f5f7f9;
|
background: #f5f7f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tabs-view-dark-background {
|
||||||
|
background: #101014;
|
||||||
|
}
|
||||||
|
|
||||||
.tabs-view-fix {
|
.tabs-view-fix {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<NLayout class="layout" :position="fixedMenu" has-sider>
|
<n-layout class="layout" :position="fixedMenu" has-sider>
|
||||||
<NLayoutSider
|
<n-layout-sider
|
||||||
v-if="navMode === 'vertical'"
|
v-if="isMixMenuNoneSub && (navMode === 'vertical' || navMode === 'horizontal-mix')"
|
||||||
show-trigger
|
show-trigger="bar"
|
||||||
@collapse="collapsed = true"
|
@collapse="collapsed = true"
|
||||||
:position="fixedMenu"
|
:position="fixedMenu"
|
||||||
@expand="collapsed = false"
|
@expand="collapsed = false"
|
||||||
@@ -15,15 +15,15 @@
|
|||||||
class="layout-sider"
|
class="layout-sider"
|
||||||
>
|
>
|
||||||
<Logo :collapsed="collapsed" />
|
<Logo :collapsed="collapsed" />
|
||||||
<AsideMenu v-model:collapsed="collapsed" />
|
<AsideMenu v-model:collapsed="collapsed" v-model:location="getMenuLocation" />
|
||||||
</NLayoutSider>
|
</n-layout-sider>
|
||||||
|
|
||||||
<NLayout :inverted="inverted">
|
<n-layout :inverted="inverted">
|
||||||
<NLayoutHeader :inverted="inverted" :position="fixedHeader">
|
<n-layout-header :inverted="getHeaderInverted" :position="fixedHeader">
|
||||||
<PageHeader v-model:collapsed="collapsed" />
|
<PageHeader v-model:collapsed="collapsed" :inverted="inverted" />
|
||||||
</NLayoutHeader>
|
</n-layout-header>
|
||||||
|
|
||||||
<NLayoutContent
|
<n-layout-content
|
||||||
class="layout-content"
|
class="layout-content"
|
||||||
:class="{ 'layout-default-background': getDarkTheme === false }"
|
:class="{ 'layout-default-background': getDarkTheme === false }"
|
||||||
>
|
>
|
||||||
@@ -50,107 +50,106 @@
|
|||||||
<!-- <NLayoutFooter v-if="getShowFooter">-->
|
<!-- <NLayoutFooter v-if="getShowFooter">-->
|
||||||
<!-- <PageFooter />-->
|
<!-- <PageFooter />-->
|
||||||
<!-- </NLayoutFooter>-->
|
<!-- </NLayoutFooter>-->
|
||||||
</NLayoutContent>
|
</n-layout-content>
|
||||||
</NLayout>
|
<n-back-top :right="100" />
|
||||||
</NLayout>
|
</n-layout>
|
||||||
|
</n-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts" setup>
|
||||||
import { defineComponent, ref, unref, computed, onMounted } from 'vue';
|
import { ref, unref, computed, onMounted } from 'vue';
|
||||||
import { Logo } from './components/Logo';
|
import { Logo } from './components/Logo';
|
||||||
import { TabsView } from './components/TagsView';
|
import { TabsView } from './components/TagsView';
|
||||||
import { MainView } from './components/Main';
|
import { MainView } from './components/Main';
|
||||||
import { AsideMenu } from './components/Menu';
|
import { AsideMenu } from './components/Menu';
|
||||||
import { PageHeader } from './components/Header';
|
import { PageHeader } from './components/Header';
|
||||||
import { PageFooter } from './components/Footer';
|
|
||||||
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 { useProjectSettingStore } from '@/store/modules/projectSetting';
|
||||||
|
|
||||||
export default defineComponent({
|
const { getDarkTheme } = useDesignSetting();
|
||||||
name: 'Layout',
|
const {
|
||||||
components: {
|
getShowFooter,
|
||||||
TabsView,
|
getNavMode,
|
||||||
MainView,
|
getNavTheme,
|
||||||
PageHeader,
|
getHeaderSetting,
|
||||||
AsideMenu,
|
getMenuSetting,
|
||||||
Logo,
|
getMultiTabsSetting,
|
||||||
PageFooter,
|
} = useProjectSetting();
|
||||||
},
|
|
||||||
setup() {
|
|
||||||
const { getDarkTheme } = useDesignSetting();
|
|
||||||
|
|
||||||
const {
|
const settingStore = useProjectSettingStore();
|
||||||
getShowFooter,
|
|
||||||
getNavMode,
|
|
||||||
getNavTheme,
|
|
||||||
getHeaderSetting,
|
|
||||||
getMenuSetting,
|
|
||||||
getMultiTabsSetting,
|
|
||||||
} = useProjectSetting();
|
|
||||||
|
|
||||||
const navMode = getNavMode;
|
const navMode = getNavMode;
|
||||||
|
|
||||||
const collapsed = ref<boolean>(false);
|
const collapsed = ref<boolean>(false);
|
||||||
|
|
||||||
const fixedHeader = computed(() => {
|
const fixedHeader = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting);
|
const { fixed } = unref(getHeaderSetting);
|
||||||
return fixed ? 'absolute' : 'static';
|
return fixed ? 'absolute' : 'static';
|
||||||
});
|
});
|
||||||
|
|
||||||
const fixedMenu = computed(() => {
|
const isMixMenuNoneSub = computed(() => {
|
||||||
const { fixed } = unref(getHeaderSetting);
|
const mixMenu = settingStore.menuSetting.mixMenu;
|
||||||
return fixed ? 'absolute' : 'static';
|
const currentRoute = useRoute();
|
||||||
});
|
if (unref(navMode) != 'horizontal-mix') return true;
|
||||||
|
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
const isMultiTabs = computed(() => {
|
const fixedMenu = computed(() => {
|
||||||
return unref(getMultiTabsSetting).show;
|
const { fixed } = unref(getHeaderSetting);
|
||||||
});
|
return fixed ? 'absolute' : 'static';
|
||||||
|
});
|
||||||
|
|
||||||
const fixedMulti = computed(() => {
|
const isMultiTabs = computed(() => {
|
||||||
return unref(getMultiTabsSetting).fixed;
|
return unref(getMultiTabsSetting).show;
|
||||||
});
|
});
|
||||||
|
|
||||||
const inverted = computed(() => {
|
const fixedMulti = computed(() => {
|
||||||
return ['dark', 'header-dark'].includes(unref(getNavTheme));
|
return unref(getMultiTabsSetting).fixed;
|
||||||
});
|
});
|
||||||
|
|
||||||
const leftMenuWidth = computed(() => {
|
const inverted = computed(() => {
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
return ['dark', 'header-dark'].includes(unref(getNavTheme));
|
||||||
return collapsed.value ? minMenuWidth : menuWidth;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
const getChangeStyle = computed(() => {
|
const getHeaderInverted = computed(() => {
|
||||||
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
const navTheme = unref(getNavTheme);
|
||||||
return {
|
return ['light', 'header-dark'].includes(navTheme) ? unref(inverted) : !unref(inverted);
|
||||||
'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
|
});
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
function watchWidth() {
|
const leftMenuWidth = computed(() => {
|
||||||
const Width = document.body.clientWidth;
|
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||||
if (Width <= 950) {
|
return collapsed.value ? minMenuWidth : menuWidth;
|
||||||
collapsed.value = true;
|
});
|
||||||
} else collapsed.value = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
const getChangeStyle = computed(() => {
|
||||||
window.addEventListener('resize', watchWidth);
|
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
|
||||||
});
|
return {
|
||||||
|
'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
const getMenuLocation = computed(() => {
|
||||||
fixedMenu,
|
return 'left';
|
||||||
fixedMulti,
|
});
|
||||||
fixedHeader,
|
|
||||||
collapsed,
|
const watchWidth = () => {
|
||||||
inverted,
|
const Width = document.body.clientWidth;
|
||||||
isMultiTabs,
|
if (Width <= 950) {
|
||||||
leftMenuWidth,
|
collapsed.value = true;
|
||||||
getChangeStyle,
|
} else collapsed.value = false;
|
||||||
navMode,
|
};
|
||||||
getShowFooter,
|
|
||||||
getDarkTheme,
|
onMounted(() => {
|
||||||
};
|
window.addEventListener('resize', watchWidth);
|
||||||
},
|
//挂载在 window 方便与在js中使用
|
||||||
|
window['$loading'] = useLoadingBar();
|
||||||
|
window['$loading'].finish();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ import {
|
|||||||
NUpload,
|
NUpload,
|
||||||
NTree,
|
NTree,
|
||||||
NSpin,
|
NSpin,
|
||||||
|
NTimePicker,
|
||||||
|
NBackTop,
|
||||||
|
NSkeleton,
|
||||||
} from 'naive-ui';
|
} from 'naive-ui';
|
||||||
|
|
||||||
const naive = create({
|
const naive = create({
|
||||||
@@ -129,6 +132,9 @@ const naive = create({
|
|||||||
NUpload,
|
NUpload,
|
||||||
NTree,
|
NTree,
|
||||||
NSpin,
|
NSpin,
|
||||||
|
NTimePicker,
|
||||||
|
NBackTop,
|
||||||
|
NSkeleton,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = {
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '/:path(.*)*',
|
path: '/:path(.*)*',
|
||||||
name: 'ErrorPage',
|
name: 'ErrorPageSon',
|
||||||
component: ErrorPage,
|
component: ErrorPage,
|
||||||
meta: {
|
meta: {
|
||||||
title: 'ErrorPage',
|
title: 'ErrorPage',
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { renderIcon } from '@/utils/index';
|
|
||||||
import { DashboardOutlined } from '@vicons/antd';
|
|
||||||
// import { RouterTransition } from '@/components/transition'
|
|
||||||
|
|
||||||
//前端路由映射表
|
|
||||||
export const constantRouterComponents = {
|
|
||||||
Layout: () => import('@/layout/index.vue'), //布局
|
|
||||||
DashboardConsole: () => import('@/views/dashboard/console/console.vue'), // 主控台
|
|
||||||
DashboardMonitor: () => import('@/views/dashboard/monitor/monitor.vue'), // 监控页
|
|
||||||
DashboardWorkplace: () => import('@/views/dashboard/workplace/workplace.vue'), // 工作台
|
|
||||||
};
|
|
||||||
|
|
||||||
//前端路由图标映射表
|
|
||||||
export const constantRouterIcon = {
|
|
||||||
DashboardOutlined: renderIcon(DashboardOutlined),
|
|
||||||
};
|
|
||||||
@@ -1,12 +1,17 @@
|
|||||||
import { adminMenus } from '@/api/system/menu';
|
import { adminMenus } from '@/api/system/menu';
|
||||||
import { constantRouterComponents, constantRouterIcon } from './constantRouterComponents';
|
import { constantRouterIcon } from './router-icons';
|
||||||
import router from '@/router/index';
|
|
||||||
import { constantRouter } from '@/router/index';
|
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { Layout, ParentLayout } from '@/router/constant';
|
||||||
|
import type { AppRouteRecordRaw } from '@/router/types';
|
||||||
|
|
||||||
|
const Iframe = () => import('@/views/iframe/index.vue');
|
||||||
|
const LayoutMap = new Map<string, () => Promise<typeof import('*.vue')>>();
|
||||||
|
|
||||||
|
LayoutMap.set('LAYOUT', Layout);
|
||||||
|
LayoutMap.set('IFRAME', Iframe);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化 后端 结构信息并递归生成层级路由表
|
* 格式化 后端 结构信息并递归生成层级路由表
|
||||||
*
|
|
||||||
* @param routerMap
|
* @param routerMap
|
||||||
* @param parent
|
* @param parent
|
||||||
* @returns {*}
|
* @returns {*}
|
||||||
@@ -19,21 +24,24 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
|||||||
// 路由名称,建议唯一
|
// 路由名称,建议唯一
|
||||||
name: item.name || '',
|
name: item.name || '',
|
||||||
// 该路由对应页面的 组件
|
// 该路由对应页面的 组件
|
||||||
component: constantRouterComponents[item.component],
|
component: item.component,
|
||||||
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
|
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
|
||||||
meta: {
|
meta: {
|
||||||
...item.meta,
|
...item.meta,
|
||||||
label: item.meta.title,
|
label: item.meta.title,
|
||||||
icon: constantRouterIcon[item.meta.icon] || null,
|
icon: constantRouterIcon[item.meta.icon] || null,
|
||||||
permission: item.meta.permission || null,
|
permissions: item.meta.permissions || null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
|
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
|
||||||
currentRouter.path = currentRouter.path.replace('//', '/');
|
currentRouter.path = currentRouter.path.replace('//', '/');
|
||||||
// 重定向
|
// 重定向
|
||||||
item.redirect && (currentRouter.redirect = item.redirect);
|
item.redirect && (currentRouter.redirect = item.redirect);
|
||||||
// 是否有子菜单,并递归处理
|
// 是否有子菜单,并递归处理
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
|
//如果未定义 redirect 默认第一个子路由为 redirect
|
||||||
|
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
|
||||||
// Recursion
|
// Recursion
|
||||||
currentRouter.children = routerGenerator(item.children, currentRouter);
|
currentRouter.children = routerGenerator(item.children, currentRouter);
|
||||||
}
|
}
|
||||||
@@ -43,7 +51,6 @@ export const routerGenerator = (routerMap, parent?): any[] => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态生成菜单
|
* 动态生成菜单
|
||||||
* @param token
|
|
||||||
* @returns {Promise<Router>}
|
* @returns {Promise<Router>}
|
||||||
*/
|
*/
|
||||||
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
||||||
@@ -51,14 +58,65 @@ export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
|
|||||||
adminMenus()
|
adminMenus()
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
const routeList = routerGenerator(result);
|
const routeList = routerGenerator(result);
|
||||||
const asyncRoutesList = [...constantRouter, ...routeList];
|
asyncImportRoute(routeList);
|
||||||
asyncRoutesList.forEach((item) => {
|
|
||||||
router.addRoute(item);
|
resolve(routeList);
|
||||||
});
|
|
||||||
resolve(asyncRoutesList);
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
reject(err);
|
reject(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查找views中对应的组件文件
|
||||||
|
* */
|
||||||
|
let viewsModules: Record<string, () => Promise<Recordable>>;
|
||||||
|
export const asyncImportRoute = (routes: AppRouteRecordRaw[] | undefined): void => {
|
||||||
|
viewsModules = viewsModules || import.meta.glob('../views/**/*.{vue,tsx}');
|
||||||
|
if (!routes) return;
|
||||||
|
routes.forEach((item) => {
|
||||||
|
if (!item.component && item.meta?.frameSrc) {
|
||||||
|
item.component = 'IFRAME';
|
||||||
|
}
|
||||||
|
const { component, name } = item;
|
||||||
|
const { children } = item;
|
||||||
|
if (component) {
|
||||||
|
const layoutFound = LayoutMap.get(component as string);
|
||||||
|
if (layoutFound) {
|
||||||
|
item.component = layoutFound;
|
||||||
|
} else {
|
||||||
|
item.component = dynamicImport(viewsModules, component as string);
|
||||||
|
}
|
||||||
|
} else if (name) {
|
||||||
|
item.component = ParentLayout;
|
||||||
|
}
|
||||||
|
children && asyncImportRoute(children);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态导入
|
||||||
|
* */
|
||||||
|
export const dynamicImport = (
|
||||||
|
viewsModules: Record<string, () => Promise<Recordable>>,
|
||||||
|
component: string
|
||||||
|
) => {
|
||||||
|
const keys = Object.keys(viewsModules);
|
||||||
|
const matchKeys = keys.filter((key) => {
|
||||||
|
let k = key.replace('../views', '');
|
||||||
|
const lastIndex = k.lastIndexOf('.');
|
||||||
|
k = k.substring(0, lastIndex);
|
||||||
|
return k === component;
|
||||||
|
});
|
||||||
|
if (matchKeys?.length === 1) {
|
||||||
|
const matchKey = matchKeys[0];
|
||||||
|
return viewsModules[matchKey];
|
||||||
|
}
|
||||||
|
if (matchKeys?.length > 1) {
|
||||||
|
console.warn(
|
||||||
|
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { ErrorPageRoute, 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 './router-guards';
|
||||||
import 'nprogress/css/nprogress.css'; // 进度条样式
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
const modules = import.meta.globEager('./modules/**/*.ts');
|
const modules = import.meta.globEager('./modules/**/*.ts');
|
||||||
|
|
||||||
const routeModuleList: RouteRecordRaw[] = [];
|
const routeModuleList: RouteRecordRaw[] = [];
|
||||||
@@ -17,7 +15,7 @@ Object.keys(modules).forEach((key) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
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);
|
||||||
@@ -41,7 +39,7 @@ export const LoginRoute: RouteRecordRaw = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//需要验证权限
|
//需要验证权限
|
||||||
export const asyncRoutes = [ErrorPageRoute, ...routeModuleList];
|
export const asyncRoutes = [...routeModuleList];
|
||||||
|
|
||||||
//普通路由 无需验证权限
|
//普通路由 无需验证权限
|
||||||
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
|
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
|
||||||
|
|||||||
32
src/router/modules/about.ts
Normal file
32
src/router/modules/about.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { Layout } from '@/router/constant';
|
||||||
|
import { ProjectOutlined } from '@vicons/antd';
|
||||||
|
import { renderIcon, renderNew } from '@/utils/index';
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'about',
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
sort: 10,
|
||||||
|
isRoot: true,
|
||||||
|
activeMenu: 'about_index',
|
||||||
|
icon: renderIcon(ProjectOutlined),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'index',
|
||||||
|
name: `about_index`,
|
||||||
|
meta: {
|
||||||
|
title: '关于',
|
||||||
|
extra: renderNew(),
|
||||||
|
activeMenu: 'about_index',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/about/index.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import { Layout, ParentLayout } from '@/router/constant';
|
import { Layout, ParentLayout } from '@/router/constant';
|
||||||
import { WalletOutlined } from '@vicons/antd';
|
import { WalletOutlined } from '@vicons/antd';
|
||||||
import { renderIcon } from '@/utils/index';
|
import { renderIcon, renderNew } from '@/utils';
|
||||||
|
|
||||||
const routeName = 'comp';
|
const routeName = 'comp';
|
||||||
|
|
||||||
@@ -63,14 +63,67 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'form',
|
||||||
|
name: `${routeName}_form`,
|
||||||
|
redirect: '/comp/form/basic',
|
||||||
|
component: ParentLayout,
|
||||||
|
meta: {
|
||||||
|
title: '表单',
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'basic',
|
||||||
|
name: `${routeName}_form_basic`,
|
||||||
|
meta: {
|
||||||
|
title: '基础使用',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/comp/form/basic.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'useForm',
|
||||||
|
name: `useForm`,
|
||||||
|
meta: {
|
||||||
|
title: 'useForm',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/comp/form/useForm.vue'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'upload',
|
path: 'upload',
|
||||||
name: `${routeName}_upload`,
|
name: `${routeName}_upload`,
|
||||||
meta: {
|
meta: {
|
||||||
title: '上传',
|
title: '上传图片',
|
||||||
},
|
},
|
||||||
component: () => import('@/views/comp/upload/index.vue'),
|
component: () => import('@/views/comp/upload/index.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'modal',
|
||||||
|
name: `${routeName}_modal`,
|
||||||
|
meta: {
|
||||||
|
title: '弹窗扩展',
|
||||||
|
},
|
||||||
|
component: () => import('@/views/comp/modal/index.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'richtext',
|
||||||
|
name: `richtext`,
|
||||||
|
meta: {
|
||||||
|
title: '富文本',
|
||||||
|
extra: renderNew(),
|
||||||
|
},
|
||||||
|
component: () => import('@/views/comp/richtext/vue-quill.vue'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'drag',
|
||||||
|
name: `Drag`,
|
||||||
|
meta: {
|
||||||
|
title: '拖拽',
|
||||||
|
extra: renderNew(),
|
||||||
|
},
|
||||||
|
component: () => import('@/views/comp/drag/index.vue'),
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: 'Dashboard',
|
title: 'Dashboard',
|
||||||
icon: renderIcon(DashboardOutlined),
|
icon: renderIcon(DashboardOutlined),
|
||||||
permission: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
|
permissions: ['dashboard_console', 'dashboard_console', 'dashboard_workplace'],
|
||||||
sort: 0,
|
sort: 0,
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
@@ -33,7 +33,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
name: `${routeName}_console`,
|
name: `${routeName}_console`,
|
||||||
meta: {
|
meta: {
|
||||||
title: '主控台',
|
title: '主控台',
|
||||||
permission: ['dashboard_console'],
|
permissions: ['dashboard_console'],
|
||||||
},
|
},
|
||||||
component: () => import('@/views/dashboard/console/console.vue'),
|
component: () => import('@/views/dashboard/console/console.vue'),
|
||||||
},
|
},
|
||||||
@@ -42,7 +42,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
// name: `${ routeName }_monitor`,
|
// name: `${ routeName }_monitor`,
|
||||||
// meta: {
|
// meta: {
|
||||||
// title: '监控页',
|
// title: '监控页',
|
||||||
// permission: ['dashboard_monitor']
|
// permissions: ['dashboard_monitor']
|
||||||
// },
|
// },
|
||||||
// component: () => import('@/views/dashboard/monitor/monitor.vue')
|
// component: () => import('@/views/dashboard/monitor/monitor.vue')
|
||||||
// },
|
// },
|
||||||
@@ -52,7 +52,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '工作台',
|
title: '工作台',
|
||||||
keepAlive: true,
|
keepAlive: true,
|
||||||
permission: ['dashboard_workplace'],
|
permissions: ['dashboard_workplace'],
|
||||||
},
|
},
|
||||||
component: () => import('@/views/dashboard/workplace/workplace.vue'),
|
component: () => import('@/views/dashboard/workplace/workplace.vue'),
|
||||||
},
|
},
|
||||||
|
|||||||
19
src/router/modules/docs.ts
Normal file
19
src/router/modules/docs.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { Layout } from '@/router/constant';
|
||||||
|
import { DocumentTextOutline } from '@vicons/ionicons5';
|
||||||
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: '/external',
|
||||||
|
name: 'https://naive-ui-admin-docs.vercel.app',
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
title: '项目文档',
|
||||||
|
icon: renderIcon(DocumentTextOutline),
|
||||||
|
sort: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
42
src/router/modules/frame.ts
Normal file
42
src/router/modules/frame.ts
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
import { Layout } from '@/router/constant';
|
||||||
|
import { DesktopOutline } from '@vicons/ionicons5';
|
||||||
|
import { renderIcon } from '@/utils/index';
|
||||||
|
|
||||||
|
const IFrame = () => import('@/views/iframe/index.vue');
|
||||||
|
|
||||||
|
const routes: Array<RouteRecordRaw> = [
|
||||||
|
{
|
||||||
|
path: '/frame',
|
||||||
|
name: 'Frame',
|
||||||
|
redirect: '/frame/docs',
|
||||||
|
component: Layout,
|
||||||
|
meta: {
|
||||||
|
title: '外部页面',
|
||||||
|
sort: 8,
|
||||||
|
icon: renderIcon(DesktopOutline),
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'docs',
|
||||||
|
name: 'frame-docs',
|
||||||
|
meta: {
|
||||||
|
title: '项目文档(内嵌)',
|
||||||
|
frameSrc: 'https://naive-ui-admin-docs.vercel.app',
|
||||||
|
},
|
||||||
|
component: IFrame,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'naive',
|
||||||
|
name: 'frame-naive',
|
||||||
|
meta: {
|
||||||
|
title: 'NaiveUi(内嵌)',
|
||||||
|
frameSrc: 'https://www.naiveui.com',
|
||||||
|
},
|
||||||
|
component: IFrame,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default routes;
|
||||||
@@ -40,6 +40,7 @@ const routes: Array<RouteRecordRaw> = [
|
|||||||
meta: {
|
meta: {
|
||||||
title: '基础详情',
|
title: '基础详情',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
activeMenu: 'basic-list',
|
||||||
},
|
},
|
||||||
component: () => import('@/views/list/basicList/info.vue'),
|
component: () => import('@/views/list/basicList/info.vue'),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
|
import type { RouteRecordRaw } from 'vue-router';
|
||||||
import { isNavigationFailure, Router } from 'vue-router';
|
import { isNavigationFailure, Router } from 'vue-router';
|
||||||
import { useUserStoreWidthOut } from '@/store/modules/user';
|
import { useUserStoreWidthOut } from '@/store/modules/user';
|
||||||
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
|
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
|
||||||
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
import { ACCESS_TOKEN } from '@/store/mutation-types';
|
||||||
import { storage } from '@/utils/Storage';
|
import { storage } from '@/utils/Storage';
|
||||||
import { PageEnum } from '@/enums/pageEnum';
|
import { PageEnum } from '@/enums/pageEnum';
|
||||||
|
import { ErrorPageRoute } from '@/router/base';
|
||||||
|
|
||||||
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
const LOGIN_PATH = PageEnum.BASE_LOGIN;
|
||||||
|
|
||||||
@@ -12,8 +14,8 @@ const whitePathList = [LOGIN_PATH]; // no redirect whitelist
|
|||||||
export function createRouterGuards(router: Router) {
|
export function createRouterGuards(router: Router) {
|
||||||
const userStore = useUserStoreWidthOut();
|
const userStore = useUserStoreWidthOut();
|
||||||
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
const asyncRouteStore = useAsyncRouteStoreWidthOut();
|
||||||
const Loading = window['$loading'] || null;
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const Loading = window['$loading'] || null;
|
||||||
Loading && Loading.start();
|
Loading && Loading.start();
|
||||||
if (from.path === LOGIN_PATH && to.name === 'errorPage') {
|
if (from.path === LOGIN_PATH && to.name === 'errorPage') {
|
||||||
next(PageEnum.BASE_HOME);
|
next(PageEnum.BASE_HOME);
|
||||||
@@ -29,7 +31,7 @@ export function createRouterGuards(router: Router) {
|
|||||||
const token = storage.get(ACCESS_TOKEN);
|
const token = storage.get(ACCESS_TOKEN);
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
// You can access without permission. You need to set the routing meta.ignoreAuth to true
|
// You can access without permissions. You need to set the routing meta.ignoreAuth to true
|
||||||
if (to.meta.ignoreAuth) {
|
if (to.meta.ignoreAuth) {
|
||||||
next();
|
next();
|
||||||
return;
|
return;
|
||||||
@@ -60,9 +62,15 @@ export function createRouterGuards(router: Router) {
|
|||||||
|
|
||||||
// 动态添加可访问路由表
|
// 动态添加可访问路由表
|
||||||
routes.forEach((item) => {
|
routes.forEach((item) => {
|
||||||
router.addRoute(item);
|
router.addRoute(item as unknown as RouteRecordRaw);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//添加404
|
||||||
|
const isErrorPage = router.getRoutes().findIndex((item) => item.name === ErrorPageRoute.name);
|
||||||
|
if (isErrorPage === -1) {
|
||||||
|
router.addRoute(ErrorPageRoute as unknown as RouteRecordRaw);
|
||||||
|
}
|
||||||
|
|
||||||
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 };
|
||||||
@@ -91,6 +99,7 @@ export function createRouterGuards(router: Router) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
|
asyncRouteStore.setKeepAliveComponents(keepAliveComponents);
|
||||||
|
const Loading = window['$loading'] || null;
|
||||||
Loading && Loading.finish();
|
Loading && Loading.finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
7
src/router/router-icons.ts
Normal file
7
src/router/router-icons.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { renderIcon } from '@/utils/index';
|
||||||
|
import { DashboardOutlined } from '@vicons/antd';
|
||||||
|
|
||||||
|
//前端路由图标映射表
|
||||||
|
export const constantRouterIcon = {
|
||||||
|
DashboardOutlined: renderIcon(DashboardOutlined),
|
||||||
|
};
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
|
import type { RouteRecordRaw, RouteMeta } from 'vue-router';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import { RoleEnum } from '@/enums/roleEnum';
|
|
||||||
|
|
||||||
export type Component<T extends any = any> =
|
export type Component<T extends any = any> =
|
||||||
| ReturnType<typeof defineComponent>
|
| ReturnType<typeof defineComponent>
|
||||||
| (() => Promise<typeof import('*.vue')>)
|
| (() => Promise<typeof import('*.vue')>)
|
||||||
| (() => Promise<T>);
|
| (() => Promise<T>);
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
export interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||||
name: string;
|
name: string;
|
||||||
meta: RouteMeta;
|
meta: RouteMeta;
|
||||||
@@ -23,7 +21,7 @@ export interface Meta {
|
|||||||
title: string;
|
title: string;
|
||||||
// 是否忽略权限
|
// 是否忽略权限
|
||||||
ignoreAuth?: boolean;
|
ignoreAuth?: boolean;
|
||||||
roles?: RoleEnum[];
|
permissions?: string[];
|
||||||
// 是否不缓存
|
// 是否不缓存
|
||||||
noKeepAlive?: boolean;
|
noKeepAlive?: boolean;
|
||||||
// 是否固定在tab上
|
// 是否固定在tab上
|
||||||
@@ -34,4 +32,24 @@ export interface Meta {
|
|||||||
frameSrc?: string;
|
frameSrc?: string;
|
||||||
// 外链跳转地址
|
// 外链跳转地址
|
||||||
externalLink?: string;
|
externalLink?: string;
|
||||||
|
//隐藏
|
||||||
|
hidden?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Menu {
|
||||||
|
title: string;
|
||||||
|
label: string;
|
||||||
|
key: string;
|
||||||
|
meta: RouteMeta;
|
||||||
|
name: string;
|
||||||
|
component?: Component | string;
|
||||||
|
components?: Component;
|
||||||
|
children?: AppRouteRecordRaw[];
|
||||||
|
props?: Recordable;
|
||||||
|
fullPath?: string;
|
||||||
|
icon?: any;
|
||||||
|
path: string;
|
||||||
|
permissions?: string[];
|
||||||
|
redirect?: string;
|
||||||
|
sort?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/settings/animateSetting.ts
Normal file
8
src/settings/animateSetting.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const animates = [
|
||||||
|
{ value: 'zoom-fade', label: '渐变' },
|
||||||
|
{ value: 'zoom-out', label: '闪现' },
|
||||||
|
{ value: 'fade-slide', label: '滑动' },
|
||||||
|
{ value: 'fade', label: '消退' },
|
||||||
|
{ value: 'fade-bottom', label: '底部消退' },
|
||||||
|
{ value: 'fade-scale', label: '缩放消退' },
|
||||||
|
];
|
||||||
@@ -31,6 +31,8 @@ const setting = {
|
|||||||
menuWidth: 200,
|
menuWidth: 200,
|
||||||
//固定菜单
|
//固定菜单
|
||||||
fixed: true,
|
fixed: true,
|
||||||
|
//分割菜单
|
||||||
|
mixMenu: false,
|
||||||
},
|
},
|
||||||
//面包屑
|
//面包屑
|
||||||
crumbsSetting: {
|
crumbsSetting: {
|
||||||
@@ -39,7 +41,11 @@ const setting = {
|
|||||||
//显示图标
|
//显示图标
|
||||||
showIcon: false,
|
showIcon: false,
|
||||||
},
|
},
|
||||||
//菜单权限模式 ROLE 前端固定角色 BACK 动态获取
|
//菜单权限模式 FIXED 前端固定路由 BACK 动态获取
|
||||||
permissionMode: 'ROLE',
|
permissionMode: 'FIXED',
|
||||||
|
//是否开启路由动画
|
||||||
|
isPageAnimate: true,
|
||||||
|
//路由动画类型
|
||||||
|
pageAnimateType: 'zoom-fade',
|
||||||
};
|
};
|
||||||
export default setting;
|
export default setting;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const DEFAULT_CONFIG: TreeHelperConfig = {
|
|||||||
|
|
||||||
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
|
const getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config);
|
||||||
|
|
||||||
interface AsyncRouteState {
|
export interface IAsyncRouteState {
|
||||||
menus: RouteRecordRaw[];
|
menus: RouteRecordRaw[];
|
||||||
routers: any[];
|
routers: any[];
|
||||||
addRouters: any[];
|
addRouters: any[];
|
||||||
@@ -50,7 +50,7 @@ function filter<T = any>(
|
|||||||
|
|
||||||
export const useAsyncRouteStore = defineStore({
|
export const useAsyncRouteStore = defineStore({
|
||||||
id: 'app-async-route',
|
id: 'app-async-route',
|
||||||
state: (): AsyncRouteState => ({
|
state: (): IAsyncRouteState => ({
|
||||||
menus: [],
|
menus: [],
|
||||||
routers: constantRouter,
|
routers: constantRouter,
|
||||||
addRouters: [],
|
addRouters: [],
|
||||||
@@ -88,12 +88,12 @@ export const useAsyncRouteStore = defineStore({
|
|||||||
},
|
},
|
||||||
async generateRoutes(data) {
|
async generateRoutes(data) {
|
||||||
let accessedRouters;
|
let accessedRouters;
|
||||||
const roleList = data.roles || [];
|
const permissionsList = data.permissions || [];
|
||||||
const routeFilter = (route) => {
|
const routeFilter = (route) => {
|
||||||
const { meta } = route;
|
const { meta } = route;
|
||||||
const { permission } = meta || {};
|
const { permissions } = meta || {};
|
||||||
if (!permission) return true;
|
if (!permissions) return true;
|
||||||
return roleList.some((role) => permission.includes(role.value));
|
return permissionsList.some((item) => permissions.includes(item.value));
|
||||||
};
|
};
|
||||||
const { getPermissionMode } = useProjectSetting();
|
const { getPermissionMode } = useProjectSetting();
|
||||||
const permissionMode = unref(getPermissionMode);
|
const permissionMode = unref(getPermissionMode);
|
||||||
@@ -107,7 +107,7 @@ export const useAsyncRouteStore = defineStore({
|
|||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
//过滤账户是否拥有某一个权限,并将菜单从加载列表移除
|
//过滤账户是否拥有某一个权限,并将菜单从加载列表移除
|
||||||
accessedRouters = filter([...asyncRoutes, ...constantRouter], routeFilter);
|
accessedRouters = filter(asyncRoutes, routeFilter);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ const {
|
|||||||
multiTabsSetting,
|
multiTabsSetting,
|
||||||
crumbsSetting,
|
crumbsSetting,
|
||||||
permissionMode,
|
permissionMode,
|
||||||
|
isPageAnimate,
|
||||||
|
pageAnimateType,
|
||||||
} = projectSetting;
|
} = projectSetting;
|
||||||
|
|
||||||
interface ProjectSettingState {
|
interface ProjectSettingState {
|
||||||
@@ -23,6 +25,8 @@ interface ProjectSettingState {
|
|||||||
multiTabsSetting: ImultiTabsSetting; //多标签
|
multiTabsSetting: ImultiTabsSetting; //多标签
|
||||||
crumbsSetting: IcrumbsSetting; //面包屑
|
crumbsSetting: IcrumbsSetting; //面包屑
|
||||||
permissionMode: string; //权限模式
|
permissionMode: string; //权限模式
|
||||||
|
isPageAnimate: boolean; //是否开启路由动画
|
||||||
|
pageAnimateType: string; //路由动画类型
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useProjectSettingStore = defineStore({
|
export const useProjectSettingStore = defineStore({
|
||||||
@@ -36,6 +40,8 @@ export const useProjectSettingStore = defineStore({
|
|||||||
multiTabsSetting,
|
multiTabsSetting,
|
||||||
crumbsSetting,
|
crumbsSetting,
|
||||||
permissionMode,
|
permissionMode,
|
||||||
|
isPageAnimate,
|
||||||
|
pageAnimateType,
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
getNavMode(): string {
|
getNavMode(): string {
|
||||||
@@ -62,6 +68,12 @@ export const useProjectSettingStore = defineStore({
|
|||||||
getPermissionMode(): string {
|
getPermissionMode(): string {
|
||||||
return this.permissionMode;
|
return this.permissionMode;
|
||||||
},
|
},
|
||||||
|
getIsPageAnimate(): boolean {
|
||||||
|
return this.isPageAnimate;
|
||||||
|
},
|
||||||
|
getPageAnimateType(): string {
|
||||||
|
return this.pageAnimateType;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setNavTheme(value: string): void {
|
setNavTheme(value: string): void {
|
||||||
|
|||||||
@@ -8,23 +8,23 @@ const Storage = createStorage({ storage: localStorage });
|
|||||||
import { getUserInfo, login } from '@/api/system/user';
|
import { getUserInfo, login } from '@/api/system/user';
|
||||||
import { storage } from '@/utils/Storage';
|
import { storage } from '@/utils/Storage';
|
||||||
|
|
||||||
interface UserState {
|
export interface IUserState {
|
||||||
token: string;
|
token: string;
|
||||||
username: string;
|
username: string;
|
||||||
welcome: string;
|
welcome: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
roles: any[];
|
permissions: any[];
|
||||||
info: any;
|
info: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUserStore = defineStore({
|
export const useUserStore = defineStore({
|
||||||
id: 'app-user',
|
id: 'app-user',
|
||||||
state: (): UserState => ({
|
state: (): IUserState => ({
|
||||||
token: Storage.get(ACCESS_TOKEN, ''),
|
token: Storage.get(ACCESS_TOKEN, ''),
|
||||||
username: '',
|
username: '',
|
||||||
welcome: '',
|
welcome: '',
|
||||||
avatar: '',
|
avatar: '',
|
||||||
roles: [],
|
permissions: [],
|
||||||
info: Storage.get(CURRENT_USER, {}),
|
info: Storage.get(CURRENT_USER, {}),
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
@@ -37,8 +37,8 @@ export const useUserStore = defineStore({
|
|||||||
getNickname(): string {
|
getNickname(): string {
|
||||||
return this.username;
|
return this.username;
|
||||||
},
|
},
|
||||||
getRoles(): [any][] {
|
getPermissions(): [any][] {
|
||||||
return this.roles;
|
return this.permissions;
|
||||||
},
|
},
|
||||||
getUserInfo(): object {
|
getUserInfo(): object {
|
||||||
return this.info;
|
return this.info;
|
||||||
@@ -51,8 +51,8 @@ export const useUserStore = defineStore({
|
|||||||
setAvatar(avatar: string) {
|
setAvatar(avatar: string) {
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
},
|
},
|
||||||
setRoles(roles) {
|
setPermissions(permissions) {
|
||||||
this.roles = roles;
|
this.permissions = permissions;
|
||||||
},
|
},
|
||||||
setUserInfo(info) {
|
setUserInfo(info) {
|
||||||
this.info = info;
|
this.info = info;
|
||||||
@@ -83,12 +83,12 @@ export const useUserStore = defineStore({
|
|||||||
getUserInfo()
|
getUserInfo()
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
const result = res;
|
const result = res;
|
||||||
if (result.roles && result.roles.length) {
|
if (result.permissions && result.permissions.length) {
|
||||||
const roles = result.roles;
|
const permissionsList = result.permissions;
|
||||||
that.setRoles(roles);
|
that.setPermissions(permissionsList);
|
||||||
that.setUserInfo(result);
|
that.setUserInfo(result);
|
||||||
} else {
|
} else {
|
||||||
reject(new Error('getInfo: roles must be a non-null array !'));
|
reject(new Error('getInfo: permissionsList must be a non-null array !'));
|
||||||
}
|
}
|
||||||
that.setAvatar(result.avatar);
|
that.setAvatar(result.avatar);
|
||||||
resolve(res);
|
resolve(res);
|
||||||
@@ -101,7 +101,7 @@ export const useUserStore = defineStore({
|
|||||||
|
|
||||||
// 登出
|
// 登出
|
||||||
async logout() {
|
async logout() {
|
||||||
this.setRoles([]);
|
this.setPermissions([]);
|
||||||
this.setUserInfo('');
|
this.setUserInfo('');
|
||||||
storage.remove(ACCESS_TOKEN);
|
storage.remove(ACCESS_TOKEN);
|
||||||
storage.remove(CURRENT_USER);
|
storage.remove(CURRENT_USER);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export const ACCESS_TOKEN = 'Access-Token'; // 用户token
|
export const ACCESS_TOKEN = 'ACCESS-TOKEN'; // 用户token
|
||||||
export const CURRENT_USER = 'Current-User'; // 当前用户信息
|
export const CURRENT_USER = 'CURRENT-USER'; // 当前用户信息
|
||||||
export const IS_LOCKSCREEN = 'Is-Lockscreen'; // 是否锁屏
|
export const IS_LOCKSCREEN = 'IS-LOCKSCREEN'; // 是否锁屏
|
||||||
export const TABS_ROUTES = 'Tabs-Routes'; // 标签页
|
export const TABS_ROUTES = 'TABS-ROUTES'; // 标签页
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IAsyncRouteState } from '@/store/modules/async-route';
|
import { IAsyncRouteState } from '@/store/modules/asyncRoute';
|
||||||
import { IUserState } from '@/store/modules/user/state';
|
import { IUserState } from '@/store/modules/user';
|
||||||
import { ILockscreenState } from '@/store/modules/lockscreen';
|
import { ILockscreenState } from '@/store/modules/lockscreen';
|
||||||
import { ITabsViewState } from '@/store/modules/tabs-view';
|
import { ITabsViewState } from '@/store/modules/tabsView';
|
||||||
|
|
||||||
export interface IStore {
|
export interface IStore {
|
||||||
asyncRoute: IAsyncRouteState;
|
asyncRoute: IAsyncRouteState;
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ a:active, a:hover {
|
|||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*滚动条凹槽的颜色,还可以设置边框属性 */
|
/* 滚动条凹槽的颜色,还可以设置边框属性 */
|
||||||
*::-webkit-scrollbar-track-piece {
|
*::-webkit-scrollbar-track-piece {
|
||||||
background-color: #f8f8f8;
|
background-color: #f8f8f8;
|
||||||
-webkit-border-radius: 2em;
|
-webkit-border-radius: 2em;
|
||||||
@@ -53,22 +53,22 @@ a:active, a:hover {
|
|||||||
border-radius: 2em;
|
border-radius: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*滚动条的宽度*/
|
/* 滚动条的宽度 */
|
||||||
*::-webkit-scrollbar {
|
*::-webkit-scrollbar {
|
||||||
width: 9px;
|
width: 9px;
|
||||||
height: 9px;
|
height: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*滚动条的设置*/
|
/* 滚动条的设置 */
|
||||||
*::-webkit-scrollbar-thumb {
|
*::-webkit-scrollbar-thumb {
|
||||||
background-color: #dddddd;
|
background-color: #ddd;
|
||||||
background-clip: padding-box;
|
background-clip: padding-box;
|
||||||
-webkit-border-radius: 2em;
|
-webkit-border-radius: 2em;
|
||||||
-moz-border-radius: 2em;
|
-moz-border-radius: 2em;
|
||||||
border-radius: 2em;
|
border-radius: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*滚动条鼠标移上去*/
|
/* 滚动条鼠标移上去 */
|
||||||
*::-webkit-scrollbar-thumb:hover {
|
*::-webkit-scrollbar-thumb:hover {
|
||||||
background-color: #bbb;
|
background-color: #bbb;
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,10 @@ body .proCard {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body .n-modal{
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
//body .proCardTabs{
|
//body .proCardTabs{
|
||||||
// .n-card__content{ padding-top: 3px}
|
// .n-card__content{ padding-top: 3px}
|
||||||
// .n-card__content:first-child{ padding-top: 3px}
|
// .n-card__content:first-child{ padding-top: 3px}
|
||||||
|
|||||||
3
src/styles/index.less
Normal file
3
src/styles/index.less
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
@import 'transition/index.less';
|
||||||
|
@import './var.less';
|
||||||
|
@import './common.less';
|
||||||
18
src/styles/transition/base.less
Normal file
18
src/styles/transition/base.less
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.transition-default() {
|
||||||
|
&-enter-active,
|
||||||
|
&-leave-active {
|
||||||
|
transition: 0.3s cubic-bezier(0.25, 0.8, 0.5, 1) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-move {
|
||||||
|
transition: transform 0.4s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-transition {
|
||||||
|
.transition-default();
|
||||||
|
}
|
||||||
|
|
||||||
|
.expand-x-transition {
|
||||||
|
.transition-default();
|
||||||
|
}
|
||||||
81
src/styles/transition/fade.less
Normal file
81
src/styles/transition/fade.less
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fade-slide */
|
||||||
|
.fade-slide-leave-active,
|
||||||
|
.fade-slide-enter-active {
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ///////////////////////////////////////////////
|
||||||
|
// Fade Bottom
|
||||||
|
// ///////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Speed: 1x
|
||||||
|
.fade-bottom-enter-active,
|
||||||
|
.fade-bottom-leave-active {
|
||||||
|
transition: opacity 0.25s, transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-bottom-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-bottom-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fade-scale
|
||||||
|
.fade-scale-leave-active,
|
||||||
|
.fade-scale-enter-active {
|
||||||
|
transition: all 0.28s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-scale-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-scale-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ///////////////////////////////////////////////
|
||||||
|
// Fade Top
|
||||||
|
// ///////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Speed: 1x
|
||||||
|
.fade-top-enter-active,
|
||||||
|
.fade-top-leave-active {
|
||||||
|
transition: opacity 0.2s, transform 0.25s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-top-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(8%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-top-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-8%);
|
||||||
|
}
|
||||||
10
src/styles/transition/index.less
Normal file
10
src/styles/transition/index.less
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
@import './base.less';
|
||||||
|
@import './fade.less';
|
||||||
|
@import './scale.less';
|
||||||
|
@import './slide.less';
|
||||||
|
@import './scroll.less';
|
||||||
|
@import './zoom.less';
|
||||||
|
|
||||||
|
.collapse-transition {
|
||||||
|
transition: 0.2s height ease-in-out, 0.2s padding-top ease-in-out, 0.2s padding-bottom ease-in-out;
|
||||||
|
}
|
||||||
21
src/styles/transition/scale.less
Normal file
21
src/styles/transition/scale.less
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.scale-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale-rotate-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0) rotate(-45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
src/styles/transition/scroll.less
Normal file
67
src/styles/transition/scroll.less
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.scroll-y-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-y-reverse-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from {
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-x-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from {
|
||||||
|
transform: translateX(-15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateX(15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-x-reverse-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-enter-from {
|
||||||
|
transform: translateX(15px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-leave-to {
|
||||||
|
transform: translateX(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/styles/transition/slide.less
Normal file
39
src/styles/transition/slide.less
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
.slide-y-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-y-reverse-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-x-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-x-reverse-transition {
|
||||||
|
.transition-default();
|
||||||
|
|
||||||
|
&-enter-from,
|
||||||
|
&-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(15px);
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/styles/transition/zoom.less
Normal file
27
src/styles/transition/zoom.less
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// zoom-out
|
||||||
|
.zoom-out-enter-active,
|
||||||
|
.zoom-out-leave-active {
|
||||||
|
transition: opacity 0.1 ease-in-out, transform 0.15s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-out-enter-from,
|
||||||
|
.zoom-out-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// zoom-fade
|
||||||
|
.zoom-fade-enter-active,
|
||||||
|
.zoom-fade-leave-active {
|
||||||
|
transition: transform 0.2s, opacity 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-fade-enter-from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.06);
|
||||||
|
}
|
||||||
4
src/styles/var.less
Normal file
4
src/styles/var.less
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
@primaryColor: #2d8cf0;
|
||||||
|
@primaryColorHover: #57a3f3;
|
||||||
|
@header-height: 64px;
|
||||||
|
@footer-height: 70px;
|
||||||
98
src/utils/Drag.ts
Normal file
98
src/utils/Drag.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
//获取相关CSS属性
|
||||||
|
const getCss = function (o, key) {
|
||||||
|
return o.currentStyle
|
||||||
|
? o.currentStyle[key]
|
||||||
|
: document.defaultView?.getComputedStyle(o, null)[key];
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
currentX: 0,
|
||||||
|
currentY: 0,
|
||||||
|
flag: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const startDrag = function (bar, target, callback?) {
|
||||||
|
const screenWidth = document.body.clientWidth; // body当前宽度
|
||||||
|
const screenHeight = document.documentElement.clientHeight; // 可见区域高度
|
||||||
|
|
||||||
|
const dragDomW = target.offsetWidth; // 对话框宽度
|
||||||
|
const dragDomH = target.offsetHeight; // 对话框高度
|
||||||
|
|
||||||
|
const minDomLeft = target.offsetLeft;
|
||||||
|
const minDomTop = target.offsetTop;
|
||||||
|
|
||||||
|
const maxDragDomLeft = screenWidth - minDomLeft - dragDomW;
|
||||||
|
const maxDragDomTop = screenHeight - minDomTop - dragDomH;
|
||||||
|
|
||||||
|
if (getCss(target, 'left') !== 'auto') {
|
||||||
|
params.left = getCss(target, 'left');
|
||||||
|
}
|
||||||
|
if (getCss(target, 'top') !== 'auto') {
|
||||||
|
params.top = getCss(target, 'top');
|
||||||
|
}
|
||||||
|
|
||||||
|
//o是移动对象
|
||||||
|
bar.onmousedown = function (event) {
|
||||||
|
params.flag = true;
|
||||||
|
if (!event) {
|
||||||
|
event = window.event;
|
||||||
|
//防止IE文字选中
|
||||||
|
bar.onselectstart = function () {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const e = event;
|
||||||
|
params.currentX = e.clientX;
|
||||||
|
params.currentY = e.clientY;
|
||||||
|
};
|
||||||
|
document.onmouseup = function () {
|
||||||
|
params.flag = false;
|
||||||
|
if (getCss(target, 'left') !== 'auto') {
|
||||||
|
params.left = getCss(target, 'left');
|
||||||
|
}
|
||||||
|
if (getCss(target, 'top') !== 'auto') {
|
||||||
|
params.top = getCss(target, 'top');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.onmousemove = function (event) {
|
||||||
|
const e: any = event ? event : window.event;
|
||||||
|
if (params.flag) {
|
||||||
|
const nowX = e.clientX,
|
||||||
|
nowY = e.clientY;
|
||||||
|
const disX = nowX - params.currentX,
|
||||||
|
disY = nowY - params.currentY;
|
||||||
|
|
||||||
|
let left = parseInt(params.left) + disX;
|
||||||
|
let top = parseInt(params.top) + disY;
|
||||||
|
|
||||||
|
// 拖出屏幕边缘
|
||||||
|
if (-left > minDomLeft) {
|
||||||
|
left = -minDomLeft;
|
||||||
|
} else if (left > maxDragDomLeft) {
|
||||||
|
left = maxDragDomLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-top > minDomTop) {
|
||||||
|
top = -minDomTop;
|
||||||
|
} else if (top > maxDragDomTop) {
|
||||||
|
top = maxDragDomTop;
|
||||||
|
}
|
||||||
|
|
||||||
|
target.style.left = left + 'px';
|
||||||
|
target.style.top = top + 'px';
|
||||||
|
|
||||||
|
if (typeof callback == 'function') {
|
||||||
|
callback((parseInt(params.left) || 0) + disX, (parseInt(params.top) || 0) + disY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.preventDefault) {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default startDrag;
|
||||||
@@ -48,7 +48,7 @@ export const createStorage = ({ prefixKey = '', storage = localStorage } = {}) =
|
|||||||
if (expire === null || expire >= Date.now()) {
|
if (expire === null || expire >= Date.now()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
this.remove(this.getKey(key));
|
this.remove(key);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/utils/dateUtil.ts
Normal file
12
src/utils/dateUtil.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { format } from 'date-fns';
|
||||||
|
|
||||||
|
const DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm';
|
||||||
|
const DATE_FORMAT = 'YYYY-MM-DD ';
|
||||||
|
|
||||||
|
export function formatToDateTime(date: null, formatStr = DATE_TIME_FORMAT): string {
|
||||||
|
return format(date, formatStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatToDate(date: null, formatStr = DATE_FORMAT): string {
|
||||||
|
return format(date, formatStr);
|
||||||
|
}
|
||||||
@@ -5,8 +5,8 @@ import { AxiosCanceler } from './axiosCancel';
|
|||||||
import { isFunction } from '@/utils/is';
|
import { isFunction } from '@/utils/is';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
import type { RequestOptions, CreateAxiosOptions, Result } from './types';
|
import type { RequestOptions, CreateAxiosOptions, Result, UploadFileParams } from './types';
|
||||||
// import { ContentTypeEnum } from '/@/enums/httpEnum';
|
import { ContentTypeEnum } from '@/enums/httpEnum';
|
||||||
|
|
||||||
export * from './axiosTransform';
|
export * from './axiosTransform';
|
||||||
|
|
||||||
@@ -23,18 +23,6 @@ export class VAxios {
|
|||||||
this.setupInterceptors();
|
this.setupInterceptors();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 创建axios实例
|
|
||||||
*/
|
|
||||||
private createAxios(config: CreateAxiosOptions): void {
|
|
||||||
this.axiosInstance = axios.create(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getTransform() {
|
|
||||||
const { transform } = this.options;
|
|
||||||
return transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAxios(): AxiosInstance {
|
getAxios(): AxiosInstance {
|
||||||
return this.axiosInstance;
|
return this.axiosInstance;
|
||||||
}
|
}
|
||||||
@@ -59,6 +47,103 @@ export class VAxios {
|
|||||||
Object.assign(this.axiosInstance.defaults.headers, headers);
|
Object.assign(this.axiosInstance.defaults.headers, headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 请求方法
|
||||||
|
*/
|
||||||
|
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
||||||
|
let conf: AxiosRequestConfig = cloneDeep(config);
|
||||||
|
const transform = this.getTransform();
|
||||||
|
|
||||||
|
const { requestOptions } = this.options;
|
||||||
|
|
||||||
|
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
||||||
|
|
||||||
|
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
|
||||||
|
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
||||||
|
conf = beforeRequestHook(conf, opt);
|
||||||
|
}
|
||||||
|
|
||||||
|
//这里重新 赋值成最新的配置
|
||||||
|
// @ts-ignore
|
||||||
|
conf.requestOptions = opt;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.axiosInstance
|
||||||
|
.request<any, AxiosResponse<Result>>(conf)
|
||||||
|
.then((res: AxiosResponse<Result>) => {
|
||||||
|
// 请求是否被取消
|
||||||
|
const isCancel = axios.isCancel(res);
|
||||||
|
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
|
||||||
|
try {
|
||||||
|
const ret = transformRequestData(res, opt);
|
||||||
|
resolve(ret);
|
||||||
|
} catch (err) {
|
||||||
|
reject(err || new Error('request error!'));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
resolve(res as unknown as Promise<T>);
|
||||||
|
})
|
||||||
|
.catch((e: Error) => {
|
||||||
|
if (requestCatch && isFunction(requestCatch)) {
|
||||||
|
reject(requestCatch(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 创建axios实例
|
||||||
|
*/
|
||||||
|
private createAxios(config: CreateAxiosOptions): void {
|
||||||
|
this.axiosInstance = axios.create(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTransform() {
|
||||||
|
const { transform } = this.options;
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 文件上传
|
||||||
|
*/
|
||||||
|
uploadFile<T = any>(config: AxiosRequestConfig, params: UploadFileParams) {
|
||||||
|
const formData = new window.FormData();
|
||||||
|
const customFilename = params.name || 'file';
|
||||||
|
|
||||||
|
if (params.filename) {
|
||||||
|
formData.append(customFilename, params.file, params.filename);
|
||||||
|
} else {
|
||||||
|
formData.append(customFilename, params.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.data) {
|
||||||
|
Object.keys(params.data).forEach((key) => {
|
||||||
|
const value = params.data![key];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((item) => {
|
||||||
|
formData.append(`${key}[]`, item);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
formData.append(key, params.data![key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axiosInstance.request<T>({
|
||||||
|
method: 'POST',
|
||||||
|
data: formData,
|
||||||
|
headers: {
|
||||||
|
'Content-type': ContentTypeEnum.FORM_DATA,
|
||||||
|
ignoreCancelToken: true,
|
||||||
|
},
|
||||||
|
...config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description: 拦截器配置
|
* @description: 拦截器配置
|
||||||
*/
|
*/
|
||||||
@@ -78,10 +163,17 @@ export class VAxios {
|
|||||||
|
|
||||||
// 请求拦截器配置处理
|
// 请求拦截器配置处理
|
||||||
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
this.axiosInstance.interceptors.request.use((config: AxiosRequestConfig) => {
|
||||||
const { headers: { ignoreCancelToken } = { ignoreCancelToken: false } } = config;
|
const {
|
||||||
!ignoreCancelToken && axiosCanceler.addPending(config);
|
headers: { ignoreCancelToken },
|
||||||
|
} = config;
|
||||||
|
const ignoreCancel =
|
||||||
|
ignoreCancelToken !== undefined
|
||||||
|
? ignoreCancelToken
|
||||||
|
: this.options.requestOptions?.ignoreCancelToken;
|
||||||
|
|
||||||
|
!ignoreCancel && axiosCanceler.addPending(config);
|
||||||
if (requestInterceptors && isFunction(requestInterceptors)) {
|
if (requestInterceptors && isFunction(requestInterceptors)) {
|
||||||
config = requestInterceptors(config);
|
config = requestInterceptors(config, this.options);
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
}, undefined);
|
}, undefined);
|
||||||
@@ -105,62 +197,4 @@ export class VAxios {
|
|||||||
isFunction(responseInterceptorsCatch) &&
|
isFunction(responseInterceptorsCatch) &&
|
||||||
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
this.axiosInstance.interceptors.response.use(undefined, responseInterceptorsCatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @description: 文件上传
|
|
||||||
// */
|
|
||||||
// uploadFiles(config: AxiosRequestConfig, params: File[]) {
|
|
||||||
// const formData = new FormData();
|
|
||||||
|
|
||||||
// Object.keys(params).forEach((key) => {
|
|
||||||
// formData.append(key, params[key as any]);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return this.request({
|
|
||||||
// ...config,
|
|
||||||
// method: 'POST',
|
|
||||||
// data: formData,
|
|
||||||
// headers: {
|
|
||||||
// 'Content-type': ContentTypeEnum.FORM_DATA,
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @description: 请求方法
|
|
||||||
*/
|
|
||||||
request<T = any>(config: AxiosRequestConfig, options?: RequestOptions): Promise<T> {
|
|
||||||
let conf: AxiosRequestConfig = cloneDeep(config);
|
|
||||||
const transform = this.getTransform();
|
|
||||||
|
|
||||||
const { requestOptions } = this.options;
|
|
||||||
|
|
||||||
const opt: RequestOptions = Object.assign({}, requestOptions, options);
|
|
||||||
|
|
||||||
const { beforeRequestHook, requestCatch, transformRequestData } = transform || {};
|
|
||||||
if (beforeRequestHook && isFunction(beforeRequestHook)) {
|
|
||||||
conf = beforeRequestHook(conf, opt);
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.axiosInstance
|
|
||||||
.request<any, AxiosResponse<Result>>(conf)
|
|
||||||
.then((res: AxiosResponse<Result>) => {
|
|
||||||
// 请求是否被取消
|
|
||||||
const isCancel = axios.isCancel(res);
|
|
||||||
if (transformRequestData && isFunction(transformRequestData) && !isCancel) {
|
|
||||||
const ret = transformRequestData(res, opt);
|
|
||||||
// ret !== undefined ? resolve(ret) : reject(new Error('request error!'));
|
|
||||||
return resolve(ret);
|
|
||||||
}
|
|
||||||
reject(res as unknown as Promise<T>);
|
|
||||||
})
|
|
||||||
.catch((e: Error) => {
|
|
||||||
if (requestCatch && isFunction(requestCatch)) {
|
|
||||||
reject(requestCatch(e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
reject(e);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user