Compare commits
No commits in common. "master" and "v3.0.1" have entirely different histories.
21
.gitignore
vendored
21
.gitignore
vendored
@ -1 +1,22 @@
|
|||||||
|
# Build and Release Folders
|
||||||
|
bin-debug/
|
||||||
|
bin-release/
|
||||||
|
[Oo]bj/
|
||||||
|
[Bb]in/
|
||||||
|
|
||||||
|
# Other files and folders
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.swf
|
||||||
|
*.air
|
||||||
|
*.ipa
|
||||||
|
*.apk
|
||||||
|
|
||||||
|
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
|
||||||
|
# should NOT be excluded as they contain compiler settings and other important
|
||||||
|
# information for Eclipse / Flash Builder.
|
||||||
|
docker_env/mysql/data/
|
||||||
|
docker_env/redis/data/
|
||||||
*/.idea
|
*/.idea
|
||||||
|
dvadmin-doc/docs/.vuepress/dist
|
126
README.md
126
README.md
@ -21,11 +21,10 @@
|
|||||||
Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||||
|
|
||||||
- 后端采用现代、快速(高性能) [FastAPI](https://fastapi.tiangolo.com/zh/) 异步框架 + 自动生成交互式API文档 + (强制类型约束)[Pydantic](https://docs.pydantic.dev/1.10/) + (高效率)[SQLAlchemy 2.0](https://docs.sqlalchemy.org/en/20/index.html);
|
- 后端采用现代、快速(高性能) [FastAPI](https://fastapi.tiangolo.com/zh/) 异步框架 + 自动生成交互式API文档 + (强制类型约束)[Pydantic](https://docs.pydantic.dev/1.10/) + (高效率)[SQLAlchemy 2.0](https://docs.sqlalchemy.org/en/20/index.html);
|
||||||
- PC端采用 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin) 、[Vue3](https://cn.vuejs.org/guide/introduction.html)、[Element Plus](https://element-plus.gitee.io/zh-CN/guide/design.html)、[TypeScript](https://www.tslang.cn/)等主流技术开发;
|
- PC端采用 [vue-element-plus-admin 2.2.0](https://gitee.com/kailong110120130/vue-element-plus-admin) 、[Vue3](https://cn.vuejs.org/guide/introduction.html)、[Element Plus](https://element-plus.gitee.io/zh-CN/guide/design.html)、[TypeScript](https://www.tslang.cn/)等主流技术开发;
|
||||||
- 移动端采用 [uni-app](https://uniapp.dcloud.net.cn/component/),[Vue2](https://v2.cn.vuejs.org/v2/guide/),[uView 2](https://www.uviewui.com/components/intro.html)为主要技术开发;
|
- 移动端采用 [uni-app](https://uniapp.dcloud.net.cn/component/),[Vue2](https://v2.cn.vuejs.org/v2/guide/),[uView 2](https://www.uviewui.com/components/intro.html)为主要技术开发;
|
||||||
- 后端加入 [Typer](https://typer.tiangolo.com/) 命令行应用,简单化数据初始化,数据表模型迁移等操作;
|
- 后端加入 [Typer](https://typer.tiangolo.com/) 命令行应用,简单化数据初始化,数据表模型迁移等操作;
|
||||||
- 后端新加入根据配置的 ORM 模型,自动生成 CRUD 代码;
|
- 已加入定时任务功能,采用 [APScheduler](https://github.com/agronholm/apscheduler) 定时任务框架 + [Redis](https://redis.io/) 消息队列 + [MongoDB](https://www.mongodb.com/) 持久存储;
|
||||||
- 定时任务功能,采用 [APScheduler](https://github.com/agronholm/apscheduler) 定时任务框架 + [Redis](https://redis.io/) 消息队列 + [MongoDB](https://www.mongodb.com/) 持久存储;
|
|
||||||
- 权限认证使用[(哈希)密码和 JWT Bearer 令牌的 OAuth2](https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/),支持多终端认证系统。
|
- 权限认证使用[(哈希)密码和 JWT Bearer 令牌的 OAuth2](https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/),支持多终端认证系统。
|
||||||
- 支持加载动态权限菜单,多方式轻松权限控制,按钮级别权限控制。
|
- 支持加载动态权限菜单,多方式轻松权限控制,按钮级别权限控制。
|
||||||
- 已加入常见的 [MySQL](https://www.mysql.com/) + [MongoDB](https://www.mongodb.com/) + [Redis](https://redis.io/) 数据库异步操作。
|
- 已加入常见的 [MySQL](https://www.mysql.com/) + [MongoDB](https://www.mongodb.com/) + [Redis](https://redis.io/) 数据库异步操作。
|
||||||
@ -43,27 +42,6 @@ Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企
|
|||||||
|
|
||||||
[小诺开源技术 (xiaonuo.vip)](https://www.xiaonuo.vip/):国内首个国密前后端分离快速开发平台
|
[小诺开源技术 (xiaonuo.vip)](https://www.xiaonuo.vip/):国内首个国密前后端分离快速开发平台
|
||||||
|
|
||||||
## 微信群
|
|
||||||
|
|
||||||
提供一个技术交流群,现在还没什么人哈哈哈哈哈,真心希望大家能够加入,积极讨论,因为本项目中还没有详细使用文档(一直在欠着,我也挺不好意思的),所以大家加入后,也可以很方便的一起讨论在使用中遇到各种问题,也可以提一些你想加入的功能,让我们更近一点,欢迎大家的加入。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
2024-4-25 目前群聊已经达到 200 人,只能通过邀请进群,不能再直接扫描群二维码进群,需要进群的可以先加我,备注进群,我就拉你进群。
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
<p align="center">
|
|
||||||
<img src="https://ktianc.oss-cn-beijing.aliyuncs.com/kinit/public/images/WechatIMG285.jpg" height="500" alt="logo"/>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 在线体验
|
## 在线体验
|
||||||
|
|
||||||
PC端演示地址:https://kinit.ktianc.top
|
PC端演示地址:https://kinit.ktianc.top
|
||||||
@ -80,71 +58,9 @@ PC端演示地址:https://kinit.ktianc.top
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
管理员账户:
|
|
||||||
|
|
||||||
- 账号:15020221010
|
- 账号:15020221010
|
||||||
- 密码:kinit2022
|
- 密码:kinit2022
|
||||||
|
|
||||||
测试账户:
|
|
||||||
|
|
||||||
- 账号:15020240125
|
|
||||||
- 密码:test
|
|
||||||
|
|
||||||
## 接口 CURD 代码自动生成
|
|
||||||
|
|
||||||
1. 目前只支持生成接口代码
|
|
||||||
2. 目前只支持使用脚本方式运行,后续会更新到页面操作
|
|
||||||
3. 代码是根据手动配置的 ORM 模型来生成的,支持参数同步,比如默认值,是否为空...
|
|
||||||
|
|
||||||
脚本文件地址:`scripts/crud_generate/main.py`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
该功能首先需要手动创建出 ORM 模型,然后会根据 ORM 模型依次创建代码,包括如下代码:
|
|
||||||
|
|
||||||
1. schema 序列化代码
|
|
||||||
|
|
||||||
schema 文件名称会使用设置的 en_name 名称,如果文件已经存在会先执行删除,再创建。
|
|
||||||
|
|
||||||
schema 代码内容生成完成后,同时会将新创建的 class 在 `__init__.py` 文件中导入。
|
|
||||||
|
|
||||||
2. dal 数据操作代码
|
|
||||||
|
|
||||||
dal 文件名称会使用默认的 `crud.py` 文件名称,目前不支持自定义。
|
|
||||||
|
|
||||||
如果 dal 文件已经存在,并且已经有代码内容,那么会将新的模型 dal class 追加到文件最后,并会合并文件内导入的 module。
|
|
||||||
|
|
||||||
3. param 请求参数代码
|
|
||||||
|
|
||||||
param 文件名取名方式与 schema 一致。
|
|
||||||
|
|
||||||
会创建出默认最简的 param class。
|
|
||||||
|
|
||||||
4. view 视图代码
|
|
||||||
|
|
||||||
view 文件名称同样会使用默认的 `view.py` 文件名称,目前不支持自定义。
|
|
||||||
|
|
||||||
如果 view 文件已经存在,与 dal 执行操作一致。
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
脚本中目前有两个方法:
|
|
||||||
|
|
||||||
```python
|
|
||||||
if __name__ == '__main__':
|
|
||||||
from apps.vadmin.auth.models import VadminUser
|
|
||||||
|
|
||||||
crud = CrudGenerate(VadminUser, "用户", "user")
|
|
||||||
# 只打印代码,不执行创建写入
|
|
||||||
crud.generate_codes()
|
|
||||||
# 创建并写入代码
|
|
||||||
crud.main()
|
|
||||||
```
|
|
||||||
|
|
||||||
目前不会去检测已有的代码,比如 `UserDal` 已经存在,还是会继续添加的。
|
|
||||||
|
|
||||||
B站 视频演示:https://www.bilibili.com/video/BV19e411a7zP/
|
|
||||||
|
|
||||||
## 源码地址
|
## 源码地址
|
||||||
|
|
||||||
gitee地址(主推):https://gitee.com/ktianc/kinit
|
gitee地址(主推):https://gitee.com/ktianc/kinit
|
||||||
@ -155,9 +71,7 @@ github地址:https://github.com/vvandk/kinit
|
|||||||
|
|
||||||
- [x] 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
- [x] 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||||
|
|
||||||
- [x] 部门管理:支持无限层级部门配置。
|
- [x] 角色管理:角色菜单权限分配。
|
||||||
|
|
||||||
- [x] 角色管理:角色菜单权限,角色部门权限分配。
|
|
||||||
|
|
||||||
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
|
|
||||||
@ -257,14 +171,10 @@ git clone https://gitee.com/ktianc/kinit.git
|
|||||||
### 准备工作
|
### 准备工作
|
||||||
|
|
||||||
```
|
```
|
||||||
后端依赖版本:
|
Python == 3.10 (其他版本均未测试)
|
||||||
Python == 3.10.x (其他版本均未测试)
|
nodejs >= 14.0 (推荐使用最新稳定版)
|
||||||
前端依赖版本:
|
Mysql >= 8.0
|
||||||
nodejs >= 18.0 < 19
|
MongoDB (推荐使用最新稳定版)
|
||||||
pnpm >= 8.1.0 < 9
|
|
||||||
数据库版本:
|
|
||||||
Mysql >= 8.0 (8 以上未测试,以下版本未测试,postgresql 未测试,更换可能会涉及调整)
|
|
||||||
MongoDB >= 7.0.12 < 8 (7 以上或以下版本均未测试)
|
|
||||||
Redis (推荐使用最新稳定版)
|
Redis (推荐使用最新稳定版)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -285,7 +195,6 @@ Redis (推荐使用最新稳定版)
|
|||||||
```python
|
```python
|
||||||
# 安全警告: 不要在生产中打开调试运行!
|
# 安全警告: 不要在生产中打开调试运行!
|
||||||
DEBUG = True # 如果当前为开发环境则改为 True,如果为生产环境则改为 False
|
DEBUG = True # 如果当前为开发环境则改为 True,如果为生产环境则改为 False
|
||||||
```
|
|
||||||
|
|
||||||
3. 修改项目数据库配置信息
|
3. 修改项目数据库配置信息
|
||||||
|
|
||||||
@ -333,7 +242,6 @@ Redis (推荐使用最新稳定版)
|
|||||||
# 文档:https://user.ip138.com/ip/doc
|
# 文档:https://user.ip138.com/ip/doc
|
||||||
IP_PARSE_ENABLE = True
|
IP_PARSE_ENABLE = True
|
||||||
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
||||||
```
|
|
||||||
|
|
||||||
4. 并在`alembic.ini`文件中配置数据库信息,用于数据库映射
|
4. 并在`alembic.ini`文件中配置数据库信息,用于数据库映射
|
||||||
|
|
||||||
@ -343,13 +251,13 @@ Redis (推荐使用最新稳定版)
|
|||||||
[dev]
|
[dev]
|
||||||
# 开发环境
|
# 开发环境
|
||||||
version_locations = %(here)s/alembic/versions_dev
|
version_locations = %(here)s/alembic/versions_dev
|
||||||
sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
sqlalchemy.url = sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
||||||
|
|
||||||
|
|
||||||
[pro]
|
[pro]
|
||||||
# 生产环境
|
# 生产环境
|
||||||
version_locations = %(here)s/alembic/versions_pro
|
version_locations = %(here)s/alembic/versions_pro
|
||||||
sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
sqlalchemy.url = sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 创建数据库
|
5. 创建数据库
|
||||||
@ -505,9 +413,7 @@ pnpm run build:pro
|
|||||||
DEBUG = False # 生产环境应该改为 False
|
DEBUG = False # 生产环境应该改为 False
|
||||||
```
|
```
|
||||||
|
|
||||||
3. (**如果没有安装数据库则不需要这一操作**)如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请执行以下操作:
|
3. 如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请修改如下内容,如果没有则不需要修改:
|
||||||
|
|
||||||
请先在对应数据库中创建用户名以及数据库,并修改以下数据库连接改为已有的数据库连接
|
|
||||||
|
|
||||||
1. 修改 API 端配置文件:
|
1. 修改 API 端配置文件:
|
||||||
|
|
||||||
@ -516,7 +422,7 @@ pnpm run build:pro
|
|||||||
```python
|
```python
|
||||||
# Mysql 数据库配置项
|
# Mysql 数据库配置项
|
||||||
# 连接引擎官方文档:https://www.osgeo.cn/sqlalchemy/core/engines.html
|
# 连接引擎官方文档:https://www.osgeo.cn/sqlalchemy/core/engines.html
|
||||||
# 数据库连接配置说明:mysql+asyncmy://数据库用户名:数据库密码@数据库地址:数据库端口/数据库名称
|
# 数据库链接配置说明:mysql+asyncmy://数据库用户名:数据库密码@数据库地址:数据库端口/数据库名称
|
||||||
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:123456@177.8.0.7:3306/kinit"
|
SQLALCHEMY_DATABASE_URL = "mysql+asyncmy://root:123456@177.8.0.7:3306/kinit"
|
||||||
|
|
||||||
# Redis 数据库配置
|
# Redis 数据库配置
|
||||||
@ -532,9 +438,9 @@ pnpm run build:pro
|
|||||||
```
|
```
|
||||||
|
|
||||||
2. 修改定时任务配置文件
|
2. 修改定时任务配置文件
|
||||||
|
|
||||||
文件路径为:`kinit-task/application/config/production.py`
|
文件路径为:`kinit-task/application/config/production.py`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
# Redis 数据库配置
|
# Redis 数据库配置
|
||||||
# 与接口是同一个数据库
|
# 与接口是同一个数据库
|
||||||
@ -551,7 +457,7 @@ pnpm run build:pro
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. 将已有的数据库在 `docker-compose.yml` 文件中注释
|
3. 将已有的数据库在 `docker-compose.yml` 文件中注释
|
||||||
|
|
||||||
4. 配置阿里云 OSS 与 IP 解析接口地址(可选)
|
4. 配置阿里云 OSS 与 IP 解析接口地址(可选)
|
||||||
|
|
||||||
文件路径:`kinit-api/application/config/production.py`
|
文件路径:`kinit-api/application/config/production.py`
|
||||||
@ -578,7 +484,7 @@ pnpm run build:pro
|
|||||||
IP_PARSE_ENABLE = False
|
IP_PARSE_ENABLE = False
|
||||||
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 前端项目打包:
|
5. 前端项目打包:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
@ -716,4 +622,4 @@ docker-compose ps -a
|
|||||||
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077860987.jpg"/></td>
|
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077860987.jpg"/></td>
|
||||||
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077870240.jpg"/></td>
|
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077870240.jpg"/></td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
@ -1,8 +1,8 @@
|
|||||||
# 环境
|
# 环境
|
||||||
VITE_NODE_ENV=development
|
NODE_ENV=development
|
||||||
|
|
||||||
# 接口前缀,没用到
|
# 接口前缀
|
||||||
# VITE_API_BASE_PATH=/api
|
VITE_API_BASE_PATH=dev
|
||||||
|
|
||||||
# 打包路径
|
# 打包路径
|
||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
@ -21,15 +21,3 @@ VITE_OUT_DIR=dist-dev
|
|||||||
|
|
||||||
# 标题
|
# 标题
|
||||||
VITE_APP_TITLE=后台系统-开发
|
VITE_APP_TITLE=后台系统-开发
|
||||||
|
|
||||||
# 是否切割css
|
|
||||||
VITE_USE_CSS_SPLIT=true
|
|
||||||
|
|
||||||
# 是否使用在线图标
|
|
||||||
VITE_USE_ONLINE_ICON=true
|
|
||||||
|
|
||||||
# 是否包分析
|
|
||||||
VITE_USE_BUNDLE_ANALYZER=true
|
|
||||||
|
|
||||||
# 是否全量引入element-plus样式
|
|
||||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=true
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 环境
|
# 环境
|
||||||
VITE_NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
# 接口前缀,没用到
|
# 接口前缀
|
||||||
# VITE_API_BASE_PATH=/api
|
VITE_API_BASE_PATH=pro
|
||||||
|
|
||||||
# 打包路径
|
# 打包路径
|
||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
@ -21,15 +21,3 @@ VITE_OUT_DIR=dist-pro
|
|||||||
|
|
||||||
# 标题
|
# 标题
|
||||||
VITE_APP_TITLE=后台系统
|
VITE_APP_TITLE=后台系统
|
||||||
|
|
||||||
# 是否切割css
|
|
||||||
VITE_USE_CSS_SPLIT=true
|
|
||||||
|
|
||||||
# 是否使用在线图标
|
|
||||||
VITE_USE_ONLINE_ICON=true
|
|
||||||
|
|
||||||
# 是否包分析
|
|
||||||
VITE_USE_BUNDLE_ANALYZER=true
|
|
||||||
|
|
||||||
# 是否全量引入element-plus样式
|
|
||||||
VITE_USE_ALL_ELEMENT_PLUS_STYLE=false
|
|
||||||
|
@ -65,7 +65,6 @@ module.exports = defineConfig({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
'vue/multi-word-component-names': 'off',
|
'vue/multi-word-component-names': 'off',
|
||||||
'vue/no-v-html': 'off',
|
'vue/no-v-html': 'off'
|
||||||
'vue/require-toggle-inside-transition': 'off'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
4
kinit-admin/.gitignore
vendored
4
kinit-admin/.gitignore
vendored
@ -1,9 +1,7 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
dist
|
||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
*-lock.*
|
*-lock.*
|
||||||
pnpm-debug
|
pnpm-debug
|
||||||
stats.html
|
|
||||||
dist-pro
|
|
||||||
.vscode
|
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
/dist*
|
/dist*
|
||||||
/public/*
|
/public/*
|
||||||
/docs/*
|
/docs/*
|
||||||
|
/vite.config.ts
|
||||||
/src/types/env.d.ts
|
/src/types/env.d.ts
|
||||||
/docs/**/*
|
/docs/**/*
|
||||||
/plop/**/*
|
/plop/**/*
|
||||||
|
3
kinit-admin/.vscode/extensions.json
vendored
Normal file
3
kinit-admin/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
|
||||||
|
}
|
19
kinit-admin/.vscode/settings.json
vendored
Normal file
19
kinit-admin/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
|
"prettier.enable": false,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
},
|
||||||
|
"[vue]": {
|
||||||
|
"editor.defaultFormatter": "rvest.vs-code-prettier-eslint"
|
||||||
|
},
|
||||||
|
"i18n-ally.localesPaths": ["src/locales"],
|
||||||
|
"i18n-ally.keystyle": "nested",
|
||||||
|
"i18n-ally.sortKeys": true,
|
||||||
|
"i18n-ally.namespace": false,
|
||||||
|
"i18n-ally.enabledParsers": ["ts"],
|
||||||
|
"i18n-ally.sourceLanguage": "zh-CN",
|
||||||
|
"i18n-ally.displayLanguage": "zh-CN",
|
||||||
|
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||||
|
"god.tsconfig": "./tsconfig.json"
|
||||||
|
}
|
1015
kinit-admin/CHANGELOG.md
Normal file
1015
kinit-admin/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
18
kinit-admin/mock/_createProductionServer.ts
Normal file
18
kinit-admin/mock/_createProductionServer.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer'
|
||||||
|
|
||||||
|
const modules = import.meta.glob('./**/*.ts', {
|
||||||
|
import: 'default',
|
||||||
|
eager: true
|
||||||
|
})
|
||||||
|
|
||||||
|
const mockModules: any[] = []
|
||||||
|
Object.keys(modules).forEach(async (key) => {
|
||||||
|
if (key.includes('_')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mockModules.push(...(modules[key] as any))
|
||||||
|
})
|
||||||
|
|
||||||
|
export function setupProdMockServer() {
|
||||||
|
createProdMockServer(mockModules)
|
||||||
|
}
|
@ -1,127 +1,115 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-element-plus-admin",
|
"name": "vue-element-plus-admin",
|
||||||
"version": "2.7.0",
|
"version": "2.2.0",
|
||||||
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
|
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
|
||||||
"author": "Archer <502431556@qq.com>",
|
"author": "Archer <502431556@qq.com>",
|
||||||
"private": false,
|
"private": false,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"i": "pnpm install",
|
"i": "pnpm install",
|
||||||
"dev": "pnpm vite --mode dev",
|
"dev": "vite --mode dev",
|
||||||
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
|
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
||||||
"build:pro": "pnpm vite build --mode pro",
|
"build:pro": "vite build --mode pro",
|
||||||
"build:dev": "pnpm vite build --mode dev",
|
"build:dev": "vite build --mode dev",
|
||||||
"serve:pro": "pnpm vite preview --mode pro",
|
"serve:pro": "vite preview --mode pro",
|
||||||
"serve:dev": "pnpm vite preview --mode dev",
|
"serve:dev": "vite preview --mode dev",
|
||||||
"npm:check": "pnpx npm-check-updates -u",
|
"npm:check": "npx npm-check-updates",
|
||||||
"clean": "pnpx rimraf node_modules",
|
"clean": "npx rimraf node_modules",
|
||||||
"clean:cache": "pnpx rimraf node_modules/.cache",
|
"clean:cache": "npx rimraf node_modules/.cache",
|
||||||
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
"lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src",
|
||||||
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
|
"lint:format": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,vue,html,md}\"",
|
||||||
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||||
"p": "plop",
|
"p": "plop"
|
||||||
"icon": "esno ./scripts/icon.ts"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iconify/iconify": "3.1.1",
|
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||||
"@iconify/vue": "4.1.1",
|
"@iconify/iconify": "^3.1.1",
|
||||||
"@vueuse/core": "10.9.0",
|
"@iconify/vue": "^4.1.1",
|
||||||
"@wangeditor/editor": "5.1.23",
|
"@vueuse/core": "^10.3.0",
|
||||||
"@wangeditor/editor-for-vue": "5.1.10",
|
"@wangeditor/editor": "^5.1.23",
|
||||||
"@zxcvbn-ts/core": "3.0.4",
|
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||||
"animate.css": "4.1.1",
|
"@zxcvbn-ts/core": "^3.0.3",
|
||||||
"axios": "1.6.7",
|
"animate.css": "^4.1.1",
|
||||||
"cropperjs": "1.6.1",
|
"axios": "^1.4.0",
|
||||||
"dayjs": "1.11.10",
|
"dayjs": "^1.11.9",
|
||||||
"driver.js": "1.3.1",
|
"driver.js": "^1.2.1",
|
||||||
"echarts": "5.5.0",
|
"echarts": "^5.4.3",
|
||||||
"echarts-wordcloud": "2.1.0",
|
"echarts-wordcloud": "^2.1.0",
|
||||||
"element-plus": "2.5.6",
|
"element-plus": "^2.3.9",
|
||||||
"lodash-es": "4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"mitt": "3.0.1",
|
"mitt": "^3.0.1",
|
||||||
"nprogress": "0.2.0",
|
"mockjs": "^1.1.0",
|
||||||
"pinia": "2.1.7",
|
"nprogress": "^0.2.0",
|
||||||
"pinia-plugin-persistedstate": "3.2.1",
|
"pinia": "^2.1.6",
|
||||||
"qrcode": "1.5.3",
|
"pinia-plugin-persist": "^1.0.0",
|
||||||
"qs": "6.11.2",
|
"qrcode": "^1.5.3",
|
||||||
"url": "0.11.3",
|
"qs": "^6.11.2",
|
||||||
"vue": "3.4.20",
|
"sortablejs": "^1.15.0",
|
||||||
"vue-draggable-plus": "0.3.5",
|
"url": "^0.11.1",
|
||||||
"vue-i18n": "9.9.1",
|
"vue": "3.3.4",
|
||||||
"vue-json-pretty": "2.3.0",
|
"vue-i18n": "9.2.2",
|
||||||
"vue-router": "4.3.0",
|
"vue-json-pretty": "^2.2.4",
|
||||||
"vue-types": "5.1.1",
|
"vue-router": "^4.2.4",
|
||||||
"xgplayer": "3.0.13"
|
"vue-types": "^5.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@amap/amap-jsapi-loader": "1.0.1",
|
"@iconify/json": "^2.2.101",
|
||||||
"@commitlint/cli": "19.0.1",
|
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||||
"@commitlint/config-conventional": "19.0.0",
|
"@kjgl77/datav-vue3": "^1.6.1",
|
||||||
"@iconify/json": "2.2.187",
|
"@purge-icons/generated": "^0.9.0",
|
||||||
"@intlify/unplugin-vue-i18n": "2.0.0",
|
"@types/lodash-es": "^4.17.8",
|
||||||
"@kjgl77/datav-vue3": "1.6.1",
|
"@types/node": "^20.4.10",
|
||||||
"@types/fs-extra": "11.0.4",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/inquirer": "9.0.7",
|
"@types/qrcode": "^1.5.1",
|
||||||
"@types/lodash-es": "4.17.12",
|
"@types/qs": "^6.9.7",
|
||||||
"@types/node": "20.11.21",
|
"@types/sortablejs": "^1.15.1",
|
||||||
"@types/nprogress": "0.2.3",
|
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||||
"@types/qrcode": "1.5.5",
|
"@typescript-eslint/parser": "^6.3.0",
|
||||||
"@types/qs": "6.9.12",
|
"@unocss/transformer-variant-group": "^0.55.0",
|
||||||
"@types/sortablejs": "1.15.8",
|
"@vitejs/plugin-legacy": "^4.1.1",
|
||||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
"@vitejs/plugin-vue": "^4.2.3",
|
||||||
"@typescript-eslint/parser": "7.1.0",
|
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||||
"@unocss/transformer-variant-group": "0.58.5",
|
"autoprefixer": "^10.4.14",
|
||||||
"@vitejs/plugin-legacy": "5.3.1",
|
"consola": "^3.2.3",
|
||||||
"@vitejs/plugin-vue": "5.0.4",
|
"cron-validate": "^1.4.5",
|
||||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
"eslint": "^8.47.0",
|
||||||
"autoprefixer": "10.4.17",
|
"eslint-config-prettier": "^9.0.0",
|
||||||
"chalk": "5.3.0",
|
"eslint-define-config": "^1.23.0",
|
||||||
"consola": "3.2.3",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"cron-validate": "1.4.5",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"eslint": "8.57.0",
|
"intro.js": "^7.2.0",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"less": "^4.2.0",
|
||||||
"eslint-define-config": "2.1.0",
|
"lint-staged": "^13.2.3",
|
||||||
"eslint-plugin-prettier": "5.1.3",
|
"lodash": "^4.17.21",
|
||||||
"eslint-plugin-vue": "9.22.0",
|
"moment": "^2.29.4",
|
||||||
"esno": "4.0.0",
|
"plop": "^3.1.2",
|
||||||
"fs-extra": "11.2.0",
|
"postcss": "^8.4.27",
|
||||||
"inquirer": "9.2.15",
|
"postcss-html": "^1.5.0",
|
||||||
"less": "4.2.0",
|
"postcss-less": "^6.0.0",
|
||||||
"lint-staged": "15.2.2",
|
"prettier": "^3.0.1",
|
||||||
"lodash": "4.17.21",
|
"rimraf": "^5.0.1",
|
||||||
"moment": "2.30.1",
|
"rollup": "^3.28.0",
|
||||||
"plop": "4.0.1",
|
"stylelint": "^15.10.2",
|
||||||
"postcss": "8.4.35",
|
"stylelint-config-html": "^1.1.0",
|
||||||
"postcss-html": "1.6.0",
|
"stylelint-config-recommended": "^13.0.0",
|
||||||
"postcss-less": "6.0.0",
|
"stylelint-config-standard": "^34.0.0",
|
||||||
"prettier": "3.2.5",
|
"stylelint-order": "^6.0.3",
|
||||||
"rimraf": "5.0.5",
|
"terser": "^5.19.2",
|
||||||
"rollup": "4.12.0",
|
"typescript": "5.1.6",
|
||||||
"rollup-plugin-visualizer": "5.12.0",
|
"unocss": "^0.55.0",
|
||||||
"stylelint": "16.2.1",
|
"vite": "4.4.9",
|
||||||
"stylelint-config-html": "1.1.0",
|
"vite-plugin-ejs": "^1.6.4",
|
||||||
"stylelint-config-recommended": "14.0.0",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"stylelint-config-standard": "36.0.0",
|
"vite-plugin-mock": "2.9.6",
|
||||||
"stylelint-order": "6.0.4",
|
"vite-plugin-progress": "^0.0.7",
|
||||||
"terser": "5.28.1",
|
"vite-plugin-purge-icons": "^0.9.2",
|
||||||
"typescript": "5.3.3",
|
|
||||||
"unocss": "0.58.5",
|
|
||||||
"vite": "5.1.4",
|
|
||||||
"vite-plugin-ejs": "1.7.0",
|
|
||||||
"vite-plugin-eslint": "1.8.1",
|
|
||||||
"vite-plugin-progress": "0.0.7",
|
|
||||||
"vite-plugin-purge-icons": "0.10.0",
|
|
||||||
"vite-plugin-style-import": "2.0.0",
|
"vite-plugin-style-import": "2.0.0",
|
||||||
"vite-plugin-svg-icons": "2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue-draggable-plus": "0.2.6",
|
"vue-draggable-plus": "^0.2.6",
|
||||||
"vite-plugin-url-copy": "1.1.3",
|
"vue-tsc": "^1.8.8",
|
||||||
"vue-tsc": "1.8.27",
|
"vue3-json-viewer": "^2.2.2"
|
||||||
"vue3-json-viewer": "2.2.2",
|
|
||||||
"zipson": "0.2.12"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.1.0",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18 <19",
|
"node": ">= 14.18.0"
|
||||||
"pnpm": ">=8.1.0 <10.0.0"
|
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
import path from 'path'
|
|
||||||
import fs from 'fs-extra'
|
|
||||||
import inquirer from 'inquirer'
|
|
||||||
import chalk from 'chalk'
|
|
||||||
import pkg from '../package.json'
|
|
||||||
|
|
||||||
interface Icon {
|
|
||||||
name: string
|
|
||||||
prefix: string
|
|
||||||
icons: string[]
|
|
||||||
}
|
|
||||||
|
|
||||||
async function generateIcon() {
|
|
||||||
const dir = path.resolve(process.cwd(), 'node_modules/@iconify/json')
|
|
||||||
|
|
||||||
const raw = await fs.readJSON(path.join(dir, 'collections.json'))
|
|
||||||
|
|
||||||
const collections = Object.entries(raw).map(([id, v]) => ({
|
|
||||||
...(v as any),
|
|
||||||
id
|
|
||||||
}))
|
|
||||||
|
|
||||||
const choices = collections.map((item) => ({ key: item.id, value: item.id, name: item.name }))
|
|
||||||
|
|
||||||
inquirer
|
|
||||||
.prompt([
|
|
||||||
// {
|
|
||||||
// type: 'list',
|
|
||||||
// name: 'useType',
|
|
||||||
// choices: [
|
|
||||||
// { key: 'local', value: 'local', name: 'Local' },
|
|
||||||
// { key: 'onLine', value: 'onLine', name: 'OnLine' }
|
|
||||||
// ],
|
|
||||||
// message: 'How to use icons?'
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
type: 'list',
|
|
||||||
name: 'iconSet',
|
|
||||||
choices: choices,
|
|
||||||
message: 'Select the icon set that needs to be generated?'
|
|
||||||
}
|
|
||||||
])
|
|
||||||
// ↓命令行问答的答案
|
|
||||||
.then(async (answers) => {
|
|
||||||
const { iconSet } = answers
|
|
||||||
// const isOnLine = useType === 'onLine'
|
|
||||||
const outputDir = path.resolve(process.cwd(), 'src/components/IconPicker/src/data')
|
|
||||||
fs.ensureDir(outputDir)
|
|
||||||
const genCollections = collections.filter((item) => [iconSet].includes(item.id))
|
|
||||||
const prefixSet: string[] = []
|
|
||||||
for (const info of genCollections) {
|
|
||||||
const data = await fs.readJSON(path.join(dir, 'json', `${info.id}.json`))
|
|
||||||
if (data) {
|
|
||||||
const { prefix } = data
|
|
||||||
const icons = Object.keys(data.icons).map((item) => `${prefix}:${item}`)
|
|
||||||
|
|
||||||
await fs.writeFileSync(
|
|
||||||
path.join('src/components/IconPicker/src/data', `icons.${prefix}.ts`),
|
|
||||||
`export default ${JSON.stringify({ name: info.name, prefix, icons })}`
|
|
||||||
)
|
|
||||||
// ↓分类处理完成,push类型名称
|
|
||||||
prefixSet.push(prefix)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(
|
|
||||||
`✨ ${chalk.cyan(`[${pkg.name}]`)}` + ' - Icon generated successfully:' + `[${prefixSet}]`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
generateIcon()
|
|
@ -2,7 +2,9 @@
|
|||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||||
|
import { isDark } from '@/utils/is'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { getSystemBaseConfigApi } from '@/api/vadmin/system/settings'
|
import { getSystemBaseConfigApi } from '@/api/vadmin/system/settings'
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
@ -15,6 +17,18 @@ const currentSize = computed(() => appStore.getCurrentSize)
|
|||||||
|
|
||||||
const greyMode = computed(() => appStore.getGreyMode)
|
const greyMode = computed(() => appStore.getGreyMode)
|
||||||
|
|
||||||
|
const { getStorage } = useStorage()
|
||||||
|
|
||||||
|
// 根据浏览器当前主题设置系统主题色
|
||||||
|
const setDefaultTheme = () => {
|
||||||
|
if (getStorage('isDark') !== null) {
|
||||||
|
appStore.setIsDark(getStorage('isDark'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const isDarkTheme = isDark()
|
||||||
|
appStore.setIsDark(isDarkTheme)
|
||||||
|
}
|
||||||
|
|
||||||
// 添加mate标签
|
// 添加mate标签
|
||||||
const addMeta = (name: string, content: string) => {
|
const addMeta = (name: string, content: string) => {
|
||||||
const meta = document.createElement('meta')
|
const meta = document.createElement('meta')
|
||||||
@ -25,9 +39,6 @@ const addMeta = (name: string, content: string) => {
|
|||||||
|
|
||||||
// 获取并设置系统配置
|
// 获取并设置系统配置
|
||||||
const setSystemConfig = async () => {
|
const setSystemConfig = async () => {
|
||||||
if (appStore.getLogoImage) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const res = await getSystemBaseConfigApi()
|
const res = await getSystemBaseConfigApi()
|
||||||
if (res) {
|
if (res) {
|
||||||
appStore.setTitle(res.data.web_title || import.meta.env.VITE_APP_TITLE)
|
appStore.setTitle(res.data.web_title || import.meta.env.VITE_APP_TITLE)
|
||||||
@ -41,7 +52,7 @@ const setSystemConfig = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appStore.initTheme()
|
setDefaultTheme()
|
||||||
setSystemConfig()
|
setSystemConfig()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import request from '@/config/axios'
|
|
||||||
|
|
||||||
export const getDeptListApi = (params: any): Promise<IResponse> => {
|
|
||||||
return request.get({ url: '/vadmin/auth/depts', params })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const delDeptListApi = (data: any): Promise<IResponse> => {
|
|
||||||
return request.delete({ url: '/vadmin/auth/depts', data })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const addDeptListApi = (data: any): Promise<IResponse> => {
|
|
||||||
return request.post({ url: '/vadmin/auth/depts', data })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const putDeptListApi = (data: any): Promise<IResponse> => {
|
|
||||||
return request.put({ url: `/vadmin/auth/depts/${data.id}`, data })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDeptTreeOptionsApi = (): Promise<IResponse> => {
|
|
||||||
return request.get({ url: '/vadmin/auth/dept/tree/options' })
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getDeptUserTreeOptionsApi = (): Promise<IResponse> => {
|
|
||||||
return request.get({ url: '/vadmin/auth/dept/user/tree/options' })
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
|
|
||||||
export const getSystemSettingsTabsApi = (data: any): Promise<IResponse> => {
|
export const getSystemSettingsTabsApi = (params: any): Promise<IResponse> => {
|
||||||
return request.post({ url: '/vadmin/system/settings/tabs', data })
|
return request.get({ url: '/vadmin/system/settings/tabs', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSystemSettingsApi = (params: any): Promise<IResponse> => {
|
export const getSystemSettingsApi = (params: any): Promise<IResponse> => {
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import BaseButton from './src/Button.vue'
|
|
||||||
|
|
||||||
export { BaseButton }
|
|
@ -1,113 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
|
||||||
import { ElButton, ComponentSize, ButtonType } from 'element-plus'
|
|
||||||
import { PropType, Component, computed, unref } from 'vue'
|
|
||||||
import { useAppStore } from '@/store/modules/app'
|
|
||||||
const appStore = useAppStore()
|
|
||||||
const getTheme = computed(() => appStore.getTheme)
|
|
||||||
const { getPrefixCls } = useDesign()
|
|
||||||
const prefixCls = getPrefixCls('button')
|
|
||||||
const props = defineProps({
|
|
||||||
size: {
|
|
||||||
type: String as PropType<ComponentSize>,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String as PropType<ButtonType>,
|
|
||||||
default: 'default'
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
plain: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
bg: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
round: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
circle: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
loadingIcon: {
|
|
||||||
type: [String, Object] as PropType<String | Component>,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
icon: {
|
|
||||||
type: [String, Object] as PropType<String | Component>,
|
|
||||||
default: undefined
|
|
||||||
},
|
|
||||||
autofocus: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
nativeType: {
|
|
||||||
type: String as PropType<'button' | 'submit' | 'reset'>,
|
|
||||||
default: 'button'
|
|
||||||
},
|
|
||||||
autoInsertSpace: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
darker: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
tag: {
|
|
||||||
type: [String, Object] as PropType<String | Component>,
|
|
||||||
default: 'button'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const emits = defineEmits(['click'])
|
|
||||||
const color = computed(() => {
|
|
||||||
const { type, link } = props
|
|
||||||
if (type === 'primary' && !link) {
|
|
||||||
return unref(getTheme).elColorPrimary
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
const style = computed(() => {
|
|
||||||
const { type, link } = props
|
|
||||||
if (type === 'primary' && !link) {
|
|
||||||
return '--el-button-text-color: #fff; --el-button-hover-text-color: #fff'
|
|
||||||
}
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ElButton
|
|
||||||
:class="`${prefixCls} color-#fff`"
|
|
||||||
v-bind="{ ...props }"
|
|
||||||
:color="color"
|
|
||||||
:style="style"
|
|
||||||
@click="() => emits('click')"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
<slot name="icon"></slot>
|
|
||||||
<slot name="loading"></slot>
|
|
||||||
</ElButton>
|
|
||||||
</template>
|
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="tsx">
|
<script lang="tsx">
|
||||||
import { ElCollapseTransition, ElTooltip, ElRow, ElCol } from 'element-plus'
|
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { ref, unref, PropType, computed, defineComponent } from 'vue'
|
import { ref, unref, PropType, computed, defineComponent } from 'vue'
|
||||||
@ -16,8 +16,6 @@ const { getPrefixCls } = useDesign()
|
|||||||
|
|
||||||
const prefixCls = getPrefixCls('descriptions')
|
const prefixCls = getPrefixCls('descriptions')
|
||||||
|
|
||||||
const defaultData = '-'
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Descriptions',
|
name: 'Descriptions',
|
||||||
props: {
|
props: {
|
||||||
@ -38,7 +36,7 @@ export default defineComponent({
|
|||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { attrs }) {
|
setup(props, { slots, attrs }) {
|
||||||
const getBindValue = computed((): any => {
|
const getBindValue = computed((): any => {
|
||||||
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
|
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
|
||||||
const obj = { ...attrs, ...props }
|
const obj = { ...attrs, ...props }
|
||||||
@ -61,10 +59,7 @@ export default defineComponent({
|
|||||||
delete obj[key]
|
delete obj[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return obj
|
||||||
labelClassName: `${prefixCls}-label`,
|
|
||||||
...obj
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 折叠
|
// 折叠
|
||||||
@ -108,51 +103,26 @@ export default defineComponent({
|
|||||||
|
|
||||||
<ElCollapseTransition>
|
<ElCollapseTransition>
|
||||||
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
|
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
|
||||||
<ElRow
|
<ElDescriptions {...unref(getBindValue)}>
|
||||||
gutter={0}
|
{{
|
||||||
{...unref(getBindValue)}
|
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
|
||||||
class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
|
default: () => {
|
||||||
>
|
return props.schema.map((item) => {
|
||||||
{props.schema.map((item) => {
|
return (
|
||||||
return (
|
<ElDescriptionsItem key={item.field} {...getBindItemValue(item)}>
|
||||||
<ElCol
|
{{
|
||||||
key={item.field}
|
label: () => (item.slots?.label ? item.slots?.label(item) : item.label),
|
||||||
span={item.span || 24 / props.column}
|
default: () =>
|
||||||
class="flex items-stretch"
|
item.slots?.default
|
||||||
>
|
? item.slots?.default(props.data)
|
||||||
{props.direction === 'horizontal' ? (
|
: get(props.data, item.field)
|
||||||
<div class="flex items-stretch bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
|
}}
|
||||||
<div
|
</ElDescriptionsItem>
|
||||||
{...getBindItemValue(item)}
|
)
|
||||||
class="w-120px text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-r-1px border-r-[var(--el-border-color-lighter)] border-r-solid "
|
})
|
||||||
>
|
}
|
||||||
{item.label}
|
}}
|
||||||
</div>
|
</ElDescriptions>
|
||||||
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
|
|
||||||
{item.slots?.default
|
|
||||||
? item.slots?.default(props.data)
|
|
||||||
: get(props.data, item.field) ?? defaultData}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div class="bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
|
|
||||||
<div
|
|
||||||
{...getBindItemValue(item)}
|
|
||||||
class="text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-b-1px border-b-[var(--el-border-color-lighter)] border-b-solid"
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</div>
|
|
||||||
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
|
|
||||||
{item.slots?.default
|
|
||||||
? item.slots?.default(props.data)
|
|
||||||
: get(props.data, item.field) ?? defaultData}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</ElCol>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</ElRow>
|
|
||||||
</div>
|
</div>
|
||||||
</ElCollapseTransition>
|
</ElCollapseTransition>
|
||||||
</div>
|
</div>
|
||||||
@ -183,13 +153,9 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.@{prefix-cls}-label) {
|
.@{prefix-cls}-content {
|
||||||
width: 150px !important;
|
:deep(.@{elNamespace}-descriptions__cell) {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// .@{prefix-cls}-content {
|
|
||||||
// :deep(.@{elNamespace}-descriptions__cell) {
|
|
||||||
// width: 0;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -10,6 +10,7 @@ const props = defineProps({
|
|||||||
modelValue: propTypes.bool.def(false),
|
modelValue: propTypes.bool.def(false),
|
||||||
title: propTypes.string.def('Dialog'),
|
title: propTypes.string.def('Dialog'),
|
||||||
fullscreen: propTypes.bool.def(true),
|
fullscreen: propTypes.bool.def(true),
|
||||||
|
|
||||||
top: propTypes.string.def('8vh'),
|
top: propTypes.string.def('8vh'),
|
||||||
height: propTypes.oneOfType([String, Number]).def('500px'),
|
height: propTypes.oneOfType([String, Number]).def('500px'),
|
||||||
width: propTypes.oneOfType([String, Number]).def('700px')
|
width: propTypes.oneOfType([String, Number]).def('700px')
|
||||||
@ -108,30 +109,26 @@ const dialogStyle = computed(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less">
|
<style lang="less">
|
||||||
.@{elNamespace}-overlay-dialog {
|
// .@{elNamespace}-overlay-dialog {
|
||||||
display: flex;
|
// display: flex;
|
||||||
justify-content: center;
|
// justify-content: center;
|
||||||
align-items: center;
|
// align-items: center;
|
||||||
}
|
// }
|
||||||
|
|
||||||
.@{elNamespace}-dialog {
|
.@{elNamespace}-dialog {
|
||||||
margin: 0 !important;
|
// margin: 0 !important;
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
height: 54px;
|
|
||||||
padding: 0;
|
|
||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
border-bottom: 1px solid var(--el-border-color);
|
border-bottom: 1px solid var(--el-border-color);
|
||||||
|
padding: 0;
|
||||||
|
height: 54px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__body {
|
&__body {
|
||||||
padding: 15px !important;
|
padding: 15px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
border-top: 1px solid var(--el-border-color);
|
border-top: 1px solid var(--el-border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
&__headerbtn {
|
&__headerbtn {
|
||||||
top: 0;
|
top: 0;
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import networkError from '@/assets/svgs/500.svg'
|
|||||||
import noPermission from '@/assets/svgs/403.svg'
|
import noPermission from '@/assets/svgs/403.svg'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
import { ElButton } from 'element-plus'
|
||||||
|
|
||||||
interface ErrorMap {
|
interface ErrorMap {
|
||||||
url: string
|
url: string
|
||||||
@ -50,7 +51,7 @@ const btnClick = () => {
|
|||||||
<img width="350" :src="errorMap[type].url" alt="" />
|
<img width="350" :src="errorMap[type].url" alt="" />
|
||||||
<div class="text-14px text-[var(--el-color-info)]">{{ errorMap[type].message }}</div>
|
<div class="text-14px text-[var(--el-color-info)]">{{ errorMap[type].message }}</div>
|
||||||
<div class="mt-20px">
|
<div class="mt-20px">
|
||||||
<BaseButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</BaseButton>
|
<ElButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,14 +9,14 @@ const prefixCls = getPrefixCls('footer')
|
|||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const footerContent = computed(() => appStore.getFooterContent)
|
const title = computed(() => appStore.getTitle)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="prefixCls"
|
:class="prefixCls"
|
||||||
class="shrink-0 text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
|
class="text-center text-[var(--el-text-color-placeholder)] bg-[var(--app-content-bg-color)] h-[var(--app-footer-height)] leading-[var(--app-footer-height)] dark:bg-[var(--el-bg-color)]"
|
||||||
>
|
>
|
||||||
{{ footerContent }}
|
Copyright ©2021-present {{ title }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -95,6 +95,9 @@ export default defineComponent({
|
|||||||
// element form 实例
|
// element form 实例
|
||||||
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
||||||
|
|
||||||
|
// useForm传入的props
|
||||||
|
const outsideProps = ref<FormProps>({})
|
||||||
|
|
||||||
const mergeProps = ref<FormProps>({})
|
const mergeProps = ref<FormProps>({})
|
||||||
|
|
||||||
const getProps = computed(() => {
|
const getProps = computed(() => {
|
||||||
@ -152,6 +155,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
const setProps = (props: FormProps = {}) => {
|
const setProps = (props: FormProps = {}) => {
|
||||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||||
|
// @ts-ignore
|
||||||
|
outsideProps.value = props
|
||||||
}
|
}
|
||||||
|
|
||||||
const delSchema = (field: string) => {
|
const delSchema = (field: string) => {
|
||||||
@ -359,31 +364,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return item.component === ComponentNameEnum.UPLOAD ? (
|
return (
|
||||||
<Com
|
|
||||||
vModel:file-list={itemVal.value}
|
|
||||||
ref={(el: any) => setComponentRefMap(el, item.field)}
|
|
||||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
|
||||||
{...setComponentProps(item)}
|
|
||||||
style={
|
|
||||||
item.componentProps?.style || {
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{{ ...slotsMap }}
|
|
||||||
</Com>
|
|
||||||
) : (
|
|
||||||
<Com
|
<Com
|
||||||
vModel={itemVal.value}
|
vModel={itemVal.value}
|
||||||
ref={(el: any) => setComponentRefMap(el, item.field)}
|
ref={(el: any) => setComponentRefMap(el, item.field)}
|
||||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||||
{...setComponentProps(item)}
|
{...setComponentProps(item)}
|
||||||
style={
|
style={item.componentProps?.style || {}}
|
||||||
item.componentProps?.style || {
|
|
||||||
width: '100%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{{ ...slotsMap }}
|
{{ ...slotsMap }}
|
||||||
</Com>
|
</Com>
|
||||||
@ -460,10 +447,6 @@ export default defineComponent({
|
|||||||
{...getFormBindValue()}
|
{...getFormBindValue()}
|
||||||
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
|
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
|
||||||
class={prefixCls}
|
class={prefixCls}
|
||||||
// @ts-ignore
|
|
||||||
onSubmit={(e: Event) => {
|
|
||||||
e.preventDefault()
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
||||||
@ -483,16 +466,4 @@ export default defineComponent({
|
|||||||
margin-right: 0 !important;
|
margin-right: 0 !important;
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{elNamespace}-form--inline {
|
|
||||||
:deep(.el-form-item__content) {
|
|
||||||
& > :first-child {
|
|
||||||
min-width: 229.5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.@{elNamespace}-input-number {
|
|
||||||
// 229.5px是兼容el-input-number的最小宽度,
|
|
||||||
min-width: 229.5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -25,7 +25,6 @@ import { Editor } from '@/components/Editor'
|
|||||||
import { Text } from '@/components/Text'
|
import { Text } from '@/components/Text'
|
||||||
import { JsonEditor } from '@/components/JsonEditor'
|
import { JsonEditor } from '@/components/JsonEditor'
|
||||||
import { ComponentName } from '../types'
|
import { ComponentName } from '../types'
|
||||||
import { IconPicker } from '@/components/IconPicker'
|
|
||||||
|
|
||||||
const componentMap: Recordable<Component, ComponentName> = {
|
const componentMap: Recordable<Component, ComponentName> = {
|
||||||
RadioGroup: ElRadioGroup,
|
RadioGroup: ElRadioGroup,
|
||||||
@ -52,7 +51,6 @@ const componentMap: Recordable<Component, ComponentName> = {
|
|||||||
TreeSelect: ElTreeSelect,
|
TreeSelect: ElTreeSelect,
|
||||||
Upload: ElUpload,
|
Upload: ElUpload,
|
||||||
JsonEditor: JsonEditor,
|
JsonEditor: JsonEditor,
|
||||||
IconPicker: IconPicker,
|
|
||||||
Text: Text
|
Text: Text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +56,6 @@ export enum ComponentNameEnum {
|
|||||||
TREE_SELECT = 'TreeSelect',
|
TREE_SELECT = 'TreeSelect',
|
||||||
UPLOAD = 'Upload',
|
UPLOAD = 'Upload',
|
||||||
JSON_EDITOR = 'JsonEditor',
|
JSON_EDITOR = 'JsonEditor',
|
||||||
ICON_PICKER = 'IconPicker',
|
|
||||||
Text = 'Text'
|
Text = 'Text'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,11 +25,6 @@ const symbolId = computed(() => {
|
|||||||
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
|
return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon
|
||||||
})
|
})
|
||||||
|
|
||||||
// 是否使用在线图标
|
|
||||||
const isUseOnline = computed(() => {
|
|
||||||
return import.meta.env.VITE_USE_ONLINE_ICON === 'true'
|
|
||||||
})
|
|
||||||
|
|
||||||
const getIconifyStyle = computed(() => {
|
const getIconifyStyle = computed(() => {
|
||||||
const { color, size } = props
|
const { color, size } = props
|
||||||
return {
|
return {
|
||||||
@ -45,10 +40,7 @@ const getIconifyStyle = computed(() => {
|
|||||||
<use :xlink:href="symbolId" />
|
<use :xlink:href="symbolId" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<template v-else>
|
<Icon v-else :icon="icon" :style="getIconifyStyle" />
|
||||||
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
|
|
||||||
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
|
|
||||||
</template>
|
|
||||||
</ElIcon>
|
</ElIcon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -57,18 +49,11 @@ const getIconifyStyle = computed(() => {
|
|||||||
|
|
||||||
.@{prefix-cls},
|
.@{prefix-cls},
|
||||||
.iconify {
|
.iconify {
|
||||||
:deep(svg) {
|
&:hover {
|
||||||
&:hover {
|
:deep(svg) {
|
||||||
// stylelint-disable-next-line
|
// stylelint-disable-next-line
|
||||||
color: v-bind(hoverColor) !important;
|
color: v-bind(hoverColor) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconify {
|
|
||||||
&:hover {
|
|
||||||
// stylelint-disable-next-line
|
|
||||||
color: v-bind(hoverColor) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import IconPicker from './src/IconPicker.vue'
|
|
||||||
|
|
||||||
export { IconPicker }
|
|
@ -1,193 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import epIcons from './data/icons.ep'
|
|
||||||
import antIcons from './data/icons.ant-design'
|
|
||||||
import tIcons from './data/icons.tdesign'
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
|
||||||
import { ElInput, ElPopover, ElScrollbar, ElTabs, ElTabPane, ElPagination } from 'element-plus'
|
|
||||||
import { useAppStore } from '@/store/modules/app'
|
|
||||||
import { computed, CSSProperties, ref, unref, watch } from 'vue'
|
|
||||||
import { nextTick } from 'vue'
|
|
||||||
|
|
||||||
const init = async (icon?: string) => {
|
|
||||||
if (!icon) return
|
|
||||||
const iconInfo = icon.split(':')
|
|
||||||
iconName.value = iconInfo[0]
|
|
||||||
const wrapIndex = icons.findIndex((item) => item.prefix === iconInfo[0])
|
|
||||||
// 查询当前icon的索引
|
|
||||||
const index = filterItemIcons(icons[wrapIndex].icons).findIndex((item) => item === icon)
|
|
||||||
// 计算当前icon的页码
|
|
||||||
await nextTick()
|
|
||||||
currentPage.value = Math.ceil((index + 1) / unref(pageSize))
|
|
||||||
}
|
|
||||||
|
|
||||||
const modelValue = defineModel<string>()
|
|
||||||
|
|
||||||
const appStore = useAppStore()
|
|
||||||
|
|
||||||
const size = computed(() => appStore.getCurrentSize)
|
|
||||||
|
|
||||||
const iconSize = computed(() => {
|
|
||||||
return unref(size) === 'small'
|
|
||||||
? 'var(--el-component-size-small)'
|
|
||||||
: unref(size) === 'large'
|
|
||||||
? 'var(--el-component-size-large)'
|
|
||||||
: 'var(--el-component-size)'
|
|
||||||
})
|
|
||||||
|
|
||||||
const iconWrapStyle = computed((): CSSProperties => {
|
|
||||||
return {
|
|
||||||
width: unref(iconSize),
|
|
||||||
height: unref(iconSize),
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
boxShadow: '0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset',
|
|
||||||
position: 'relative',
|
|
||||||
left: '-1px',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
|
||||||
|
|
||||||
const prefixCls = getPrefixCls('icon-picker')
|
|
||||||
|
|
||||||
const icons = [epIcons, antIcons, tIcons]
|
|
||||||
|
|
||||||
const iconName = ref(icons[0].prefix)
|
|
||||||
|
|
||||||
const currentIconNameIndex = computed(() => {
|
|
||||||
return icons.findIndex((item) => item.prefix === unref(iconName))
|
|
||||||
})
|
|
||||||
|
|
||||||
const tabChange = () => {
|
|
||||||
currentPage.value = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
const pageSize = ref(49)
|
|
||||||
|
|
||||||
const currentPage = ref(1)
|
|
||||||
|
|
||||||
const filterIcons = (icons: string[]) => {
|
|
||||||
const start = (unref(currentPage) - 1) * unref(pageSize)
|
|
||||||
const end = unref(currentPage) * unref(pageSize)
|
|
||||||
return icons.slice(start, end)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => modelValue.value,
|
|
||||||
async (val) => {
|
|
||||||
await nextTick()
|
|
||||||
val && init(val)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const popoverShow = () => {
|
|
||||||
init(unref(modelValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
const iconSelect = (icon: string) => {
|
|
||||||
// 如果是同一个icon则不做处理,则相当于点击了清空按钮
|
|
||||||
if (icon === unref(modelValue)) {
|
|
||||||
modelValue.value = ''
|
|
||||||
return
|
|
||||||
}
|
|
||||||
modelValue.value = icon
|
|
||||||
}
|
|
||||||
|
|
||||||
const search = ref('')
|
|
||||||
|
|
||||||
const filterItemIcons = (icons: string[]) => {
|
|
||||||
return icons.filter((item) => item.includes(unref(search)))
|
|
||||||
}
|
|
||||||
|
|
||||||
const inputClear = () => {
|
|
||||||
init(unref(modelValue))
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="prefixCls" class="flex justify-center items-center box">
|
|
||||||
<ElInput disabled v-model="modelValue" clearable />
|
|
||||||
<ElPopover
|
|
||||||
placement="bottom"
|
|
||||||
trigger="click"
|
|
||||||
:width="450"
|
|
||||||
popper-style="box-shadow: rgb(14 18 22 / 35%) 0px 10px 38px -10px, rgb(14 18 22 / 20%) 0px 10px 20px -15px; height: 380px;"
|
|
||||||
@show="popoverShow"
|
|
||||||
>
|
|
||||||
<template #reference>
|
|
||||||
<div :style="iconWrapStyle">
|
|
||||||
<Icon v-if="modelValue" :icon="modelValue" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<ElScrollbar class="h-[calc(100%-50px)]!">
|
|
||||||
<ElInput
|
|
||||||
v-model="search"
|
|
||||||
class="mb-20px"
|
|
||||||
clearable
|
|
||||||
placeholder="搜索图标"
|
|
||||||
@clear="inputClear"
|
|
||||||
/>
|
|
||||||
<ElTabs tab-position="left" v-model="iconName" @tab-change="tabChange">
|
|
||||||
<ElTabPane v-for="item in icons" :key="item.name" :label="item.name" :name="item.prefix">
|
|
||||||
<div class="flex flex-wrap box-border">
|
|
||||||
<div
|
|
||||||
v-for="icon in filterIcons(filterItemIcons(item.icons))"
|
|
||||||
:key="icon"
|
|
||||||
:style="{
|
|
||||||
width: iconSize,
|
|
||||||
height: iconSize,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
border: `1px solid ${
|
|
||||||
icon === modelValue ? 'var(--el-color-primary)' : 'var(--el-border-color)'
|
|
||||||
}`,
|
|
||||||
boxSizing: 'border-box',
|
|
||||||
margin: '2px',
|
|
||||||
transition: 'all 0.3s'
|
|
||||||
}"
|
|
||||||
class="hover:border-color-[var(--el-color-primary)]!"
|
|
||||||
@click="iconSelect(icon)"
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
:icon="icon"
|
|
||||||
:color="icon === modelValue ? 'var(--el-color-primary)' : 'inherit'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ElTabPane>
|
|
||||||
</ElTabs>
|
|
||||||
</ElScrollbar>
|
|
||||||
<div
|
|
||||||
class="h-50px absolute bottom-0 left-0 flex items-center pl-[var(--el-popover-padding)] pr-[var(--el-popover-padding)]"
|
|
||||||
>
|
|
||||||
<ElPagination
|
|
||||||
v-model:current-page="currentPage"
|
|
||||||
v-model:page-size="pageSize"
|
|
||||||
:pager-count="5"
|
|
||||||
small
|
|
||||||
:page-sizes="[100, 200, 300, 400]"
|
|
||||||
layout="total, prev, pager, next, jumper"
|
|
||||||
:total="filterItemIcons(icons[currentIconNameIndex].icons).length"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</ElPopover>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
@prefix-cls: ~'@{namespace}-icon-picker';
|
|
||||||
|
|
||||||
.@{prefix-cls} {
|
|
||||||
:deep(.@{elNamespace}-input__wrapper) {
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -1,795 +0,0 @@
|
|||||||
export default {
|
|
||||||
name: 'Ant Design Icons',
|
|
||||||
prefix: 'ant-design',
|
|
||||||
icons: [
|
|
||||||
'ant-design:account-book-filled',
|
|
||||||
'ant-design:account-book-outlined',
|
|
||||||
'ant-design:account-book-twotone',
|
|
||||||
'ant-design:aim-outlined',
|
|
||||||
'ant-design:alert-filled',
|
|
||||||
'ant-design:alert-outlined',
|
|
||||||
'ant-design:alert-twotone',
|
|
||||||
'ant-design:alibaba-outlined',
|
|
||||||
'ant-design:align-center-outlined',
|
|
||||||
'ant-design:align-left-outlined',
|
|
||||||
'ant-design:align-right-outlined',
|
|
||||||
'ant-design:alipay-circle-filled',
|
|
||||||
'ant-design:alipay-circle-outlined',
|
|
||||||
'ant-design:alipay-outlined',
|
|
||||||
'ant-design:alipay-square-filled',
|
|
||||||
'ant-design:aliwangwang-filled',
|
|
||||||
'ant-design:aliwangwang-outlined',
|
|
||||||
'ant-design:aliyun-outlined',
|
|
||||||
'ant-design:amazon-circle-filled',
|
|
||||||
'ant-design:amazon-outlined',
|
|
||||||
'ant-design:amazon-square-filled',
|
|
||||||
'ant-design:android-filled',
|
|
||||||
'ant-design:android-outlined',
|
|
||||||
'ant-design:ant-cloud-outlined',
|
|
||||||
'ant-design:ant-design-outlined',
|
|
||||||
'ant-design:apartment-outlined',
|
|
||||||
'ant-design:api-filled',
|
|
||||||
'ant-design:api-outlined',
|
|
||||||
'ant-design:api-twotone',
|
|
||||||
'ant-design:apple-filled',
|
|
||||||
'ant-design:apple-outlined',
|
|
||||||
'ant-design:appstore-add-outlined',
|
|
||||||
'ant-design:appstore-filled',
|
|
||||||
'ant-design:appstore-outlined',
|
|
||||||
'ant-design:appstore-twotone',
|
|
||||||
'ant-design:area-chart-outlined',
|
|
||||||
'ant-design:arrow-down-outlined',
|
|
||||||
'ant-design:arrow-left-outlined',
|
|
||||||
'ant-design:arrow-right-outlined',
|
|
||||||
'ant-design:arrow-up-outlined',
|
|
||||||
'ant-design:arrows-alt-outlined',
|
|
||||||
'ant-design:audio-filled',
|
|
||||||
'ant-design:audio-muted-outlined',
|
|
||||||
'ant-design:audio-outlined',
|
|
||||||
'ant-design:audio-twotone',
|
|
||||||
'ant-design:audit-outlined',
|
|
||||||
'ant-design:backward-filled',
|
|
||||||
'ant-design:backward-outlined',
|
|
||||||
'ant-design:bank-filled',
|
|
||||||
'ant-design:bank-outlined',
|
|
||||||
'ant-design:bank-twotone',
|
|
||||||
'ant-design:bar-chart-outlined',
|
|
||||||
'ant-design:barcode-outlined',
|
|
||||||
'ant-design:bars-outlined',
|
|
||||||
'ant-design:behance-circle-filled',
|
|
||||||
'ant-design:behance-outlined',
|
|
||||||
'ant-design:behance-square-filled',
|
|
||||||
'ant-design:behance-square-outlined',
|
|
||||||
'ant-design:bell-filled',
|
|
||||||
'ant-design:bell-outlined',
|
|
||||||
'ant-design:bell-twotone',
|
|
||||||
'ant-design:bg-colors-outlined',
|
|
||||||
'ant-design:block-outlined',
|
|
||||||
'ant-design:bold-outlined',
|
|
||||||
'ant-design:book-filled',
|
|
||||||
'ant-design:book-outlined',
|
|
||||||
'ant-design:book-twotone',
|
|
||||||
'ant-design:border-bottom-outlined',
|
|
||||||
'ant-design:border-horizontal-outlined',
|
|
||||||
'ant-design:border-inner-outlined',
|
|
||||||
'ant-design:border-left-outlined',
|
|
||||||
'ant-design:border-outer-outlined',
|
|
||||||
'ant-design:border-outlined',
|
|
||||||
'ant-design:border-right-outlined',
|
|
||||||
'ant-design:border-top-outlined',
|
|
||||||
'ant-design:border-verticle-outlined',
|
|
||||||
'ant-design:borderless-table-outlined',
|
|
||||||
'ant-design:box-plot-filled',
|
|
||||||
'ant-design:box-plot-outlined',
|
|
||||||
'ant-design:box-plot-twotone',
|
|
||||||
'ant-design:branches-outlined',
|
|
||||||
'ant-design:bug-filled',
|
|
||||||
'ant-design:bug-outlined',
|
|
||||||
'ant-design:bug-twotone',
|
|
||||||
'ant-design:build-filled',
|
|
||||||
'ant-design:build-outlined',
|
|
||||||
'ant-design:build-twotone',
|
|
||||||
'ant-design:bulb-filled',
|
|
||||||
'ant-design:bulb-outlined',
|
|
||||||
'ant-design:bulb-twotone',
|
|
||||||
'ant-design:calculator-filled',
|
|
||||||
'ant-design:calculator-outlined',
|
|
||||||
'ant-design:calculator-twotone',
|
|
||||||
'ant-design:calendar-filled',
|
|
||||||
'ant-design:calendar-outlined',
|
|
||||||
'ant-design:calendar-twotone',
|
|
||||||
'ant-design:camera-filled',
|
|
||||||
'ant-design:camera-outlined',
|
|
||||||
'ant-design:camera-twotone',
|
|
||||||
'ant-design:car-filled',
|
|
||||||
'ant-design:car-outlined',
|
|
||||||
'ant-design:car-twotone',
|
|
||||||
'ant-design:caret-down-filled',
|
|
||||||
'ant-design:caret-down-outlined',
|
|
||||||
'ant-design:caret-left-filled',
|
|
||||||
'ant-design:caret-left-outlined',
|
|
||||||
'ant-design:caret-right-filled',
|
|
||||||
'ant-design:caret-right-outlined',
|
|
||||||
'ant-design:caret-up-filled',
|
|
||||||
'ant-design:caret-up-outlined',
|
|
||||||
'ant-design:carry-out-filled',
|
|
||||||
'ant-design:carry-out-outlined',
|
|
||||||
'ant-design:carry-out-twotone',
|
|
||||||
'ant-design:check-circle-filled',
|
|
||||||
'ant-design:check-circle-outlined',
|
|
||||||
'ant-design:check-circle-twotone',
|
|
||||||
'ant-design:check-outlined',
|
|
||||||
'ant-design:check-square-filled',
|
|
||||||
'ant-design:check-square-outlined',
|
|
||||||
'ant-design:check-square-twotone',
|
|
||||||
'ant-design:chrome-filled',
|
|
||||||
'ant-design:chrome-outlined',
|
|
||||||
'ant-design:ci-circle-filled',
|
|
||||||
'ant-design:ci-circle-outlined',
|
|
||||||
'ant-design:ci-circle-twotone',
|
|
||||||
'ant-design:ci-outlined',
|
|
||||||
'ant-design:ci-twotone',
|
|
||||||
'ant-design:clear-outlined',
|
|
||||||
'ant-design:clock-circle-filled',
|
|
||||||
'ant-design:clock-circle-outlined',
|
|
||||||
'ant-design:clock-circle-twotone',
|
|
||||||
'ant-design:close-circle-filled',
|
|
||||||
'ant-design:close-circle-outlined',
|
|
||||||
'ant-design:close-circle-twotone',
|
|
||||||
'ant-design:close-outlined',
|
|
||||||
'ant-design:close-square-filled',
|
|
||||||
'ant-design:close-square-outlined',
|
|
||||||
'ant-design:close-square-twotone',
|
|
||||||
'ant-design:cloud-download-outlined',
|
|
||||||
'ant-design:cloud-filled',
|
|
||||||
'ant-design:cloud-outlined',
|
|
||||||
'ant-design:cloud-server-outlined',
|
|
||||||
'ant-design:cloud-sync-outlined',
|
|
||||||
'ant-design:cloud-twotone',
|
|
||||||
'ant-design:cloud-upload-outlined',
|
|
||||||
'ant-design:cluster-outlined',
|
|
||||||
'ant-design:code-filled',
|
|
||||||
'ant-design:code-outlined',
|
|
||||||
'ant-design:code-sandbox-circle-filled',
|
|
||||||
'ant-design:code-sandbox-outlined',
|
|
||||||
'ant-design:code-sandbox-square-filled',
|
|
||||||
'ant-design:code-twotone',
|
|
||||||
'ant-design:codepen-circle-filled',
|
|
||||||
'ant-design:codepen-circle-outlined',
|
|
||||||
'ant-design:codepen-outlined',
|
|
||||||
'ant-design:codepen-square-filled',
|
|
||||||
'ant-design:coffee-outlined',
|
|
||||||
'ant-design:column-height-outlined',
|
|
||||||
'ant-design:column-width-outlined',
|
|
||||||
'ant-design:comment-outlined',
|
|
||||||
'ant-design:compass-filled',
|
|
||||||
'ant-design:compass-outlined',
|
|
||||||
'ant-design:compass-twotone',
|
|
||||||
'ant-design:compress-outlined',
|
|
||||||
'ant-design:console-sql-outlined',
|
|
||||||
'ant-design:contacts-filled',
|
|
||||||
'ant-design:contacts-outlined',
|
|
||||||
'ant-design:contacts-twotone',
|
|
||||||
'ant-design:container-filled',
|
|
||||||
'ant-design:container-outlined',
|
|
||||||
'ant-design:container-twotone',
|
|
||||||
'ant-design:control-filled',
|
|
||||||
'ant-design:control-outlined',
|
|
||||||
'ant-design:control-twotone',
|
|
||||||
'ant-design:copy-filled',
|
|
||||||
'ant-design:copy-outlined',
|
|
||||||
'ant-design:copy-twotone',
|
|
||||||
'ant-design:copyright-circle-filled',
|
|
||||||
'ant-design:copyright-circle-outlined',
|
|
||||||
'ant-design:copyright-circle-twotone',
|
|
||||||
'ant-design:copyright-outlined',
|
|
||||||
'ant-design:copyright-twotone',
|
|
||||||
'ant-design:credit-card-filled',
|
|
||||||
'ant-design:credit-card-outlined',
|
|
||||||
'ant-design:credit-card-twotone',
|
|
||||||
'ant-design:crown-filled',
|
|
||||||
'ant-design:crown-outlined',
|
|
||||||
'ant-design:crown-twotone',
|
|
||||||
'ant-design:customer-service-filled',
|
|
||||||
'ant-design:customer-service-outlined',
|
|
||||||
'ant-design:customer-service-twotone',
|
|
||||||
'ant-design:dash-outlined',
|
|
||||||
'ant-design:dashboard-filled',
|
|
||||||
'ant-design:dashboard-outlined',
|
|
||||||
'ant-design:dashboard-twotone',
|
|
||||||
'ant-design:database-filled',
|
|
||||||
'ant-design:database-outlined',
|
|
||||||
'ant-design:database-twotone',
|
|
||||||
'ant-design:delete-column-outlined',
|
|
||||||
'ant-design:delete-filled',
|
|
||||||
'ant-design:delete-outlined',
|
|
||||||
'ant-design:delete-row-outlined',
|
|
||||||
'ant-design:delete-twotone',
|
|
||||||
'ant-design:delivered-procedure-outlined',
|
|
||||||
'ant-design:deployment-unit-outlined',
|
|
||||||
'ant-design:desktop-outlined',
|
|
||||||
'ant-design:diff-filled',
|
|
||||||
'ant-design:diff-outlined',
|
|
||||||
'ant-design:diff-twotone',
|
|
||||||
'ant-design:dingding-outlined',
|
|
||||||
'ant-design:dingtalk-circle-filled',
|
|
||||||
'ant-design:dingtalk-outlined',
|
|
||||||
'ant-design:dingtalk-square-filled',
|
|
||||||
'ant-design:disconnect-outlined',
|
|
||||||
'ant-design:dislike-filled',
|
|
||||||
'ant-design:dislike-outlined',
|
|
||||||
'ant-design:dislike-twotone',
|
|
||||||
'ant-design:dollar-circle-filled',
|
|
||||||
'ant-design:dollar-circle-outlined',
|
|
||||||
'ant-design:dollar-circle-twotone',
|
|
||||||
'ant-design:dollar-outlined',
|
|
||||||
'ant-design:dollar-twotone',
|
|
||||||
'ant-design:dot-chart-outlined',
|
|
||||||
'ant-design:double-left-outlined',
|
|
||||||
'ant-design:double-right-outlined',
|
|
||||||
'ant-design:down-circle-filled',
|
|
||||||
'ant-design:down-circle-outlined',
|
|
||||||
'ant-design:down-circle-twotone',
|
|
||||||
'ant-design:down-outlined',
|
|
||||||
'ant-design:down-square-filled',
|
|
||||||
'ant-design:down-square-outlined',
|
|
||||||
'ant-design:down-square-twotone',
|
|
||||||
'ant-design:download-outlined',
|
|
||||||
'ant-design:drag-outlined',
|
|
||||||
'ant-design:dribbble-circle-filled',
|
|
||||||
'ant-design:dribbble-outlined',
|
|
||||||
'ant-design:dribbble-square-filled',
|
|
||||||
'ant-design:dribbble-square-outlined',
|
|
||||||
'ant-design:dropbox-circle-filled',
|
|
||||||
'ant-design:dropbox-outlined',
|
|
||||||
'ant-design:dropbox-square-filled',
|
|
||||||
'ant-design:edit-filled',
|
|
||||||
'ant-design:edit-outlined',
|
|
||||||
'ant-design:edit-twotone',
|
|
||||||
'ant-design:ellipsis-outlined',
|
|
||||||
'ant-design:enter-outlined',
|
|
||||||
'ant-design:environment-filled',
|
|
||||||
'ant-design:environment-outlined',
|
|
||||||
'ant-design:environment-twotone',
|
|
||||||
'ant-design:euro-circle-filled',
|
|
||||||
'ant-design:euro-circle-outlined',
|
|
||||||
'ant-design:euro-circle-twotone',
|
|
||||||
'ant-design:euro-outlined',
|
|
||||||
'ant-design:euro-twotone',
|
|
||||||
'ant-design:exception-outlined',
|
|
||||||
'ant-design:exclamation-circle-filled',
|
|
||||||
'ant-design:exclamation-circle-outlined',
|
|
||||||
'ant-design:exclamation-circle-twotone',
|
|
||||||
'ant-design:exclamation-outlined',
|
|
||||||
'ant-design:expand-alt-outlined',
|
|
||||||
'ant-design:expand-outlined',
|
|
||||||
'ant-design:experiment-filled',
|
|
||||||
'ant-design:experiment-outlined',
|
|
||||||
'ant-design:experiment-twotone',
|
|
||||||
'ant-design:export-outlined',
|
|
||||||
'ant-design:eye-filled',
|
|
||||||
'ant-design:eye-invisible-filled',
|
|
||||||
'ant-design:eye-invisible-outlined',
|
|
||||||
'ant-design:eye-invisible-twotone',
|
|
||||||
'ant-design:eye-outlined',
|
|
||||||
'ant-design:eye-twotone',
|
|
||||||
'ant-design:facebook-filled',
|
|
||||||
'ant-design:facebook-outlined',
|
|
||||||
'ant-design:fall-outlined',
|
|
||||||
'ant-design:fast-backward-filled',
|
|
||||||
'ant-design:fast-backward-outlined',
|
|
||||||
'ant-design:fast-forward-filled',
|
|
||||||
'ant-design:fast-forward-outlined',
|
|
||||||
'ant-design:field-binary-outlined',
|
|
||||||
'ant-design:field-number-outlined',
|
|
||||||
'ant-design:field-string-outlined',
|
|
||||||
'ant-design:field-time-outlined',
|
|
||||||
'ant-design:file-add-filled',
|
|
||||||
'ant-design:file-add-outlined',
|
|
||||||
'ant-design:file-add-twotone',
|
|
||||||
'ant-design:file-done-outlined',
|
|
||||||
'ant-design:file-excel-filled',
|
|
||||||
'ant-design:file-excel-outlined',
|
|
||||||
'ant-design:file-excel-twotone',
|
|
||||||
'ant-design:file-exclamation-filled',
|
|
||||||
'ant-design:file-exclamation-outlined',
|
|
||||||
'ant-design:file-exclamation-twotone',
|
|
||||||
'ant-design:file-filled',
|
|
||||||
'ant-design:file-gif-outlined',
|
|
||||||
'ant-design:file-image-filled',
|
|
||||||
'ant-design:file-image-outlined',
|
|
||||||
'ant-design:file-image-twotone',
|
|
||||||
'ant-design:file-jpg-outlined',
|
|
||||||
'ant-design:file-markdown-filled',
|
|
||||||
'ant-design:file-markdown-outlined',
|
|
||||||
'ant-design:file-markdown-twotone',
|
|
||||||
'ant-design:file-outlined',
|
|
||||||
'ant-design:file-pdf-filled',
|
|
||||||
'ant-design:file-pdf-outlined',
|
|
||||||
'ant-design:file-pdf-twotone',
|
|
||||||
'ant-design:file-ppt-filled',
|
|
||||||
'ant-design:file-ppt-outlined',
|
|
||||||
'ant-design:file-ppt-twotone',
|
|
||||||
'ant-design:file-protect-outlined',
|
|
||||||
'ant-design:file-search-outlined',
|
|
||||||
'ant-design:file-sync-outlined',
|
|
||||||
'ant-design:file-text-filled',
|
|
||||||
'ant-design:file-text-outlined',
|
|
||||||
'ant-design:file-text-twotone',
|
|
||||||
'ant-design:file-twotone',
|
|
||||||
'ant-design:file-unknown-filled',
|
|
||||||
'ant-design:file-unknown-outlined',
|
|
||||||
'ant-design:file-unknown-twotone',
|
|
||||||
'ant-design:file-word-filled',
|
|
||||||
'ant-design:file-word-outlined',
|
|
||||||
'ant-design:file-word-twotone',
|
|
||||||
'ant-design:file-zip-filled',
|
|
||||||
'ant-design:file-zip-outlined',
|
|
||||||
'ant-design:file-zip-twotone',
|
|
||||||
'ant-design:filter-filled',
|
|
||||||
'ant-design:filter-outlined',
|
|
||||||
'ant-design:filter-twotone',
|
|
||||||
'ant-design:fire-filled',
|
|
||||||
'ant-design:fire-outlined',
|
|
||||||
'ant-design:fire-twotone',
|
|
||||||
'ant-design:flag-filled',
|
|
||||||
'ant-design:flag-outlined',
|
|
||||||
'ant-design:flag-twotone',
|
|
||||||
'ant-design:folder-add-filled',
|
|
||||||
'ant-design:folder-add-outlined',
|
|
||||||
'ant-design:folder-add-twotone',
|
|
||||||
'ant-design:folder-filled',
|
|
||||||
'ant-design:folder-open-filled',
|
|
||||||
'ant-design:folder-open-outlined',
|
|
||||||
'ant-design:folder-open-twotone',
|
|
||||||
'ant-design:folder-outlined',
|
|
||||||
'ant-design:folder-twotone',
|
|
||||||
'ant-design:folder-view-outlined',
|
|
||||||
'ant-design:font-colors-outlined',
|
|
||||||
'ant-design:font-size-outlined',
|
|
||||||
'ant-design:fork-outlined',
|
|
||||||
'ant-design:form-outlined',
|
|
||||||
'ant-design:format-painter-filled',
|
|
||||||
'ant-design:format-painter-outlined',
|
|
||||||
'ant-design:forward-filled',
|
|
||||||
'ant-design:forward-outlined',
|
|
||||||
'ant-design:frown-filled',
|
|
||||||
'ant-design:frown-outlined',
|
|
||||||
'ant-design:frown-twotone',
|
|
||||||
'ant-design:fullscreen-exit-outlined',
|
|
||||||
'ant-design:fullscreen-outlined',
|
|
||||||
'ant-design:function-outlined',
|
|
||||||
'ant-design:fund-filled',
|
|
||||||
'ant-design:fund-outlined',
|
|
||||||
'ant-design:fund-projection-screen-outlined',
|
|
||||||
'ant-design:fund-twotone',
|
|
||||||
'ant-design:fund-view-outlined',
|
|
||||||
'ant-design:funnel-plot-filled',
|
|
||||||
'ant-design:funnel-plot-outlined',
|
|
||||||
'ant-design:funnel-plot-twotone',
|
|
||||||
'ant-design:gateway-outlined',
|
|
||||||
'ant-design:gif-outlined',
|
|
||||||
'ant-design:gift-filled',
|
|
||||||
'ant-design:gift-outlined',
|
|
||||||
'ant-design:gift-twotone',
|
|
||||||
'ant-design:github-filled',
|
|
||||||
'ant-design:github-outlined',
|
|
||||||
'ant-design:gitlab-filled',
|
|
||||||
'ant-design:gitlab-outlined',
|
|
||||||
'ant-design:global-outlined',
|
|
||||||
'ant-design:gold-filled',
|
|
||||||
'ant-design:gold-outlined',
|
|
||||||
'ant-design:gold-twotone',
|
|
||||||
'ant-design:golden-filled',
|
|
||||||
'ant-design:google-circle-filled',
|
|
||||||
'ant-design:google-outlined',
|
|
||||||
'ant-design:google-plus-circle-filled',
|
|
||||||
'ant-design:google-plus-outlined',
|
|
||||||
'ant-design:google-plus-square-filled',
|
|
||||||
'ant-design:google-square-filled',
|
|
||||||
'ant-design:group-outlined',
|
|
||||||
'ant-design:hdd-filled',
|
|
||||||
'ant-design:hdd-outlined',
|
|
||||||
'ant-design:hdd-twotone',
|
|
||||||
'ant-design:heart-filled',
|
|
||||||
'ant-design:heart-outlined',
|
|
||||||
'ant-design:heart-twotone',
|
|
||||||
'ant-design:heat-map-outlined',
|
|
||||||
'ant-design:highlight-filled',
|
|
||||||
'ant-design:highlight-outlined',
|
|
||||||
'ant-design:highlight-twotone',
|
|
||||||
'ant-design:history-outlined',
|
|
||||||
'ant-design:holder-outlined',
|
|
||||||
'ant-design:home-filled',
|
|
||||||
'ant-design:home-outlined',
|
|
||||||
'ant-design:home-twotone',
|
|
||||||
'ant-design:hourglass-filled',
|
|
||||||
'ant-design:hourglass-outlined',
|
|
||||||
'ant-design:hourglass-twotone',
|
|
||||||
'ant-design:html5-filled',
|
|
||||||
'ant-design:html5-outlined',
|
|
||||||
'ant-design:html5-twotone',
|
|
||||||
'ant-design:idcard-filled',
|
|
||||||
'ant-design:idcard-outlined',
|
|
||||||
'ant-design:idcard-twotone',
|
|
||||||
'ant-design:ie-circle-filled',
|
|
||||||
'ant-design:ie-outlined',
|
|
||||||
'ant-design:ie-square-filled',
|
|
||||||
'ant-design:import-outlined',
|
|
||||||
'ant-design:inbox-outlined',
|
|
||||||
'ant-design:info-circle-filled',
|
|
||||||
'ant-design:info-circle-outlined',
|
|
||||||
'ant-design:info-circle-twotone',
|
|
||||||
'ant-design:info-outlined',
|
|
||||||
'ant-design:insert-row-above-outlined',
|
|
||||||
'ant-design:insert-row-below-outlined',
|
|
||||||
'ant-design:insert-row-left-outlined',
|
|
||||||
'ant-design:insert-row-right-outlined',
|
|
||||||
'ant-design:instagram-filled',
|
|
||||||
'ant-design:instagram-outlined',
|
|
||||||
'ant-design:insurance-filled',
|
|
||||||
'ant-design:insurance-outlined',
|
|
||||||
'ant-design:insurance-twotone',
|
|
||||||
'ant-design:interaction-filled',
|
|
||||||
'ant-design:interaction-outlined',
|
|
||||||
'ant-design:interaction-twotone',
|
|
||||||
'ant-design:issues-close-outlined',
|
|
||||||
'ant-design:italic-outlined',
|
|
||||||
'ant-design:key-outlined',
|
|
||||||
'ant-design:laptop-outlined',
|
|
||||||
'ant-design:layout-filled',
|
|
||||||
'ant-design:layout-outlined',
|
|
||||||
'ant-design:layout-twotone',
|
|
||||||
'ant-design:left-circle-filled',
|
|
||||||
'ant-design:left-circle-outlined',
|
|
||||||
'ant-design:left-circle-twotone',
|
|
||||||
'ant-design:left-outlined',
|
|
||||||
'ant-design:left-square-filled',
|
|
||||||
'ant-design:left-square-outlined',
|
|
||||||
'ant-design:left-square-twotone',
|
|
||||||
'ant-design:like-filled',
|
|
||||||
'ant-design:like-outlined',
|
|
||||||
'ant-design:like-twotone',
|
|
||||||
'ant-design:line-chart-outlined',
|
|
||||||
'ant-design:line-height-outlined',
|
|
||||||
'ant-design:line-outlined',
|
|
||||||
'ant-design:link-outlined',
|
|
||||||
'ant-design:linkedin-filled',
|
|
||||||
'ant-design:linkedin-outlined',
|
|
||||||
'ant-design:loading-3-quarters-outlined',
|
|
||||||
'ant-design:loading-outlined',
|
|
||||||
'ant-design:lock-filled',
|
|
||||||
'ant-design:lock-outlined',
|
|
||||||
'ant-design:lock-twotone',
|
|
||||||
'ant-design:login-outlined',
|
|
||||||
'ant-design:logout-outlined',
|
|
||||||
'ant-design:mac-command-filled',
|
|
||||||
'ant-design:mac-command-outlined',
|
|
||||||
'ant-design:mail-filled',
|
|
||||||
'ant-design:mail-outlined',
|
|
||||||
'ant-design:mail-twotone',
|
|
||||||
'ant-design:man-outlined',
|
|
||||||
'ant-design:medicine-box-filled',
|
|
||||||
'ant-design:medicine-box-outlined',
|
|
||||||
'ant-design:medicine-box-twotone',
|
|
||||||
'ant-design:medium-circle-filled',
|
|
||||||
'ant-design:medium-outlined',
|
|
||||||
'ant-design:medium-square-filled',
|
|
||||||
'ant-design:medium-workmark-outlined',
|
|
||||||
'ant-design:meh-filled',
|
|
||||||
'ant-design:meh-outlined',
|
|
||||||
'ant-design:meh-twotone',
|
|
||||||
'ant-design:menu-fold-outlined',
|
|
||||||
'ant-design:menu-outlined',
|
|
||||||
'ant-design:menu-unfold-outlined',
|
|
||||||
'ant-design:merge-cells-outlined',
|
|
||||||
'ant-design:message-filled',
|
|
||||||
'ant-design:message-outlined',
|
|
||||||
'ant-design:message-twotone',
|
|
||||||
'ant-design:minus-circle-filled',
|
|
||||||
'ant-design:minus-circle-outlined',
|
|
||||||
'ant-design:minus-circle-twotone',
|
|
||||||
'ant-design:minus-outlined',
|
|
||||||
'ant-design:minus-square-filled',
|
|
||||||
'ant-design:minus-square-outlined',
|
|
||||||
'ant-design:minus-square-twotone',
|
|
||||||
'ant-design:mobile-filled',
|
|
||||||
'ant-design:mobile-outlined',
|
|
||||||
'ant-design:mobile-twotone',
|
|
||||||
'ant-design:money-collect-filled',
|
|
||||||
'ant-design:money-collect-outlined',
|
|
||||||
'ant-design:money-collect-twotone',
|
|
||||||
'ant-design:monitor-outlined',
|
|
||||||
'ant-design:more-outlined',
|
|
||||||
'ant-design:node-collapse-outlined',
|
|
||||||
'ant-design:node-expand-outlined',
|
|
||||||
'ant-design:node-index-outlined',
|
|
||||||
'ant-design:notification-filled',
|
|
||||||
'ant-design:notification-outlined',
|
|
||||||
'ant-design:notification-twotone',
|
|
||||||
'ant-design:number-outlined',
|
|
||||||
'ant-design:one-to-one-outlined',
|
|
||||||
'ant-design:ordered-list-outlined',
|
|
||||||
'ant-design:paper-clip-outlined',
|
|
||||||
'ant-design:partition-outlined',
|
|
||||||
'ant-design:pause-circle-filled',
|
|
||||||
'ant-design:pause-circle-outlined',
|
|
||||||
'ant-design:pause-circle-twotone',
|
|
||||||
'ant-design:pause-outlined',
|
|
||||||
'ant-design:pay-circle-filled',
|
|
||||||
'ant-design:pay-circle-outlined',
|
|
||||||
'ant-design:percentage-outlined',
|
|
||||||
'ant-design:phone-filled',
|
|
||||||
'ant-design:phone-outlined',
|
|
||||||
'ant-design:phone-twotone',
|
|
||||||
'ant-design:pic-center-outlined',
|
|
||||||
'ant-design:pic-left-outlined',
|
|
||||||
'ant-design:pic-right-outlined',
|
|
||||||
'ant-design:picture-filled',
|
|
||||||
'ant-design:picture-outlined',
|
|
||||||
'ant-design:picture-twotone',
|
|
||||||
'ant-design:pie-chart-filled',
|
|
||||||
'ant-design:pie-chart-outlined',
|
|
||||||
'ant-design:pie-chart-twotone',
|
|
||||||
'ant-design:play-circle-filled',
|
|
||||||
'ant-design:play-circle-outlined',
|
|
||||||
'ant-design:play-circle-twotone',
|
|
||||||
'ant-design:play-square-filled',
|
|
||||||
'ant-design:play-square-outlined',
|
|
||||||
'ant-design:play-square-twotone',
|
|
||||||
'ant-design:plus-circle-filled',
|
|
||||||
'ant-design:plus-circle-outlined',
|
|
||||||
'ant-design:plus-circle-twotone',
|
|
||||||
'ant-design:plus-outlined',
|
|
||||||
'ant-design:plus-square-filled',
|
|
||||||
'ant-design:plus-square-outlined',
|
|
||||||
'ant-design:plus-square-twotone',
|
|
||||||
'ant-design:pound-circle-filled',
|
|
||||||
'ant-design:pound-circle-outlined',
|
|
||||||
'ant-design:pound-circle-twotone',
|
|
||||||
'ant-design:pound-outlined',
|
|
||||||
'ant-design:poweroff-outlined',
|
|
||||||
'ant-design:printer-filled',
|
|
||||||
'ant-design:printer-outlined',
|
|
||||||
'ant-design:printer-twotone',
|
|
||||||
'ant-design:profile-filled',
|
|
||||||
'ant-design:profile-outlined',
|
|
||||||
'ant-design:profile-twotone',
|
|
||||||
'ant-design:project-filled',
|
|
||||||
'ant-design:project-outlined',
|
|
||||||
'ant-design:project-twotone',
|
|
||||||
'ant-design:property-safety-filled',
|
|
||||||
'ant-design:property-safety-outlined',
|
|
||||||
'ant-design:property-safety-twotone',
|
|
||||||
'ant-design:pull-request-outlined',
|
|
||||||
'ant-design:pushpin-filled',
|
|
||||||
'ant-design:pushpin-outlined',
|
|
||||||
'ant-design:pushpin-twotone',
|
|
||||||
'ant-design:qq-circle-filled',
|
|
||||||
'ant-design:qq-outlined',
|
|
||||||
'ant-design:qq-square-filled',
|
|
||||||
'ant-design:qrcode-outlined',
|
|
||||||
'ant-design:question-circle-filled',
|
|
||||||
'ant-design:question-circle-outlined',
|
|
||||||
'ant-design:question-circle-twotone',
|
|
||||||
'ant-design:question-outlined',
|
|
||||||
'ant-design:radar-chart-outlined',
|
|
||||||
'ant-design:radius-bottomleft-outlined',
|
|
||||||
'ant-design:radius-bottomright-outlined',
|
|
||||||
'ant-design:radius-setting-outlined',
|
|
||||||
'ant-design:radius-upleft-outlined',
|
|
||||||
'ant-design:radius-upright-outlined',
|
|
||||||
'ant-design:read-filled',
|
|
||||||
'ant-design:read-outlined',
|
|
||||||
'ant-design:reconciliation-filled',
|
|
||||||
'ant-design:reconciliation-outlined',
|
|
||||||
'ant-design:reconciliation-twotone',
|
|
||||||
'ant-design:red-envelope-filled',
|
|
||||||
'ant-design:red-envelope-outlined',
|
|
||||||
'ant-design:red-envelope-twotone',
|
|
||||||
'ant-design:reddit-circle-filled',
|
|
||||||
'ant-design:reddit-outlined',
|
|
||||||
'ant-design:reddit-square-filled',
|
|
||||||
'ant-design:redo-outlined',
|
|
||||||
'ant-design:reload-outlined',
|
|
||||||
'ant-design:rest-filled',
|
|
||||||
'ant-design:rest-outlined',
|
|
||||||
'ant-design:rest-twotone',
|
|
||||||
'ant-design:retweet-outlined',
|
|
||||||
'ant-design:right-circle-filled',
|
|
||||||
'ant-design:right-circle-outlined',
|
|
||||||
'ant-design:right-circle-twotone',
|
|
||||||
'ant-design:right-outlined',
|
|
||||||
'ant-design:right-square-filled',
|
|
||||||
'ant-design:right-square-outlined',
|
|
||||||
'ant-design:right-square-twotone',
|
|
||||||
'ant-design:rise-outlined',
|
|
||||||
'ant-design:robot-filled',
|
|
||||||
'ant-design:robot-outlined',
|
|
||||||
'ant-design:rocket-filled',
|
|
||||||
'ant-design:rocket-outlined',
|
|
||||||
'ant-design:rocket-twotone',
|
|
||||||
'ant-design:rollback-outlined',
|
|
||||||
'ant-design:rotate-left-outlined',
|
|
||||||
'ant-design:rotate-right-outlined',
|
|
||||||
'ant-design:safety-certificate-filled',
|
|
||||||
'ant-design:safety-certificate-outlined',
|
|
||||||
'ant-design:safety-certificate-twotone',
|
|
||||||
'ant-design:safety-outlined',
|
|
||||||
'ant-design:save-filled',
|
|
||||||
'ant-design:save-outlined',
|
|
||||||
'ant-design:save-twotone',
|
|
||||||
'ant-design:scan-outlined',
|
|
||||||
'ant-design:schedule-filled',
|
|
||||||
'ant-design:schedule-outlined',
|
|
||||||
'ant-design:schedule-twotone',
|
|
||||||
'ant-design:scissor-outlined',
|
|
||||||
'ant-design:search-outlined',
|
|
||||||
'ant-design:security-scan-filled',
|
|
||||||
'ant-design:security-scan-outlined',
|
|
||||||
'ant-design:security-scan-twotone',
|
|
||||||
'ant-design:select-outlined',
|
|
||||||
'ant-design:send-outlined',
|
|
||||||
'ant-design:setting-filled',
|
|
||||||
'ant-design:setting-outlined',
|
|
||||||
'ant-design:setting-twotone',
|
|
||||||
'ant-design:shake-outlined',
|
|
||||||
'ant-design:share-alt-outlined',
|
|
||||||
'ant-design:shop-filled',
|
|
||||||
'ant-design:shop-outlined',
|
|
||||||
'ant-design:shop-twotone',
|
|
||||||
'ant-design:shopping-cart-outlined',
|
|
||||||
'ant-design:shopping-filled',
|
|
||||||
'ant-design:shopping-outlined',
|
|
||||||
'ant-design:shopping-twotone',
|
|
||||||
'ant-design:shrink-outlined',
|
|
||||||
'ant-design:signal-filled',
|
|
||||||
'ant-design:sisternode-outlined',
|
|
||||||
'ant-design:sketch-circle-filled',
|
|
||||||
'ant-design:sketch-outlined',
|
|
||||||
'ant-design:sketch-square-filled',
|
|
||||||
'ant-design:skin-filled',
|
|
||||||
'ant-design:skin-outlined',
|
|
||||||
'ant-design:skin-twotone',
|
|
||||||
'ant-design:skype-filled',
|
|
||||||
'ant-design:skype-outlined',
|
|
||||||
'ant-design:slack-circle-filled',
|
|
||||||
'ant-design:slack-outlined',
|
|
||||||
'ant-design:slack-square-filled',
|
|
||||||
'ant-design:slack-square-outlined',
|
|
||||||
'ant-design:sliders-filled',
|
|
||||||
'ant-design:sliders-outlined',
|
|
||||||
'ant-design:sliders-twotone',
|
|
||||||
'ant-design:small-dash-outlined',
|
|
||||||
'ant-design:smile-filled',
|
|
||||||
'ant-design:smile-outlined',
|
|
||||||
'ant-design:smile-twotone',
|
|
||||||
'ant-design:snippets-filled',
|
|
||||||
'ant-design:snippets-outlined',
|
|
||||||
'ant-design:snippets-twotone',
|
|
||||||
'ant-design:solution-outlined',
|
|
||||||
'ant-design:sort-ascending-outlined',
|
|
||||||
'ant-design:sort-descending-outlined',
|
|
||||||
'ant-design:sound-filled',
|
|
||||||
'ant-design:sound-outlined',
|
|
||||||
'ant-design:sound-twotone',
|
|
||||||
'ant-design:split-cells-outlined',
|
|
||||||
'ant-design:star-filled',
|
|
||||||
'ant-design:star-outlined',
|
|
||||||
'ant-design:star-twotone',
|
|
||||||
'ant-design:step-backward-filled',
|
|
||||||
'ant-design:step-backward-outlined',
|
|
||||||
'ant-design:step-forward-filled',
|
|
||||||
'ant-design:step-forward-outlined',
|
|
||||||
'ant-design:stock-outlined',
|
|
||||||
'ant-design:stop-filled',
|
|
||||||
'ant-design:stop-outlined',
|
|
||||||
'ant-design:stop-twotone',
|
|
||||||
'ant-design:strikethrough-outlined',
|
|
||||||
'ant-design:subnode-outlined',
|
|
||||||
'ant-design:swap-left-outlined',
|
|
||||||
'ant-design:swap-outlined',
|
|
||||||
'ant-design:swap-right-outlined',
|
|
||||||
'ant-design:switcher-filled',
|
|
||||||
'ant-design:switcher-outlined',
|
|
||||||
'ant-design:switcher-twotone',
|
|
||||||
'ant-design:sync-outlined',
|
|
||||||
'ant-design:table-outlined',
|
|
||||||
'ant-design:tablet-filled',
|
|
||||||
'ant-design:tablet-outlined',
|
|
||||||
'ant-design:tablet-twotone',
|
|
||||||
'ant-design:tag-filled',
|
|
||||||
'ant-design:tag-outlined',
|
|
||||||
'ant-design:tag-twotone',
|
|
||||||
'ant-design:tags-filled',
|
|
||||||
'ant-design:tags-outlined',
|
|
||||||
'ant-design:tags-twotone',
|
|
||||||
'ant-design:taobao-circle-filled',
|
|
||||||
'ant-design:taobao-circle-outlined',
|
|
||||||
'ant-design:taobao-outlined',
|
|
||||||
'ant-design:taobao-square-filled',
|
|
||||||
'ant-design:team-outlined',
|
|
||||||
'ant-design:thunderbolt-filled',
|
|
||||||
'ant-design:thunderbolt-outlined',
|
|
||||||
'ant-design:thunderbolt-twotone',
|
|
||||||
'ant-design:to-top-outlined',
|
|
||||||
'ant-design:tool-filled',
|
|
||||||
'ant-design:tool-outlined',
|
|
||||||
'ant-design:tool-twotone',
|
|
||||||
'ant-design:trademark-circle-filled',
|
|
||||||
'ant-design:trademark-circle-outlined',
|
|
||||||
'ant-design:trademark-circle-twotone',
|
|
||||||
'ant-design:trademark-outlined',
|
|
||||||
'ant-design:transaction-outlined',
|
|
||||||
'ant-design:translation-outlined',
|
|
||||||
'ant-design:trophy-filled',
|
|
||||||
'ant-design:trophy-outlined',
|
|
||||||
'ant-design:trophy-twotone',
|
|
||||||
'ant-design:twitter-circle-filled',
|
|
||||||
'ant-design:twitter-outlined',
|
|
||||||
'ant-design:twitter-square-filled',
|
|
||||||
'ant-design:underline-outlined',
|
|
||||||
'ant-design:undo-outlined',
|
|
||||||
'ant-design:ungroup-outlined',
|
|
||||||
'ant-design:unlock-filled',
|
|
||||||
'ant-design:unlock-outlined',
|
|
||||||
'ant-design:unlock-twotone',
|
|
||||||
'ant-design:unordered-list-outlined',
|
|
||||||
'ant-design:up-circle-filled',
|
|
||||||
'ant-design:up-circle-outlined',
|
|
||||||
'ant-design:up-circle-twotone',
|
|
||||||
'ant-design:up-outlined',
|
|
||||||
'ant-design:up-square-filled',
|
|
||||||
'ant-design:up-square-outlined',
|
|
||||||
'ant-design:up-square-twotone',
|
|
||||||
'ant-design:upload-outlined',
|
|
||||||
'ant-design:usb-filled',
|
|
||||||
'ant-design:usb-outlined',
|
|
||||||
'ant-design:usb-twotone',
|
|
||||||
'ant-design:user-add-outlined',
|
|
||||||
'ant-design:user-delete-outlined',
|
|
||||||
'ant-design:user-outlined',
|
|
||||||
'ant-design:user-switch-outlined',
|
|
||||||
'ant-design:usergroup-add-outlined',
|
|
||||||
'ant-design:usergroup-delete-outlined',
|
|
||||||
'ant-design:verified-outlined',
|
|
||||||
'ant-design:vertical-align-bottom-outlined',
|
|
||||||
'ant-design:vertical-align-middle-outlined',
|
|
||||||
'ant-design:vertical-align-top-outlined',
|
|
||||||
'ant-design:vertical-left-outlined',
|
|
||||||
'ant-design:vertical-right-outlined',
|
|
||||||
'ant-design:video-camera-add-outlined',
|
|
||||||
'ant-design:video-camera-filled',
|
|
||||||
'ant-design:video-camera-outlined',
|
|
||||||
'ant-design:video-camera-twotone',
|
|
||||||
'ant-design:wallet-filled',
|
|
||||||
'ant-design:wallet-outlined',
|
|
||||||
'ant-design:wallet-twotone',
|
|
||||||
'ant-design:warning-filled',
|
|
||||||
'ant-design:warning-outlined',
|
|
||||||
'ant-design:warning-twotone',
|
|
||||||
'ant-design:wechat-filled',
|
|
||||||
'ant-design:wechat-outlined',
|
|
||||||
'ant-design:weibo-circle-filled',
|
|
||||||
'ant-design:weibo-circle-outlined',
|
|
||||||
'ant-design:weibo-outlined',
|
|
||||||
'ant-design:weibo-square-filled',
|
|
||||||
'ant-design:weibo-square-outlined',
|
|
||||||
'ant-design:whats-app-outlined',
|
|
||||||
'ant-design:wifi-outlined',
|
|
||||||
'ant-design:windows-filled',
|
|
||||||
'ant-design:windows-outlined',
|
|
||||||
'ant-design:woman-outlined',
|
|
||||||
'ant-design:yahoo-filled',
|
|
||||||
'ant-design:yahoo-outlined',
|
|
||||||
'ant-design:youtube-filled',
|
|
||||||
'ant-design:youtube-outlined',
|
|
||||||
'ant-design:yuque-filled',
|
|
||||||
'ant-design:yuque-outlined',
|
|
||||||
'ant-design:zhihu-circle-filled',
|
|
||||||
'ant-design:zhihu-outlined',
|
|
||||||
'ant-design:zhihu-square-filled',
|
|
||||||
'ant-design:zoom-in-outlined',
|
|
||||||
'ant-design:zoom-out-outlined'
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
export default {
|
|
||||||
name: 'Element Plus',
|
|
||||||
prefix: 'ep',
|
|
||||||
icons: [
|
|
||||||
'ep:add-location',
|
|
||||||
'ep:aim',
|
|
||||||
'ep:alarm-clock',
|
|
||||||
'ep:apple',
|
|
||||||
'ep:arrow-down',
|
|
||||||
'ep:arrow-down-bold',
|
|
||||||
'ep:arrow-left',
|
|
||||||
'ep:arrow-left-bold',
|
|
||||||
'ep:arrow-right',
|
|
||||||
'ep:arrow-right-bold',
|
|
||||||
'ep:arrow-up',
|
|
||||||
'ep:arrow-up-bold',
|
|
||||||
'ep:avatar',
|
|
||||||
'ep:back',
|
|
||||||
'ep:baseball',
|
|
||||||
'ep:basketball',
|
|
||||||
'ep:bell',
|
|
||||||
'ep:bell-filled',
|
|
||||||
'ep:bicycle',
|
|
||||||
'ep:bottom',
|
|
||||||
'ep:bottom-left',
|
|
||||||
'ep:bottom-right',
|
|
||||||
'ep:bowl',
|
|
||||||
'ep:box',
|
|
||||||
'ep:briefcase',
|
|
||||||
'ep:brush',
|
|
||||||
'ep:brush-filled',
|
|
||||||
'ep:burger',
|
|
||||||
'ep:calendar',
|
|
||||||
'ep:camera',
|
|
||||||
'ep:camera-filled',
|
|
||||||
'ep:caret-bottom',
|
|
||||||
'ep:caret-left',
|
|
||||||
'ep:caret-right',
|
|
||||||
'ep:caret-top',
|
|
||||||
'ep:cellphone',
|
|
||||||
'ep:chat-dot-round',
|
|
||||||
'ep:chat-dot-square',
|
|
||||||
'ep:chat-line-round',
|
|
||||||
'ep:chat-line-square',
|
|
||||||
'ep:chat-round',
|
|
||||||
'ep:chat-square',
|
|
||||||
'ep:check',
|
|
||||||
'ep:checked',
|
|
||||||
'ep:cherry',
|
|
||||||
'ep:chicken',
|
|
||||||
'ep:chrome-filled',
|
|
||||||
'ep:circle-check',
|
|
||||||
'ep:circle-check-filled',
|
|
||||||
'ep:circle-close',
|
|
||||||
'ep:circle-close-filled',
|
|
||||||
'ep:circle-plus',
|
|
||||||
'ep:circle-plus-filled',
|
|
||||||
'ep:clock',
|
|
||||||
'ep:close',
|
|
||||||
'ep:close-bold',
|
|
||||||
'ep:cloudy',
|
|
||||||
'ep:coffee',
|
|
||||||
'ep:coffee-cup',
|
|
||||||
'ep:coin',
|
|
||||||
'ep:cold-drink',
|
|
||||||
'ep:collection',
|
|
||||||
'ep:collection-tag',
|
|
||||||
'ep:comment',
|
|
||||||
'ep:compass',
|
|
||||||
'ep:connection',
|
|
||||||
'ep:coordinate',
|
|
||||||
'ep:copy-document',
|
|
||||||
'ep:cpu',
|
|
||||||
'ep:credit-card',
|
|
||||||
'ep:crop',
|
|
||||||
'ep:d-arrow-left',
|
|
||||||
'ep:d-arrow-right',
|
|
||||||
'ep:d-caret',
|
|
||||||
'ep:data-analysis',
|
|
||||||
'ep:data-board',
|
|
||||||
'ep:data-line',
|
|
||||||
'ep:delete',
|
|
||||||
'ep:delete-filled',
|
|
||||||
'ep:delete-location',
|
|
||||||
'ep:dessert',
|
|
||||||
'ep:discount',
|
|
||||||
'ep:dish',
|
|
||||||
'ep:dish-dot',
|
|
||||||
'ep:document',
|
|
||||||
'ep:document-add',
|
|
||||||
'ep:document-checked',
|
|
||||||
'ep:document-copy',
|
|
||||||
'ep:document-delete',
|
|
||||||
'ep:document-remove',
|
|
||||||
'ep:download',
|
|
||||||
'ep:drizzling',
|
|
||||||
'ep:edit',
|
|
||||||
'ep:edit-pen',
|
|
||||||
'ep:eleme',
|
|
||||||
'ep:eleme-filled',
|
|
||||||
'ep:element-plus',
|
|
||||||
'ep:expand',
|
|
||||||
'ep:failed',
|
|
||||||
'ep:female',
|
|
||||||
'ep:files',
|
|
||||||
'ep:film',
|
|
||||||
'ep:filter',
|
|
||||||
'ep:finished',
|
|
||||||
'ep:first-aid-kit',
|
|
||||||
'ep:flag',
|
|
||||||
'ep:fold',
|
|
||||||
'ep:folder',
|
|
||||||
'ep:folder-add',
|
|
||||||
'ep:folder-checked',
|
|
||||||
'ep:folder-delete',
|
|
||||||
'ep:folder-opened',
|
|
||||||
'ep:folder-remove',
|
|
||||||
'ep:food',
|
|
||||||
'ep:football',
|
|
||||||
'ep:fork-spoon',
|
|
||||||
'ep:fries',
|
|
||||||
'ep:full-screen',
|
|
||||||
'ep:goblet',
|
|
||||||
'ep:goblet-full',
|
|
||||||
'ep:goblet-square',
|
|
||||||
'ep:goblet-square-full',
|
|
||||||
'ep:gold-medal',
|
|
||||||
'ep:goods',
|
|
||||||
'ep:goods-filled',
|
|
||||||
'ep:grape',
|
|
||||||
'ep:grid',
|
|
||||||
'ep:guide',
|
|
||||||
'ep:handbag',
|
|
||||||
'ep:headset',
|
|
||||||
'ep:help',
|
|
||||||
'ep:help-filled',
|
|
||||||
'ep:hide',
|
|
||||||
'ep:histogram',
|
|
||||||
'ep:home-filled',
|
|
||||||
'ep:hot-water',
|
|
||||||
'ep:house',
|
|
||||||
'ep:ice-cream',
|
|
||||||
'ep:ice-cream-round',
|
|
||||||
'ep:ice-cream-square',
|
|
||||||
'ep:ice-drink',
|
|
||||||
'ep:ice-tea',
|
|
||||||
'ep:info-filled',
|
|
||||||
'ep:iphone',
|
|
||||||
'ep:key',
|
|
||||||
'ep:knife-fork',
|
|
||||||
'ep:lightning',
|
|
||||||
'ep:link',
|
|
||||||
'ep:list',
|
|
||||||
'ep:loading',
|
|
||||||
'ep:location',
|
|
||||||
'ep:location-filled',
|
|
||||||
'ep:location-information',
|
|
||||||
'ep:lock',
|
|
||||||
'ep:lollipop',
|
|
||||||
'ep:magic-stick',
|
|
||||||
'ep:magnet',
|
|
||||||
'ep:male',
|
|
||||||
'ep:management',
|
|
||||||
'ep:map-location',
|
|
||||||
'ep:medal',
|
|
||||||
'ep:memo',
|
|
||||||
'ep:menu',
|
|
||||||
'ep:message',
|
|
||||||
'ep:message-box',
|
|
||||||
'ep:mic',
|
|
||||||
'ep:microphone',
|
|
||||||
'ep:milk-tea',
|
|
||||||
'ep:minus',
|
|
||||||
'ep:money',
|
|
||||||
'ep:monitor',
|
|
||||||
'ep:moon',
|
|
||||||
'ep:moon-night',
|
|
||||||
'ep:more',
|
|
||||||
'ep:more-filled',
|
|
||||||
'ep:mostly-cloudy',
|
|
||||||
'ep:mouse',
|
|
||||||
'ep:mug',
|
|
||||||
'ep:mute',
|
|
||||||
'ep:mute-notification',
|
|
||||||
'ep:no-smoking',
|
|
||||||
'ep:notebook',
|
|
||||||
'ep:notification',
|
|
||||||
'ep:odometer',
|
|
||||||
'ep:office-building',
|
|
||||||
'ep:open',
|
|
||||||
'ep:operation',
|
|
||||||
'ep:opportunity',
|
|
||||||
'ep:orange',
|
|
||||||
'ep:paperclip',
|
|
||||||
'ep:partly-cloudy',
|
|
||||||
'ep:pear',
|
|
||||||
'ep:phone',
|
|
||||||
'ep:phone-filled',
|
|
||||||
'ep:picture',
|
|
||||||
'ep:picture-filled',
|
|
||||||
'ep:picture-rounded',
|
|
||||||
'ep:pie-chart',
|
|
||||||
'ep:place',
|
|
||||||
'ep:platform',
|
|
||||||
'ep:plus',
|
|
||||||
'ep:pointer',
|
|
||||||
'ep:position',
|
|
||||||
'ep:postcard',
|
|
||||||
'ep:pouring',
|
|
||||||
'ep:present',
|
|
||||||
'ep:price-tag',
|
|
||||||
'ep:printer',
|
|
||||||
'ep:promotion',
|
|
||||||
'ep:quartz-watch',
|
|
||||||
'ep:question-filled',
|
|
||||||
'ep:rank',
|
|
||||||
'ep:reading',
|
|
||||||
'ep:reading-lamp',
|
|
||||||
'ep:refresh',
|
|
||||||
'ep:refresh-left',
|
|
||||||
'ep:refresh-right',
|
|
||||||
'ep:refrigerator',
|
|
||||||
'ep:remove',
|
|
||||||
'ep:remove-filled',
|
|
||||||
'ep:right',
|
|
||||||
'ep:scale-to-original',
|
|
||||||
'ep:school',
|
|
||||||
'ep:scissor',
|
|
||||||
'ep:search',
|
|
||||||
'ep:select',
|
|
||||||
'ep:sell',
|
|
||||||
'ep:semi-select',
|
|
||||||
'ep:service',
|
|
||||||
'ep:set-up',
|
|
||||||
'ep:setting',
|
|
||||||
'ep:share',
|
|
||||||
'ep:ship',
|
|
||||||
'ep:shop',
|
|
||||||
'ep:shopping-bag',
|
|
||||||
'ep:shopping-cart',
|
|
||||||
'ep:shopping-cart-full',
|
|
||||||
'ep:shopping-trolley',
|
|
||||||
'ep:smoking',
|
|
||||||
'ep:soccer',
|
|
||||||
'ep:sold-out',
|
|
||||||
'ep:sort',
|
|
||||||
'ep:sort-down',
|
|
||||||
'ep:sort-up',
|
|
||||||
'ep:stamp',
|
|
||||||
'ep:star',
|
|
||||||
'ep:star-filled',
|
|
||||||
'ep:stopwatch',
|
|
||||||
'ep:success-filled',
|
|
||||||
'ep:sugar',
|
|
||||||
'ep:suitcase',
|
|
||||||
'ep:suitcase-line',
|
|
||||||
'ep:sunny',
|
|
||||||
'ep:sunrise',
|
|
||||||
'ep:sunset',
|
|
||||||
'ep:switch',
|
|
||||||
'ep:switch-button',
|
|
||||||
'ep:switch-filled',
|
|
||||||
'ep:takeaway-box',
|
|
||||||
'ep:ticket',
|
|
||||||
'ep:tickets',
|
|
||||||
'ep:timer',
|
|
||||||
'ep:toilet-paper',
|
|
||||||
'ep:tools',
|
|
||||||
'ep:top',
|
|
||||||
'ep:top-left',
|
|
||||||
'ep:top-right',
|
|
||||||
'ep:trend-charts',
|
|
||||||
'ep:trophy',
|
|
||||||
'ep:trophy-base',
|
|
||||||
'ep:turn-off',
|
|
||||||
'ep:umbrella',
|
|
||||||
'ep:unlock',
|
|
||||||
'ep:upload',
|
|
||||||
'ep:upload-filled',
|
|
||||||
'ep:user',
|
|
||||||
'ep:user-filled',
|
|
||||||
'ep:van',
|
|
||||||
'ep:video-camera',
|
|
||||||
'ep:video-camera-filled',
|
|
||||||
'ep:video-pause',
|
|
||||||
'ep:video-play',
|
|
||||||
'ep:view',
|
|
||||||
'ep:wallet',
|
|
||||||
'ep:wallet-filled',
|
|
||||||
'ep:warn-triangle-filled',
|
|
||||||
'ep:warning',
|
|
||||||
'ep:warning-filled',
|
|
||||||
'ep:watch',
|
|
||||||
'ep:watermelon',
|
|
||||||
'ep:wind-power',
|
|
||||||
'ep:zoom-in',
|
|
||||||
'ep:zoom-out'
|
|
||||||
]
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
import ImageCropping from './src/ImageCropping.vue'
|
|
||||||
|
|
||||||
export { ImageCropping }
|
|
@ -1,245 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
|
||||||
import { nextTick, unref, ref, watch, onBeforeUnmount, onMounted, computed } from 'vue'
|
|
||||||
import Cropper from 'cropperjs'
|
|
||||||
import 'cropperjs/dist/cropper.min.css'
|
|
||||||
import { ElDivider, ElUpload, UploadFile, ElMessage, ElTooltip } from 'element-plus'
|
|
||||||
import { useDebounceFn } from '@vueuse/core'
|
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
|
||||||
const prefixCls = getPrefixCls('image-cropping')
|
|
||||||
const props = defineProps({
|
|
||||||
imageUrl: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
cropBoxWidth: {
|
|
||||||
type: Number,
|
|
||||||
default: 200
|
|
||||||
},
|
|
||||||
cropBoxHeight: {
|
|
||||||
type: Number,
|
|
||||||
default: 200
|
|
||||||
},
|
|
||||||
boxWidth: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 425
|
|
||||||
},
|
|
||||||
boxHeight: {
|
|
||||||
type: [Number, String],
|
|
||||||
default: 320
|
|
||||||
},
|
|
||||||
showResult: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
showActions: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const getBase64 = useDebounceFn(() => {
|
|
||||||
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
|
|
||||||
}, 80)
|
|
||||||
const resetCropBox = () => {
|
|
||||||
const containerData = unref(cropperRef)?.getContainerData()
|
|
||||||
unref(cropperRef)?.setCropBoxData({
|
|
||||||
width: props.cropBoxWidth,
|
|
||||||
height: props.cropBoxHeight,
|
|
||||||
left: (containerData?.width || 0) / 2 - 100,
|
|
||||||
top: (containerData?.height || 0) / 2 - 100
|
|
||||||
})
|
|
||||||
imgBase64.value = unref(cropperRef)?.getCroppedCanvas()?.toDataURL() ?? ''
|
|
||||||
}
|
|
||||||
const getBoxStyle = computed(() => {
|
|
||||||
return {
|
|
||||||
width: `${props.boxWidth}px`,
|
|
||||||
height: `${props.boxHeight}px`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const getCropBoxStyle = computed(() => {
|
|
||||||
return {
|
|
||||||
width: `${props.cropBoxWidth}px`,
|
|
||||||
height: `${props.cropBoxHeight}px`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 获取对应的缩小倍数的宽高
|
|
||||||
const getScaleSize = (scale: number) => {
|
|
||||||
return {
|
|
||||||
width: props.cropBoxWidth * scale + 'px',
|
|
||||||
height: props.cropBoxHeight * scale + 'px'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const imgBase64 = ref('')
|
|
||||||
const imgRef = ref<HTMLImageElement>()
|
|
||||||
const cropperRef = ref<Cropper>()
|
|
||||||
const intiCropper = () => {
|
|
||||||
if (!unref(imgRef)) return
|
|
||||||
const imgEl = unref(imgRef)!
|
|
||||||
cropperRef.value = new Cropper(imgEl, {
|
|
||||||
aspectRatio: 1,
|
|
||||||
viewMode: 1,
|
|
||||||
dragMode: 'move',
|
|
||||||
// cropBoxResizable: false,
|
|
||||||
// cropBoxMovable: false,
|
|
||||||
toggleDragModeOnDblclick: false,
|
|
||||||
checkCrossOrigin: false,
|
|
||||||
ready() {
|
|
||||||
resetCropBox()
|
|
||||||
},
|
|
||||||
cropmove() {
|
|
||||||
getBase64()
|
|
||||||
},
|
|
||||||
zoom() {
|
|
||||||
getBase64()
|
|
||||||
},
|
|
||||||
crop() {
|
|
||||||
getBase64()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const uploadChange = (uploadFile: UploadFile) => {
|
|
||||||
// 判断是否是图片
|
|
||||||
if (uploadFile?.raw?.type.indexOf('image') === -1) {
|
|
||||||
ElMessage.error('请上传图片格式的文件')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!uploadFile.raw) return
|
|
||||||
// 获取图片的访问地址
|
|
||||||
const url = URL.createObjectURL(uploadFile.raw)
|
|
||||||
unref(cropperRef)?.replace(url)
|
|
||||||
}
|
|
||||||
const reset = () => {
|
|
||||||
unref(cropperRef)?.reset()
|
|
||||||
}
|
|
||||||
const rotate = (deg: number) => {
|
|
||||||
unref(cropperRef)?.rotate(deg)
|
|
||||||
}
|
|
||||||
const scaleX = ref(1)
|
|
||||||
const scaleY = ref(1)
|
|
||||||
const scale = (type: 'scaleX' | 'scaleY') => {
|
|
||||||
if (type === 'scaleX') {
|
|
||||||
scaleX.value = scaleX.value === 1 ? -1 : 1
|
|
||||||
unref(cropperRef)?.[type](unref(scaleX))
|
|
||||||
} else {
|
|
||||||
scaleY.value = scaleY.value === 1 ? -1 : 1
|
|
||||||
unref(cropperRef)?.[type](unref(scaleY))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const zoom = (num: number) => {
|
|
||||||
unref(cropperRef)?.zoom(num)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
intiCropper()
|
|
||||||
})
|
|
||||||
watch(
|
|
||||||
() => props.imageUrl,
|
|
||||||
async (url) => {
|
|
||||||
if (url) {
|
|
||||||
unref(cropperRef)?.replace(url)
|
|
||||||
await nextTick()
|
|
||||||
resetCropBox()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
unref(cropperRef)?.destroy()
|
|
||||||
})
|
|
||||||
defineExpose({
|
|
||||||
cropperExpose: cropperRef
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:class="{
|
|
||||||
[prefixCls]: true,
|
|
||||||
'flex items-center': showResult
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<div :style="getBoxStyle" class="flex justify-center items-center">
|
|
||||||
<img
|
|
||||||
v-show="imageUrl"
|
|
||||||
ref="imgRef"
|
|
||||||
:src="imageUrl"
|
|
||||||
class="block max-w-full"
|
|
||||||
crossorigin="anonymous"
|
|
||||||
alt=""
|
|
||||||
srcset=""
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div v-if="showActions" class="mt-10px flex items-center">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<ElTooltip content="选择文件" placement="bottom">
|
|
||||||
<ElUpload
|
|
||||||
action="''"
|
|
||||||
accept="image/*"
|
|
||||||
:auto-upload="false"
|
|
||||||
:show-file-list="false"
|
|
||||||
:on-change="uploadChange"
|
|
||||||
>
|
|
||||||
<BaseButton size="small" type="primary" class="mt-2px"
|
|
||||||
><Icon icon="ep:upload-filled"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElUpload>
|
|
||||||
</ElTooltip>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-end flex-1">
|
|
||||||
<ElTooltip content="重置" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="reset"
|
|
||||||
><Icon icon="ep:refresh"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="逆时针旋转" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="rotate(-45)"
|
|
||||||
><Icon icon="ant-design:rotate-left-outlined"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="顺时针旋转" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="rotate(45)"
|
|
||||||
><Icon icon="ant-design:rotate-right-outlined"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="水平翻转" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="scale('scaleX')"
|
|
||||||
><Icon icon="vaadin:arrows-long-h"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="垂直翻转" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="scale('scaleY')"
|
|
||||||
><Icon icon="vaadin:arrows-long-v"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="放大" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="zoom(0.1)"
|
|
||||||
><Icon icon="ant-design:zoom-in-outlined"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
<ElTooltip content="缩小" placement="bottom">
|
|
||||||
<BaseButton size="small" type="primary" @click="zoom(-0.1)"
|
|
||||||
><Icon icon="ant-design:zoom-out-outlined"
|
|
||||||
/></BaseButton>
|
|
||||||
</ElTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="imgBase64 && showResult" class="ml-20px">
|
|
||||||
<div class="flex justify-center items-center">
|
|
||||||
<img :src="imgBase64" class="rounded-[50%]" :style="getCropBoxStyle" />
|
|
||||||
</div>
|
|
||||||
<ElDivider />
|
|
||||||
<div class="flex justify-center items-center">
|
|
||||||
<img :src="imgBase64" class="rounded-[50%]" :style="getScaleSize(0.2)" />
|
|
||||||
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.25)" />
|
|
||||||
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.3)" />
|
|
||||||
<img :src="imgBase64" class="rounded-[50%] ml-20px" :style="getScaleSize(0.35)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -86,7 +86,7 @@ const getPasswordStrength = computed(() => {
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border-color: var(--el-color-white);
|
border-color: var(--el-color-white);
|
||||||
border-style: solid;
|
border-style: solid;
|
||||||
border-width: 0 5px;
|
border-width: 0 5px 0 5px;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,13 @@ watch(
|
|||||||
show.value = true
|
show.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
show.value = !collapse
|
if (!collapse) {
|
||||||
|
setTimeout(() => {
|
||||||
|
show.value = !collapse
|
||||||
|
}, 400)
|
||||||
|
} else {
|
||||||
|
show.value = !collapse
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ export default defineComponent({
|
|||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
default: () => {
|
default: () => {
|
||||||
const { renderMenuItem } = useRenderMenuItem()
|
const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
|
||||||
return renderMenuItem(unref(routers))
|
return renderMenuItem(unref(routers))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -123,10 +123,30 @@ export default defineComponent({
|
|||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
@prefix-cls: ~'@{namespace}-menu';
|
@prefix-cls: ~'@{namespace}-menu';
|
||||||
|
|
||||||
|
// .is-active--after {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 0;
|
||||||
|
// right: 0;
|
||||||
|
// width: 4px;
|
||||||
|
// height: 100%;
|
||||||
|
// background-color: var(--el-color-primary);
|
||||||
|
// content: '';
|
||||||
|
// }
|
||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: width var(--transition-time-02);
|
transition: width var(--transition-time-02);
|
||||||
|
|
||||||
|
// &:after {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 0;
|
||||||
|
// right: 0;
|
||||||
|
// height: 100%;
|
||||||
|
// width: 1px;
|
||||||
|
// background-color: var(--el-border-color);
|
||||||
|
// content: '';
|
||||||
|
// }
|
||||||
|
|
||||||
:deep(.@{elNamespace}-menu) {
|
:deep(.@{elNamespace}-menu) {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
@ -148,6 +168,7 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 设置选中时的高亮背景和高亮颜色
|
// 设置选中时的高亮背景和高亮颜色
|
||||||
|
.@{elNamespace}-sub-menu.is-active,
|
||||||
.@{elNamespace}-menu-item.is-active {
|
.@{elNamespace}-menu-item.is-active {
|
||||||
color: var(--left-menu-text-active-color) !important;
|
color: var(--left-menu-text-active-color) !important;
|
||||||
background-color: var(--left-menu-bg-active-color) !important;
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
@ -159,6 +180,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
.@{elNamespace}-menu-item.is-active {
|
.@{elNamespace}-menu-item.is-active {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
// &:after {
|
||||||
|
// .is-active--after;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置子菜单的背景颜色
|
// 设置子菜单的背景颜色
|
||||||
@ -178,11 +203,16 @@ export default defineComponent({
|
|||||||
& > .is-active > .@{elNamespace}-sub-menu__title {
|
& > .is-active > .@{elNamespace}-sub-menu__title {
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: var(--left-menu-collapse-bg-active-color) !important;
|
background-color: var(--left-menu-collapse-bg-active-color) !important;
|
||||||
|
|
||||||
|
// &:after {
|
||||||
|
// .is-active--after;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 折叠动画的时候,就需要把文字给隐藏掉
|
// 折叠动画的时候,就需要把文字给隐藏掉
|
||||||
:deep(.horizontal-collapse-transition) {
|
:deep(.horizontal-collapse-transition) {
|
||||||
|
// transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;
|
||||||
.@{prefix-cls}__title {
|
.@{prefix-cls}__title {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@ -205,7 +235,7 @@ export default defineComponent({
|
|||||||
.@{elNamespace}-menu-item.is-active {
|
.@{elNamespace}-menu-item.is-active {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,6 +254,16 @@ export default defineComponent({
|
|||||||
<style lang="less">
|
<style lang="less">
|
||||||
@prefix-cls: ~'@{namespace}-menu-popper';
|
@prefix-cls: ~'@{namespace}-menu-popper';
|
||||||
|
|
||||||
|
// .is-active--after {
|
||||||
|
// position: absolute;
|
||||||
|
// top: 0;
|
||||||
|
// right: 0;
|
||||||
|
// width: 4px;
|
||||||
|
// height: 100%;
|
||||||
|
// background-color: var(--el-color-primary);
|
||||||
|
// content: '';
|
||||||
|
// }
|
||||||
|
|
||||||
.@{prefix-cls}--vertical,
|
.@{prefix-cls}--vertical,
|
||||||
.@{prefix-cls}--horizontal {
|
.@{prefix-cls}--horizontal {
|
||||||
// 设置选中时子标题的颜色
|
// 设置选中时子标题的颜色
|
||||||
@ -250,6 +290,10 @@ export default defineComponent({
|
|||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--left-menu-bg-active-color) !important;
|
background-color: var(--left-menu-bg-active-color) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// &:after {
|
||||||
|
// .is-active--after;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -2,49 +2,57 @@ import { ElSubMenu, ElMenuItem } from 'element-plus'
|
|||||||
import { hasOneShowingChild } from '../helper'
|
import { hasOneShowingChild } from '../helper'
|
||||||
import { isUrl } from '@/utils/is'
|
import { isUrl } from '@/utils/is'
|
||||||
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||||
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { pathResolve } from '@/utils/routerHelper'
|
import { pathResolve } from '@/utils/routerHelper'
|
||||||
|
|
||||||
const { renderMenuTitle } = useRenderMenuTitle()
|
const { renderMenuTitle } = useRenderMenuTitle()
|
||||||
|
|
||||||
export const useRenderMenuItem = () =>
|
export const useRenderMenuItem = (
|
||||||
// allRouters: AppRouteRecordRaw[] = [],
|
// allRouters: AppRouteRecordRaw[] = [],
|
||||||
{
|
menuMode: 'vertical' | 'horizontal'
|
||||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
) => {
|
||||||
return routers
|
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||||
.filter((v) => !v.meta?.hidden)
|
return routers.map((v) => {
|
||||||
.map((v) => {
|
const meta = v.meta ?? {}
|
||||||
const meta = v.meta ?? {}
|
if (!meta.hidden) {
|
||||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||||
|
|
||||||
if (
|
if (
|
||||||
oneShowingChild &&
|
oneShowingChild &&
|
||||||
(!(onlyOneChild?.children?.length !== 0) || onlyOneChild?.noShowingChildren) &&
|
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||||
!meta?.alwaysShow
|
!meta?.alwaysShow
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ElMenuItem
|
<ElMenuItem index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
|
||||||
index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
|
{{
|
||||||
>
|
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||||
{{
|
}}
|
||||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
</ElMenuItem>
|
||||||
}}
|
)
|
||||||
</ElMenuItem>
|
} else {
|
||||||
)
|
const { getPrefixCls } = useDesign()
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<ElSubMenu index={fullPath}>
|
|
||||||
{{
|
|
||||||
title: () => renderMenuTitle(meta),
|
|
||||||
default: () => renderMenuItem(v.children!, fullPath)
|
|
||||||
}}
|
|
||||||
</ElSubMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
const preFixCls = getPrefixCls('menu-popper')
|
||||||
renderMenuItem
|
return (
|
||||||
}
|
<ElSubMenu
|
||||||
|
index={fullPath}
|
||||||
|
popperClass={
|
||||||
|
menuMode === 'vertical' ? `${preFixCls}--vertical` : `${preFixCls}--horizontal`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
title: () => renderMenuTitle(meta),
|
||||||
|
default: () => renderMenuItem(v.children!, fullPath)
|
||||||
|
}}
|
||||||
|
</ElSubMenu>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
renderMenuItem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,14 +10,10 @@ export const useRenderMenuTitle = () => {
|
|||||||
return icon ? (
|
return icon ? (
|
||||||
<>
|
<>
|
||||||
<Icon icon={meta.icon}></Icon>
|
<Icon icon={meta.icon}></Icon>
|
||||||
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
<span class="v-menu__title">{t(title as string)}</span>
|
||||||
{t(title as string)}
|
|
||||||
</span>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
<span class="v-menu__title">{t(title as string)}</span>
|
||||||
{t(title as string)}
|
|
||||||
</span>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ const disabledClick = () => {
|
|||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
&--disabled {
|
&--disabled {
|
||||||
background: rgb(255 255 255 / 95%);
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
@ -88,9 +88,6 @@ const newSchema = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
},
|
|
||||||
label: () => {
|
|
||||||
return <span> </span>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -120,14 +117,11 @@ const setProps = (props: SearchProps = {}) => {
|
|||||||
outsideProps.value = props
|
outsideProps.value = props
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaRef = ref<FormSchema[]>([])
|
|
||||||
|
|
||||||
// 监听表单结构化数组,重新生成formModel
|
// 监听表单结构化数组,重新生成formModel
|
||||||
watch(
|
watch(
|
||||||
() => unref(newSchema),
|
() => unref(newSchema),
|
||||||
async (schema = []) => {
|
async (schema = []) => {
|
||||||
formModel.value = initModel(schema, unref(formModel))
|
formModel.value = initModel(schema, unref(formModel))
|
||||||
schemaRef.value = schema
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
@ -247,7 +241,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
|
|||||||
hide-required-asterisk
|
hide-required-asterisk
|
||||||
:inline="getProps.inline"
|
:inline="getProps.inline"
|
||||||
:is-col="getProps.isCol"
|
:is-col="getProps.isCol"
|
||||||
:schema="schemaRef"
|
:schema="newSchema"
|
||||||
@register="formRegister"
|
@register="formRegister"
|
||||||
@validate="onFormValidate"
|
@validate="onFormValidate"
|
||||||
/>
|
/>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ElButton } from 'element-plus'
|
||||||
import { useIcon } from '@/hooks/web/useIcon'
|
import { useIcon } from '@/hooks/web/useIcon'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
@ -30,7 +31,7 @@ const onExpand = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BaseButton
|
<ElButton
|
||||||
v-if="showSearch"
|
v-if="showSearch"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="searchLoading"
|
:loading="searchLoading"
|
||||||
@ -38,21 +39,21 @@ const onExpand = () => {
|
|||||||
@click="onSearch"
|
@click="onSearch"
|
||||||
>
|
>
|
||||||
{{ t('common.query') }}
|
{{ t('common.query') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
<BaseButton
|
<ElButton
|
||||||
v-if="showReset"
|
v-if="showReset"
|
||||||
:loading="resetLoading"
|
:loading="resetLoading"
|
||||||
:icon="useIcon({ icon: 'ep:refresh-right' })"
|
:icon="useIcon({ icon: 'ep:refresh-right' })"
|
||||||
@click="onReset"
|
@click="onReset"
|
||||||
>
|
>
|
||||||
{{ t('common.reset') }}
|
{{ t('common.reset') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
<BaseButton
|
<ElButton
|
||||||
v-if="showExpand"
|
v-if="showExpand"
|
||||||
:icon="useIcon({ icon: visible ? 'ep:arrow-up' : 'ep:arrow-down' })"
|
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
|
||||||
text
|
text
|
||||||
@click="onExpand"
|
@click="onExpand"
|
||||||
>
|
>
|
||||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||||
import { ref, unref } from 'vue'
|
import { ref, unref, computed, watch } from 'vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||||
|
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||||
import { useCssVar } from '@vueuse/core'
|
import { useCssVar } from '@vueuse/core'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { trim, setCssVar, getCssVar } from '@/utils'
|
import { trim, setCssVar } from '@/utils'
|
||||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||||
@ -13,7 +14,7 @@ import { useStorage } from '@/hooks/web/useStorage'
|
|||||||
import { useClipboard } from '@vueuse/core'
|
import { useClipboard } from '@vueuse/core'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
|
|
||||||
const { clear: storageClear } = useStorage('localStorage')
|
const { removeStorage } = useStorage()
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -23,6 +24,8 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
// 主题色相关
|
// 主题色相关
|
||||||
@ -39,27 +42,70 @@ const setSystemTheme = (color: string) => {
|
|||||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||||
|
|
||||||
const setHeaderTheme = (color: string) => {
|
const setHeaderTheme = (color: string) => {
|
||||||
appStore.setHeaderTheme(color)
|
const isDarkColor = colorIsDark(color)
|
||||||
|
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||||||
|
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||||||
|
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||||||
|
setCssVar('--top-header-bg-color', color)
|
||||||
|
setCssVar('--top-header-text-color', textColor)
|
||||||
|
setCssVar('--top-header-hover-color', textHoverColor)
|
||||||
|
appStore.setTheme({
|
||||||
|
topHeaderBgColor: color,
|
||||||
|
topHeaderTextColor: textColor,
|
||||||
|
topHeaderHoverColor: textHoverColor,
|
||||||
|
topToolBorderColor
|
||||||
|
})
|
||||||
|
if (unref(layout) === 'top') {
|
||||||
|
setMenuTheme(color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 菜单主题相关
|
// 菜单主题相关
|
||||||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||||||
|
|
||||||
const setMenuTheme = (color: string) => {
|
const setMenuTheme = (color: string) => {
|
||||||
appStore.setMenuTheme(color)
|
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
||||||
|
const isDarkColor = colorIsDark(color)
|
||||||
|
const theme: Recordable = {
|
||||||
|
// 左侧菜单边框颜色
|
||||||
|
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
||||||
|
// 左侧菜单背景颜色
|
||||||
|
leftMenuBgColor: color,
|
||||||
|
// 左侧菜单浅色背景颜色
|
||||||
|
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
|
||||||
|
// 左侧菜单选中背景颜色
|
||||||
|
leftMenuBgActiveColor: isDarkColor
|
||||||
|
? 'var(--el-color-primary)'
|
||||||
|
: hexToRGB(unref(primaryColor), 0.1),
|
||||||
|
// 左侧菜单收起选中背景颜色
|
||||||
|
leftMenuCollapseBgActiveColor: isDarkColor
|
||||||
|
? 'var(--el-color-primary)'
|
||||||
|
: hexToRGB(unref(primaryColor), 0.1),
|
||||||
|
// 左侧菜单字体颜色
|
||||||
|
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
||||||
|
// 左侧菜单选中字体颜色
|
||||||
|
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
||||||
|
// logo字体颜色
|
||||||
|
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||||||
|
// logo边框颜色
|
||||||
|
logoBorderColor: isDarkColor ? color : '#eee'
|
||||||
|
}
|
||||||
|
appStore.setTheme(theme)
|
||||||
|
appStore.setCssVarTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch(
|
// 监听layout变化,重置一些主题色
|
||||||
// () => layout.value,
|
watch(
|
||||||
// (n) => {
|
() => layout.value,
|
||||||
// if (n === 'top' && !appStore.getIsDark) {
|
(n) => {
|
||||||
// headerTheme.value = '#fff'
|
if (n === 'top' && !appStore.getIsDark) {
|
||||||
// setHeaderTheme('#fff')
|
headerTheme.value = '#fff'
|
||||||
// } else {
|
setHeaderTheme('#fff')
|
||||||
// setMenuTheme(unref(menuTheme))
|
} else {
|
||||||
// }
|
setMenuTheme(unref(menuTheme))
|
||||||
// }
|
}
|
||||||
// )
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 拷贝
|
// 拷贝
|
||||||
const copyConfig = async () => {
|
const copyConfig = async () => {
|
||||||
@ -128,8 +174,7 @@ const copyConfig = async () => {
|
|||||||
// 头部边框颜色
|
// 头部边框颜色
|
||||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||||
}
|
}
|
||||||
`,
|
`
|
||||||
legacy: true
|
|
||||||
})
|
})
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
ElMessage.error(t('setting.copyFailed'))
|
ElMessage.error(t('setting.copyFailed'))
|
||||||
@ -143,15 +188,11 @@ const copyConfig = async () => {
|
|||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
storageClear()
|
removeStorage('layout')
|
||||||
|
removeStorage('theme')
|
||||||
|
removeStorage('isDark')
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeChange = () => {
|
|
||||||
const color = getCssVar('--el-bg-color')
|
|
||||||
setMenuTheme(color)
|
|
||||||
setHeaderTheme(color)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -171,7 +212,7 @@ const themeChange = () => {
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- 主题 -->
|
<!-- 主题 -->
|
||||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||||
<ThemeSwitch @change="themeChange" />
|
<ThemeSwitch />
|
||||||
|
|
||||||
<!-- 布局 -->
|
<!-- 布局 -->
|
||||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||||
@ -212,21 +253,23 @@ const themeChange = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 菜单主题 -->
|
<!-- 菜单主题 -->
|
||||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
<template v-if="layout !== 'top'">
|
||||||
<ColorRadioPicker
|
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||||
v-model="menuTheme"
|
<ColorRadioPicker
|
||||||
:schema="[
|
v-model="menuTheme"
|
||||||
'#fff',
|
:schema="[
|
||||||
'#001529',
|
'#fff',
|
||||||
'#212121',
|
'#001529',
|
||||||
'#273352',
|
'#212121',
|
||||||
'#191b24',
|
'#273352',
|
||||||
'#383f45',
|
'#191b24',
|
||||||
'#001628',
|
'#383f45',
|
||||||
'#344058'
|
'#001628',
|
||||||
]"
|
'#344058'
|
||||||
@change="setMenuTheme"
|
]"
|
||||||
/>
|
@change="setMenuTheme"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 界面显示 -->
|
<!-- 界面显示 -->
|
||||||
@ -235,14 +278,12 @@ const themeChange = () => {
|
|||||||
|
|
||||||
<ElDivider />
|
<ElDivider />
|
||||||
<div>
|
<div>
|
||||||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
<ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
|
||||||
t('setting.copy')
|
|
||||||
}}</BaseButton>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5px">
|
<div class="mt-5px">
|
||||||
<BaseButton type="danger" class="w-full" @click="clear">
|
<ElButton type="danger" class="w-full" @click="clear">
|
||||||
{{ t('setting.clearAndReset') }}
|
{{ t('setting.clearAndReset') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</ElDrawer>
|
</ElDrawer>
|
||||||
</template>
|
</template>
|
||||||
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||||
import { ref, unref } from 'vue'
|
import { ref, unref, computed, watch } from 'vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||||
|
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||||
import { useCssVar } from '@vueuse/core'
|
import { useCssVar } from '@vueuse/core'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { trim, setCssVar, getCssVar } from '@/utils'
|
import { trim, setCssVar } from '@/utils'
|
||||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||||
@ -14,7 +15,7 @@ import { useClipboard } from '@vueuse/core'
|
|||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
|
|
||||||
const { clear: storageClear } = useStorage('localStorage')
|
const { removeStorage } = useStorage()
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -28,6 +29,8 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
// 主题色相关
|
// 主题色相关
|
||||||
@ -44,27 +47,70 @@ const setSystemTheme = (color: string) => {
|
|||||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||||
|
|
||||||
const setHeaderTheme = (color: string) => {
|
const setHeaderTheme = (color: string) => {
|
||||||
appStore.setHeaderTheme(color)
|
const isDarkColor = colorIsDark(color)
|
||||||
|
const textColor = isDarkColor ? '#fff' : 'inherit'
|
||||||
|
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
||||||
|
const topToolBorderColor = isDarkColor ? color : '#eee'
|
||||||
|
setCssVar('--top-header-bg-color', color)
|
||||||
|
setCssVar('--top-header-text-color', textColor)
|
||||||
|
setCssVar('--top-header-hover-color', textHoverColor)
|
||||||
|
appStore.setTheme({
|
||||||
|
topHeaderBgColor: color,
|
||||||
|
topHeaderTextColor: textColor,
|
||||||
|
topHeaderHoverColor: textHoverColor,
|
||||||
|
topToolBorderColor
|
||||||
|
})
|
||||||
|
if (unref(layout) === 'top') {
|
||||||
|
setMenuTheme(color)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 菜单主题相关
|
// 菜单主题相关
|
||||||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||||||
|
|
||||||
const setMenuTheme = (color: string) => {
|
const setMenuTheme = (color: string) => {
|
||||||
appStore.setMenuTheme(color)
|
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
||||||
|
const isDarkColor = colorIsDark(color)
|
||||||
|
const theme: Recordable = {
|
||||||
|
// 左侧菜单边框颜色
|
||||||
|
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
||||||
|
// 左侧菜单背景颜色
|
||||||
|
leftMenuBgColor: color,
|
||||||
|
// 左侧菜单浅色背景颜色
|
||||||
|
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
|
||||||
|
// 左侧菜单选中背景颜色
|
||||||
|
leftMenuBgActiveColor: isDarkColor
|
||||||
|
? 'var(--el-color-primary)'
|
||||||
|
: hexToRGB(unref(primaryColor), 0.1),
|
||||||
|
// 左侧菜单收起选中背景颜色
|
||||||
|
leftMenuCollapseBgActiveColor: isDarkColor
|
||||||
|
? 'var(--el-color-primary)'
|
||||||
|
: hexToRGB(unref(primaryColor), 0.1),
|
||||||
|
// 左侧菜单字体颜色
|
||||||
|
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
||||||
|
// 左侧菜单选中字体颜色
|
||||||
|
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
||||||
|
// logo字体颜色
|
||||||
|
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
||||||
|
// logo边框颜色
|
||||||
|
logoBorderColor: isDarkColor ? color : '#eee'
|
||||||
|
}
|
||||||
|
appStore.setTheme(theme)
|
||||||
|
appStore.setCssVarTheme()
|
||||||
}
|
}
|
||||||
|
|
||||||
// watch(
|
// 监听layout变化,重置一些主题色
|
||||||
// () => layout.value,
|
watch(
|
||||||
// (n) => {
|
() => layout.value,
|
||||||
// if (n === 'top' && !appStore.getIsDark) {
|
(n) => {
|
||||||
// headerTheme.value = '#fff'
|
if (n === 'top' && !appStore.getIsDark) {
|
||||||
// setHeaderTheme('#fff')
|
headerTheme.value = '#fff'
|
||||||
// } else {
|
setHeaderTheme('#fff')
|
||||||
// setMenuTheme(unref(menuTheme))
|
} else {
|
||||||
// }
|
setMenuTheme(unref(menuTheme))
|
||||||
// }
|
}
|
||||||
// )
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// 拷贝
|
// 拷贝
|
||||||
const copyConfig = async () => {
|
const copyConfig = async () => {
|
||||||
@ -133,8 +179,7 @@ const copyConfig = async () => {
|
|||||||
// 头部边框颜色
|
// 头部边框颜色
|
||||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||||
}
|
}
|
||||||
`,
|
`
|
||||||
legacy: true
|
|
||||||
})
|
})
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
ElMessage.error(t('setting.copyFailed'))
|
ElMessage.error(t('setting.copyFailed'))
|
||||||
@ -148,15 +193,11 @@ const copyConfig = async () => {
|
|||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
storageClear()
|
removeStorage('layout')
|
||||||
|
removeStorage('theme')
|
||||||
|
removeStorage('isDark')
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
const themeChange = () => {
|
|
||||||
const color = getCssVar('--el-bg-color')
|
|
||||||
setMenuTheme(color)
|
|
||||||
setHeaderTheme(color)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -178,7 +219,7 @@ const themeChange = () => {
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- 主题 -->
|
<!-- 主题 -->
|
||||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||||
<ThemeSwitch @change="themeChange" />
|
<ThemeSwitch />
|
||||||
|
|
||||||
<!-- 布局 -->
|
<!-- 布局 -->
|
||||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||||
@ -219,21 +260,23 @@ const themeChange = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 菜单主题 -->
|
<!-- 菜单主题 -->
|
||||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
<template v-if="layout !== 'top'">
|
||||||
<ColorRadioPicker
|
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||||
v-model="menuTheme"
|
<ColorRadioPicker
|
||||||
:schema="[
|
v-model="menuTheme"
|
||||||
'#fff',
|
:schema="[
|
||||||
'#001529',
|
'#fff',
|
||||||
'#212121',
|
'#001529',
|
||||||
'#273352',
|
'#212121',
|
||||||
'#191b24',
|
'#273352',
|
||||||
'#383f45',
|
'#191b24',
|
||||||
'#001628',
|
'#383f45',
|
||||||
'#344058'
|
'#001628',
|
||||||
]"
|
'#344058'
|
||||||
@change="setMenuTheme"
|
]"
|
||||||
/>
|
@change="setMenuTheme"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 界面显示 -->
|
<!-- 界面显示 -->
|
||||||
@ -242,14 +285,14 @@ const themeChange = () => {
|
|||||||
|
|
||||||
<ElDivider />
|
<ElDivider />
|
||||||
<div>
|
<div>
|
||||||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
<ElButton type="primary" class="w-full" @click="copyConfig">{{
|
||||||
t('setting.copy')
|
t('setting.copy')
|
||||||
}}</BaseButton>
|
}}</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5px">
|
<div class="mt-5px">
|
||||||
<BaseButton type="danger" class="w-full" @click="clear">
|
<ElButton type="danger" class="w-full" @click="clear">
|
||||||
{{ t('setting.clearAndReset') }}
|
{{ t('setting.clearAndReset') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</ElDrawer>
|
</ElDrawer>
|
||||||
</div>
|
</div>
|
||||||
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
border: 2px solid #e5e7eb;
|
border: 2px solid #e5e7eb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
&::before {
|
&:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
|||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
&:after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
@ -88,9 +88,6 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
showTitle.value = !collapse
|
showTitle.value = !collapse
|
||||||
}
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -205,12 +202,11 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
class={[
|
class={[
|
||||||
'!absolute top-0 z-3000',
|
'!absolute top-0 z-4000',
|
||||||
{
|
{
|
||||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||||
'!w-[var(--left-menu-max-width)] border-r-1 border-r-solid border-[var(--el-border-color)]':
|
'!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
|
||||||
unref(showMenu) || unref(fixedMenu),
|
|
||||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
@ -5,9 +5,7 @@ import {
|
|||||||
ElPagination,
|
ElPagination,
|
||||||
ComponentSize,
|
ComponentSize,
|
||||||
ElTooltipProps,
|
ElTooltipProps,
|
||||||
ElImage,
|
ElImage
|
||||||
ElEmpty,
|
|
||||||
ElCard
|
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
@ -17,10 +15,9 @@ import { set, get } from 'lodash-es'
|
|||||||
import { CSSProperties } from 'vue'
|
import { CSSProperties } from 'vue'
|
||||||
import { getSlot } from '@/utils/tsxHelper'
|
import { getSlot } from '@/utils/tsxHelper'
|
||||||
import TableActions from './components/TableActions.vue'
|
import TableActions from './components/TableActions.vue'
|
||||||
|
// import Sortable from 'sortablejs'
|
||||||
|
// import { Icon } from '@/components/Icon'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { createVideoViewer } from '@/components/VideoPlayer'
|
|
||||||
import { Icon } from '@/components/Icon'
|
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
@ -63,13 +60,8 @@ export default defineComponent({
|
|||||||
type: Array as PropType<Recordable[]>,
|
type: Array as PropType<Recordable[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
// 图片自动预览字段数组
|
// 是否自动预览
|
||||||
imagePreview: {
|
preview: {
|
||||||
type: Array as PropType<string[]>,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
// 视频自动预览字段数组
|
|
||||||
videoPreview: {
|
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
@ -80,7 +72,7 @@ export default defineComponent({
|
|||||||
border: propTypes.bool.def(true),
|
border: propTypes.bool.def(true),
|
||||||
size: {
|
size: {
|
||||||
type: String as PropType<ComponentSize>,
|
type: String as PropType<ComponentSize>,
|
||||||
validator: (v: ComponentSize) => ['default', 'small', 'large'].includes(v)
|
validator: (v: ComponentSize) => ['medium', 'small', 'mini'].includes(v)
|
||||||
},
|
},
|
||||||
fit: propTypes.bool.def(true),
|
fit: propTypes.bool.def(true),
|
||||||
showHeader: propTypes.bool.def(true),
|
showHeader: propTypes.bool.def(true),
|
||||||
@ -199,27 +191,9 @@ export default defineComponent({
|
|||||||
default: 'fixed'
|
default: 'fixed'
|
||||||
},
|
},
|
||||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||||
flexible: propTypes.bool.def(false),
|
flexible: propTypes.bool.def(false)
|
||||||
// 自定义内容
|
|
||||||
customContent: propTypes.bool.def(false),
|
|
||||||
cardBodyStyle: {
|
|
||||||
type: Object as PropType<CSSProperties>,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
cardBodyClass: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
cardWrapStyle: {
|
|
||||||
type: Object as PropType<CSSProperties>,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
cardWrapClass: {
|
|
||||||
type: String as PropType<string>,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
|
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
||||||
setup(props, { attrs, emit, slots, expose }) {
|
setup(props, { attrs, emit, slots, expose }) {
|
||||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||||
|
|
||||||
@ -244,6 +218,33 @@ export default defineComponent({
|
|||||||
return propsObj
|
return propsObj
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// const sortableEl = ref()
|
||||||
|
// 初始化拖拽
|
||||||
|
// const initDropTable = () => {
|
||||||
|
// const el = unref(elTableRef)?.$el.querySelector('.el-table__body tbody')
|
||||||
|
// if (!el) return
|
||||||
|
// if (unref(sortableEl)) unref(sortableEl).destroy()
|
||||||
|
|
||||||
|
// sortableEl.value = Sortable.create(el, {
|
||||||
|
// handle: '.table-move',
|
||||||
|
// animation: 180,
|
||||||
|
// onEnd(e: any) {
|
||||||
|
// emit('sortable-change', e)
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
// watch(
|
||||||
|
// () => getProps.value.sortable,
|
||||||
|
// async (v) => {
|
||||||
|
// await nextTick()
|
||||||
|
// v && initDropTable()
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// immediate: true
|
||||||
|
// }
|
||||||
|
// )
|
||||||
|
|
||||||
const setProps = (props: TableProps = {}) => {
|
const setProps = (props: TableProps = {}) => {
|
||||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||||
outsideProps.value = { ...props } as any
|
outsideProps.value = { ...props } as any
|
||||||
@ -264,7 +265,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
const addColumn = (column: TableColumn, index?: number) => {
|
const addColumn = (column: TableColumn, index?: number) => {
|
||||||
const { columns } = unref(getProps)
|
const { columns } = unref(getProps)
|
||||||
if (index !== void 0) {
|
if (index) {
|
||||||
columns.splice(index, 0, column)
|
columns.splice(index, 0, column)
|
||||||
} else {
|
} else {
|
||||||
columns.push(column)
|
columns.push(column)
|
||||||
@ -349,13 +350,11 @@ export default defineComponent({
|
|||||||
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
|
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
|
||||||
delete bindValue.columns
|
delete bindValue.columns
|
||||||
delete bindValue.data
|
delete bindValue.data
|
||||||
delete bindValue.align
|
|
||||||
return bindValue
|
return bindValue
|
||||||
})
|
})
|
||||||
|
|
||||||
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||||
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
|
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
|
||||||
unref(getProps)
|
|
||||||
return columnsChildren.map((v) => {
|
return columnsChildren.map((v) => {
|
||||||
if (v.show === false) return null
|
if (v.show === false) return null
|
||||||
const props = { ...v } as any
|
const props = { ...v } as any
|
||||||
@ -366,20 +365,20 @@ export default defineComponent({
|
|||||||
const slots = {
|
const slots = {
|
||||||
default: (...args: any[]) => {
|
default: (...args: any[]) => {
|
||||||
const data = args[0]
|
const data = args[0]
|
||||||
let isPreview = false
|
let isImageUrl = false
|
||||||
isPreview =
|
if (preview.length) {
|
||||||
imagePreview.some((item) => (item as string) === v.field) ||
|
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||||
videoPreview.some((item) => (item as string) === v.field)
|
}
|
||||||
|
|
||||||
return children && children.length
|
return children && children.length
|
||||||
? renderTreeTableColumn(children)
|
? renderTreeTableColumn(children)
|
||||||
: props?.slots?.default
|
: props?.slots?.default
|
||||||
? props.slots.default(...args)
|
? props.slots.default(...args)
|
||||||
: v?.formatter
|
: v?.formatter
|
||||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||||
: isPreview
|
: isImageUrl
|
||||||
? renderPreview(get(data.row, v.field), v.field)
|
? renderPreview(get(data.row, v.field))
|
||||||
: get(data.row, v.field)
|
: get(data.row, v.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (props?.slots?.header) {
|
if (props?.slots?.header) {
|
||||||
@ -400,32 +399,17 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderPreview = (url: string, field: string) => {
|
const renderPreview = (url: string) => {
|
||||||
const { imagePreview, videoPreview } = unref(getProps)
|
|
||||||
return (
|
return (
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
{imagePreview.includes(field) ? (
|
<ElImage
|
||||||
<ElImage
|
src={url}
|
||||||
src={url}
|
fit="cover"
|
||||||
fit="cover"
|
class="w-[100%] h-100px"
|
||||||
class="w-[100%]"
|
lazy
|
||||||
lazy
|
preview-src-list={[url]}
|
||||||
preview-src-list={[url]}
|
preview-teleported
|
||||||
preview-teleported
|
/>
|
||||||
/>
|
|
||||||
) : videoPreview.includes(field) ? (
|
|
||||||
<BaseButton
|
|
||||||
type="primary"
|
|
||||||
icon={<Icon icon="ep:video-play" />}
|
|
||||||
onClick={() => {
|
|
||||||
createVideoViewer({
|
|
||||||
url
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
预览
|
|
||||||
</BaseButton>
|
|
||||||
) : null}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -440,8 +424,7 @@ export default defineComponent({
|
|||||||
headerAlign,
|
headerAlign,
|
||||||
showOverflowTooltip,
|
showOverflowTooltip,
|
||||||
reserveSelection,
|
reserveSelection,
|
||||||
imagePreview,
|
preview
|
||||||
videoPreview
|
|
||||||
} = unref(getProps)
|
} = unref(getProps)
|
||||||
|
|
||||||
return (columnsChildren || columns).map((v) => {
|
return (columnsChildren || columns).map((v) => {
|
||||||
@ -467,7 +450,6 @@ export default defineComponent({
|
|||||||
reserveSelection={reserveSelection}
|
reserveSelection={reserveSelection}
|
||||||
align="center"
|
align="center"
|
||||||
headerAlign="center"
|
headerAlign="center"
|
||||||
selectable={v.selectable}
|
|
||||||
width="50px"
|
width="50px"
|
||||||
fixed="left"
|
fixed="left"
|
||||||
></ElTableColumn>
|
></ElTableColumn>
|
||||||
@ -482,20 +464,20 @@ export default defineComponent({
|
|||||||
default: (...args: any[]) => {
|
default: (...args: any[]) => {
|
||||||
const data = args[0]
|
const data = args[0]
|
||||||
|
|
||||||
let isPreview = false
|
let isImageUrl = false
|
||||||
isPreview =
|
if (preview.length) {
|
||||||
imagePreview.some((item) => (item as string) === v.field) ||
|
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||||
videoPreview.some((item) => (item as string) === v.field)
|
}
|
||||||
|
|
||||||
return children && children.length
|
return children && children.length
|
||||||
? renderTreeTableColumn(children)
|
? renderTreeTableColumn(children)
|
||||||
: props?.slots?.default
|
: props?.slots?.default
|
||||||
? props.slots.default(...args)
|
? props.slots.default(...args)
|
||||||
: v?.formatter
|
: v?.formatter
|
||||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||||
: isPreview
|
: isImageUrl
|
||||||
? renderPreview(get(data.row, v.field), v.field)
|
? renderPreview(get(data.row, v.field))
|
||||||
: get(data.row, v.field)
|
: get(data.row, v.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (props?.slots?.header) {
|
if (props?.slots?.header) {
|
||||||
@ -527,79 +509,52 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
const toolbar = getSlot(slots, 'toolbar')
|
const toolbar = getSlot(slots, 'toolbar')
|
||||||
|
|
||||||
|
// const { sortable } = unref(getProps)
|
||||||
|
|
||||||
|
// const sortableEl = sortable ? (
|
||||||
|
// <ElTableColumn
|
||||||
|
// className="table-move cursor-move"
|
||||||
|
// type="sortable"
|
||||||
|
// prop="sortable"
|
||||||
|
// width="60px"
|
||||||
|
// align="center"
|
||||||
|
// >
|
||||||
|
// <Icon icon="ant-design:drag-outlined" />
|
||||||
|
// </ElTableColumn>
|
||||||
|
// ) : null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div v-loading={unref(getProps).loading}>
|
<div v-loading={unref(getProps).loading}>
|
||||||
{unref(getProps).customContent ? (
|
<div class="flex justify-between mb-1">
|
||||||
<div class="flex flex-wrap">
|
<div>{toolbar}</div>
|
||||||
{unref(getProps)?.data?.length ? (
|
<div class="pt-2">
|
||||||
unref(getProps)?.data.map((item) => {
|
{unref(getProps).showAction ? (
|
||||||
const cardSlots = {
|
<TableActions
|
||||||
default: () => {
|
activeUID={unref(getProps).activeUID}
|
||||||
return getSlot(slots, 'content', item)
|
columns={unref(getProps).columns}
|
||||||
}
|
el-table-ref={elTableRef}
|
||||||
}
|
onChangSize={changSize}
|
||||||
if (getSlot(slots, 'content-header')) {
|
onRefresh={refresh}
|
||||||
cardSlots['header'] = () => {
|
/>
|
||||||
return getSlot(slots, 'content-header', item)
|
) : null}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (getSlot(slots, 'content-footer')) {
|
|
||||||
cardSlots['footer'] = () => {
|
|
||||||
return getSlot(slots, 'content-footer', item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<ElCard
|
|
||||||
shadow="hover"
|
|
||||||
class={unref(getProps).cardWrapClass}
|
|
||||||
style={unref(getProps).cardWrapStyle}
|
|
||||||
bodyClass={unref(getProps).cardBodyClass}
|
|
||||||
bodyStyle={unref(getProps).cardBodyStyle}
|
|
||||||
>
|
|
||||||
{cardSlots}
|
|
||||||
</ElCard>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div class="flex flex-1 justify-center">
|
|
||||||
<ElEmpty description="暂无数据" />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</div>
|
||||||
<>
|
|
||||||
<div class="flex justify-between mb-1">
|
|
||||||
<div>{toolbar}</div>
|
|
||||||
<div class="pt-2">
|
|
||||||
{unref(getProps).showAction ? (
|
|
||||||
<TableActions
|
|
||||||
activeUID={unref(getProps).activeUID}
|
|
||||||
columns={unref(getProps).columns}
|
|
||||||
el-table-ref={elTableRef}
|
|
||||||
onChangSize={changSize}
|
|
||||||
onRefresh={refresh}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ElTable
|
|
||||||
ref={elTableRef}
|
|
||||||
data={unref(getProps).data}
|
|
||||||
{...unref(getBindValue)}
|
|
||||||
header-cell-style={
|
|
||||||
appStore.getIsDark
|
|
||||||
? { color: '#CFD3DC', 'background-color': '#000' }
|
|
||||||
: { color: '#000', 'background-color': '#f5f7fa' }
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{{
|
|
||||||
default: () => renderTableColumn(),
|
|
||||||
...tableSlots
|
|
||||||
}}
|
|
||||||
</ElTable>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
|
<ElTable
|
||||||
|
ref={elTableRef}
|
||||||
|
data={unref(getProps).data}
|
||||||
|
{...unref(getBindValue)}
|
||||||
|
header-cell-style={
|
||||||
|
appStore.getIsDark
|
||||||
|
? { color: '#CFD3DC', 'background-color': '#000' }
|
||||||
|
: { color: '#000', 'background-color': '#f5f7fa' }
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
default: () => renderTableColumn(),
|
||||||
|
...tableSlots
|
||||||
|
}}
|
||||||
|
</ElTable>
|
||||||
{unref(getProps).pagination ? (
|
{unref(getProps).pagination ? (
|
||||||
<ElPagination
|
<ElPagination
|
||||||
v-model:pageSize={pageSizeRef.value}
|
v-model:pageSize={pageSizeRef.value}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
ElPopover,
|
ElPopover,
|
||||||
ElCheckbox,
|
ElCheckbox,
|
||||||
ElScrollbar,
|
ElScrollbar,
|
||||||
|
ElButton,
|
||||||
ElTable,
|
ElTable,
|
||||||
ElDivider
|
ElDivider
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
@ -22,10 +23,14 @@ import { useStorage } from '@/hooks/web/useStorage'
|
|||||||
import cloneDeep from 'lodash/cloneDeep'
|
import cloneDeep from 'lodash/cloneDeep'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { moveElementToIndex } from '@/utils/index'
|
import { moveElementToIndex } from '@/utils/index'
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
const appStore = useAppStore()
|
||||||
|
const sizeMap = computed(() => appStore.sizeMap)
|
||||||
|
|
||||||
const { setStorage, getStorage, removeStorage } = useStorage()
|
const { setStorage, getStorage, removeStorage } = useStorage()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'TableActions',
|
name: 'TableActions',
|
||||||
props: {
|
props: {
|
||||||
@ -42,10 +47,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
emits: ['refresh', 'changSize'],
|
emits: ['refresh', 'changSize'],
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const appStore = useAppStore()
|
|
||||||
const sizeMap = computed(() => appStore.sizeMap)
|
|
||||||
const { t } = useI18n()
|
|
||||||
|
|
||||||
const refresh = () => {
|
const refresh = () => {
|
||||||
emit('refresh')
|
emit('refresh')
|
||||||
}
|
}
|
||||||
@ -262,9 +263,9 @@ export default defineComponent({
|
|||||||
{t('common.SerialNumberColumn')}
|
{t('common.SerialNumberColumn')}
|
||||||
</ElCheckbox>
|
</ElCheckbox>
|
||||||
</div>
|
</div>
|
||||||
<BaseButton type="primary" link onClick={resetTableColumns}>
|
<ElButton type="primary" link onClick={resetTableColumns}>
|
||||||
{t('common.reset')}
|
{t('common.reset')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<ElScrollbar max-height="400px">
|
<ElScrollbar max-height="400px">
|
||||||
<VueDraggable
|
<VueDraggable
|
||||||
|
@ -210,14 +210,13 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
|||||||
// 所有右键菜单组件的元素
|
// 所有右键菜单组件的元素
|
||||||
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
|
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
|
||||||
|
|
||||||
// 右键菜单状态改变的时候
|
// 右键菜单装填改变的时候
|
||||||
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
|
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
for (const v of unref(itemRefs)) {
|
for (const v of unref(itemRefs)) {
|
||||||
const elDropdownMenuRef = v.elDropdownMenuRef
|
const elDropdownMenuRef = v.elDropdownMenuRef
|
||||||
if (tagItem.fullPath !== v.tagItem.fullPath) {
|
if (tagItem.fullPath !== v.tagItem.fullPath) {
|
||||||
elDropdownMenuRef?.handleClose()
|
elDropdownMenuRef?.handleClose()
|
||||||
setSelectTag(tagItem)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -583,3 +582,4 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@/hooks/web/useTagsView
|
||||||
|
@ -9,8 +9,6 @@ const { getPrefixCls } = useDesign()
|
|||||||
|
|
||||||
const prefixCls = getPrefixCls('theme-switch')
|
const prefixCls = getPrefixCls('theme-switch')
|
||||||
|
|
||||||
const emit = defineEmits(['change'])
|
|
||||||
|
|
||||||
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
|
const Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })
|
||||||
|
|
||||||
const CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon', color: '#fde047' })
|
const CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon', color: '#fde047' })
|
||||||
@ -25,7 +23,6 @@ const blackColor = 'var(--el-color-black)'
|
|||||||
|
|
||||||
const themeChange = (val: boolean) => {
|
const themeChange = (val: boolean) => {
|
||||||
appStore.setIsDark(val)
|
appStore.setIsDark(val)
|
||||||
emit('change', val)
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import LockDialog from './components/LockDialog.vue'
|
import LockDialog from './components/LockDialog.vue'
|
||||||
import { ref, computed } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
@ -14,7 +14,7 @@ const lockStore = useLockStore()
|
|||||||
|
|
||||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -73,13 +73,13 @@ const user = computed(() => authStore.getUser)
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<BaseButton @click="toHome" link>个人主页</BaseButton>
|
<ElButton @click="toHome" link>个人主页</ElButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<BaseButton @click="toGitee" link>Gitee</BaseButton>
|
<ElButton @click="toGitee" link>Gitee</ElButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<BaseButton @click="toGithub" link>Github</BaseButton>
|
<ElButton @click="toGithub" link>Github</ElButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem divided>
|
<ElDropdownItem divided>
|
||||||
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
||||||
|
@ -7,6 +7,7 @@ import { useForm } from '@/hooks/web/useForm'
|
|||||||
import { reactive, computed } from 'vue'
|
import { reactive, computed } from 'vue'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
import { FormSchema } from '@/components/Form'
|
import { FormSchema } from '@/components/Form'
|
||||||
|
import { ElButton } from 'element-plus'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { useLockStore } from '@/store/modules/lock'
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
|
|
||||||
@ -86,14 +87,14 @@ const handleLock = async () => {
|
|||||||
</div>
|
</div>
|
||||||
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
|
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<BaseButton type="primary" @click="handleLock">{{ t('lock.lock') }}</BaseButton>
|
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:global(.v-lock-dialog) {
|
:global(.v-lock-dialog) {
|
||||||
@media (width <= 767px) {
|
@media (max-width: 767px) {
|
||||||
max-width: calc(100vw - 16px);
|
max-width: calc(100vw - 16px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ElInput } from 'element-plus'
|
import { ElInput, ElButton } from 'element-plus'
|
||||||
import { useLockStore } from '@/store/modules/lock'
|
import { useLockStore } from '@/store/modules/lock'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { useNow } from '@/hooks/web/useNow'
|
import { useNow } from '@/hooks/web/useNow'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import { Icon } from '@/components/Icon'
|
import { Icon } from '@/components/Icon'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
const password = ref('')
|
const password = ref('')
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@ -92,7 +92,7 @@ function handleShowForm(show = false) {
|
|||||||
{{ t('lock.message') }}
|
{{ t('lock.message') }}
|
||||||
</span>
|
</span>
|
||||||
<div :class="`${prefixCls}-entry__footer enter-x`">
|
<div :class="`${prefixCls}-entry__footer enter-x`">
|
||||||
<BaseButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-2 mr-2 enter-x"
|
class="mt-2 mr-2 enter-x"
|
||||||
@ -101,8 +101,8 @@ function handleShowForm(show = false) {
|
|||||||
@click="handleShowForm(true)"
|
@click="handleShowForm(true)"
|
||||||
>
|
>
|
||||||
{{ t('common.back') }}
|
{{ t('common.back') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
<BaseButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
size="small"
|
size="small"
|
||||||
class="mt-2 mr-2 enter-x"
|
class="mt-2 mr-2 enter-x"
|
||||||
@ -111,8 +111,8 @@ function handleShowForm(show = false) {
|
|||||||
@click="goLogin"
|
@click="goLogin"
|
||||||
>
|
>
|
||||||
{{ t('lock.backToLogin') }}
|
{{ t('lock.backToLogin') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
<BaseButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
size="small"
|
size="small"
|
||||||
@ -121,7 +121,7 @@ function handleShowForm(show = false) {
|
|||||||
:disabled="loading"
|
:disabled="loading"
|
||||||
>
|
>
|
||||||
{{ t('lock.entrySystem') }}
|
{{ t('lock.entrySystem') }}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -190,7 +190,6 @@ function handleShowForm(show = false) {
|
|||||||
font-size: 90px;
|
font-size: 90px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: @screen-lg) {
|
@media screen and (min-width: @screen-lg) {
|
||||||
span:not(.meridiem) {
|
span:not(.meridiem) {
|
||||||
font-size: 220px;
|
font-size: 220px;
|
||||||
@ -202,7 +201,6 @@ function handleShowForm(show = false) {
|
|||||||
font-size: 260px;
|
font-size: 260px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: @screen-2xl) {
|
@media screen and (min-width: @screen-2xl) {
|
||||||
span:not(.meridiem) {
|
span:not(.meridiem) {
|
||||||
font-size: 320px;
|
font-size: 320px;
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { VNode, createVNode, render } from 'vue'
|
|
||||||
import VideoPlayer from './src/VideoPlayer.vue'
|
|
||||||
import { isClient } from '@/utils/is'
|
|
||||||
import { VideoPlayerViewer } from '@/components/VideoPlayerViewer'
|
|
||||||
import { toAnyString } from '@/utils'
|
|
||||||
|
|
||||||
export { VideoPlayer }
|
|
||||||
|
|
||||||
let instance: Nullable<VNode> = null
|
|
||||||
|
|
||||||
export function createVideoViewer(options: { url: string; poster?: string; show?: boolean }) {
|
|
||||||
if (!isClient) return
|
|
||||||
const { url, poster } = options
|
|
||||||
|
|
||||||
const propsData: Partial<{ url: string; poster?: string; show?: boolean; id?: string }> = {}
|
|
||||||
const container = document.createElement('div')
|
|
||||||
const id = toAnyString()
|
|
||||||
container.id = id
|
|
||||||
propsData.url = url
|
|
||||||
propsData.poster = poster
|
|
||||||
propsData.show = true
|
|
||||||
propsData.id = id
|
|
||||||
|
|
||||||
document.body.appendChild(container)
|
|
||||||
instance = createVNode(VideoPlayerViewer, propsData)
|
|
||||||
render(instance, container)
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import Player from 'xgplayer'
|
|
||||||
import { ref, unref, onMounted, watch, onBeforeUnmount, nextTick } from 'vue'
|
|
||||||
import 'xgplayer/dist/index.min.css'
|
|
||||||
const props = defineProps({
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
poster: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const playerRef = ref<Player>()
|
|
||||||
const videoEl = ref<HTMLDivElement>()
|
|
||||||
const intiPlayer = () => {
|
|
||||||
if (!unref(videoEl)) return
|
|
||||||
new Player({
|
|
||||||
autoplay: false,
|
|
||||||
...props,
|
|
||||||
el: unref(videoEl)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onMounted(() => {
|
|
||||||
intiPlayer()
|
|
||||||
})
|
|
||||||
watch(
|
|
||||||
() => props,
|
|
||||||
async (newProps) => {
|
|
||||||
await nextTick()
|
|
||||||
if (newProps) {
|
|
||||||
unref(playerRef)?.setConfig(newProps)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
deep: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
unref(playerRef)?.destroy()
|
|
||||||
})
|
|
||||||
defineExpose({
|
|
||||||
playerExpose: () => unref(playerRef)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div ref="videoEl"></div>
|
|
||||||
</template>
|
|
@ -1,3 +0,0 @@
|
|||||||
import VideoPlayerViewer from './src/VideoPlayerViewer.vue'
|
|
||||||
|
|
||||||
export { VideoPlayerViewer }
|
|
@ -1,46 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { VideoPlayer } from '@/components/VideoPlayer'
|
|
||||||
import { ElOverlay } from 'element-plus'
|
|
||||||
import { ref, nextTick } from 'vue'
|
|
||||||
import { Icon } from '@/components/Icon'
|
|
||||||
const props = defineProps({
|
|
||||||
show: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
url: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
poster: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
},
|
|
||||||
id: {
|
|
||||||
type: String,
|
|
||||||
default: ''
|
|
||||||
}
|
|
||||||
})
|
|
||||||
const visible = ref(props.show)
|
|
||||||
const close = async () => {
|
|
||||||
visible.value = false
|
|
||||||
await nextTick()
|
|
||||||
const wrap = document.getElementById(props.id)
|
|
||||||
if (!wrap) return
|
|
||||||
document.body.removeChild(wrap)
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<ElOverlay v-show="visible" @click="close">
|
|
||||||
<div class="w-full h-full flex justify-center items-center relative" @click="close">
|
|
||||||
<div
|
|
||||||
class="w-44px h-44px color-[#fff] bg-[var(--el-text-color-regular)] rounded-full border-[#fff] flex justify-center items-center cursor-pointer absolute top-40px right-40px"
|
|
||||||
@click="close"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:close" :size="24" />
|
|
||||||
</div>
|
|
||||||
<VideoPlayer :url="url" :poster="poster" />
|
|
||||||
</div>
|
|
||||||
</ElOverlay>
|
|
||||||
</template>
|
|
@ -1,3 +0,0 @@
|
|||||||
import Waterfall from './src/Waterfall.vue'
|
|
||||||
|
|
||||||
export { Waterfall }
|
|
@ -1,234 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { propTypes } from '@/utils/propTypes'
|
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
|
||||||
import { ref, nextTick, unref, onMounted, watch } from 'vue'
|
|
||||||
import { useEventListener, useIntersectionObserver } from '@vueuse/core'
|
|
||||||
import { debounce } from 'lodash-es'
|
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
|
||||||
|
|
||||||
const prefixCls = getPrefixCls('waterfall')
|
|
||||||
|
|
||||||
const emit = defineEmits(['loadMore'])
|
|
||||||
|
|
||||||
const prop = defineProps({
|
|
||||||
data: propTypes.arrayOf(propTypes.any),
|
|
||||||
reset: propTypes.bool.def(true),
|
|
||||||
width: propTypes.number.def(200),
|
|
||||||
gap: propTypes.number.def(20),
|
|
||||||
props: propTypes.objectOf(propTypes.string).def({
|
|
||||||
src: 'src',
|
|
||||||
height: 'height'
|
|
||||||
}),
|
|
||||||
cols: propTypes.number.def(undefined),
|
|
||||||
loadingText: propTypes.string.def('加载中...'),
|
|
||||||
loading: propTypes.bool.def(false),
|
|
||||||
end: propTypes.bool.def(false),
|
|
||||||
endText: propTypes.string.def('没有更多了'),
|
|
||||||
autoCenter: propTypes.bool.def(true),
|
|
||||||
layout: propTypes.oneOf(['javascript', 'flex']).def('flex')
|
|
||||||
})
|
|
||||||
|
|
||||||
const wrapEl = ref<HTMLDivElement>()
|
|
||||||
|
|
||||||
const heights = ref<number[]>([])
|
|
||||||
|
|
||||||
const wrapHeight = ref(0)
|
|
||||||
|
|
||||||
const wrapWidth = ref(0)
|
|
||||||
|
|
||||||
const loadMore = ref<HTMLDivElement>()
|
|
||||||
|
|
||||||
// 首先确定列数 = 页面宽度 / 图片宽度
|
|
||||||
const innerCols = ref(0)
|
|
||||||
|
|
||||||
const filterData = ref<any[]>([])
|
|
||||||
|
|
||||||
const filterWaterfall = async () => {
|
|
||||||
filterData.value = []
|
|
||||||
const { props, width, gap } = prop
|
|
||||||
const data = prop.data as any[]
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
const container = unref(wrapEl) as HTMLElement
|
|
||||||
if (!container) return
|
|
||||||
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
|
|
||||||
|
|
||||||
const length = data.length
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
if (i < unref(innerCols)) {
|
|
||||||
heights.value[i] = data[i][props.height as string]
|
|
||||||
filterData.value.push({
|
|
||||||
...data[i],
|
|
||||||
top: 0,
|
|
||||||
left: i * (width + gap)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 其他行,先找出最矮的那一列 和 索引
|
|
||||||
// 假设最小高度是第一个元素
|
|
||||||
let minHeight = heights.value[0]
|
|
||||||
let index = 0
|
|
||||||
// 找出最小高度
|
|
||||||
for (let j = 1; j < unref(innerCols); j++) {
|
|
||||||
if (unref(heights)[j] < minHeight) {
|
|
||||||
minHeight = unref(heights)[j]
|
|
||||||
index = j
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新最矮高度
|
|
||||||
heights.value[index] += data[i][props.height as string] + gap
|
|
||||||
filterData.value.push({
|
|
||||||
...data[i],
|
|
||||||
top: minHeight + gap,
|
|
||||||
left: index * (width + gap)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wrapHeight.value = Math.max(...unref(heights))
|
|
||||||
wrapWidth.value = unref(innerCols) * (width + gap) - gap
|
|
||||||
}
|
|
||||||
|
|
||||||
const flexWaterfall = async () => {
|
|
||||||
const { width, gap } = prop
|
|
||||||
const data = prop.data as any[]
|
|
||||||
await nextTick()
|
|
||||||
|
|
||||||
const container = unref(wrapEl) as HTMLElement
|
|
||||||
if (!container) return
|
|
||||||
innerCols.value = prop.cols ?? Math.floor(container.clientWidth / (width + gap))
|
|
||||||
|
|
||||||
const length = data.length
|
|
||||||
// 根据列数,创建数组
|
|
||||||
const arr = new Array(unref(innerCols)).fill([])
|
|
||||||
// 循环data,依次插入到arr中
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
const index = i % unref(innerCols)
|
|
||||||
arr[index] = [...arr[index], data[i]]
|
|
||||||
}
|
|
||||||
filterData.value = arr
|
|
||||||
}
|
|
||||||
|
|
||||||
const initLayout = () => {
|
|
||||||
const { layout } = prop
|
|
||||||
if (layout === 'javascript') {
|
|
||||||
filterWaterfall()
|
|
||||||
} else if (layout === 'flex') {
|
|
||||||
flexWaterfall()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => [prop.data, prop.cols],
|
|
||||||
() => {
|
|
||||||
initLayout()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
immediate: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (unref(prop.reset)) {
|
|
||||||
useEventListener(window, 'resize', debounce(initLayout, 300))
|
|
||||||
}
|
|
||||||
useIntersectionObserver(
|
|
||||||
unref(loadMore),
|
|
||||||
([{ isIntersecting }]) => {
|
|
||||||
if (isIntersecting && !prop.loading && !prop.end) {
|
|
||||||
emit('loadMore')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
threshold: 0.1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
:class="[
|
|
||||||
prefixCls,
|
|
||||||
'flex',
|
|
||||||
'items-center',
|
|
||||||
{
|
|
||||||
'justify-center': autoCenter
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
ref="wrapEl"
|
|
||||||
:style="{
|
|
||||||
height: `${layout === 'javascript' ? wrapHeight + 40 : 'auto'}px`
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<template v-if="layout === 'javascript'">
|
|
||||||
<div class="relative" :style="{ width: `${wrapWidth}px`, height: `${wrapHeight + 40}px` }">
|
|
||||||
<div
|
|
||||||
v-for="(item, $index) in filterData"
|
|
||||||
:class="[
|
|
||||||
`${prefixCls}-item__${$index}`,
|
|
||||||
{
|
|
||||||
absolute: layout === 'javascript'
|
|
||||||
}
|
|
||||||
]"
|
|
||||||
:key="`water-${$index}`"
|
|
||||||
:style="{
|
|
||||||
width: `${width}px`,
|
|
||||||
height: `${item[props.height as string]}px`,
|
|
||||||
top: `${item.top}px`,
|
|
||||||
left: `${item.left}px`
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<img :src="item[props.src as string]" class="w-full h-full block" alt="" srcset="" />
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref="loadMore"
|
|
||||||
class="h-40px flex justify-center absolute w-full"
|
|
||||||
:style="{
|
|
||||||
top: `${wrapHeight + gap}px`
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ end ? endText : loadingText }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="layout === 'flex'">
|
|
||||||
<div
|
|
||||||
class="relative flex pb-40px"
|
|
||||||
:style="{
|
|
||||||
width: cols ? '100%' : 'auto'
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(item, $index) in filterData"
|
|
||||||
:key="`waterWrap-${$index}`"
|
|
||||||
class="flex-1"
|
|
||||||
:style="{
|
|
||||||
marginRight: $index === filterData.length - 1 ? '0' : `${gap}px`
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
v-for="(child, i) in item"
|
|
||||||
:key="`waterWrap-${$index}-${i}`"
|
|
||||||
:style="{
|
|
||||||
marginBottom: `${gap}px`,
|
|
||||||
width: cols ? '100%' : `${width}px`,
|
|
||||||
height: cols ? 'auto' : `${child[props.height as string]}px`
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<img :src="child[props.src as string]" class="w-full h-full block" alt="" srcset="" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
ref="loadMore"
|
|
||||||
class="h-40px flex justify-center absolute w-full items-center"
|
|
||||||
:style="{
|
|
||||||
bottom: 0
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
{{ end ? endText : loadingText }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
@ -1,10 +1,8 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { Icon } from './Icon'
|
import { Icon } from './Icon'
|
||||||
import { Permission } from './Permission'
|
import { Permission } from './Permission'
|
||||||
import { BaseButton } from './Button'
|
|
||||||
|
|
||||||
export const setupGlobCom = (app: App<Element>): void => {
|
export const setupGlobCom = (app: App<Element>): void => {
|
||||||
app.component('Icon', Icon)
|
app.component('Icon', Icon)
|
||||||
app.component('Permission', Permission)
|
app.component('Permission', Permission)
|
||||||
app.component('BaseButton', BaseButton)
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
||||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
import qs from 'qs'
|
import qs from 'qs'
|
||||||
import { config } from './config'
|
import { config } from './config'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
@ -7,6 +9,8 @@ import request from '@/config/axios'
|
|||||||
|
|
||||||
const { result_code, unauthorized_code, request_timeout } = config
|
const { result_code, unauthorized_code, request_timeout } = config
|
||||||
|
|
||||||
|
const { getStorage, setStorage } = useStorage()
|
||||||
|
|
||||||
// 创建axios实例
|
// 创建axios实例
|
||||||
const service: AxiosInstance = axios.create({
|
const service: AxiosInstance = axios.create({
|
||||||
baseURL: '/api', // api 的 base_url
|
baseURL: '/api', // api 的 base_url
|
||||||
@ -17,10 +21,10 @@ const service: AxiosInstance = axios.create({
|
|||||||
// request拦截器
|
// request拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config: InternalAxiosRequestConfig) => {
|
(config: InternalAxiosRequestConfig) => {
|
||||||
const authStore = useAuthStoreWithOut()
|
const appStore = useAppStore()
|
||||||
const token = authStore.getToken
|
const token = getStorage(appStore.getToken)
|
||||||
if (token !== '') {
|
if (token !== '') {
|
||||||
;(config.headers as any)[authStore.getTokenKey ?? 'Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
;(config.headers as any)['Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
config.method === 'post' &&
|
config.method === 'post' &&
|
||||||
@ -83,18 +87,18 @@ service.interceptors.response.use(
|
|||||||
if (refresh === '1') {
|
if (refresh === '1') {
|
||||||
// 因token快过期,刷新token
|
// 因token快过期,刷新token
|
||||||
refreshToken().then((res) => {
|
refreshToken().then((res) => {
|
||||||
const authStore = useAuthStoreWithOut()
|
const appStore = useAppStore()
|
||||||
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||||
authStore.setRefreshToken(res.data.refresh_token)
|
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return response.data
|
return response.data
|
||||||
} else if (code === unauthorized_code) {
|
} else if (code === unauthorized_code) {
|
||||||
// 因token无效,token过期导致
|
// 因token无效,token过期导致
|
||||||
refreshToken().then((res) => {
|
refreshToken().then((res) => {
|
||||||
const authStore = useAuthStoreWithOut()
|
const appStore = useAppStore()
|
||||||
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||||
authStore.setRefreshToken(res.data.refresh_token)
|
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||||
ElMessage.error('操作失败,请重试')
|
ElMessage.error('操作失败,请重试')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -104,7 +108,7 @@ service.interceptors.response.use(
|
|||||||
(error: AxiosError) => {
|
(error: AxiosError) => {
|
||||||
console.log('err', error)
|
console.log('err', error)
|
||||||
let { message } = error
|
let { message } = error
|
||||||
const authStore = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
const status = error.response?.status
|
const status = error.response?.status
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 400:
|
case 400:
|
||||||
@ -113,7 +117,7 @@ service.interceptors.response.use(
|
|||||||
case 401:
|
case 401:
|
||||||
// 强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
// 强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
||||||
authStore.logout()
|
authStore.logout()
|
||||||
message = '认证已失效,请重新登录'
|
message = '认证已过期,请重新登录'
|
||||||
break
|
break
|
||||||
case 403:
|
case 403:
|
||||||
// 强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
// 强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
||||||
@ -154,8 +158,8 @@ service.interceptors.response.use(
|
|||||||
|
|
||||||
// 刷新Token
|
// 刷新Token
|
||||||
const refreshToken = (): Promise<IResponse> => {
|
const refreshToken = (): Promise<IResponse> => {
|
||||||
const authStore = useAuthStoreWithOut()
|
const appStore = useAppStore()
|
||||||
const data = authStore.getRefreshToken
|
const data = getStorage(appStore.getRefreshToken)
|
||||||
return request.post({ url: '/auth/token/refresh', data })
|
return request.post({ url: '/auth/token/refresh', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,11 +5,11 @@ import { isArray } from '@/utils/is'
|
|||||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
// 全部权限
|
// 全部权限
|
||||||
const all_permission = ['*.*.*']
|
const all_permission = ['*.*.*']
|
||||||
const hasPermission = (value: string | string[]): boolean => {
|
const hasPermission = (value: string | string[]): boolean => {
|
||||||
const authStore = useAuthStoreWithOut()
|
|
||||||
const permissions = authStore.getPermissions
|
const permissions = authStore.getPermissions
|
||||||
if (!value) {
|
if (!value) {
|
||||||
throw new Error(t('permission.hasPermission'))
|
throw new Error(t('permission.hasPermission'))
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
const useClipboard = () => {
|
|
||||||
const copied = ref(false)
|
|
||||||
const text = ref('')
|
|
||||||
const isSupported = ref(false)
|
|
||||||
|
|
||||||
if (!navigator.clipboard && !document.execCommand) {
|
|
||||||
isSupported.value = false
|
|
||||||
} else {
|
|
||||||
isSupported.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const copy = (str: string) => {
|
|
||||||
if (navigator.clipboard) {
|
|
||||||
navigator.clipboard.writeText(str).then(() => {
|
|
||||||
text.value = str
|
|
||||||
copied.value = true
|
|
||||||
resetCopied()
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const input = document.createElement('input')
|
|
||||||
input.setAttribute('readonly', 'readonly')
|
|
||||||
input.setAttribute('value', str)
|
|
||||||
document.body.appendChild(input)
|
|
||||||
input.select()
|
|
||||||
input.setSelectionRange(0, 9999)
|
|
||||||
if (document.execCommand('copy')) {
|
|
||||||
text.value = str
|
|
||||||
document.execCommand('copy')
|
|
||||||
copied.value = true
|
|
||||||
resetCopied()
|
|
||||||
}
|
|
||||||
document.body.removeChild(input)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetCopied = () => {
|
|
||||||
setTimeout(() => {
|
|
||||||
copied.value = false
|
|
||||||
}, 1500)
|
|
||||||
}
|
|
||||||
|
|
||||||
return { copy, text, copied, isSupported }
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useClipboard }
|
|
@ -79,14 +79,19 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
|||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const schemaItem = crudSchema[i]
|
const schemaItem = crudSchema[i]
|
||||||
// 判断是否隐藏
|
// 判断是否隐藏
|
||||||
const searchSchemaItem = {
|
if (!schemaItem?.search?.hidden) {
|
||||||
component: schemaItem?.search?.component || 'Input',
|
const searchSchemaItem = {
|
||||||
...schemaItem.search,
|
component: schemaItem?.search?.component || 'Input',
|
||||||
field: schemaItem.field,
|
...schemaItem.search,
|
||||||
label: schemaItem.search?.label || schemaItem.label
|
field: schemaItem.field,
|
||||||
}
|
label: schemaItem.label
|
||||||
|
}
|
||||||
|
|
||||||
searchSchema.push(searchSchemaItem)
|
// 删除不必要的字段
|
||||||
|
delete searchSchemaItem.hidden
|
||||||
|
|
||||||
|
searchSchema.push(searchSchemaItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return searchSchema
|
return searchSchema
|
||||||
@ -98,8 +103,8 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
|
|||||||
conversion: (schema: CrudSchema) => {
|
conversion: (schema: CrudSchema) => {
|
||||||
if (!schema?.table?.hidden) {
|
if (!schema?.table?.hidden) {
|
||||||
return {
|
return {
|
||||||
...schema,
|
...schema.table,
|
||||||
...schema.table
|
...schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,14 +127,19 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
|||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
const formItem = crudSchema[i]
|
const formItem = crudSchema[i]
|
||||||
// 判断是否隐藏
|
// 判断是否隐藏
|
||||||
const formSchemaItem = {
|
if (!formItem?.form?.hidden) {
|
||||||
component: formItem?.form?.component || 'Input',
|
const formSchemaItem = {
|
||||||
...formItem.form,
|
component: formItem?.form?.component || 'Input',
|
||||||
field: formItem.field,
|
...formItem.form,
|
||||||
label: formItem.form?.label || formItem.label
|
field: formItem.field,
|
||||||
}
|
label: formItem.label
|
||||||
|
}
|
||||||
|
|
||||||
formSchema.push(formSchemaItem)
|
// 删除不必要的字段
|
||||||
|
delete formSchemaItem.hidden
|
||||||
|
|
||||||
|
formSchema.push(formSchemaItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return formSchema
|
return formSchema
|
||||||
|
@ -2,7 +2,6 @@ import type { Form, FormExpose } from '@/components/Form'
|
|||||||
import type { ElForm, ElFormItem } from 'element-plus'
|
import type { ElForm, ElFormItem } from 'element-plus'
|
||||||
import { ref, unref, nextTick } from 'vue'
|
import { ref, unref, nextTick } from 'vue'
|
||||||
import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
|
import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
|
||||||
import { isEmptyVal, isObject } from '@/utils/is'
|
|
||||||
|
|
||||||
export const useForm = () => {
|
export const useForm = () => {
|
||||||
// From实例
|
// From实例
|
||||||
@ -94,27 +93,9 @@ export const useForm = () => {
|
|||||||
* @description 获取表单数据
|
* @description 获取表单数据
|
||||||
* @returns form data
|
* @returns form data
|
||||||
*/
|
*/
|
||||||
getFormData: async <T = Recordable>(filterEmptyVal = true): Promise<T> => {
|
getFormData: async <T = Recordable>(): Promise<T> => {
|
||||||
const form = await getForm()
|
const form = await getForm()
|
||||||
const model = form?.formModel as any
|
return form?.formModel as T
|
||||||
if (filterEmptyVal) {
|
|
||||||
// 使用reduce过滤空值,并返回一个新对象
|
|
||||||
return Object.keys(model).reduce((prev, next) => {
|
|
||||||
const value = model[next]
|
|
||||||
if (!isEmptyVal(value)) {
|
|
||||||
if (isObject(value)) {
|
|
||||||
if (Object.keys(value).length > 0) {
|
|
||||||
prev[next] = value
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prev[next] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return prev
|
|
||||||
}, {}) as T
|
|
||||||
} else {
|
|
||||||
return model as T
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { ref, onBeforeUnmount } from 'vue'
|
|
||||||
|
|
||||||
const useNetwork = () => {
|
|
||||||
const online = ref(true)
|
|
||||||
|
|
||||||
const updateNetwork = () => {
|
|
||||||
online.value = navigator.onLine
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('online', updateNetwork)
|
|
||||||
window.addEventListener('offline', updateNetwork)
|
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
|
||||||
window.removeEventListener('online', updateNetwork)
|
|
||||||
window.removeEventListener('offline', updateNetwork)
|
|
||||||
})
|
|
||||||
|
|
||||||
return { online }
|
|
||||||
}
|
|
||||||
|
|
||||||
export { useNetwork }
|
|
@ -1,15 +1,13 @@
|
|||||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
export const usePageLoading = () => {
|
export const usePageLoading = () => {
|
||||||
const loadStart = () => {
|
const loadStart = () => {
|
||||||
const appStore = useAppStoreWithOut()
|
|
||||||
|
|
||||||
appStore.setPageLoading(true)
|
appStore.setPageLoading(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadDone = () => {
|
const loadDone = () => {
|
||||||
const appStore = useAppStoreWithOut()
|
|
||||||
|
|
||||||
appStore.setPageLoading(false)
|
appStore.setPageLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ const getValueType = (value: any) => {
|
|||||||
return type.slice(8, -1)
|
return type.slice(8, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'localStorage') => {
|
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
|
||||||
const setStorage = (key: string, value: any) => {
|
const setStorage = (key: string, value: any) => {
|
||||||
const valueType = getValueType(value)
|
const valueType = getValueType(value)
|
||||||
window[type].setItem(key, JSON.stringify({ type: valueType, value }))
|
window[type].setItem(key, JSON.stringify({ type: valueType, value }))
|
||||||
|
@ -3,10 +3,10 @@ import { isString } from '@/utils/is'
|
|||||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
|
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
export const useTitle = (newTitle?: string) => {
|
export const useTitle = (newTitle?: string) => {
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const appStore = useAppStoreWithOut()
|
|
||||||
|
|
||||||
const title = ref(
|
const title = ref(
|
||||||
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
|
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
|
||||||
)
|
)
|
||||||
|
@ -71,14 +71,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
background-color: var(--app-content-bg-color);
|
background-color: var(--app-content-bg-color);
|
||||||
.@{prefix-cls}-content-scrollbar {
|
:deep(.@{elNamespace}-scrollbar__view) {
|
||||||
& > :deep(.el-scrollbar__wrap) {
|
height: 100% !important;
|
||||||
& > .@{elNamespace}-scrollbar__view {
|
|
||||||
display: flex;
|
|
||||||
height: 100% !important;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,6 +6,10 @@ import { computed } from 'vue'
|
|||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
|
const layout = computed(() => appStore.getLayout)
|
||||||
|
|
||||||
|
const fixedHeader = computed(() => appStore.getFixedHeader)
|
||||||
|
|
||||||
const footer = computed(() => appStore.getFooter)
|
const footer = computed(() => appStore.getFooter)
|
||||||
|
|
||||||
const tagsViewStore = useTagsViewStore()
|
const tagsViewStore = useTagsViewStore()
|
||||||
@ -13,12 +17,39 @@ const tagsViewStore = useTagsViewStore()
|
|||||||
const getCaches = computed((): string[] => {
|
const getCaches = computed((): string[] => {
|
||||||
return tagsViewStore.getCachedViews
|
return tagsViewStore.getCachedViews
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const tagsView = computed(() => appStore.getTagsView)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<section
|
<section
|
||||||
:class="[
|
:class="[
|
||||||
'flex-1 p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]'
|
'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color-new)] dark:bg-[var(--el-bg-color)]',
|
||||||
|
{
|
||||||
|
'!min-h-[calc(100%-var(--app-footer-height))]':
|
||||||
|
(fixedHeader &&
|
||||||
|
(layout === 'classic' || layout === 'topLeft' || layout === 'top') &&
|
||||||
|
footer) ||
|
||||||
|
(!tagsView && layout === 'top' && footer),
|
||||||
|
|
||||||
|
'!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':
|
||||||
|
tagsView && layout === 'top' && footer,
|
||||||
|
|
||||||
|
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':
|
||||||
|
!fixedHeader && layout === 'classic' && footer,
|
||||||
|
|
||||||
|
'!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':
|
||||||
|
!fixedHeader && layout === 'topLeft' && footer,
|
||||||
|
|
||||||
|
// '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height)-var(--top-tool-height))]':
|
||||||
|
// !fixedHeader && layout === 'top' && footer,
|
||||||
|
|
||||||
|
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':
|
||||||
|
fixedHeader && layout === 'cutMenu' && footer,
|
||||||
|
|
||||||
|
'!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':
|
||||||
|
!fixedHeader && layout === 'cutMenu' && footer
|
||||||
|
}
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
<router-view>
|
<router-view>
|
||||||
|
@ -42,7 +42,8 @@ export default defineComponent({
|
|||||||
id={`${variables.namespace}-tool-header`}
|
id={`${variables.namespace}-tool-header`}
|
||||||
class={[
|
class={[
|
||||||
prefixCls,
|
prefixCls,
|
||||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
|
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
|
||||||
|
'dark:bg-[var(--el-bg-color)]'
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
{layout.value !== 'top' ? (
|
{layout.value !== 'top' ? (
|
||||||
|
@ -195,8 +195,8 @@ export const useRenderLayout = () => {
|
|||||||
`${prefixCls}-content`,
|
`${prefixCls}-content`,
|
||||||
'w-full',
|
'w-full',
|
||||||
{
|
{
|
||||||
'h-[calc(100%-var(--top-tool-height))]': !fixedHeader.value,
|
'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
|
||||||
'h-[calc(100%-var(--tags-view-height)-var(--top-tool-height))]': fixedHeader.value
|
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -50,8 +50,6 @@ export default {
|
|||||||
notSpace: 'Spaces are not allowed',
|
notSpace: 'Spaces are not allowed',
|
||||||
notSpecialCharacters: 'Special characters are not allowed',
|
notSpecialCharacters: 'Special characters are not allowed',
|
||||||
isEqual: 'The two are not equal',
|
isEqual: 'The two are not equal',
|
||||||
// 列设置
|
|
||||||
setting: 'Setting',
|
|
||||||
selectAll: 'Select all',
|
selectAll: 'Select all',
|
||||||
SerialNumberColumn: 'Index column'
|
SerialNumberColumn: 'Index column'
|
||||||
},
|
},
|
||||||
@ -188,15 +186,7 @@ export default {
|
|||||||
permission: 'Permission test page',
|
permission: 'Permission test page',
|
||||||
function: 'Function',
|
function: 'Function',
|
||||||
multipleTabs: 'Multiple tabs',
|
multipleTabs: 'Multiple tabs',
|
||||||
details: 'Details',
|
details: 'Details'
|
||||||
iconPicker: 'Icon picker',
|
|
||||||
request: 'Request',
|
|
||||||
waterfall: 'Waterfall',
|
|
||||||
imageCropping: 'Image cropping',
|
|
||||||
videoPlayer: 'Video player',
|
|
||||||
// 表格视频预览
|
|
||||||
tableVideoPreview: 'Table video preview',
|
|
||||||
cardTable: 'Card table'
|
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
hasPermission: 'Please set the operation permission value'
|
hasPermission: 'Please set the operation permission value'
|
||||||
@ -342,8 +332,7 @@ export default {
|
|||||||
lazyLoad: 'Lazy load',
|
lazyLoad: 'Lazy load',
|
||||||
upload: 'Upload',
|
upload: 'Upload',
|
||||||
// 用户头像
|
// 用户头像
|
||||||
userAvatar: 'User avatar',
|
userAvatar: 'User avatar'
|
||||||
iconPicker: 'Icon picker'
|
|
||||||
},
|
},
|
||||||
guideDemo: {
|
guideDemo: {
|
||||||
guide: 'Guide',
|
guide: 'Guide',
|
||||||
@ -472,9 +461,7 @@ export default {
|
|||||||
fixedHeaderOrAuto: 'Fixed header or auto',
|
fixedHeaderOrAuto: 'Fixed header or auto',
|
||||||
getSelections: 'Get selections',
|
getSelections: 'Get selections',
|
||||||
preview: 'Preview',
|
preview: 'Preview',
|
||||||
showOrHiddenSortable: 'Show or hidden sortable',
|
showOrHiddenSortable: 'Show or hidden sortable'
|
||||||
videoPreview: 'Video preview',
|
|
||||||
cardTable: 'Card table'
|
|
||||||
},
|
},
|
||||||
richText: {
|
richText: {
|
||||||
richText: 'Rich text',
|
richText: 'Rich text',
|
||||||
|
@ -50,7 +50,6 @@ export default {
|
|||||||
notSpace: '不能包含空格',
|
notSpace: '不能包含空格',
|
||||||
notSpecialCharacters: '不能包含特殊字符',
|
notSpecialCharacters: '不能包含特殊字符',
|
||||||
isEqual: '两次输入不一致',
|
isEqual: '两次输入不一致',
|
||||||
setting: '设置',
|
|
||||||
selectAll: '全选',
|
selectAll: '全选',
|
||||||
SerialNumberColumn: '序号列'
|
SerialNumberColumn: '序号列'
|
||||||
},
|
},
|
||||||
@ -85,7 +84,7 @@ export default {
|
|||||||
sizeIcon: '尺寸图标',
|
sizeIcon: '尺寸图标',
|
||||||
localeIcon: '多语言图标',
|
localeIcon: '多语言图标',
|
||||||
tagsView: '标签页',
|
tagsView: '标签页',
|
||||||
logo: 'Logo',
|
logo: '标志',
|
||||||
greyMode: '灰色模式',
|
greyMode: '灰色模式',
|
||||||
fixedHeader: '固定头部',
|
fixedHeader: '固定头部',
|
||||||
headerTheme: '头部主题',
|
headerTheme: '头部主题',
|
||||||
@ -185,14 +184,7 @@ export default {
|
|||||||
permission: '权限测试页',
|
permission: '权限测试页',
|
||||||
function: '功能',
|
function: '功能',
|
||||||
multipleTabs: '多开标签页',
|
multipleTabs: '多开标签页',
|
||||||
details: '详情页',
|
details: '详情页'
|
||||||
iconPicker: '图标选择器',
|
|
||||||
request: '请求',
|
|
||||||
waterfall: '瀑布流',
|
|
||||||
imageCropping: '图片裁剪',
|
|
||||||
videoPlayer: '视频播放器',
|
|
||||||
tableVideoPreview: '表格视频预览',
|
|
||||||
cardTable: '卡片表格'
|
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
hasPermission: '请设置操作权限值'
|
hasPermission: '请设置操作权限值'
|
||||||
@ -335,8 +327,7 @@ export default {
|
|||||||
customContent: '自定义内容',
|
customContent: '自定义内容',
|
||||||
lazyLoad: '懒加载',
|
lazyLoad: '懒加载',
|
||||||
upload: '上传',
|
upload: '上传',
|
||||||
userAvatar: '用户头像',
|
userAvatar: '用户头像'
|
||||||
iconPicker: '图标选择器'
|
|
||||||
},
|
},
|
||||||
guideDemo: {
|
guideDemo: {
|
||||||
guide: '引导页',
|
guide: '引导页',
|
||||||
@ -463,9 +454,7 @@ export default {
|
|||||||
fixedHeaderOrAuto: '固定头部/自动',
|
fixedHeaderOrAuto: '固定头部/自动',
|
||||||
getSelections: '获取多选数据',
|
getSelections: '获取多选数据',
|
||||||
preview: '封面',
|
preview: '封面',
|
||||||
showOrHiddenSortable: '显示/隐藏排序',
|
showOrHiddenSortable: '显示/隐藏排序'
|
||||||
videoPreview: '视频预览',
|
|
||||||
cardTable: '卡片表格'
|
|
||||||
},
|
},
|
||||||
richText: {
|
richText: {
|
||||||
richText: '富文本',
|
richText: '富文本',
|
||||||
@ -541,7 +530,7 @@ export default {
|
|||||||
menu: {
|
menu: {
|
||||||
menuName: '菜单名称',
|
menuName: '菜单名称',
|
||||||
icon: '图标',
|
icon: '图标',
|
||||||
permission: '按钮权限',
|
permission: '权限标识',
|
||||||
component: '组件',
|
component: '组件',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'vue/jsx'
|
|
||||||
|
|
||||||
// 引入windi css
|
// 引入windi css
|
||||||
import '@/plugins/unocss'
|
import '@/plugins/unocss'
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { hasRoute } from './router'
|
|
||||||
import router from './router'
|
import router from './router'
|
||||||
|
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||||
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { useTitle } from '@/hooks/web/useTitle'
|
import { useTitle } from '@/hooks/web/useTitle'
|
||||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||||
@ -8,19 +9,23 @@ import { usePageLoading } from '@/hooks/web/usePageLoading'
|
|||||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { getRoleMenusApi } from '@/api/login'
|
import { getRoleMenusApi } from '@/api/login'
|
||||||
|
|
||||||
|
const permissionStore = usePermissionStoreWithOut()
|
||||||
|
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
|
const { getStorage, setStorage } = useStorage()
|
||||||
|
|
||||||
const { start, done } = useNProgress()
|
const { start, done } = useNProgress()
|
||||||
|
|
||||||
const { loadStart, loadDone } = usePageLoading()
|
const { loadStart, loadDone } = usePageLoading()
|
||||||
|
|
||||||
const whiteList = ['/login', '/docs/privacy', '/docs/agreement'] // 不重定向白名单
|
const whiteList = ['/login'] // 不重定向白名单
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
start()
|
start()
|
||||||
loadStart()
|
loadStart()
|
||||||
const permissionStore = usePermissionStoreWithOut()
|
if (getStorage(appStore.getToken)) {
|
||||||
const authStore = useAuthStoreWithOut()
|
|
||||||
|
|
||||||
if (authStore.getToken) {
|
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({ path: '/' })
|
next({ path: '/' })
|
||||||
} else if (to.path === '/reset/password') {
|
} else if (to.path === '/reset/password') {
|
||||||
@ -30,9 +35,6 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
await authStore.setUserInfo()
|
await authStore.setUserInfo()
|
||||||
}
|
}
|
||||||
if (permissionStore.getIsAddRouters) {
|
if (permissionStore.getIsAddRouters) {
|
||||||
if (!hasRoute(to.path)) {
|
|
||||||
authStore.logout('认证已过期,请重新登录!')
|
|
||||||
}
|
|
||||||
next()
|
next()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,6 +42,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
// 开发者可根据实际情况进行修改
|
// 开发者可根据实际情况进行修改
|
||||||
const res = await getRoleMenusApi()
|
const res = await getRoleMenusApi()
|
||||||
const routers = res.data || []
|
const routers = res.data || []
|
||||||
|
setStorage('roleRouters', routers)
|
||||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||||
permissionStore.getAddRouters.forEach((route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
|
@ -12,13 +12,7 @@ export const setupElementPlus = (app: App<Element>) => {
|
|||||||
app.use(plugin)
|
app.use(plugin)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 为了开发环境启动更快,一次性引入所有样式
|
|
||||||
if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
|
|
||||||
import('element-plus/dist/index.css')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
components.forEach((component) => {
|
components.forEach((component) => {
|
||||||
app.component(component.name!, component)
|
app.component(component.name, component)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1 +1,3 @@
|
|||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
|
|
||||||
|
import '@purge-icons/generated'
|
||||||
|
@ -70,37 +70,6 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
|||||||
noTagsView: true
|
noTagsView: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/docs',
|
|
||||||
name: 'Docs',
|
|
||||||
meta: {
|
|
||||||
hidden: true,
|
|
||||||
title: '在线文档',
|
|
||||||
noTagsView: true
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'privacy',
|
|
||||||
name: 'Privacy',
|
|
||||||
component: () => import('@/views/Vadmin/Docs/Privacy.vue'),
|
|
||||||
meta: {
|
|
||||||
hidden: true,
|
|
||||||
title: '隐私政策',
|
|
||||||
noTagsView: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'agreement',
|
|
||||||
name: 'Agreement',
|
|
||||||
component: () => import('@/views/Vadmin/Docs/Agreement.vue'),
|
|
||||||
meta: {
|
|
||||||
hidden: true,
|
|
||||||
title: '用户协议',
|
|
||||||
noTagsView: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/404',
|
||||||
component: () => import('@/views/Error/404.vue'),
|
component: () => import('@/views/Error/404.vue'),
|
||||||
@ -146,17 +115,7 @@ const router = createRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const resetRouter = (): void => {
|
export const resetRouter = (): void => {
|
||||||
const resetWhiteNameList = [
|
const resetWhiteNameList = ['Login', 'NoFind', 'Root']
|
||||||
'Login',
|
|
||||||
'NoFind',
|
|
||||||
'Root',
|
|
||||||
'ResetPassword',
|
|
||||||
'Redirect',
|
|
||||||
'Home',
|
|
||||||
'Docs',
|
|
||||||
'Privacy',
|
|
||||||
'Agreement'
|
|
||||||
]
|
|
||||||
router.getRoutes().forEach((route) => {
|
router.getRoutes().forEach((route) => {
|
||||||
const { name } = route
|
const { name } = route
|
||||||
if (name && !resetWhiteNameList.includes(name as string)) {
|
if (name && !resetWhiteNameList.includes(name as string)) {
|
||||||
@ -165,12 +124,6 @@ export const resetRouter = (): void => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 判断是否已经有某个路径的路由配置
|
|
||||||
export const hasRoute = (path: string): boolean => {
|
|
||||||
const resolvedRoute = router.resolve(path)
|
|
||||||
return resolvedRoute.matched.length > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setupRouter = (app: App<Element>) => {
|
export const setupRouter = (app: App<Element>) => {
|
||||||
app.use(router)
|
app.use(router)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
import piniaPersist from 'pinia-plugin-persist'
|
||||||
|
|
||||||
// pinia-plugin-persistedstate 持久化存储官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
|
|
||||||
|
|
||||||
const store = createPinia()
|
const store = createPinia()
|
||||||
|
|
||||||
store.use(piniaPluginPersistedstate)
|
store.use(piniaPersist)
|
||||||
|
|
||||||
export const setupStore = (app: App<Element>) => {
|
export const setupStore = (app: App<Element>) => {
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
@ -2,10 +2,9 @@ import { defineStore } from 'pinia'
|
|||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { setCssVar, humpToUnderline } from '@/utils'
|
import { setCssVar, humpToUnderline } from '@/utils'
|
||||||
import { ElMessage, ComponentSize } from 'element-plus'
|
import { ElMessage, ComponentSize } from 'element-plus'
|
||||||
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { useCssVar } from '@vueuse/core'
|
|
||||||
import { unref } from 'vue'
|
const { getStorage, setStorage } = useStorage()
|
||||||
import { useDark } from '@vueuse/core'
|
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
breadcrumb: boolean
|
breadcrumb: boolean
|
||||||
@ -26,6 +25,7 @@ interface AppState {
|
|||||||
pageLoading: boolean
|
pageLoading: boolean
|
||||||
layout: LayoutType
|
layout: LayoutType
|
||||||
title: string
|
title: string
|
||||||
|
userInfo: string
|
||||||
isDark: boolean
|
isDark: boolean
|
||||||
currentSize: ComponentSize
|
currentSize: ComponentSize
|
||||||
sizeMap: ComponentSize[]
|
sizeMap: ComponentSize[]
|
||||||
@ -34,6 +34,8 @@ interface AppState {
|
|||||||
theme: ThemeTypes
|
theme: ThemeTypes
|
||||||
fixedMenu: boolean
|
fixedMenu: boolean
|
||||||
|
|
||||||
|
token: string
|
||||||
|
refreshToken: string
|
||||||
logoImage: string
|
logoImage: string
|
||||||
footerContent: string
|
footerContent: string
|
||||||
icpNumber: string
|
icpNumber: string
|
||||||
@ -42,6 +44,7 @@ interface AppState {
|
|||||||
export const useAppStore = defineStore('app', {
|
export const useAppStore = defineStore('app', {
|
||||||
state: (): AppState => {
|
state: (): AppState => {
|
||||||
return {
|
return {
|
||||||
|
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其它项目冲突
|
||||||
sizeMap: ['default', 'large', 'small'],
|
sizeMap: ['default', 'large', 'small'],
|
||||||
mobile: false, // 是否是移动端
|
mobile: false, // 是否是移动端
|
||||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||||
@ -60,14 +63,14 @@ export const useAppStore = defineStore('app', {
|
|||||||
fixedHeader: true, // 固定toolheader
|
fixedHeader: true, // 固定toolheader
|
||||||
footer: true, // 显示页脚
|
footer: true, // 显示页脚
|
||||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||||
dynamicRouter: true, // 是否动态路由
|
dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
|
||||||
serverDynamicRouter: true, // 是否服务端渲染动态路由
|
serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
|
||||||
fixedMenu: false, // 是否固定菜单
|
fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
|
||||||
|
|
||||||
layout: 'classic', // layout布局
|
layout: getStorage('layout') || 'classic', // layout布局
|
||||||
isDark: false, // 是否是暗黑模式
|
isDark: getStorage('isDark'), // 是否是暗黑模式
|
||||||
currentSize: 'default', // 组件尺寸
|
currentSize: getStorage('default') || 'default', // 组件尺寸
|
||||||
theme: {
|
theme: getStorage('theme') || {
|
||||||
// 主题色
|
// 主题色
|
||||||
elColorPrimary: '#409eff',
|
elColorPrimary: '#409eff',
|
||||||
// 左侧菜单边框颜色
|
// 左侧菜单边框颜色
|
||||||
@ -98,6 +101,8 @@ export const useAppStore = defineStore('app', {
|
|||||||
topToolBorderColor: '#eee'
|
topToolBorderColor: '#eee'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
token: 'Token', // 存储Token字段
|
||||||
|
refreshToken: 'RefreshToken', // 存储刷新Token字段
|
||||||
logoImage: '', // logo图片
|
logoImage: '', // logo图片
|
||||||
footerContent: '', // 页脚内容
|
footerContent: '', // 页脚内容
|
||||||
icpNumber: '' // 备案号
|
icpNumber: '' // 备案号
|
||||||
@ -161,6 +166,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
getTitle(): string {
|
getTitle(): string {
|
||||||
return this.title
|
return this.title
|
||||||
},
|
},
|
||||||
|
getUserInfo(): string {
|
||||||
|
return this.userInfo
|
||||||
|
},
|
||||||
getIsDark(): boolean {
|
getIsDark(): boolean {
|
||||||
return this.isDark
|
return this.isDark
|
||||||
},
|
},
|
||||||
@ -183,6 +191,12 @@ export const useAppStore = defineStore('app', {
|
|||||||
getLogoImage(): string {
|
getLogoImage(): string {
|
||||||
return this.logoImage
|
return this.logoImage
|
||||||
},
|
},
|
||||||
|
getToken(): string {
|
||||||
|
return this.token
|
||||||
|
},
|
||||||
|
getRefreshToken(): string {
|
||||||
|
return this.refreshToken
|
||||||
|
},
|
||||||
getFooterContent(): string {
|
getFooterContent(): string {
|
||||||
return this.footerContent
|
return this.footerContent
|
||||||
},
|
},
|
||||||
@ -231,12 +245,15 @@ export const useAppStore = defineStore('app', {
|
|||||||
this.greyMode = greyMode
|
this.greyMode = greyMode
|
||||||
},
|
},
|
||||||
setDynamicRouter(dynamicRouter: boolean) {
|
setDynamicRouter(dynamicRouter: boolean) {
|
||||||
|
setStorage('dynamicRouter', dynamicRouter)
|
||||||
this.dynamicRouter = dynamicRouter
|
this.dynamicRouter = dynamicRouter
|
||||||
},
|
},
|
||||||
setServerDynamicRouter(serverDynamicRouter: boolean) {
|
setServerDynamicRouter(serverDynamicRouter: boolean) {
|
||||||
|
setStorage('serverDynamicRouter', serverDynamicRouter)
|
||||||
this.serverDynamicRouter = serverDynamicRouter
|
this.serverDynamicRouter = serverDynamicRouter
|
||||||
},
|
},
|
||||||
setFixedMenu(fixedMenu: boolean) {
|
setFixedMenu(fixedMenu: boolean) {
|
||||||
|
setStorage('fixedMenu', fixedMenu)
|
||||||
this.fixedMenu = fixedMenu
|
this.fixedMenu = fixedMenu
|
||||||
},
|
},
|
||||||
setPageLoading(pageLoading: boolean) {
|
setPageLoading(pageLoading: boolean) {
|
||||||
@ -248,6 +265,7 @@ export const useAppStore = defineStore('app', {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.layout = layout
|
this.layout = layout
|
||||||
|
setStorage('layout', this.layout)
|
||||||
},
|
},
|
||||||
setTitle(title: string) {
|
setTitle(title: string) {
|
||||||
this.title = title
|
this.title = title
|
||||||
@ -261,22 +279,23 @@ export const useAppStore = defineStore('app', {
|
|||||||
document.documentElement.classList.add('light')
|
document.documentElement.classList.add('light')
|
||||||
document.documentElement.classList.remove('dark')
|
document.documentElement.classList.remove('dark')
|
||||||
}
|
}
|
||||||
this.setPrimaryLight()
|
setStorage('isDark', this.isDark)
|
||||||
},
|
},
|
||||||
setCurrentSize(currentSize: ComponentSize) {
|
setCurrentSize(currentSize: ComponentSize) {
|
||||||
this.currentSize = currentSize
|
this.currentSize = currentSize
|
||||||
|
setStorage('currentSize', this.currentSize)
|
||||||
},
|
},
|
||||||
setMobile(mobile: boolean) {
|
setMobile(mobile: boolean) {
|
||||||
this.mobile = mobile
|
this.mobile = mobile
|
||||||
},
|
},
|
||||||
setTheme(theme: ThemeTypes) {
|
setTheme(theme: ThemeTypes) {
|
||||||
this.theme = Object.assign(this.theme, theme)
|
this.theme = Object.assign(this.theme, theme)
|
||||||
|
setStorage('theme', this.theme)
|
||||||
},
|
},
|
||||||
setCssVarTheme() {
|
setCssVarTheme() {
|
||||||
for (const key in this.theme) {
|
for (const key in this.theme) {
|
||||||
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
||||||
}
|
}
|
||||||
this.setPrimaryLight()
|
|
||||||
},
|
},
|
||||||
setFooter(footer: boolean) {
|
setFooter(footer: boolean) {
|
||||||
this.footer = footer
|
this.footer = footer
|
||||||
@ -290,75 +309,8 @@ export const useAppStore = defineStore('app', {
|
|||||||
},
|
},
|
||||||
setIcpNumber(icpNumber: string) {
|
setIcpNumber(icpNumber: string) {
|
||||||
this.icpNumber = icpNumber
|
this.icpNumber = icpNumber
|
||||||
},
|
|
||||||
setPrimaryLight() {
|
|
||||||
if (this.theme.elColorPrimary) {
|
|
||||||
const elColorPrimary = this.theme.elColorPrimary
|
|
||||||
const color = this.isDark ? '#000000' : '#ffffff'
|
|
||||||
const lightList = [3, 5, 7, 8, 9]
|
|
||||||
lightList.forEach((v) => {
|
|
||||||
setCssVar(`--el-color-primary-light-${v}`, mix(color, elColorPrimary, v / 10))
|
|
||||||
})
|
|
||||||
setCssVar(`--el-color-primary-dark-2`, mix(color, elColorPrimary, 0.2))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setMenuTheme(color: string) {
|
|
||||||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
|
||||||
const isDarkColor = colorIsDark(color)
|
|
||||||
const theme: Recordable = {
|
|
||||||
// 左侧菜单边框颜色
|
|
||||||
leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',
|
|
||||||
// 左侧菜单背景颜色
|
|
||||||
leftMenuBgColor: color,
|
|
||||||
// 左侧菜单浅色背景颜色
|
|
||||||
leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,
|
|
||||||
// 左侧菜单选中背景颜色
|
|
||||||
leftMenuBgActiveColor: isDarkColor
|
|
||||||
? 'var(--el-color-primary)'
|
|
||||||
: hexToRGB(unref(primaryColor), 0.1),
|
|
||||||
// 左侧菜单收起选中背景颜色
|
|
||||||
leftMenuCollapseBgActiveColor: isDarkColor
|
|
||||||
? 'var(--el-color-primary)'
|
|
||||||
: hexToRGB(unref(primaryColor), 0.1),
|
|
||||||
// 左侧菜单字体颜色
|
|
||||||
leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',
|
|
||||||
// 左侧菜单选中字体颜色
|
|
||||||
leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',
|
|
||||||
// logo字体颜色
|
|
||||||
logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',
|
|
||||||
// logo边框颜色
|
|
||||||
logoBorderColor: isDarkColor ? color : '#eee'
|
|
||||||
}
|
|
||||||
this.setTheme(theme)
|
|
||||||
this.setCssVarTheme()
|
|
||||||
},
|
|
||||||
setHeaderTheme(color: string) {
|
|
||||||
const isDarkColor = colorIsDark(color)
|
|
||||||
const textColor = isDarkColor ? '#fff' : 'inherit'
|
|
||||||
const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'
|
|
||||||
const topToolBorderColor = isDarkColor ? color : '#eee'
|
|
||||||
setCssVar('--top-header-bg-color', color)
|
|
||||||
setCssVar('--top-header-text-color', textColor)
|
|
||||||
setCssVar('--top-header-hover-color', textHoverColor)
|
|
||||||
this.setTheme({
|
|
||||||
topHeaderBgColor: color,
|
|
||||||
topHeaderTextColor: textColor,
|
|
||||||
topHeaderHoverColor: textHoverColor,
|
|
||||||
topToolBorderColor
|
|
||||||
})
|
|
||||||
if (this.getLayout === 'top') {
|
|
||||||
this.setMenuTheme(color)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
initTheme() {
|
|
||||||
const isDark = useDark({
|
|
||||||
valueDark: 'dark',
|
|
||||||
valueLight: 'light'
|
|
||||||
})
|
|
||||||
isDark.value = this.getIsDark
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
persist: true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useAppStoreWithOut = () => {
|
export const useAppStoreWithOut = () => {
|
||||||
|
@ -2,6 +2,7 @@ import { defineStore } from 'pinia'
|
|||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { UserLoginType } from '@/api/login/types'
|
import { UserLoginType } from '@/api/login/types'
|
||||||
import { loginApi } from '@/api/login'
|
import { loginApi } from '@/api/login'
|
||||||
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { useStorage } from '@/hooks/web/useStorage'
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user'
|
import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user'
|
||||||
import { resetRouter } from '@/router'
|
import { resetRouter } from '@/router'
|
||||||
@ -9,7 +10,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
|
|||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
const { clear } = useStorage()
|
const { setStorage, clear } = useStorage()
|
||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
id?: number
|
id?: number
|
||||||
@ -31,9 +32,6 @@ export interface AuthState {
|
|||||||
isUser: boolean // 是否已经登录并获取到用户信息
|
isUser: boolean // 是否已经登录并获取到用户信息
|
||||||
roles: string[] // 当前用户角色 role_key 列表
|
roles: string[] // 当前用户角色 role_key 列表
|
||||||
permissions: string[] // 当前用户权限列表
|
permissions: string[] // 当前用户权限列表
|
||||||
tokenKey: string // 提交认证请求时,设置的 header key
|
|
||||||
token: string // 认证 token
|
|
||||||
refreshToken: string // 刷新 token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = defineStore('auth', {
|
export const useAuthStore = defineStore('auth', {
|
||||||
@ -42,22 +40,10 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
user: {},
|
user: {},
|
||||||
roles: [],
|
roles: [],
|
||||||
permissions: [],
|
permissions: [],
|
||||||
isUser: false,
|
isUser: false
|
||||||
tokenKey: 'Authorization',
|
|
||||||
token: '',
|
|
||||||
refreshToken: ''
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getTokenKey(): string {
|
|
||||||
return this.tokenKey
|
|
||||||
},
|
|
||||||
getToken(): string {
|
|
||||||
return this.token
|
|
||||||
},
|
|
||||||
getRefreshToken(): string {
|
|
||||||
return this.refreshToken
|
|
||||||
},
|
|
||||||
getUser(): UserState {
|
getUser(): UserState {
|
||||||
return this.user
|
return this.user
|
||||||
},
|
},
|
||||||
@ -72,34 +58,24 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setToken(token: string) {
|
|
||||||
this.token = token
|
|
||||||
},
|
|
||||||
setRefreshToken(refreshToken: string) {
|
|
||||||
this.refreshToken = refreshToken
|
|
||||||
},
|
|
||||||
async login(formData: UserLoginType) {
|
async login(formData: UserLoginType) {
|
||||||
formData.platform = '0'
|
formData.platform = '0'
|
||||||
const res = await loginApi(formData)
|
const res = await loginApi(formData)
|
||||||
if (res) {
|
if (res) {
|
||||||
this.token = `${res.data.token_type} ${res.data.access_token}`
|
const appStore = useAppStore()
|
||||||
this.refreshToken = res.data.refresh_token
|
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||||
|
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||||
// 获取当前登录用户的信息
|
// 获取当前登录用户的信息
|
||||||
await this.setUserInfo()
|
await this.setUserInfo()
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
reset() {
|
logout(message?: string) {
|
||||||
|
clear()
|
||||||
this.user = {}
|
this.user = {}
|
||||||
this.roles = []
|
this.roles = []
|
||||||
this.permissions = []
|
this.permissions = []
|
||||||
this.isUser = false
|
this.isUser = false
|
||||||
this.token = ''
|
|
||||||
this.refreshToken = ''
|
|
||||||
},
|
|
||||||
logout(message?: string) {
|
|
||||||
clear()
|
|
||||||
this.reset()
|
|
||||||
const tagsViewStore = useTagsViewStore()
|
const tagsViewStore = useTagsViewStore()
|
||||||
tagsViewStore.delAllViews()
|
tagsViewStore.delAllViews()
|
||||||
resetRouter()
|
resetRouter()
|
||||||
@ -129,8 +105,7 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
})
|
})
|
||||||
this.permissions = res.data.permissions
|
this.permissions = res.data.permissions
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
persist: true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useAuthStoreWithOut = () => {
|
export const useAuthStoreWithOut = () => {
|
||||||
|
@ -10,11 +10,7 @@ export const useDictStore = defineStore('dict', {
|
|||||||
state: (): DictState => ({
|
state: (): DictState => ({
|
||||||
dictObj: {}
|
dictObj: {}
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {},
|
||||||
getDictObjData(): Recordable {
|
|
||||||
return this.dictObj
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions: {
|
actions: {
|
||||||
async getDictObj(dictTypes: string[]) {
|
async getDictObj(dictTypes: string[]) {
|
||||||
const result: Recordable = {}
|
const result: Recordable = {}
|
||||||
@ -38,8 +34,7 @@ export const useDictStore = defineStore('dict', {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
persist: true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useDictStoreWithOut = () => {
|
export const useDictStoreWithOut = () => {
|
||||||
|
@ -5,7 +5,7 @@ import en from 'element-plus/es/locale/lang/en'
|
|||||||
import { useStorage } from '@/hooks/web/useStorage'
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { LocaleDropdownType } from '@/components/LocaleDropdown'
|
import { LocaleDropdownType } from '@/components/LocaleDropdown'
|
||||||
|
|
||||||
const { getStorage, setStorage } = useStorage('localStorage')
|
const { getStorage, setStorage } = useStorage()
|
||||||
|
|
||||||
const elLocaleMap = {
|
const elLocaleMap = {
|
||||||
'zh-CN': zhCn,
|
'zh-CN': zhCn,
|
||||||
@ -51,8 +51,7 @@ export const useLocaleStore = defineStore('locales', {
|
|||||||
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
|
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
|
||||||
setStorage('lang', localeMap?.lang)
|
setStorage('lang', localeMap?.lang)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
persist: true
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useLocaleStoreWithOut = () => {
|
export const useLocaleStoreWithOut = () => {
|
||||||
|
@ -40,7 +40,10 @@ export const useLockStore = defineStore('lock', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
persist: true
|
persist: {
|
||||||
|
enabled: true,
|
||||||
|
strategies: [{ key: 'lock', storage: localStorage }]
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useLockStoreWithOut = () => {
|
export const useLockStoreWithOut = () => {
|
||||||
|
@ -60,9 +60,6 @@ export const usePermissionStore = defineStore('permission', {
|
|||||||
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
||||||
this.menuTabRouters = routers
|
this.menuTabRouters = routers
|
||||||
}
|
}
|
||||||
},
|
|
||||||
persist: {
|
|
||||||
paths: ['routers', 'addRouters', 'menuTabRouters']
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -4,7 +4,12 @@ import { getRawRoute } from '@/utils/routerHelper'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { store } from '../index'
|
import { store } from '../index'
|
||||||
import { findIndex } from '@/utils'
|
import { findIndex } from '@/utils'
|
||||||
import { useAuthStoreWithOut } from './auth'
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
|
import { useAppStoreWithOut } from './app'
|
||||||
|
|
||||||
|
const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
|
const { getStorage } = useStorage()
|
||||||
|
|
||||||
export interface TagsViewState {
|
export interface TagsViewState {
|
||||||
visitedViews: RouteLocationNormalizedLoaded[]
|
visitedViews: RouteLocationNormalizedLoaded[]
|
||||||
@ -90,9 +95,8 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||||||
},
|
},
|
||||||
// 删除所有tag
|
// 删除所有tag
|
||||||
delAllVisitedViews() {
|
delAllVisitedViews() {
|
||||||
const authStore = useAuthStoreWithOut()
|
|
||||||
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
||||||
this.visitedViews = authStore.getUser
|
this.visitedViews = getStorage(appStore.getUserInfo)
|
||||||
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
||||||
: []
|
: []
|
||||||
},
|
},
|
||||||
@ -153,8 +157,7 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
persist: false
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useTagsViewStoreWithOut = () => {
|
export const useTagsViewStoreWithOut = () => {
|
||||||
|
@ -151,22 +151,3 @@ const subtractLight = (color: string, amount: number) => {
|
|||||||
const c = cc < 0 ? 0 : cc
|
const c = cc < 0 ? 0 : cc
|
||||||
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
|
return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mixes two colors.
|
|
||||||
*
|
|
||||||
* @param {string} color1 - The first color, should be a 6-digit hexadecimal color code starting with `#`.
|
|
||||||
* @param {string} color2 - The second color, should be a 6-digit hexadecimal color code starting with `#`.
|
|
||||||
* @param {number} [weight=0.5] - The weight of color1 in the mix, should be a number between 0 and 1, where 0 represents 100% of color2, and 1 represents 100% of color1.
|
|
||||||
* @returns {string} The mixed color, a 6-digit hexadecimal color code starting with `#`.
|
|
||||||
*/
|
|
||||||
export const mix = (color1: string, color2: string, weight: number = 0.5): string => {
|
|
||||||
let color = '#'
|
|
||||||
for (let i = 0; i <= 2; i++) {
|
|
||||||
const c1 = parseInt(color1.substring(1 + i * 2, 3 + i * 2), 16)
|
|
||||||
const c2 = parseInt(color2.substring(1 + i * 2, 3 + i * 2), 16)
|
|
||||||
const c = Math.round(c1 * weight + c2 * (1 - weight))
|
|
||||||
color += c.toString(16).padStart(2, '0')
|
|
||||||
}
|
|
||||||
return color
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// import type { Plugin } from 'vue'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param component 需要注册的组件
|
* @param component 需要注册的组件
|
||||||
@ -45,10 +47,6 @@ export const setCssVar = (prop: string, val: any, dom = document.documentElement
|
|||||||
dom.style.setProperty(prop, val)
|
dom.style.setProperty(prop, val)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCssVar = (prop: string, dom = document.documentElement) => {
|
|
||||||
return getComputedStyle(dom).getPropertyValue(prop)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查找数组对象的某个下标
|
* 查找数组对象的某个下标
|
||||||
* @param {Array} ary 查找的数组
|
* @param {Array} ary 查找的数组
|
||||||
@ -125,17 +123,6 @@ export function firstUpperCase(str: string) {
|
|||||||
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
|
return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 把对象转为formData
|
|
||||||
*/
|
|
||||||
export function objToFormData(obj: Recordable) {
|
|
||||||
const formData = new FormData()
|
|
||||||
Object.keys(obj).forEach((key) => {
|
|
||||||
formData.append(key, obj[key])
|
|
||||||
})
|
|
||||||
return formData
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据当前时间获取祝福语
|
// 根据当前时间获取祝福语
|
||||||
export const getGreeting = (): string => {
|
export const getGreeting = (): string => {
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'
|
import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types'
|
||||||
import { CSSProperties } from 'vue'
|
import { CSSProperties } from 'vue'
|
||||||
|
|
||||||
|
// 自定义扩展vue-types
|
||||||
type PropTypes = VueTypesInterface & {
|
type PropTypes = VueTypesInterface & {
|
||||||
readonly style: VueTypeValidableDef<CSSProperties>
|
readonly style: VueTypeValidableDef<CSSProperties>
|
||||||
}
|
}
|
||||||
|
|
||||||
const newPropTypes = createTypes({
|
const propTypes = createTypes({
|
||||||
func: undefined,
|
func: undefined,
|
||||||
bool: undefined,
|
bool: undefined,
|
||||||
string: undefined,
|
string: undefined,
|
||||||
@ -14,12 +15,15 @@ const newPropTypes = createTypes({
|
|||||||
integer: undefined
|
integer: undefined
|
||||||
}) as PropTypes
|
}) as PropTypes
|
||||||
|
|
||||||
class propTypes extends newPropTypes {
|
// 需要自定义扩展的类型
|
||||||
static get style() {
|
// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
||||||
return toValidableType('style', {
|
propTypes.extend([
|
||||||
type: [String, Object]
|
{
|
||||||
})
|
name: 'style',
|
||||||
|
getter: true,
|
||||||
|
type: [String, Object],
|
||||||
|
default: undefined
|
||||||
}
|
}
|
||||||
}
|
])
|
||||||
|
|
||||||
export { propTypes }
|
export { propTypes }
|
||||||
|
@ -3,7 +3,6 @@ import { ref } from 'vue'
|
|||||||
import Finance from './components/Finance.vue'
|
import Finance from './components/Finance.vue'
|
||||||
import { ElTabs, ElTabPane } from 'element-plus'
|
import { ElTabs, ElTabPane } from 'element-plus'
|
||||||
import User from './components/User.vue'
|
import User from './components/User.vue'
|
||||||
import { ContentWrap } from '@/components/ContentWrap'
|
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'DashboardAnalysis'
|
name: 'DashboardAnalysis'
|
||||||
@ -13,7 +12,7 @@ const activeName = ref('user')
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ContentWrap>
|
<div class="p-5">
|
||||||
<ElTabs v-model="activeName">
|
<ElTabs v-model="activeName">
|
||||||
<ElTabPane label="财务分析" name="finance" :lazy="true">
|
<ElTabPane label="财务分析" name="finance" :lazy="true">
|
||||||
<Finance />
|
<Finance />
|
||||||
@ -22,7 +21,7 @@ const activeName = ref('user')
|
|||||||
<User />
|
<User />
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
</ContentWrap>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
@ -155,12 +155,12 @@ initMap()
|
|||||||
background-color: #f13737;
|
background-color: #f13737;
|
||||||
box-shadow: 0px 0px 15px #f61212;
|
box-shadow: 0px 0px 15px #f61212;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
--webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名,也就是我们前面keyframes定义的动画名*/
|
-webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名,也就是我们前面keyframes定义的动画名*/
|
||||||
--webkit-animation-duration: 1s; /*动画持续时间*/
|
-webkit-animation-duration: 1s; /*动画持续时间*/
|
||||||
--webkit-animation-timing-function: ease; /*动画频率,和transition-timing-function是一样的*/
|
-webkit-animation-timing-function: ease; /*动画频率,和transition-timing-function是一样的*/
|
||||||
--webkit-animation-delay: 0s; /*动画延迟时间*/
|
-webkit-animation-delay: 0s; /*动画延迟时间*/
|
||||||
--webkit-animation-iteration-count: infinite; /*定义循环资料,infinite为无限次*/
|
-webkit-animation-iteration-count: infinite; /*定义循环资料,infinite为无限次*/
|
||||||
--webkit-animation-direction: alternate; /*定义动画方式*/
|
-webkit-animation-direction: alternate; /*定义动画方式*/
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -3,14 +3,14 @@ import { ElCard, ElRow, ElCol, ElTabs, ElTabPane, ElAvatar } from 'element-plus'
|
|||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import InfoWrite from './components/InfoWrite.vue'
|
import InfoWrite from './components/InfoWrite.vue'
|
||||||
import PasswordWrite from './components/PasswordWrite.vue'
|
import PasswordWrite from './components/PasswordWrite.vue'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import avatar from '@/assets/imgs/avatar.jpg'
|
import avatar from '@/assets/imgs/avatar.jpg'
|
||||||
import { selectDictLabel, DictDetail } from '@/utils/dict'
|
import { selectDictLabel, DictDetail } from '@/utils/dict'
|
||||||
import { useDictStore } from '@/store/modules/dict'
|
import { useDictStore } from '@/store/modules/dict'
|
||||||
|
|
||||||
const activeName = ref('info')
|
const activeName = ref('info')
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
let genderOptions = ref<DictDetail[]>([])
|
let genderOptions = ref<DictDetail[]>([])
|
||||||
|
|
||||||
|
@ -3,14 +3,13 @@ import { Form, FormSchema } from '@/components/Form'
|
|||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElButton, ElMessage } from 'element-plus'
|
||||||
import { postCurrentUserUpdateInfo } from '@/api/vadmin/auth/user'
|
import { postCurrentUserUpdateInfo } from '@/api/vadmin/auth/user'
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
|
||||||
const { required, isTelephone } = useValidator()
|
const { required, isTelephone } = useValidator()
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
const formSchema = reactive<FormSchema[]>([
|
const formSchema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -93,9 +92,9 @@ const formSchema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[50%]">
|
<div class="w-[50%]">
|
||||||
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
||||||
保存
|
保存
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -3,14 +3,13 @@ import { Form, FormSchema } from '@/components/Form'
|
|||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElButton, ElMessage } from 'element-plus'
|
||||||
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
|
||||||
const { required } = useValidator()
|
const { required } = useValidator()
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
const formSchema = reactive<FormSchema[]>([
|
const formSchema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -58,9 +57,9 @@ const formSchema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[50%]">
|
<div class="w-[50%]">
|
||||||
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
||||||
保存
|
保存
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -102,8 +101,7 @@ const save = async () => {
|
|||||||
const res = await postCurrentUserResetPassword(formData)
|
const res = await postCurrentUserResetPassword(formData)
|
||||||
if (res) {
|
if (res) {
|
||||||
elForm?.resetFields()
|
elForm?.resetFields()
|
||||||
authStore.logout()
|
ElMessage.success('保存成功')
|
||||||
ElMessage.warning('请重新登录')
|
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
@ -9,7 +9,6 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { ElScrollbar } from 'element-plus'
|
import { ElScrollbar } from 'element-plus'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import { ElButton } from 'element-plus'
|
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -29,11 +28,6 @@ const toTelephoneLogin = () => {
|
|||||||
const toPasswordLogin = () => {
|
const toPasswordLogin = () => {
|
||||||
isPasswordLogin.value = true
|
isPasswordLogin.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
const icpNumber = computed(() => appStore.getIcpNumber)
|
|
||||||
const toICO = () => {
|
|
||||||
window.open('https://beian.miit.gov.cn/#/Integrated/index')
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -94,9 +88,6 @@ const toICO = () => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
<div class="text-14px text-white font-normal absolute bottom-5 right-10">
|
|
||||||
<ElButton type="info" link @click="toICO">{{ icpNumber }}</ElButton>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ElScrollbar>
|
</ElScrollbar>
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import { Form } from '@/components/Form'
|
import { Form } from '@/components/Form'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ElCheckbox, ElLink } from 'element-plus'
|
import { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { getRoleMenusApi } from '@/api/login'
|
import { getRoleMenusApi } from '@/api/login'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||||
import { UserLoginType } from '@/api/login/types'
|
import { UserLoginType } from '@/api/login/types'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { FormSchema } from '@/components/Form'
|
import { FormSchema } from '@/components/Form'
|
||||||
import { Icon } from '@/components/Icon'
|
import { Icon } from '@/components/Icon'
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
|
||||||
const emit = defineEmits(['to-telephone'])
|
const emit = defineEmits(['to-telephone'])
|
||||||
|
|
||||||
@ -21,9 +21,10 @@ const { required } = useValidator()
|
|||||||
|
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
|
||||||
const { currentRoute, addRoute, push } = useRouter()
|
const { currentRoute, addRoute, push } = useRouter()
|
||||||
|
const { setStorage } = useStorage()
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -121,19 +122,14 @@ const schema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<BaseButton
|
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={signIn}>
|
||||||
loading={loading.value}
|
|
||||||
type="primary"
|
|
||||||
class="w-[100%]"
|
|
||||||
onClick={signIn}
|
|
||||||
>
|
|
||||||
{t('login.login')}
|
{t('login.login')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[100%] mt-15px">
|
<div class="w-[100%] mt-15px">
|
||||||
<BaseButton class="w-[100%]" onClick={toTelephoneLogin}>
|
<ElButton class="w-[100%]" onClick={toTelephoneLogin}>
|
||||||
{t('login.smsLogin')}
|
{t('login.smsLogin')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -245,6 +241,7 @@ const getMenu = async () => {
|
|||||||
const res = await getRoleMenusApi()
|
const res = await getRoleMenusApi()
|
||||||
if (res) {
|
if (res) {
|
||||||
const routers = res.data || []
|
const routers = res.data || []
|
||||||
|
setStorage('roleRouters', routers)
|
||||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||||
permissionStore.getAddRouters.forEach((route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
|
@ -3,16 +3,16 @@ import { Form } from '@/components/Form'
|
|||||||
import { reactive, ref, watch } from 'vue'
|
import { reactive, ref, watch } from 'vue'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { ElInput, FormRules, ElDivider, ElMessage } from 'element-plus'
|
import { ElButton, ElInput, FormRules, ElDivider, ElMessage } from 'element-plus'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
import { FormSchema } from '@/components/Form'
|
import { FormSchema } from '@/components/Form'
|
||||||
import { postSMSCodeApi } from '@/api/login'
|
import { postSMSCodeApi } from '@/api/login'
|
||||||
import { UserLoginType } from '@/api/login/types'
|
import { UserLoginType } from '@/api/login/types'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { RouteLocationNormalizedLoaded, useRouter, RouteRecordRaw } from 'vue-router'
|
import { RouteLocationNormalizedLoaded, useRouter, RouteRecordRaw } from 'vue-router'
|
||||||
import { getRoleMenusApi } from '@/api/login'
|
import { getRoleMenusApi } from '@/api/login'
|
||||||
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import { BaseButton } from '@/components/Button'
|
|
||||||
|
|
||||||
const emit = defineEmits(['to-password'])
|
const emit = defineEmits(['to-password'])
|
||||||
|
|
||||||
@ -22,7 +22,8 @@ const { t } = useI18n()
|
|||||||
const { required } = useValidator()
|
const { required } = useValidator()
|
||||||
const { currentRoute, addRoute, push } = useRouter()
|
const { currentRoute, addRoute, push } = useRouter()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
|
const { setStorage } = useStorage()
|
||||||
|
|
||||||
const schema = reactive<FormSchema[]>([
|
const schema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -73,13 +74,13 @@ const schema = reactive<FormSchema[]>([
|
|||||||
<>
|
<>
|
||||||
<ElDivider direction="vertical" />
|
<ElDivider direction="vertical" />
|
||||||
{SMSCodeStatus.value ? (
|
{SMSCodeStatus.value ? (
|
||||||
<BaseButton type="primary" link onClick={getSMSCode}>
|
<ElButton type="primary" link onClick={getSMSCode}>
|
||||||
{t('login.getSMSCode')}
|
{t('login.getSMSCode')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
) : (
|
) : (
|
||||||
<BaseButton type="primary" disabled={!SMSCodeStatus.value} link>
|
<ElButton type="primary" disabled={!SMSCodeStatus.value} link>
|
||||||
{SMSCodeNumber.value + t('login.SMSCodeRetry')}
|
{SMSCodeNumber.value + t('login.SMSCodeRetry')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -109,19 +110,19 @@ const schema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<BaseButton
|
<ElButton
|
||||||
type="primary"
|
type="primary"
|
||||||
class="w-[100%]"
|
class="w-[100%]"
|
||||||
loading={loading.value}
|
loading={loading.value}
|
||||||
onClick={telephoneCodeLogin}
|
onClick={telephoneCodeLogin}
|
||||||
>
|
>
|
||||||
{t('login.login')}
|
{t('login.login')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[100%] mt-15px">
|
<div class="w-[100%] mt-15px">
|
||||||
<BaseButton class="w-[100%]" onClick={toPasswordLogin}>
|
<ElButton class="w-[100%]" onClick={toPasswordLogin}>
|
||||||
{t('login.passwordLogin')}
|
{t('login.passwordLogin')}
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -215,6 +216,7 @@ const getMenu = async () => {
|
|||||||
const res = await getRoleMenusApi()
|
const res = await getRoleMenusApi()
|
||||||
if (res) {
|
if (res) {
|
||||||
const routers = res.data || []
|
const routers = res.data || []
|
||||||
|
setStorage('roleRouters', routers)
|
||||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||||
permissionStore.getAddRouters.forEach((route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
|
@ -3,19 +3,21 @@ import { Form, FormSchema } from '@/components/Form'
|
|||||||
import { useForm } from '@/hooks/web/useForm'
|
import { useForm } from '@/hooks/web/useForm'
|
||||||
import { computed, reactive, ref, watch } from 'vue'
|
import { computed, reactive, ref, watch } from 'vue'
|
||||||
import { useValidator } from '@/hooks/web/useValidator'
|
import { useValidator } from '@/hooks/web/useValidator'
|
||||||
import { useAuthStore } from '@/store/modules/auth'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElButton, ElMessage } from 'element-plus'
|
||||||
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
||||||
import { getRoleMenusApi } from '@/api/login'
|
import { getRoleMenusApi } from '@/api/login'
|
||||||
|
import { useStorage } from '@/hooks/web/useStorage'
|
||||||
import { usePermissionStore } from '@/store/modules/permission'
|
import { usePermissionStore } from '@/store/modules/permission'
|
||||||
import { RouteLocationNormalizedLoaded, RouteRecordRaw, useRouter } from 'vue-router'
|
import { RouteLocationNormalizedLoaded, RouteRecordRaw, useRouter } from 'vue-router'
|
||||||
import { useAppStore } from '@/store/modules/app'
|
import { useAppStore } from '@/store/modules/app'
|
||||||
import { Footer } from '@/components/Footer'
|
import { Footer } from '@/components/Footer'
|
||||||
|
|
||||||
const { required } = useValidator()
|
const { required } = useValidator()
|
||||||
|
const { setStorage } = useStorage()
|
||||||
const { addRoute, push, currentRoute } = useRouter()
|
const { addRoute, push, currentRoute } = useRouter()
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
@ -110,6 +112,7 @@ const getMenu = async () => {
|
|||||||
const res = await getRoleMenusApi()
|
const res = await getRoleMenusApi()
|
||||||
if (res) {
|
if (res) {
|
||||||
const routers = res.data || []
|
const routers = res.data || []
|
||||||
|
setStorage('roleRouters', routers)
|
||||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||||
permissionStore.getAddRouters.forEach((route) => {
|
permissionStore.getAddRouters.forEach((route) => {
|
||||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||||
@ -134,9 +137,9 @@ const getMenu = async () => {
|
|||||||
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
||||||
/>
|
/>
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<BaseButton :loading="loading" type="primary" class="w-[100%]" @click="save">
|
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="save">
|
||||||
重置密码
|
重置密码
|
||||||
</BaseButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user