Compare commits
123 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
dd2e3a65af | ||
|
7adc37990a | ||
|
8a75c11182 | ||
|
7fd7366e32 | ||
|
1354ea21e4 | ||
|
2d6108be44 | ||
|
ef5c73b468 | ||
|
afe3b042d9 | ||
|
5af56e956c | ||
|
1cc3fb0c3b | ||
|
39194d51c0 | ||
|
8d582b482c | ||
|
13e124c02a | ||
|
10ea077735 | ||
|
e191f76809 | ||
|
e91dca078b | ||
|
e1d388dbfc | ||
|
39cd05fb44 | ||
|
8364ef731e | ||
|
cff85f09d5 | ||
|
eff099877e | ||
|
303777910c | ||
|
76bd8ed3fe | ||
|
53c12a787e | ||
|
2970fe16da | ||
|
4229620d8d | ||
|
9c148dbb6b | ||
|
74c0b0a484 | ||
|
974322a2b8 | ||
|
7ca8bd3244 | ||
|
332e728ca3 | ||
|
bc5fa85ba6 | ||
|
aafcb6527f | ||
|
03d8bd2eb4 | ||
|
7a33c4d4f7 | ||
|
bc5f239cb7 | ||
|
53a694be36 | ||
|
9f635dc5f4 | ||
|
b3b427edef | ||
|
adc7b21fc2 | ||
|
61f39a7c64 | ||
|
13bfb7d7b8 | ||
|
8265cbc6d0 | ||
|
4d240c24d2 | ||
|
2c39c91108 | ||
|
df611901c6 | ||
|
ae44370f78 | ||
|
149812914e | ||
|
b08cd1ca42 | ||
|
1e7dbec10b | ||
|
9bb0d17fb8 | ||
|
953cdda006 | ||
|
3ccbc2c4b2 | ||
|
057915375a | ||
|
64f221fb3f | ||
|
0189fa867a | ||
|
c5cfe3ffcb | ||
|
5053d59f62 | ||
|
f8c748a15a | ||
|
7036c1fc02 | ||
|
20a425ed8c | ||
|
3054471123 | ||
|
cab33828dd | ||
|
3adfa91560 | ||
|
65c204287f | ||
|
aca6c3d4f9 | ||
|
3f8b0efccc | ||
|
364a5b3491 | ||
|
a632d7cfd7 | ||
|
09abd68f8e | ||
|
56757b8101 | ||
|
19a19b4533 | ||
|
b4d0ed3a3e | ||
|
cab330dd77 | ||
|
8bcfbe7512 | ||
|
675592a5a8 | ||
|
9ca8e6911b | ||
|
b013ac4a87 | ||
|
851d66d594 | ||
|
cb33aa470e | ||
|
28487cd02c | ||
|
edab311363 | ||
|
ece609662a | ||
|
35fff39af1 | ||
|
e08f0e153c | ||
|
01f1a9e88e | ||
|
254d0e5958 | ||
|
7329231c61 | ||
|
76a5b9b467 | ||
|
cde6b1b497 | ||
|
5d9801dbd7 | ||
|
a5476a92d3 | ||
|
7eb590a697 | ||
|
65f92947f5 | ||
|
244da5fdd5 | ||
|
ebc0095ca6 | ||
|
9ceeacb97b | ||
|
bade36dd1b | ||
|
d16382c90c | ||
|
e026182838 | ||
|
905dccc243 | ||
|
a90ed5c4ea | ||
|
a44c7c2bae | ||
|
1396520ea3 | ||
|
0ec1876584 | ||
|
72f614ed12 | ||
|
29037d7184 | ||
|
518f9d4a47 | ||
|
26ffb4c167 | ||
|
d9ffa98d13 | ||
|
713f4bdcda | ||
|
21980a6e34 | ||
|
4a9bf1fdc3 | ||
|
0a1b4f1881 | ||
|
7a8cca62e0 | ||
|
3bf7a5a3a3 | ||
|
08c681e608 | ||
|
66806dac91 | ||
|
dc34f018b3 | ||
|
43ee394c21 | ||
|
572f494a88 | ||
|
54f8671899 | ||
|
61e9a336d3 |
21
.gitignore
vendored
21
.gitignore
vendored
@ -1,22 +1 @@
|
|||||||
# 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
|
|
116
README.md
116
README.md
@ -21,10 +21,11 @@
|
|||||||
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 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/)等主流技术开发;
|
- 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/)等主流技术开发;
|
||||||
- 移动端采用 [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/) 命令行应用,简单化数据初始化,数据表模型迁移等操作;
|
||||||
- 已加入定时任务功能,采用 [APScheduler](https://github.com/agronholm/apscheduler) 定时任务框架 + [Redis](https://redis.io/) 消息队列 + [MongoDB](https://www.mongodb.com/) 持久存储;
|
- 后端新加入根据配置的 ORM 模型,自动生成 CRUD 代码;
|
||||||
|
- 定时任务功能,采用 [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/) 数据库异步操作。
|
||||||
@ -42,6 +43,27 @@ 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
|
||||||
@ -58,9 +80,71 @@ 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
|
||||||
@ -71,7 +155,9 @@ github地址:https://github.com/vvandk/kinit
|
|||||||
|
|
||||||
- [x] 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
- [x] 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||||
|
|
||||||
- [x] 角色管理:角色菜单权限分配。
|
- [x] 部门管理:支持无限层级部门配置。
|
||||||
|
|
||||||
|
- [x] 角色管理:角色菜单权限,角色部门权限分配。
|
||||||
|
|
||||||
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||||
|
|
||||||
@ -171,10 +257,14 @@ git clone https://gitee.com/ktianc/kinit.git
|
|||||||
### 准备工作
|
### 准备工作
|
||||||
|
|
||||||
```
|
```
|
||||||
Python == 3.10 (其他版本均未测试)
|
后端依赖版本:
|
||||||
nodejs >= 14.0 (推荐使用最新稳定版)
|
Python == 3.10.x (其他版本均未测试)
|
||||||
Mysql >= 8.0
|
前端依赖版本:
|
||||||
MongoDB (推荐使用最新稳定版)
|
nodejs >= 18.0 < 19
|
||||||
|
pnpm >= 8.1.0 < 9
|
||||||
|
数据库版本:
|
||||||
|
Mysql >= 8.0 (8 以上未测试,以下版本未测试,postgresql 未测试,更换可能会涉及调整)
|
||||||
|
MongoDB >= 7.0.12 < 8 (7 以上或以下版本均未测试)
|
||||||
Redis (推荐使用最新稳定版)
|
Redis (推荐使用最新稳定版)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -195,6 +285,7 @@ Redis (推荐使用最新稳定版)
|
|||||||
```python
|
```python
|
||||||
# 安全警告: 不要在生产中打开调试运行!
|
# 安全警告: 不要在生产中打开调试运行!
|
||||||
DEBUG = True # 如果当前为开发环境则改为 True,如果为生产环境则改为 False
|
DEBUG = True # 如果当前为开发环境则改为 True,如果为生产环境则改为 False
|
||||||
|
```
|
||||||
|
|
||||||
3. 修改项目数据库配置信息
|
3. 修改项目数据库配置信息
|
||||||
|
|
||||||
@ -242,6 +333,7 @@ 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`文件中配置数据库信息,用于数据库映射
|
||||||
|
|
||||||
@ -251,13 +343,13 @@ Redis (推荐使用最新稳定版)
|
|||||||
[dev]
|
[dev]
|
||||||
# 开发环境
|
# 开发环境
|
||||||
version_locations = %(here)s/alembic/versions_dev
|
version_locations = %(here)s/alembic/versions_dev
|
||||||
sqlalchemy.url = sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
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 = sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
sqlalchemy.url = mysql+pymysql://root:123456@127.0.0.1/kinit
|
||||||
```
|
```
|
||||||
|
|
||||||
5. 创建数据库
|
5. 创建数据库
|
||||||
@ -413,7 +505,9 @@ pnpm run build:pro
|
|||||||
DEBUG = False # 生产环境应该改为 False
|
DEBUG = False # 生产环境应该改为 False
|
||||||
```
|
```
|
||||||
|
|
||||||
3. 如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请修改如下内容,如果没有则不需要修改:
|
3. (**如果没有安装数据库则不需要这一操作**)如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请执行以下操作:
|
||||||
|
|
||||||
|
请先在对应数据库中创建用户名以及数据库,并修改以下数据库连接改为已有的数据库连接
|
||||||
|
|
||||||
1. 修改 API 端配置文件:
|
1. 修改 API 端配置文件:
|
||||||
|
|
||||||
@ -422,7 +516,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 数据库配置
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# 环境
|
# 环境
|
||||||
NODE_ENV=development
|
VITE_NODE_ENV=development
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀,没用到
|
||||||
VITE_API_BASE_PATH=dev
|
# VITE_API_BASE_PATH=/api
|
||||||
|
|
||||||
# 打包路径
|
# 打包路径
|
||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
@ -21,3 +21,15 @@ 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 @@
|
|||||||
# 环境
|
# 环境
|
||||||
NODE_ENV=production
|
VITE_NODE_ENV=production
|
||||||
|
|
||||||
# 接口前缀
|
# 接口前缀,没用到
|
||||||
VITE_API_BASE_PATH=pro
|
# VITE_API_BASE_PATH=/api
|
||||||
|
|
||||||
# 打包路径
|
# 打包路径
|
||||||
VITE_BASE_PATH=/
|
VITE_BASE_PATH=/
|
||||||
@ -21,3 +21,15 @@ 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,6 +65,7 @@ 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,7 +1,9 @@
|
|||||||
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,7 +3,6 @@
|
|||||||
/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
3
kinit-admin/.vscode/extensions.json
vendored
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"recommendations": ["vue.volar", "lokalise.i18n-ally"]
|
|
||||||
}
|
|
19
kinit-admin/.vscode/settings.json
vendored
19
kinit-admin/.vscode/settings.json
vendored
@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@
|
|||||||
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,115 +1,127 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-element-plus-admin",
|
"name": "vue-element-plus-admin",
|
||||||
"version": "2.2.0",
|
"version": "2.7.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": "vite --mode dev",
|
"dev": "pnpm vite --mode dev",
|
||||||
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
|
||||||
"build:pro": "vite build --mode pro",
|
"build:pro": "pnpm vite build --mode pro",
|
||||||
"build:dev": "vite build --mode dev",
|
"build:dev": "pnpm vite build --mode dev",
|
||||||
"serve:pro": "vite preview --mode pro",
|
"serve:pro": "pnpm vite preview --mode pro",
|
||||||
"serve:dev": "vite preview --mode dev",
|
"serve:dev": "pnpm vite preview --mode dev",
|
||||||
"npm:check": "npx npm-check-updates",
|
"npm:check": "pnpx npm-check-updates -u",
|
||||||
"clean": "npx rimraf node_modules",
|
"clean": "pnpx rimraf node_modules",
|
||||||
"clean:cache": "npx rimraf node_modules/.cache",
|
"clean:cache": "pnpx 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": {
|
||||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
"@iconify/iconify": "3.1.1",
|
||||||
"@iconify/iconify": "^3.1.1",
|
"@iconify/vue": "4.1.1",
|
||||||
"@iconify/vue": "^4.1.1",
|
"@vueuse/core": "10.9.0",
|
||||||
"@vueuse/core": "^10.3.0",
|
"@wangeditor/editor": "5.1.23",
|
||||||
"@wangeditor/editor": "^5.1.23",
|
"@wangeditor/editor-for-vue": "5.1.10",
|
||||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
"@zxcvbn-ts/core": "3.0.4",
|
||||||
"@zxcvbn-ts/core": "^3.0.3",
|
"animate.css": "4.1.1",
|
||||||
"animate.css": "^4.1.1",
|
"axios": "1.6.7",
|
||||||
"axios": "^1.4.0",
|
"cropperjs": "1.6.1",
|
||||||
"dayjs": "^1.11.9",
|
"dayjs": "1.11.10",
|
||||||
"driver.js": "^1.2.1",
|
"driver.js": "1.3.1",
|
||||||
"echarts": "^5.4.3",
|
"echarts": "5.5.0",
|
||||||
"echarts-wordcloud": "^2.1.0",
|
"echarts-wordcloud": "2.1.0",
|
||||||
"element-plus": "^2.3.9",
|
"element-plus": "2.5.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "4.17.21",
|
||||||
"mitt": "^3.0.1",
|
"mitt": "3.0.1",
|
||||||
"mockjs": "^1.1.0",
|
"nprogress": "0.2.0",
|
||||||
"nprogress": "^0.2.0",
|
"pinia": "2.1.7",
|
||||||
"pinia": "^2.1.6",
|
"pinia-plugin-persistedstate": "3.2.1",
|
||||||
"pinia-plugin-persist": "^1.0.0",
|
"qrcode": "1.5.3",
|
||||||
"qrcode": "^1.5.3",
|
"qs": "6.11.2",
|
||||||
"qs": "^6.11.2",
|
"url": "0.11.3",
|
||||||
"sortablejs": "^1.15.0",
|
"vue": "3.4.20",
|
||||||
"url": "^0.11.1",
|
"vue-draggable-plus": "0.3.5",
|
||||||
"vue": "3.3.4",
|
"vue-i18n": "9.9.1",
|
||||||
"vue-i18n": "9.2.2",
|
"vue-json-pretty": "2.3.0",
|
||||||
"vue-json-pretty": "^2.2.4",
|
"vue-router": "4.3.0",
|
||||||
"vue-router": "^4.2.4",
|
"vue-types": "5.1.1",
|
||||||
"vue-types": "^5.1.1"
|
"xgplayer": "3.0.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@iconify/json": "^2.2.101",
|
"@amap/amap-jsapi-loader": "1.0.1",
|
||||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
"@commitlint/cli": "19.0.1",
|
||||||
"@kjgl77/datav-vue3": "^1.6.1",
|
"@commitlint/config-conventional": "19.0.0",
|
||||||
"@purge-icons/generated": "^0.9.0",
|
"@iconify/json": "2.2.187",
|
||||||
"@types/lodash-es": "^4.17.8",
|
"@intlify/unplugin-vue-i18n": "2.0.0",
|
||||||
"@types/node": "^20.4.10",
|
"@kjgl77/datav-vue3": "1.6.1",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/fs-extra": "11.0.4",
|
||||||
"@types/qrcode": "^1.5.1",
|
"@types/inquirer": "9.0.7",
|
||||||
"@types/qs": "^6.9.7",
|
"@types/lodash-es": "4.17.12",
|
||||||
"@types/sortablejs": "^1.15.1",
|
"@types/node": "20.11.21",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
"@types/nprogress": "0.2.3",
|
||||||
"@typescript-eslint/parser": "^6.3.0",
|
"@types/qrcode": "1.5.5",
|
||||||
"@unocss/transformer-variant-group": "^0.55.0",
|
"@types/qs": "6.9.12",
|
||||||
"@vitejs/plugin-legacy": "^4.1.1",
|
"@types/sortablejs": "1.15.8",
|
||||||
"@vitejs/plugin-vue": "^4.2.3",
|
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
"@typescript-eslint/parser": "7.1.0",
|
||||||
"autoprefixer": "^10.4.14",
|
"@unocss/transformer-variant-group": "0.58.5",
|
||||||
"consola": "^3.2.3",
|
"@vitejs/plugin-legacy": "5.3.1",
|
||||||
"cron-validate": "^1.4.5",
|
"@vitejs/plugin-vue": "5.0.4",
|
||||||
"eslint": "^8.47.0",
|
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||||
"eslint-config-prettier": "^9.0.0",
|
"autoprefixer": "10.4.17",
|
||||||
"eslint-define-config": "^1.23.0",
|
"chalk": "5.3.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"consola": "3.2.3",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"cron-validate": "1.4.5",
|
||||||
"intro.js": "^7.2.0",
|
"eslint": "8.57.0",
|
||||||
"less": "^4.2.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"lint-staged": "^13.2.3",
|
"eslint-define-config": "2.1.0",
|
||||||
"lodash": "^4.17.21",
|
"eslint-plugin-prettier": "5.1.3",
|
||||||
"moment": "^2.29.4",
|
"eslint-plugin-vue": "9.22.0",
|
||||||
"plop": "^3.1.2",
|
"esno": "4.0.0",
|
||||||
"postcss": "^8.4.27",
|
"fs-extra": "11.2.0",
|
||||||
"postcss-html": "^1.5.0",
|
"inquirer": "9.2.15",
|
||||||
"postcss-less": "^6.0.0",
|
"less": "4.2.0",
|
||||||
"prettier": "^3.0.1",
|
"lint-staged": "15.2.2",
|
||||||
"rimraf": "^5.0.1",
|
"lodash": "4.17.21",
|
||||||
"rollup": "^3.28.0",
|
"moment": "2.30.1",
|
||||||
"stylelint": "^15.10.2",
|
"plop": "4.0.1",
|
||||||
"stylelint-config-html": "^1.1.0",
|
"postcss": "8.4.35",
|
||||||
"stylelint-config-recommended": "^13.0.0",
|
"postcss-html": "1.6.0",
|
||||||
"stylelint-config-standard": "^34.0.0",
|
"postcss-less": "6.0.0",
|
||||||
"stylelint-order": "^6.0.3",
|
"prettier": "3.2.5",
|
||||||
"terser": "^5.19.2",
|
"rimraf": "5.0.5",
|
||||||
"typescript": "5.1.6",
|
"rollup": "4.12.0",
|
||||||
"unocss": "^0.55.0",
|
"rollup-plugin-visualizer": "5.12.0",
|
||||||
"vite": "4.4.9",
|
"stylelint": "16.2.1",
|
||||||
"vite-plugin-ejs": "^1.6.4",
|
"stylelint-config-html": "1.1.0",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"stylelint-config-recommended": "14.0.0",
|
||||||
"vite-plugin-mock": "2.9.6",
|
"stylelint-config-standard": "36.0.0",
|
||||||
"vite-plugin-progress": "^0.0.7",
|
"stylelint-order": "6.0.4",
|
||||||
"vite-plugin-purge-icons": "^0.9.2",
|
"terser": "5.28.1",
|
||||||
|
"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",
|
||||||
"vue-tsc": "^1.8.8",
|
"vite-plugin-url-copy": "1.1.3",
|
||||||
"vue3-json-viewer": "^2.2.2"
|
"vue-tsc": "1.8.27",
|
||||||
|
"vue3-json-viewer": "2.2.2",
|
||||||
|
"zipson": "0.2.12"
|
||||||
},
|
},
|
||||||
|
"packageManager": "pnpm@8.1.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 14.18.0"
|
"node": ">=18 <19",
|
||||||
|
"pnpm": ">=8.1.0 <10.0.0"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
71
kinit-admin/scripts/icon.ts
Normal file
71
kinit-admin/scripts/icon.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
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,9 +2,7 @@
|
|||||||
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()
|
||||||
@ -17,18 +15,6 @@ 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')
|
||||||
@ -39,6 +25,9 @@ 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)
|
||||||
@ -52,7 +41,7 @@ const setSystemConfig = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setDefaultTheme()
|
appStore.initTheme()
|
||||||
setSystemConfig()
|
setSystemConfig()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
25
kinit-admin/src/api/vadmin/auth/dept.ts
Normal file
25
kinit-admin/src/api/vadmin/auth/dept.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
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 = (params: any): Promise<IResponse> => {
|
export const getSystemSettingsTabsApi = (data: any): Promise<IResponse> => {
|
||||||
return request.get({ url: '/vadmin/system/settings/tabs', params })
|
return request.post({ url: '/vadmin/system/settings/tabs', data })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getSystemSettingsApi = (params: any): Promise<IResponse> => {
|
export const getSystemSettingsApi = (params: any): Promise<IResponse> => {
|
||||||
|
3
kinit-admin/src/components/Button/index.ts
Normal file
3
kinit-admin/src/components/Button/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import BaseButton from './src/Button.vue'
|
||||||
|
|
||||||
|
export { BaseButton }
|
113
kinit-admin/src/components/Button/src/Button.vue
Normal file
113
kinit-admin/src/components/Button/src/Button.vue
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<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, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
|
import { ElCollapseTransition, ElTooltip, ElRow, ElCol } 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,6 +16,8 @@ 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: {
|
||||||
@ -36,7 +38,7 @@ export default defineComponent({
|
|||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(props, { slots, attrs }) {
|
setup(props, { 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 }
|
||||||
@ -59,7 +61,10 @@ export default defineComponent({
|
|||||||
delete obj[key]
|
delete obj[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return obj
|
return {
|
||||||
|
labelClassName: `${prefixCls}-label`,
|
||||||
|
...obj
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 折叠
|
// 折叠
|
||||||
@ -103,26 +108,51 @@ 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']}>
|
||||||
<ElDescriptions {...unref(getBindValue)}>
|
<ElRow
|
||||||
{{
|
gutter={0}
|
||||||
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
|
{...unref(getBindValue)}
|
||||||
default: () => {
|
class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
|
||||||
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
|
>
|
||||||
|
{props.direction === 'horizontal' ? (
|
||||||
|
<div class="flex items-stretch bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
|
||||||
|
<div
|
||||||
|
{...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>
|
||||||
|
<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)
|
? item.slots?.default(props.data)
|
||||||
: get(props.data, item.field)
|
: get(props.data, item.field) ?? defaultData}
|
||||||
}}
|
</div>
|
||||||
</ElDescriptionsItem>
|
</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>
|
||||||
}}
|
|
||||||
</ElDescriptions>
|
|
||||||
</div>
|
</div>
|
||||||
</ElCollapseTransition>
|
</ElCollapseTransition>
|
||||||
</div>
|
</div>
|
||||||
@ -153,9 +183,13 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.@{prefix-cls}-content {
|
:deep(.@{prefix-cls}-label) {
|
||||||
:deep(.@{elNamespace}-descriptions__cell) {
|
width: 150px !important;
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// .@{prefix-cls}-content {
|
||||||
|
// :deep(.@{elNamespace}-descriptions__cell) {
|
||||||
|
// width: 0;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
</style>
|
</style>
|
||||||
|
@ -10,7 +10,6 @@ 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')
|
||||||
@ -109,26 +108,30 @@ 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,7 +4,6 @@ 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
|
||||||
@ -51,7 +50,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">
|
||||||
<ElButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</ElButton>
|
<BaseButton type="primary" @click="btnClick">{{ errorMap[type].buttonText }}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -9,14 +9,14 @@ const prefixCls = getPrefixCls('footer')
|
|||||||
|
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
const title = computed(() => appStore.getTitle)
|
const footerContent = computed(() => appStore.getFooterContent)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="prefixCls"
|
:class="prefixCls"
|
||||||
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)]"
|
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)]"
|
||||||
>
|
>
|
||||||
Copyright ©2021-present {{ title }}
|
{{ footerContent }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -95,9 +95,6 @@ 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(() => {
|
||||||
@ -155,8 +152,6 @@ 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) => {
|
||||||
@ -364,13 +359,31 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return item.component === ComponentNameEnum.UPLOAD ? (
|
||||||
|
<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={item.componentProps?.style || {}}
|
style={
|
||||||
|
item.componentProps?.style || {
|
||||||
|
width: '100%'
|
||||||
|
}
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{{ ...slotsMap }}
|
{{ ...slotsMap }}
|
||||||
</Com>
|
</Com>
|
||||||
@ -447,6 +460,10 @@ 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()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
||||||
@ -466,4 +483,16 @@ 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,6 +25,7 @@ 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,
|
||||||
@ -51,6 +52,7 @@ const componentMap: Recordable<Component, ComponentName> = {
|
|||||||
TreeSelect: ElTreeSelect,
|
TreeSelect: ElTreeSelect,
|
||||||
Upload: ElUpload,
|
Upload: ElUpload,
|
||||||
JsonEditor: JsonEditor,
|
JsonEditor: JsonEditor,
|
||||||
|
IconPicker: IconPicker,
|
||||||
Text: Text
|
Text: Text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ 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,6 +25,11 @@ 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 {
|
||||||
@ -40,7 +45,10 @@ const getIconifyStyle = computed(() => {
|
|||||||
<use :xlink:href="symbolId" />
|
<use :xlink:href="symbolId" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<Icon v-else :icon="icon" :style="getIconifyStyle" />
|
<template v-else>
|
||||||
|
<Icon v-if="isUseOnline" :icon="icon" :style="getIconifyStyle" />
|
||||||
|
<div v-else :class="`${icon} iconify`" :style="getIconifyStyle"></div>
|
||||||
|
</template>
|
||||||
</ElIcon>
|
</ElIcon>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -49,11 +57,18 @@ const getIconifyStyle = computed(() => {
|
|||||||
|
|
||||||
.@{prefix-cls},
|
.@{prefix-cls},
|
||||||
.iconify {
|
.iconify {
|
||||||
&:hover {
|
|
||||||
:deep(svg) {
|
:deep(svg) {
|
||||||
|
&:hover {
|
||||||
// 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>
|
||||||
|
3
kinit-admin/src/components/IconPicker/index.ts
Normal file
3
kinit-admin/src/components/IconPicker/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import IconPicker from './src/IconPicker.vue'
|
||||||
|
|
||||||
|
export { IconPicker }
|
193
kinit-admin/src/components/IconPicker/src/IconPicker.vue
Normal file
193
kinit-admin/src/components/IconPicker/src/IconPicker.vue
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
<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>
|
@ -0,0 +1,795 @@
|
|||||||
|
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'
|
||||||
|
]
|
||||||
|
}
|
299
kinit-admin/src/components/IconPicker/src/data/icons.ep.ts
Normal file
299
kinit-admin/src/components/IconPicker/src/data/icons.ep.ts
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
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'
|
||||||
|
]
|
||||||
|
}
|
1209
kinit-admin/src/components/IconPicker/src/data/icons.tdesign.ts
Normal file
1209
kinit-admin/src/components/IconPicker/src/data/icons.tdesign.ts
Normal file
File diff suppressed because it is too large
Load Diff
3
kinit-admin/src/components/ImageCropping/index.ts
Normal file
3
kinit-admin/src/components/ImageCropping/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ImageCropping from './src/ImageCropping.vue'
|
||||||
|
|
||||||
|
export { ImageCropping }
|
245
kinit-admin/src/components/ImageCropping/src/ImageCropping.vue
Normal file
245
kinit-admin/src/components/ImageCropping/src/ImageCropping.vue
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
<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 0 5px;
|
border-width: 0 5px;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,13 +30,7 @@ watch(
|
|||||||
show.value = true
|
show.value = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!collapse) {
|
|
||||||
setTimeout(() => {
|
|
||||||
show.value = !collapse
|
show.value = !collapse
|
||||||
}, 400)
|
|
||||||
} else {
|
|
||||||
show.value = !collapse
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ export default defineComponent({
|
|||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
default: () => {
|
default: () => {
|
||||||
const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
|
const { renderMenuItem } = useRenderMenuItem()
|
||||||
return renderMenuItem(unref(routers))
|
return renderMenuItem(unref(routers))
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -123,30 +123,10 @@ 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;
|
||||||
@ -168,7 +148,6 @@ 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;
|
||||||
@ -180,10 +159,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
.@{elNamespace}-menu-item.is-active {
|
.@{elNamespace}-menu-item.is-active {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
// &:after {
|
|
||||||
// .is-active--after;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置子菜单的背景颜色
|
// 设置子菜单的背景颜色
|
||||||
@ -203,16 +178,11 @@ 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;
|
||||||
}
|
}
|
||||||
@ -235,7 +205,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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,16 +224,6 @@ 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 {
|
||||||
// 设置选中时子标题的颜色
|
// 设置选中时子标题的颜色
|
||||||
@ -290,10 +250,6 @@ 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,45 +2,38 @@ 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 = '/') => {
|
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||||
return routers.map((v) => {
|
return routers
|
||||||
|
.filter((v) => !v.meta?.hidden)
|
||||||
|
.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 || onlyOneChild?.noShowingChildren) &&
|
(!(onlyOneChild?.children?.length !== 0) || onlyOneChild?.noShowingChildren) &&
|
||||||
!meta?.alwaysShow
|
!meta?.alwaysShow
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<ElMenuItem index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
|
<ElMenuItem
|
||||||
|
index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
|
||||||
|
>
|
||||||
{{
|
{{
|
||||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||||
}}
|
}}
|
||||||
</ElMenuItem>
|
</ElMenuItem>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const { getPrefixCls } = useDesign()
|
|
||||||
|
|
||||||
const preFixCls = getPrefixCls('menu-popper')
|
|
||||||
return (
|
return (
|
||||||
<ElSubMenu
|
<ElSubMenu index={fullPath}>
|
||||||
index={fullPath}
|
|
||||||
popperClass={
|
|
||||||
menuMode === 'vertical' ? `${preFixCls}--vertical` : `${preFixCls}--horizontal`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{{
|
{{
|
||||||
title: () => renderMenuTitle(meta),
|
title: () => renderMenuTitle(meta),
|
||||||
default: () => renderMenuItem(v.children!, fullPath)
|
default: () => renderMenuItem(v.children!, fullPath)
|
||||||
@ -48,11 +41,10 @@ export const useRenderMenuItem = (
|
|||||||
</ElSubMenu>
|
</ElSubMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
renderMenuItem
|
renderMenuItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,14 @@ export const useRenderMenuTitle = () => {
|
|||||||
return icon ? (
|
return icon ? (
|
||||||
<>
|
<>
|
||||||
<Icon icon={meta.icon}></Icon>
|
<Icon icon={meta.icon}></Icon>
|
||||||
<span class="v-menu__title">{t(title as string)}</span>
|
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||||
|
{t(title as string)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<span class="v-menu__title">{t(title as string)}</span>
|
<span class="v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap">
|
||||||
|
{t(title as string)}
|
||||||
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,7 +242,7 @@ const disabledClick = () => {
|
|||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
&--disabled {
|
&--disabled {
|
||||||
background: rgba(255, 255, 255, 0.95);
|
background: rgb(255 255 255 / 95%);
|
||||||
|
|
||||||
& > div {
|
& > div {
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
|
@ -88,6 +88,9 @@ const newSchema = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
label: () => {
|
||||||
|
return <span> </span>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -117,11 +120,14 @@ 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,
|
||||||
@ -241,7 +247,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="newSchema"
|
:schema="schemaRef"
|
||||||
@register="formRegister"
|
@register="formRegister"
|
||||||
@validate="onFormValidate"
|
@validate="onFormValidate"
|
||||||
/>
|
/>
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<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'
|
||||||
@ -31,7 +30,7 @@ const onExpand = () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ElButton
|
<BaseButton
|
||||||
v-if="showSearch"
|
v-if="showSearch"
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="searchLoading"
|
:loading="searchLoading"
|
||||||
@ -39,21 +38,21 @@ const onExpand = () => {
|
|||||||
@click="onSearch"
|
@click="onSearch"
|
||||||
>
|
>
|
||||||
{{ t('common.query') }}
|
{{ t('common.query') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
<ElButton
|
<BaseButton
|
||||||
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') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
<ElButton
|
<BaseButton
|
||||||
v-if="showExpand"
|
v-if="showExpand"
|
||||||
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
|
:icon="useIcon({ icon: visible ? 'ep:arrow-up' : 'ep:arrow-down' })"
|
||||||
text
|
text
|
||||||
@click="onExpand"
|
@click="onExpand"
|
||||||
>
|
>
|
||||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||||
import { ref, unref, computed, watch } from 'vue'
|
import { ref, unref } 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 } from '@/utils'
|
import { trim, setCssVar, getCssVar } 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 +13,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 { removeStorage } = useStorage()
|
const { clear: storageClear } = useStorage('localStorage')
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -24,8 +23,6 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const layout = computed(() => appStore.getLayout)
|
|
||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
// 主题色相关
|
// 主题色相关
|
||||||
@ -42,70 +39,27 @@ const setSystemTheme = (color: string) => {
|
|||||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||||
|
|
||||||
const setHeaderTheme = (color: string) => {
|
const setHeaderTheme = (color: string) => {
|
||||||
const isDarkColor = colorIsDark(color)
|
appStore.setHeaderTheme(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) => {
|
||||||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
appStore.setMenuTheme(color)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听layout变化,重置一些主题色
|
// watch(
|
||||||
watch(
|
// () => layout.value,
|
||||||
() => layout.value,
|
// (n) => {
|
||||||
(n) => {
|
// if (n === 'top' && !appStore.getIsDark) {
|
||||||
if (n === 'top' && !appStore.getIsDark) {
|
// headerTheme.value = '#fff'
|
||||||
headerTheme.value = '#fff'
|
// setHeaderTheme('#fff')
|
||||||
setHeaderTheme('#fff')
|
// } else {
|
||||||
} else {
|
// setMenuTheme(unref(menuTheme))
|
||||||
setMenuTheme(unref(menuTheme))
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// )
|
||||||
)
|
|
||||||
|
|
||||||
// 拷贝
|
// 拷贝
|
||||||
const copyConfig = async () => {
|
const copyConfig = async () => {
|
||||||
@ -174,7 +128,8 @@ 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'))
|
||||||
@ -188,11 +143,15 @@ const copyConfig = async () => {
|
|||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
removeStorage('layout')
|
storageClear()
|
||||||
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>
|
||||||
@ -212,7 +171,7 @@ const clear = () => {
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- 主题 -->
|
<!-- 主题 -->
|
||||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||||
<ThemeSwitch />
|
<ThemeSwitch @change="themeChange" />
|
||||||
|
|
||||||
<!-- 布局 -->
|
<!-- 布局 -->
|
||||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||||
@ -253,7 +212,6 @@ const clear = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 菜单主题 -->
|
<!-- 菜单主题 -->
|
||||||
<template v-if="layout !== 'top'">
|
|
||||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||||
<ColorRadioPicker
|
<ColorRadioPicker
|
||||||
v-model="menuTheme"
|
v-model="menuTheme"
|
||||||
@ -269,7 +227,6 @@ const clear = () => {
|
|||||||
]"
|
]"
|
||||||
@change="setMenuTheme"
|
@change="setMenuTheme"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 界面显示 -->
|
<!-- 界面显示 -->
|
||||||
@ -278,12 +235,14 @@ const clear = () => {
|
|||||||
|
|
||||||
<ElDivider />
|
<ElDivider />
|
||||||
<div>
|
<div>
|
||||||
<ElButton type="primary" class="w-full" @click="copyConfig">{{ t('setting.copy') }}</ElButton>
|
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||||||
|
t('setting.copy')
|
||||||
|
}}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5px">
|
<div class="mt-5px">
|
||||||
<ElButton type="danger" class="w-full" @click="clear">
|
<BaseButton type="danger" class="w-full" @click="clear">
|
||||||
{{ t('setting.clearAndReset') }}
|
{{ t('setting.clearAndReset') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</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,12 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||||
import { ref, unref, computed, watch } from 'vue'
|
import { ref, unref } 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 } from '@/utils'
|
import { trim, setCssVar, getCssVar } 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'
|
||||||
@ -15,7 +14,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 { removeStorage } = useStorage()
|
const { clear: storageClear } = useStorage('localStorage')
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -29,8 +28,6 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
const layout = computed(() => appStore.getLayout)
|
|
||||||
|
|
||||||
const drawer = ref(false)
|
const drawer = ref(false)
|
||||||
|
|
||||||
// 主题色相关
|
// 主题色相关
|
||||||
@ -47,70 +44,27 @@ const setSystemTheme = (color: string) => {
|
|||||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||||
|
|
||||||
const setHeaderTheme = (color: string) => {
|
const setHeaderTheme = (color: string) => {
|
||||||
const isDarkColor = colorIsDark(color)
|
appStore.setHeaderTheme(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) => {
|
||||||
const primaryColor = useCssVar('--el-color-primary', document.documentElement)
|
appStore.setMenuTheme(color)
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听layout变化,重置一些主题色
|
// watch(
|
||||||
watch(
|
// () => layout.value,
|
||||||
() => layout.value,
|
// (n) => {
|
||||||
(n) => {
|
// if (n === 'top' && !appStore.getIsDark) {
|
||||||
if (n === 'top' && !appStore.getIsDark) {
|
// headerTheme.value = '#fff'
|
||||||
headerTheme.value = '#fff'
|
// setHeaderTheme('#fff')
|
||||||
setHeaderTheme('#fff')
|
// } else {
|
||||||
} else {
|
// setMenuTheme(unref(menuTheme))
|
||||||
setMenuTheme(unref(menuTheme))
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// )
|
||||||
)
|
|
||||||
|
|
||||||
// 拷贝
|
// 拷贝
|
||||||
const copyConfig = async () => {
|
const copyConfig = async () => {
|
||||||
@ -179,7 +133,8 @@ 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'))
|
||||||
@ -193,11 +148,15 @@ const copyConfig = async () => {
|
|||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
const clear = () => {
|
const clear = () => {
|
||||||
removeStorage('layout')
|
storageClear()
|
||||||
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>
|
||||||
@ -219,7 +178,7 @@ const clear = () => {
|
|||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<!-- 主题 -->
|
<!-- 主题 -->
|
||||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||||
<ThemeSwitch />
|
<ThemeSwitch @change="themeChange" />
|
||||||
|
|
||||||
<!-- 布局 -->
|
<!-- 布局 -->
|
||||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||||
@ -260,7 +219,6 @@ const clear = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 菜单主题 -->
|
<!-- 菜单主题 -->
|
||||||
<template v-if="layout !== 'top'">
|
|
||||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||||
<ColorRadioPicker
|
<ColorRadioPicker
|
||||||
v-model="menuTheme"
|
v-model="menuTheme"
|
||||||
@ -276,7 +234,6 @@ const clear = () => {
|
|||||||
]"
|
]"
|
||||||
@change="setMenuTheme"
|
@change="setMenuTheme"
|
||||||
/>
|
/>
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 界面显示 -->
|
<!-- 界面显示 -->
|
||||||
@ -285,14 +242,14 @@ const clear = () => {
|
|||||||
|
|
||||||
<ElDivider />
|
<ElDivider />
|
||||||
<div>
|
<div>
|
||||||
<ElButton type="primary" class="w-full" @click="copyConfig">{{
|
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||||||
t('setting.copy')
|
t('setting.copy')
|
||||||
}}</ElButton>
|
}}</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5px">
|
<div class="mt-5px">
|
||||||
<ElButton type="danger" class="w-full" @click="clear">
|
<BaseButton type="danger" class="w-full" @click="clear">
|
||||||
{{ t('setting.clearAndReset') }}
|
{{ t('setting.clearAndReset') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</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,6 +88,9 @@ export default defineComponent({
|
|||||||
} else {
|
} else {
|
||||||
showTitle.value = !collapse
|
showTitle.value = !collapse
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -202,11 +205,12 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
<Menu
|
<Menu
|
||||||
class={[
|
class={[
|
||||||
'!absolute top-0 z-4000',
|
'!absolute top-0 z-3000',
|
||||||
{
|
{
|
||||||
'!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)]': unref(showMenu) || unref(fixedMenu),
|
'!w-[var(--left-menu-max-width)] border-r-1 border-r-solid border-[var(--el-border-color)]':
|
||||||
|
unref(showMenu) || unref(fixedMenu),
|
||||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
|
@ -5,7 +5,9 @@ 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'
|
||||||
@ -15,9 +17,10 @@ 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()
|
||||||
|
|
||||||
@ -60,8 +63,13 @@ export default defineComponent({
|
|||||||
type: Array as PropType<Recordable[]>,
|
type: Array as PropType<Recordable[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
// 是否自动预览
|
// 图片自动预览字段数组
|
||||||
preview: {
|
imagePreview: {
|
||||||
|
type: Array as PropType<string[]>,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 视频自动预览字段数组
|
||||||
|
videoPreview: {
|
||||||
type: Array as PropType<string[]>,
|
type: Array as PropType<string[]>,
|
||||||
default: () => []
|
default: () => []
|
||||||
},
|
},
|
||||||
@ -72,7 +80,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) => ['medium', 'small', 'mini'].includes(v)
|
validator: (v: ComponentSize) => ['default', 'small', 'large'].includes(v)
|
||||||
},
|
},
|
||||||
fit: propTypes.bool.def(true),
|
fit: propTypes.bool.def(true),
|
||||||
showHeader: propTypes.bool.def(true),
|
showHeader: propTypes.bool.def(true),
|
||||||
@ -191,9 +199,27 @@ 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: () => ({})
|
||||||
},
|
},
|
||||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
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'],
|
||||||
setup(props, { attrs, emit, slots, expose }) {
|
setup(props, { attrs, emit, slots, expose }) {
|
||||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||||
|
|
||||||
@ -218,33 +244,6 @@ 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
|
||||||
@ -265,7 +264,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) {
|
if (index !== void 0) {
|
||||||
columns.splice(index, 0, column)
|
columns.splice(index, 0, column)
|
||||||
} else {
|
} else {
|
||||||
columns.push(column)
|
columns.push(column)
|
||||||
@ -350,11 +349,13 @@ 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, preview } = unref(getProps)
|
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
|
||||||
|
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
|
||||||
@ -365,10 +366,10 @@ export default defineComponent({
|
|||||||
const slots = {
|
const slots = {
|
||||||
default: (...args: any[]) => {
|
default: (...args: any[]) => {
|
||||||
const data = args[0]
|
const data = args[0]
|
||||||
let isImageUrl = false
|
let isPreview = false
|
||||||
if (preview.length) {
|
isPreview =
|
||||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
imagePreview.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)
|
||||||
@ -376,8 +377,8 @@ export default defineComponent({
|
|||||||
? 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)
|
||||||
: isImageUrl
|
: isPreview
|
||||||
? renderPreview(get(data.row, v.field))
|
? renderPreview(get(data.row, v.field), v.field)
|
||||||
: get(data.row, v.field)
|
: get(data.row, v.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -399,17 +400,32 @@ export default defineComponent({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderPreview = (url: string) => {
|
const renderPreview = (url: string, field: 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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -424,7 +440,8 @@ export default defineComponent({
|
|||||||
headerAlign,
|
headerAlign,
|
||||||
showOverflowTooltip,
|
showOverflowTooltip,
|
||||||
reserveSelection,
|
reserveSelection,
|
||||||
preview
|
imagePreview,
|
||||||
|
videoPreview
|
||||||
} = unref(getProps)
|
} = unref(getProps)
|
||||||
|
|
||||||
return (columnsChildren || columns).map((v) => {
|
return (columnsChildren || columns).map((v) => {
|
||||||
@ -450,6 +467,7 @@ 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>
|
||||||
@ -464,10 +482,10 @@ export default defineComponent({
|
|||||||
default: (...args: any[]) => {
|
default: (...args: any[]) => {
|
||||||
const data = args[0]
|
const data = args[0]
|
||||||
|
|
||||||
let isImageUrl = false
|
let isPreview = false
|
||||||
if (preview.length) {
|
isPreview =
|
||||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
imagePreview.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)
|
||||||
@ -475,8 +493,8 @@ export default defineComponent({
|
|||||||
? 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)
|
||||||
: isImageUrl
|
: isPreview
|
||||||
? renderPreview(get(data.row, v.field))
|
? renderPreview(get(data.row, v.field), v.field)
|
||||||
: get(data.row, v.field)
|
: get(data.row, v.field)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,22 +527,47 @@ 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 flex-wrap">
|
||||||
|
{unref(getProps)?.data?.length ? (
|
||||||
|
unref(getProps)?.data.map((item) => {
|
||||||
|
const cardSlots = {
|
||||||
|
default: () => {
|
||||||
|
return getSlot(slots, 'content', item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getSlot(slots, 'content-header')) {
|
||||||
|
cardSlots['header'] = () => {
|
||||||
|
return getSlot(slots, 'content-header', item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 class="flex justify-between mb-1">
|
<div class="flex justify-between mb-1">
|
||||||
<div>{toolbar}</div>
|
<div>{toolbar}</div>
|
||||||
<div class="pt-2">
|
<div class="pt-2">
|
||||||
@ -539,7 +582,6 @@ export default defineComponent({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ElTable
|
<ElTable
|
||||||
ref={elTableRef}
|
ref={elTableRef}
|
||||||
data={unref(getProps).data}
|
data={unref(getProps).data}
|
||||||
@ -555,6 +597,9 @@ export default defineComponent({
|
|||||||
...tableSlots
|
...tableSlots
|
||||||
}}
|
}}
|
||||||
</ElTable>
|
</ElTable>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{unref(getProps).pagination ? (
|
{unref(getProps).pagination ? (
|
||||||
<ElPagination
|
<ElPagination
|
||||||
v-model:pageSize={pageSizeRef.value}
|
v-model:pageSize={pageSizeRef.value}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
ElPopover,
|
ElPopover,
|
||||||
ElCheckbox,
|
ElCheckbox,
|
||||||
ElScrollbar,
|
ElScrollbar,
|
||||||
ElButton,
|
|
||||||
ElTable,
|
ElTable,
|
||||||
ElDivider
|
ElDivider
|
||||||
} from 'element-plus'
|
} from 'element-plus'
|
||||||
@ -23,14 +22,10 @@ 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: {
|
||||||
@ -47,6 +42,10 @@ 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')
|
||||||
}
|
}
|
||||||
@ -263,9 +262,9 @@ export default defineComponent({
|
|||||||
{t('common.SerialNumberColumn')}
|
{t('common.SerialNumberColumn')}
|
||||||
</ElCheckbox>
|
</ElCheckbox>
|
||||||
</div>
|
</div>
|
||||||
<ElButton type="primary" link onClick={resetTableColumns}>
|
<BaseButton type="primary" link onClick={resetTableColumns}>
|
||||||
{t('common.reset')}
|
{t('common.reset')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<ElScrollbar max-height="400px">
|
<ElScrollbar max-height="400px">
|
||||||
<VueDraggable
|
<VueDraggable
|
||||||
|
@ -210,13 +210,14 @@ 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -582,4 +583,3 @@ watch(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@/hooks/web/useTagsView
|
|
||||||
|
@ -9,6 +9,8 @@ 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' })
|
||||||
@ -23,6 +25,7 @@ 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@ -73,13 +73,13 @@ const user = computed(() => authStore.getUser)
|
|||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<ElButton @click="toHome" link>个人主页</ElButton>
|
<BaseButton @click="toHome" link>个人主页</BaseButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<ElButton @click="toGitee" link>Gitee</ElButton>
|
<BaseButton @click="toGitee" link>Gitee</BaseButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem>
|
<ElDropdownItem>
|
||||||
<ElButton @click="toGithub" link>Github</ElButton>
|
<BaseButton @click="toGithub" link>Github</BaseButton>
|
||||||
</ElDropdownItem>
|
</ElDropdownItem>
|
||||||
<ElDropdownItem divided>
|
<ElDropdownItem divided>
|
||||||
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
||||||
|
@ -7,7 +7,6 @@ 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'
|
||||||
|
|
||||||
@ -87,14 +86,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>
|
||||||
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
|
<BaseButton type="primary" @click="handleLock">{{ t('lock.lock') }}</BaseButton>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
:global(.v-lock-dialog) {
|
:global(.v-lock-dialog) {
|
||||||
@media (max-width: 767px) {
|
@media (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, ElButton } from 'element-plus'
|
import { ElInput } 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
|
|
||||||
const authStore = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
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`">
|
||||||
<ElButton
|
<BaseButton
|
||||||
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') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
<ElButton
|
<BaseButton
|
||||||
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') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
<ElButton
|
<BaseButton
|
||||||
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') }}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -190,6 +190,7 @@ 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;
|
||||||
@ -201,6 +202,7 @@ 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;
|
||||||
|
27
kinit-admin/src/components/VideoPlayer/index.ts
Normal file
27
kinit-admin/src/components/VideoPlayer/index.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
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)
|
||||||
|
}
|
51
kinit-admin/src/components/VideoPlayer/src/VideoPlayer.vue
Normal file
51
kinit-admin/src/components/VideoPlayer/src/VideoPlayer.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<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>
|
3
kinit-admin/src/components/VideoPlayerViewer/index.ts
Normal file
3
kinit-admin/src/components/VideoPlayerViewer/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import VideoPlayerViewer from './src/VideoPlayerViewer.vue'
|
||||||
|
|
||||||
|
export { VideoPlayerViewer }
|
@ -0,0 +1,46 @@
|
|||||||
|
<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>
|
3
kinit-admin/src/components/Waterfall/index.ts
Normal file
3
kinit-admin/src/components/Waterfall/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import Waterfall from './src/Waterfall.vue'
|
||||||
|
|
||||||
|
export { Waterfall }
|
234
kinit-admin/src/components/Waterfall/src/Waterfall.vue
Normal file
234
kinit-admin/src/components/Waterfall/src/Waterfall.vue
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
<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,8 +1,10 @@
|
|||||||
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,7 +1,5 @@
|
|||||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
||||||
import { useStorage } from '@/hooks/web/useStorage'
|
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||||
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'
|
||||||
@ -9,8 +7,6 @@ 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
|
||||||
@ -21,10 +17,10 @@ const service: AxiosInstance = axios.create({
|
|||||||
// request拦截器
|
// request拦截器
|
||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
(config: InternalAxiosRequestConfig) => {
|
(config: InternalAxiosRequestConfig) => {
|
||||||
const appStore = useAppStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
const token = getStorage(appStore.getToken)
|
const token = authStore.getToken
|
||||||
if (token !== '') {
|
if (token !== '') {
|
||||||
;(config.headers as any)['Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
;(config.headers as any)[authStore.getTokenKey ?? 'Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
config.method === 'post' &&
|
config.method === 'post' &&
|
||||||
@ -87,18 +83,18 @@ service.interceptors.response.use(
|
|||||||
if (refresh === '1') {
|
if (refresh === '1') {
|
||||||
// 因token快过期,刷新token
|
// 因token快过期,刷新token
|
||||||
refreshToken().then((res) => {
|
refreshToken().then((res) => {
|
||||||
const appStore = useAppStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
||||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
authStore.setRefreshToken(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 appStore = useAppStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
||||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
authStore.setRefreshToken(res.data.refresh_token)
|
||||||
ElMessage.error('操作失败,请重试')
|
ElMessage.error('操作失败,请重试')
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -108,7 +104,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 = useAuthStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
const status = error.response?.status
|
const status = error.response?.status
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 400:
|
case 400:
|
||||||
@ -117,7 +113,7 @@ service.interceptors.response.use(
|
|||||||
case 401:
|
case 401:
|
||||||
// 强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
// 强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
||||||
authStore.logout()
|
authStore.logout()
|
||||||
message = '认证已过期,请重新登录'
|
message = '认证已失效,请重新登录'
|
||||||
break
|
break
|
||||||
case 403:
|
case 403:
|
||||||
// 强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
// 强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
||||||
@ -158,8 +154,8 @@ service.interceptors.response.use(
|
|||||||
|
|
||||||
// 刷新Token
|
// 刷新Token
|
||||||
const refreshToken = (): Promise<IResponse> => {
|
const refreshToken = (): Promise<IResponse> => {
|
||||||
const appStore = useAppStore()
|
const authStore = useAuthStoreWithOut()
|
||||||
const data = getStorage(appStore.getRefreshToken)
|
const data = authStore.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'))
|
||||||
|
47
kinit-admin/src/hooks/web/useClipboard.ts
Normal file
47
kinit-admin/src/hooks/web/useClipboard.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
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,20 +79,15 @@ 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]
|
||||||
// 判断是否隐藏
|
// 判断是否隐藏
|
||||||
if (!schemaItem?.search?.hidden) {
|
|
||||||
const searchSchemaItem = {
|
const searchSchemaItem = {
|
||||||
component: schemaItem?.search?.component || 'Input',
|
component: schemaItem?.search?.component || 'Input',
|
||||||
...schemaItem.search,
|
...schemaItem.search,
|
||||||
field: schemaItem.field,
|
field: schemaItem.field,
|
||||||
label: schemaItem.label
|
label: schemaItem.search?.label || schemaItem.label
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除不必要的字段
|
|
||||||
delete searchSchemaItem.hidden
|
|
||||||
|
|
||||||
searchSchema.push(searchSchemaItem)
|
searchSchema.push(searchSchemaItem)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return searchSchema
|
return searchSchema
|
||||||
}
|
}
|
||||||
@ -103,8 +98,8 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
|
|||||||
conversion: (schema: CrudSchema) => {
|
conversion: (schema: CrudSchema) => {
|
||||||
if (!schema?.table?.hidden) {
|
if (!schema?.table?.hidden) {
|
||||||
return {
|
return {
|
||||||
...schema.table,
|
...schema,
|
||||||
...schema
|
...schema.table
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -127,20 +122,15 @@ 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]
|
||||||
// 判断是否隐藏
|
// 判断是否隐藏
|
||||||
if (!formItem?.form?.hidden) {
|
|
||||||
const formSchemaItem = {
|
const formSchemaItem = {
|
||||||
component: formItem?.form?.component || 'Input',
|
component: formItem?.form?.component || 'Input',
|
||||||
...formItem.form,
|
...formItem.form,
|
||||||
field: formItem.field,
|
field: formItem.field,
|
||||||
label: formItem.label
|
label: formItem.form?.label || formItem.label
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除不必要的字段
|
|
||||||
delete formSchemaItem.hidden
|
|
||||||
|
|
||||||
formSchema.push(formSchemaItem)
|
formSchema.push(formSchemaItem)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return formSchema
|
return formSchema
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ 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实例
|
||||||
@ -93,9 +94,27 @@ export const useForm = () => {
|
|||||||
* @description 获取表单数据
|
* @description 获取表单数据
|
||||||
* @returns form data
|
* @returns form data
|
||||||
*/
|
*/
|
||||||
getFormData: async <T = Recordable>(): Promise<T> => {
|
getFormData: async <T = Recordable>(filterEmptyVal = true): Promise<T> => {
|
||||||
const form = await getForm()
|
const form = await getForm()
|
||||||
return form?.formModel as T
|
const model = form?.formModel as any
|
||||||
|
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
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
21
kinit-admin/src/hooks/web/useNetwork.ts
Normal file
21
kinit-admin/src/hooks/web/useNetwork.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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,13 +1,15 @@
|
|||||||
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' = 'sessionStorage') => {
|
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'localStorage') => {
|
||||||
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,8 +71,14 @@ export default defineComponent({
|
|||||||
|
|
||||||
.@{prefix-cls} {
|
.@{prefix-cls} {
|
||||||
background-color: var(--app-content-bg-color);
|
background-color: var(--app-content-bg-color);
|
||||||
:deep(.@{elNamespace}-scrollbar__view) {
|
.@{prefix-cls}-content-scrollbar {
|
||||||
|
& > :deep(.el-scrollbar__wrap) {
|
||||||
|
& > .@{elNamespace}-scrollbar__view {
|
||||||
|
display: flex;
|
||||||
height: 100% !important;
|
height: 100% !important;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -6,10 +6,6 @@ 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()
|
||||||
@ -17,39 +13,12 @@ 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="[
|
||||||
'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)]',
|
'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)]'
|
||||||
{
|
|
||||||
'!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,8 +42,7 @@ 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(--app-footer-height))]': !fixedHeader.value,
|
'h-[calc(100%-var(--top-tool-height))]': !fixedHeader.value,
|
||||||
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
|
'h-[calc(100%-var(--tags-view-height)-var(--top-tool-height))]': fixedHeader.value
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
@ -50,6 +50,8 @@ 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'
|
||||||
},
|
},
|
||||||
@ -186,7 +188,15 @@ 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'
|
||||||
@ -332,7 +342,8 @@ 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',
|
||||||
@ -461,7 +472,9 @@ 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,6 +50,7 @@ export default {
|
|||||||
notSpace: '不能包含空格',
|
notSpace: '不能包含空格',
|
||||||
notSpecialCharacters: '不能包含特殊字符',
|
notSpecialCharacters: '不能包含特殊字符',
|
||||||
isEqual: '两次输入不一致',
|
isEqual: '两次输入不一致',
|
||||||
|
setting: '设置',
|
||||||
selectAll: '全选',
|
selectAll: '全选',
|
||||||
SerialNumberColumn: '序号列'
|
SerialNumberColumn: '序号列'
|
||||||
},
|
},
|
||||||
@ -84,7 +85,7 @@ export default {
|
|||||||
sizeIcon: '尺寸图标',
|
sizeIcon: '尺寸图标',
|
||||||
localeIcon: '多语言图标',
|
localeIcon: '多语言图标',
|
||||||
tagsView: '标签页',
|
tagsView: '标签页',
|
||||||
logo: '标志',
|
logo: 'Logo',
|
||||||
greyMode: '灰色模式',
|
greyMode: '灰色模式',
|
||||||
fixedHeader: '固定头部',
|
fixedHeader: '固定头部',
|
||||||
headerTheme: '头部主题',
|
headerTheme: '头部主题',
|
||||||
@ -184,7 +185,14 @@ export default {
|
|||||||
permission: '权限测试页',
|
permission: '权限测试页',
|
||||||
function: '功能',
|
function: '功能',
|
||||||
multipleTabs: '多开标签页',
|
multipleTabs: '多开标签页',
|
||||||
details: '详情页'
|
details: '详情页',
|
||||||
|
iconPicker: '图标选择器',
|
||||||
|
request: '请求',
|
||||||
|
waterfall: '瀑布流',
|
||||||
|
imageCropping: '图片裁剪',
|
||||||
|
videoPlayer: '视频播放器',
|
||||||
|
tableVideoPreview: '表格视频预览',
|
||||||
|
cardTable: '卡片表格'
|
||||||
},
|
},
|
||||||
permission: {
|
permission: {
|
||||||
hasPermission: '请设置操作权限值'
|
hasPermission: '请设置操作权限值'
|
||||||
@ -327,7 +335,8 @@ export default {
|
|||||||
customContent: '自定义内容',
|
customContent: '自定义内容',
|
||||||
lazyLoad: '懒加载',
|
lazyLoad: '懒加载',
|
||||||
upload: '上传',
|
upload: '上传',
|
||||||
userAvatar: '用户头像'
|
userAvatar: '用户头像',
|
||||||
|
iconPicker: '图标选择器'
|
||||||
},
|
},
|
||||||
guideDemo: {
|
guideDemo: {
|
||||||
guide: '引导页',
|
guide: '引导页',
|
||||||
@ -454,7 +463,9 @@ export default {
|
|||||||
fixedHeaderOrAuto: '固定头部/自动',
|
fixedHeaderOrAuto: '固定头部/自动',
|
||||||
getSelections: '获取多选数据',
|
getSelections: '获取多选数据',
|
||||||
preview: '封面',
|
preview: '封面',
|
||||||
showOrHiddenSortable: '显示/隐藏排序'
|
showOrHiddenSortable: '显示/隐藏排序',
|
||||||
|
videoPreview: '视频预览',
|
||||||
|
cardTable: '卡片表格'
|
||||||
},
|
},
|
||||||
richText: {
|
richText: {
|
||||||
richText: '富文本',
|
richText: '富文本',
|
||||||
@ -530,7 +541,7 @@ export default {
|
|||||||
menu: {
|
menu: {
|
||||||
menuName: '菜单名称',
|
menuName: '菜单名称',
|
||||||
icon: '图标',
|
icon: '图标',
|
||||||
permission: '权限标识',
|
permission: '按钮权限',
|
||||||
component: '组件',
|
component: '组件',
|
||||||
path: '路径',
|
path: '路径',
|
||||||
status: '状态',
|
status: '状态',
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'vue/jsx'
|
||||||
|
|
||||||
// 引入windi css
|
// 引入windi css
|
||||||
import '@/plugins/unocss'
|
import '@/plugins/unocss'
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
|
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'
|
||||||
@ -9,23 +8,19 @@ 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'] // 不重定向白名单
|
const whiteList = ['/login', '/docs/privacy', '/docs/agreement'] // 不重定向白名单
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
start()
|
start()
|
||||||
loadStart()
|
loadStart()
|
||||||
if (getStorage(appStore.getToken)) {
|
const permissionStore = usePermissionStoreWithOut()
|
||||||
|
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') {
|
||||||
@ -35,6 +30,9 @@ 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
|
||||||
}
|
}
|
||||||
@ -42,7 +40,6 @@ 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,7 +12,13 @@ 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,3 +1 @@
|
|||||||
import 'virtual:svg-icons-register'
|
import 'virtual:svg-icons-register'
|
||||||
|
|
||||||
import '@purge-icons/generated'
|
|
||||||
|
@ -70,6 +70,37 @@ 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'),
|
||||||
@ -115,7 +146,17 @@ const router = createRouter({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export const resetRouter = (): void => {
|
export const resetRouter = (): void => {
|
||||||
const resetWhiteNameList = ['Login', 'NoFind', 'Root']
|
const resetWhiteNameList = [
|
||||||
|
'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)) {
|
||||||
@ -124,6 +165,12 @@ 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,10 +1,12 @@
|
|||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import piniaPersist from 'pinia-plugin-persist'
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
|
// pinia-plugin-persistedstate 持久化存储官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/
|
||||||
|
|
||||||
const store = createPinia()
|
const store = createPinia()
|
||||||
|
|
||||||
store.use(piniaPersist)
|
store.use(piniaPluginPersistedstate)
|
||||||
|
|
||||||
export const setupStore = (app: App<Element>) => {
|
export const setupStore = (app: App<Element>) => {
|
||||||
app.use(store)
|
app.use(store)
|
||||||
|
@ -2,9 +2,10 @@ 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 { useStorage } from '@/hooks/web/useStorage'
|
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
|
||||||
|
import { useCssVar } from '@vueuse/core'
|
||||||
const { getStorage, setStorage } = useStorage()
|
import { unref } from 'vue'
|
||||||
|
import { useDark } from '@vueuse/core'
|
||||||
|
|
||||||
interface AppState {
|
interface AppState {
|
||||||
breadcrumb: boolean
|
breadcrumb: boolean
|
||||||
@ -25,7 +26,6 @@ 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,8 +34,6 @@ 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
|
||||||
@ -44,7 +42,6 @@ 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, // 标题
|
||||||
@ -63,14 +60,14 @@ export const useAppStore = defineStore('app', {
|
|||||||
fixedHeader: true, // 固定toolheader
|
fixedHeader: true, // 固定toolheader
|
||||||
footer: true, // 显示页脚
|
footer: true, // 显示页脚
|
||||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||||
dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
|
dynamicRouter: true, // 是否动态路由
|
||||||
serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
|
serverDynamicRouter: true, // 是否服务端渲染动态路由
|
||||||
fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
|
fixedMenu: false, // 是否固定菜单
|
||||||
|
|
||||||
layout: getStorage('layout') || 'classic', // layout布局
|
layout: 'classic', // layout布局
|
||||||
isDark: getStorage('isDark'), // 是否是暗黑模式
|
isDark: false, // 是否是暗黑模式
|
||||||
currentSize: getStorage('default') || 'default', // 组件尺寸
|
currentSize: 'default', // 组件尺寸
|
||||||
theme: getStorage('theme') || {
|
theme: {
|
||||||
// 主题色
|
// 主题色
|
||||||
elColorPrimary: '#409eff',
|
elColorPrimary: '#409eff',
|
||||||
// 左侧菜单边框颜色
|
// 左侧菜单边框颜色
|
||||||
@ -101,8 +98,6 @@ export const useAppStore = defineStore('app', {
|
|||||||
topToolBorderColor: '#eee'
|
topToolBorderColor: '#eee'
|
||||||
},
|
},
|
||||||
|
|
||||||
token: 'Token', // 存储Token字段
|
|
||||||
refreshToken: 'RefreshToken', // 存储刷新Token字段
|
|
||||||
logoImage: '', // logo图片
|
logoImage: '', // logo图片
|
||||||
footerContent: '', // 页脚内容
|
footerContent: '', // 页脚内容
|
||||||
icpNumber: '' // 备案号
|
icpNumber: '' // 备案号
|
||||||
@ -166,9 +161,6 @@ 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
|
||||||
},
|
},
|
||||||
@ -191,12 +183,6 @@ 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
|
||||||
},
|
},
|
||||||
@ -245,15 +231,12 @@ 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) {
|
||||||
@ -265,7 +248,6 @@ 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
|
||||||
@ -279,23 +261,22 @@ 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')
|
||||||
}
|
}
|
||||||
setStorage('isDark', this.isDark)
|
this.setPrimaryLight()
|
||||||
},
|
},
|
||||||
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
|
||||||
@ -309,8 +290,75 @@ 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,7 +2,6 @@ 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'
|
||||||
@ -10,7 +9,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 { setStorage, clear } = useStorage()
|
const { clear } = useStorage()
|
||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
id?: number
|
id?: number
|
||||||
@ -32,6 +31,9 @@ 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', {
|
||||||
@ -40,10 +42,22 @@ 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
|
||||||
},
|
},
|
||||||
@ -58,24 +72,34 @@ 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) {
|
||||||
const appStore = useAppStore()
|
this.token = `${res.data.token_type} ${res.data.access_token}`
|
||||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
this.refreshToken = res.data.refresh_token
|
||||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
|
||||||
// 获取当前登录用户的信息
|
// 获取当前登录用户的信息
|
||||||
await this.setUserInfo()
|
await this.setUserInfo()
|
||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
},
|
},
|
||||||
logout(message?: string) {
|
reset() {
|
||||||
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()
|
||||||
@ -105,7 +129,8 @@ export const useAuthStore = defineStore('auth', {
|
|||||||
})
|
})
|
||||||
this.permissions = res.data.permissions
|
this.permissions = res.data.permissions
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
persist: true
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useAuthStoreWithOut = () => {
|
export const useAuthStoreWithOut = () => {
|
||||||
|
@ -10,7 +10,11 @@ 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 = {}
|
||||||
@ -34,7 +38,8 @@ 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()
|
const { getStorage, setStorage } = useStorage('localStorage')
|
||||||
|
|
||||||
const elLocaleMap = {
|
const elLocaleMap = {
|
||||||
'zh-CN': zhCn,
|
'zh-CN': zhCn,
|
||||||
@ -51,7 +51,8 @@ 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,10 +40,7 @@ export const useLockStore = defineStore('lock', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
persist: {
|
persist: true
|
||||||
enabled: true,
|
|
||||||
strategies: [{ key: 'lock', storage: localStorage }]
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useLockStoreWithOut = () => {
|
export const useLockStoreWithOut = () => {
|
||||||
|
@ -60,6 +60,9 @@ 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,12 +4,7 @@ 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 { useStorage } from '@/hooks/web/useStorage'
|
import { useAuthStoreWithOut } from './auth'
|
||||||
import { useAppStoreWithOut } from './app'
|
|
||||||
|
|
||||||
const appStore = useAppStoreWithOut()
|
|
||||||
|
|
||||||
const { getStorage } = useStorage()
|
|
||||||
|
|
||||||
export interface TagsViewState {
|
export interface TagsViewState {
|
||||||
visitedViews: RouteLocationNormalizedLoaded[]
|
visitedViews: RouteLocationNormalizedLoaded[]
|
||||||
@ -95,8 +90,9 @@ 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 = getStorage(appStore.getUserInfo)
|
this.visitedViews = authStore.getUser
|
||||||
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
||||||
: []
|
: []
|
||||||
},
|
},
|
||||||
@ -157,7 +153,8 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
persist: false
|
||||||
})
|
})
|
||||||
|
|
||||||
export const useTagsViewStoreWithOut = () => {
|
export const useTagsViewStoreWithOut = () => {
|
||||||
|
@ -151,3 +151,22 @@ 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,5 +1,3 @@
|
|||||||
// import type { Plugin } from 'vue'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param component 需要注册的组件
|
* @param component 需要注册的组件
|
||||||
@ -47,6 +45,10 @@ 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 查找的数组
|
||||||
@ -123,6 +125,17 @@ 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,12 +1,11 @@
|
|||||||
import { createTypes, VueTypesInterface, VueTypeValidableDef } from 'vue-types'
|
import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } 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 propTypes = createTypes({
|
const newPropTypes = createTypes({
|
||||||
func: undefined,
|
func: undefined,
|
||||||
bool: undefined,
|
bool: undefined,
|
||||||
string: undefined,
|
string: undefined,
|
||||||
@ -15,15 +14,12 @@ const propTypes = createTypes({
|
|||||||
integer: undefined
|
integer: undefined
|
||||||
}) as PropTypes
|
}) as PropTypes
|
||||||
|
|
||||||
// 需要自定义扩展的类型
|
class propTypes extends newPropTypes {
|
||||||
// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
static get style() {
|
||||||
propTypes.extend([
|
return toValidableType('style', {
|
||||||
{
|
type: [String, Object]
|
||||||
name: 'style',
|
})
|
||||||
getter: true,
|
|
||||||
type: [String, Object],
|
|
||||||
default: undefined
|
|
||||||
}
|
}
|
||||||
])
|
}
|
||||||
|
|
||||||
export { propTypes }
|
export { propTypes }
|
||||||
|
@ -3,6 +3,7 @@ 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'
|
||||||
@ -12,7 +13,7 @@ const activeName = ref('user')
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5">
|
<ContentWrap>
|
||||||
<ElTabs v-model="activeName">
|
<ElTabs v-model="activeName">
|
||||||
<ElTabPane label="财务分析" name="finance" :lazy="true">
|
<ElTabPane label="财务分析" name="finance" :lazy="true">
|
||||||
<Finance />
|
<Finance />
|
||||||
@ -21,7 +22,7 @@ const activeName = ref('user')
|
|||||||
<User />
|
<User />
|
||||||
</ElTabPane>
|
</ElTabPane>
|
||||||
</ElTabs>
|
</ElTabs>
|
||||||
</div>
|
</ContentWrap>
|
||||||
</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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
let genderOptions = ref<DictDetail[]>([])
|
let genderOptions = ref<DictDetail[]>([])
|
||||||
|
|
||||||
|
@ -3,13 +3,14 @@ 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
import { ElButton, ElMessage } from 'element-plus'
|
import { 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const formSchema = reactive<FormSchema[]>([
|
const formSchema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -92,9 +93,9 @@ const formSchema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[50%]">
|
<div class="w-[50%]">
|
||||||
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
||||||
保存
|
保存
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -3,13 +3,14 @@ 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
import { ElButton, ElMessage } from 'element-plus'
|
import { 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const formSchema = reactive<FormSchema[]>([
|
const formSchema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -57,9 +58,9 @@ const formSchema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[50%]">
|
<div class="w-[50%]">
|
||||||
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
<BaseButton loading={loading.value} type="primary" class="w-[100%]" onClick={save}>
|
||||||
保存
|
保存
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -101,7 +102,8 @@ const save = async () => {
|
|||||||
const res = await postCurrentUserResetPassword(formData)
|
const res = await postCurrentUserResetPassword(formData)
|
||||||
if (res) {
|
if (res) {
|
||||||
elForm?.resetFields()
|
elForm?.resetFields()
|
||||||
ElMessage.success('保存成功')
|
authStore.logout()
|
||||||
|
ElMessage.warning('请重新登录')
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
@ -9,6 +9,7 @@ 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()
|
||||||
|
|
||||||
@ -28,6 +29,11 @@ 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>
|
||||||
@ -88,6 +94,9 @@ const toPasswordLogin = () => {
|
|||||||
/>
|
/>
|
||||||
</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 { ElButton, ElCheckbox, ElLink } from 'element-plus'
|
import { 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } 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,10 +21,9 @@ const { required } = useValidator()
|
|||||||
|
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
const authStore = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
const { currentRoute, addRoute, push } = useRouter()
|
const { currentRoute, addRoute, push } = useRouter()
|
||||||
const { setStorage } = useStorage()
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@ -122,14 +121,19 @@ const schema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<ElButton loading={loading.value} type="primary" class="w-[100%]" onClick={signIn}>
|
<BaseButton
|
||||||
|
loading={loading.value}
|
||||||
|
type="primary"
|
||||||
|
class="w-[100%]"
|
||||||
|
onClick={signIn}
|
||||||
|
>
|
||||||
{t('login.login')}
|
{t('login.login')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[100%] mt-15px">
|
<div class="w-[100%] mt-15px">
|
||||||
<ElButton class="w-[100%]" onClick={toTelephoneLogin}>
|
<BaseButton class="w-[100%]" onClick={toTelephoneLogin}>
|
||||||
{t('login.smsLogin')}
|
{t('login.smsLogin')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -241,7 +245,6 @@ 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 { ElButton, ElInput, FormRules, ElDivider, ElMessage } from 'element-plus'
|
import { 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } 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,8 +22,7 @@ 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
const { setStorage } = useStorage()
|
|
||||||
|
|
||||||
const schema = reactive<FormSchema[]>([
|
const schema = reactive<FormSchema[]>([
|
||||||
{
|
{
|
||||||
@ -74,13 +73,13 @@ const schema = reactive<FormSchema[]>([
|
|||||||
<>
|
<>
|
||||||
<ElDivider direction="vertical" />
|
<ElDivider direction="vertical" />
|
||||||
{SMSCodeStatus.value ? (
|
{SMSCodeStatus.value ? (
|
||||||
<ElButton type="primary" link onClick={getSMSCode}>
|
<BaseButton type="primary" link onClick={getSMSCode}>
|
||||||
{t('login.getSMSCode')}
|
{t('login.getSMSCode')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
) : (
|
) : (
|
||||||
<ElButton type="primary" disabled={!SMSCodeStatus.value} link>
|
<BaseButton type="primary" disabled={!SMSCodeStatus.value} link>
|
||||||
{SMSCodeNumber.value + t('login.SMSCodeRetry')}
|
{SMSCodeNumber.value + t('login.SMSCodeRetry')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
@ -110,19 +109,19 @@ const schema = reactive<FormSchema[]>([
|
|||||||
return (
|
return (
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<div class="w-[100%]">
|
<div class="w-[100%]">
|
||||||
<ElButton
|
<BaseButton
|
||||||
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')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-[100%] mt-15px">
|
<div class="w-[100%] mt-15px">
|
||||||
<ElButton class="w-[100%]" onClick={toPasswordLogin}>
|
<BaseButton class="w-[100%]" onClick={toPasswordLogin}>
|
||||||
{t('login.passwordLogin')}
|
{t('login.passwordLogin')}
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -216,7 +215,6 @@ 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,21 +3,19 @@ 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 { useAuthStoreWithOut } from '@/store/modules/auth'
|
import { useAuthStore } from '@/store/modules/auth'
|
||||||
import { ElButton, ElMessage } from 'element-plus'
|
import { 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 = useAuthStoreWithOut()
|
const authStore = useAuthStore()
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
const permissionStore = usePermissionStore()
|
const permissionStore = usePermissionStore()
|
||||||
|
|
||||||
@ -112,7 +110,6 @@ 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) // 动态添加可访问路由表
|
||||||
@ -137,9 +134,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%]">
|
||||||
<ElButton :loading="loading" type="primary" class="w-[100%]" @click="save">
|
<BaseButton :loading="loading" type="primary" class="w-[100%]" @click="save">
|
||||||
重置密码
|
重置密码
|
||||||
</ElButton>
|
</BaseButton>
|
||||||
</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