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
|
||||
dvadmin-doc/docs/.vuepress/dist
|
126
README.md
126
README.md
@ -21,10 +21,11 @@
|
||||
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);
|
||||
- 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)为主要技术开发;
|
||||
- 后端加入 [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/),支持多终端认证系统。
|
||||
- 支持加载动态权限菜单,多方式轻松权限控制,按钮级别权限控制。
|
||||
- 已加入常见的 [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/):国内首个国密前后端分离快速开发平台
|
||||
|
||||
## 微信群
|
||||
|
||||
提供一个技术交流群,现在还没什么人哈哈哈哈哈,真心希望大家能够加入,积极讨论,因为本项目中还没有详细使用文档(一直在欠着,我也挺不好意思的),所以大家加入后,也可以很方便的一起讨论在使用中遇到各种问题,也可以提一些你想加入的功能,让我们更近一点,欢迎大家的加入。
|
||||
|
||||
|
||||
|
||||
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
|
||||
@ -58,9 +80,71 @@ PC端演示地址:https://kinit.ktianc.top
|
||||
</div>
|
||||
|
||||
|
||||
管理员账户:
|
||||
|
||||
- 账号:15020221010
|
||||
- 密码: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
|
||||
@ -71,7 +155,9 @@ github地址:https://github.com/vvandk/kinit
|
||||
|
||||
- [x] 菜单管理:配置系统菜单,操作权限,按钮权限标识、后端接口权限等。
|
||||
|
||||
- [x] 角色管理:角色菜单权限分配。
|
||||
- [x] 部门管理:支持无限层级部门配置。
|
||||
|
||||
- [x] 角色管理:角色菜单权限,角色部门权限分配。
|
||||
|
||||
- [x] 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
|
||||
|
||||
@ -171,10 +257,14 @@ git clone https://gitee.com/ktianc/kinit.git
|
||||
### 准备工作
|
||||
|
||||
```
|
||||
Python == 3.10 (其他版本均未测试)
|
||||
nodejs >= 14.0 (推荐使用最新稳定版)
|
||||
Mysql >= 8.0
|
||||
MongoDB (推荐使用最新稳定版)
|
||||
后端依赖版本:
|
||||
Python == 3.10.x (其他版本均未测试)
|
||||
前端依赖版本:
|
||||
nodejs >= 18.0 < 19
|
||||
pnpm >= 8.1.0 < 9
|
||||
数据库版本:
|
||||
Mysql >= 8.0 (8 以上未测试,以下版本未测试,postgresql 未测试,更换可能会涉及调整)
|
||||
MongoDB >= 7.0.12 < 8 (7 以上或以下版本均未测试)
|
||||
Redis (推荐使用最新稳定版)
|
||||
```
|
||||
|
||||
@ -195,6 +285,7 @@ Redis (推荐使用最新稳定版)
|
||||
```python
|
||||
# 安全警告: 不要在生产中打开调试运行!
|
||||
DEBUG = True # 如果当前为开发环境则改为 True,如果为生产环境则改为 False
|
||||
```
|
||||
|
||||
3. 修改项目数据库配置信息
|
||||
|
||||
@ -242,6 +333,7 @@ Redis (推荐使用最新稳定版)
|
||||
# 文档:https://user.ip138.com/ip/doc
|
||||
IP_PARSE_ENABLE = True
|
||||
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
||||
```
|
||||
|
||||
4. 并在`alembic.ini`文件中配置数据库信息,用于数据库映射
|
||||
|
||||
@ -251,13 +343,13 @@ Redis (推荐使用最新稳定版)
|
||||
[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]
|
||||
# 生产环境
|
||||
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. 创建数据库
|
||||
@ -413,7 +505,9 @@ pnpm run build:pro
|
||||
DEBUG = False # 生产环境应该改为 False
|
||||
```
|
||||
|
||||
3. 如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请修改如下内容,如果没有则不需要修改:
|
||||
3. (**如果没有安装数据库则不需要这一操作**)如果已有 Mysql 或者 Redis 或者 MongoDB 数据库,请执行以下操作:
|
||||
|
||||
请先在对应数据库中创建用户名以及数据库,并修改以下数据库连接改为已有的数据库连接
|
||||
|
||||
1. 修改 API 端配置文件:
|
||||
|
||||
@ -422,7 +516,7 @@ pnpm run build:pro
|
||||
```python
|
||||
# Mysql 数据库配置项
|
||||
# 连接引擎官方文档: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"
|
||||
|
||||
# Redis 数据库配置
|
||||
@ -438,9 +532,9 @@ pnpm run build:pro
|
||||
```
|
||||
|
||||
2. 修改定时任务配置文件
|
||||
|
||||
|
||||
文件路径为:`kinit-task/application/config/production.py`
|
||||
|
||||
|
||||
```python
|
||||
# Redis 数据库配置
|
||||
# 与接口是同一个数据库
|
||||
@ -457,7 +551,7 @@ pnpm run build:pro
|
||||
```
|
||||
|
||||
3. 将已有的数据库在 `docker-compose.yml` 文件中注释
|
||||
|
||||
|
||||
4. 配置阿里云 OSS 与 IP 解析接口地址(可选)
|
||||
|
||||
文件路径:`kinit-api/application/config/production.py`
|
||||
@ -484,7 +578,7 @@ pnpm run build:pro
|
||||
IP_PARSE_ENABLE = False
|
||||
IP_PARSE_TOKEN = "IP_PARSE_TOKEN"
|
||||
```
|
||||
|
||||
|
||||
5. 前端项目打包:
|
||||
|
||||
```shell
|
||||
@ -622,4 +716,4 @@ docker-compose ps -a
|
||||
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077860987.jpg"/></td>
|
||||
<td><img src="https://k-typora.oss-cn-beijing.aliyuncs.com/kinit/1670077870240.jpg"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</table>
|
||||
|
@ -1,8 +1,8 @@
|
||||
# 环境
|
||||
NODE_ENV=development
|
||||
VITE_NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASE_PATH=dev
|
||||
# 接口前缀,没用到
|
||||
# VITE_API_BASE_PATH=/api
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
@ -21,3 +21,15 @@ VITE_OUT_DIR=dist-dev
|
||||
|
||||
# 标题
|
||||
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=/
|
||||
@ -21,3 +21,15 @@ VITE_OUT_DIR=dist-pro
|
||||
|
||||
# 标题
|
||||
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/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
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
*-lock.*
|
||||
pnpm-debug
|
||||
stats.html
|
||||
dist-pro
|
||||
.vscode
|
||||
|
@ -3,7 +3,6 @@
|
||||
/dist*
|
||||
/public/*
|
||||
/docs/*
|
||||
/vite.config.ts
|
||||
/src/types/env.d.ts
|
||||
/docs/**/*
|
||||
/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",
|
||||
"version": "2.2.0",
|
||||
"version": "2.7.0",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode dev",
|
||||
"ts:check": "vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:dev": "vite build --mode dev",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"npm:check": "npx npm-check-updates",
|
||||
"clean": "npx rimraf node_modules",
|
||||
"clean:cache": "npx rimraf node_modules/.cache",
|
||||
"dev": "pnpm vite --mode dev",
|
||||
"ts:check": "pnpm vue-tsc --noEmit --skipLibCheck",
|
||||
"build:pro": "pnpm vite build --mode pro",
|
||||
"build:dev": "pnpm vite build --mode dev",
|
||||
"serve:pro": "pnpm vite preview --mode pro",
|
||||
"serve:dev": "pnpm vite preview --mode dev",
|
||||
"npm:check": "pnpx npm-check-updates -u",
|
||||
"clean": "pnpx rimraf node_modules",
|
||||
"clean:cache": "pnpx rimraf node_modules/.cache",
|
||||
"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: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": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"driver.js": "^1.2.1",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.3.9",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
"sortablejs": "^1.15.0",
|
||||
"url": "^0.11.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.1"
|
||||
"@iconify/iconify": "3.1.1",
|
||||
"@iconify/vue": "4.1.1",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@wangeditor/editor": "5.1.23",
|
||||
"@wangeditor/editor-for-vue": "5.1.10",
|
||||
"@zxcvbn-ts/core": "3.0.4",
|
||||
"animate.css": "4.1.1",
|
||||
"axios": "1.6.7",
|
||||
"cropperjs": "1.6.1",
|
||||
"dayjs": "1.11.10",
|
||||
"driver.js": "1.3.1",
|
||||
"echarts": "5.5.0",
|
||||
"echarts-wordcloud": "2.1.0",
|
||||
"element-plus": "2.5.6",
|
||||
"lodash-es": "4.17.21",
|
||||
"mitt": "3.0.1",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"pinia-plugin-persistedstate": "3.2.1",
|
||||
"qrcode": "1.5.3",
|
||||
"qs": "6.11.2",
|
||||
"url": "0.11.3",
|
||||
"vue": "3.4.20",
|
||||
"vue-draggable-plus": "0.3.5",
|
||||
"vue-i18n": "9.9.1",
|
||||
"vue-json-pretty": "2.3.0",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-types": "5.1.1",
|
||||
"xgplayer": "3.0.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.2.101",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@kjgl77/datav-vue3": "^1.6.1",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^20.4.10",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@unocss/transformer-variant-group": "^0.55.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"consola": "^3.2.3",
|
||||
"cron-validate": "^1.4.5",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-define-config": "^1.23.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"intro.js": "^7.2.0",
|
||||
"less": "^4.2.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"plop": "^3.1.2",
|
||||
"postcss": "^8.4.27",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^3.0.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^3.28.0",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.19.2",
|
||||
"typescript": "5.1.6",
|
||||
"unocss": "^0.55.0",
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
"vite-plugin-progress": "^0.0.7",
|
||||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"@amap/amap-jsapi-loader": "1.0.1",
|
||||
"@commitlint/cli": "19.0.1",
|
||||
"@commitlint/config-conventional": "19.0.0",
|
||||
"@iconify/json": "2.2.187",
|
||||
"@intlify/unplugin-vue-i18n": "2.0.0",
|
||||
"@kjgl77/datav-vue3": "1.6.1",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/inquirer": "9.0.7",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "20.11.21",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@types/qrcode": "1.5.5",
|
||||
"@types/qs": "6.9.12",
|
||||
"@types/sortablejs": "1.15.8",
|
||||
"@typescript-eslint/eslint-plugin": "7.1.0",
|
||||
"@typescript-eslint/parser": "7.1.0",
|
||||
"@unocss/transformer-variant-group": "0.58.5",
|
||||
"@vitejs/plugin-legacy": "5.3.1",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||
"autoprefixer": "10.4.17",
|
||||
"chalk": "5.3.0",
|
||||
"consola": "3.2.3",
|
||||
"cron-validate": "1.4.5",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-config-prettier": "9.1.0",
|
||||
"eslint-define-config": "2.1.0",
|
||||
"eslint-plugin-prettier": "5.1.3",
|
||||
"eslint-plugin-vue": "9.22.0",
|
||||
"esno": "4.0.0",
|
||||
"fs-extra": "11.2.0",
|
||||
"inquirer": "9.2.15",
|
||||
"less": "4.2.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"lodash": "4.17.21",
|
||||
"moment": "2.30.1",
|
||||
"plop": "4.0.1",
|
||||
"postcss": "8.4.35",
|
||||
"postcss-html": "1.6.0",
|
||||
"postcss-less": "6.0.0",
|
||||
"prettier": "3.2.5",
|
||||
"rimraf": "5.0.5",
|
||||
"rollup": "4.12.0",
|
||||
"rollup-plugin-visualizer": "5.12.0",
|
||||
"stylelint": "16.2.1",
|
||||
"stylelint-config-html": "1.1.0",
|
||||
"stylelint-config-recommended": "14.0.0",
|
||||
"stylelint-config-standard": "36.0.0",
|
||||
"stylelint-order": "6.0.4",
|
||||
"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-svg-icons": "^2.0.1",
|
||||
"vue-draggable-plus": "^0.2.6",
|
||||
"vue-tsc": "^1.8.8",
|
||||
"vue3-json-viewer": "^2.2.2"
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vue-draggable-plus": "0.2.6",
|
||||
"vite-plugin-url-copy": "1.1.3",
|
||||
"vue-tsc": "1.8.27",
|
||||
"vue3-json-viewer": "2.2.2",
|
||||
"zipson": "0.2.12"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.0",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
"node": ">=18 <19",
|
||||
"pnpm": ">=8.1.0 <10.0.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"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 { useAppStore } from '@/store/modules/app'
|
||||
import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||
import { isDark } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { getSystemBaseConfigApi } from '@/api/vadmin/system/settings'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
@ -17,18 +15,6 @@ const currentSize = computed(() => appStore.getCurrentSize)
|
||||
|
||||
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标签
|
||||
const addMeta = (name: string, content: string) => {
|
||||
const meta = document.createElement('meta')
|
||||
@ -39,6 +25,9 @@ const addMeta = (name: string, content: string) => {
|
||||
|
||||
// 获取并设置系统配置
|
||||
const setSystemConfig = async () => {
|
||||
if (appStore.getLogoImage) {
|
||||
return
|
||||
}
|
||||
const res = await getSystemBaseConfigApi()
|
||||
if (res) {
|
||||
appStore.setTitle(res.data.web_title || import.meta.env.VITE_APP_TITLE)
|
||||
@ -52,7 +41,7 @@ const setSystemConfig = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
setDefaultTheme()
|
||||
appStore.initTheme()
|
||||
setSystemConfig()
|
||||
</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'
|
||||
|
||||
export const getSystemSettingsTabsApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/system/settings/tabs', params })
|
||||
export const getSystemSettingsTabsApi = (data: any): Promise<IResponse> => {
|
||||
return request.post({ url: '/vadmin/system/settings/tabs', data })
|
||||
}
|
||||
|
||||
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">
|
||||
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
|
||||
import { ElCollapseTransition, ElTooltip, ElRow, ElCol } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ref, unref, PropType, computed, defineComponent } from 'vue'
|
||||
@ -16,6 +16,8 @@ const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('descriptions')
|
||||
|
||||
const defaultData = '-'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Descriptions',
|
||||
props: {
|
||||
@ -36,7 +38,7 @@ export default defineComponent({
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
setup(props, { slots, attrs }) {
|
||||
setup(props, { attrs }) {
|
||||
const getBindValue = computed((): any => {
|
||||
const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']
|
||||
const obj = { ...attrs, ...props }
|
||||
@ -59,7 +61,10 @@ export default defineComponent({
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
return {
|
||||
labelClassName: `${prefixCls}-label`,
|
||||
...obj
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠
|
||||
@ -103,26 +108,51 @@ export default defineComponent({
|
||||
|
||||
<ElCollapseTransition>
|
||||
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
|
||||
<ElDescriptions {...unref(getBindValue)}>
|
||||
{{
|
||||
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
|
||||
default: () => {
|
||||
return props.schema.map((item) => {
|
||||
return (
|
||||
<ElDescriptionsItem key={item.field} {...getBindItemValue(item)}>
|
||||
{{
|
||||
label: () => (item.slots?.label ? item.slots?.label(item) : item.label),
|
||||
default: () =>
|
||||
item.slots?.default
|
||||
? item.slots?.default(props.data)
|
||||
: get(props.data, item.field)
|
||||
}}
|
||||
</ElDescriptionsItem>
|
||||
)
|
||||
})
|
||||
}
|
||||
}}
|
||||
</ElDescriptions>
|
||||
<ElRow
|
||||
gutter={0}
|
||||
{...unref(getBindValue)}
|
||||
class="outline-1px outline-[var(--el-border-color-lighter)] outline-solid"
|
||||
>
|
||||
{props.schema.map((item) => {
|
||||
return (
|
||||
<ElCol
|
||||
key={item.field}
|
||||
span={item.span || 24 / props.column}
|
||||
class="flex items-stretch"
|
||||
>
|
||||
{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)
|
||||
: get(props.data, item.field) ?? defaultData}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div class="bg-[var(--el-fill-color-light)] outline-1px outline-[var(--el-border-color-lighter)] outline-solid flex-1">
|
||||
<div
|
||||
{...getBindItemValue(item)}
|
||||
class="text-left px-8px py-11px font-700 color-[var(--el-text-color-regular)] border-b-1px border-b-[var(--el-border-color-lighter)] border-b-solid"
|
||||
>
|
||||
{item.label}
|
||||
</div>
|
||||
<div class="flex-1 px-8px py-11px bg-[var(--el-bg-color)] color-[var(--el-text-color-primary)] text-size-14px">
|
||||
{item.slots?.default
|
||||
? item.slots?.default(props.data)
|
||||
: get(props.data, item.field) ?? defaultData}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</ElCol>
|
||||
)
|
||||
})}
|
||||
</ElRow>
|
||||
</div>
|
||||
</ElCollapseTransition>
|
||||
</div>
|
||||
@ -153,9 +183,13 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
|
||||
.@{prefix-cls}-content {
|
||||
:deep(.@{elNamespace}-descriptions__cell) {
|
||||
width: 0;
|
||||
}
|
||||
:deep(.@{prefix-cls}-label) {
|
||||
width: 150px !important;
|
||||
}
|
||||
|
||||
// .@{prefix-cls}-content {
|
||||
// :deep(.@{elNamespace}-descriptions__cell) {
|
||||
// width: 0;
|
||||
// }
|
||||
// }
|
||||
</style>
|
||||
|
@ -10,7 +10,6 @@ const props = defineProps({
|
||||
modelValue: propTypes.bool.def(false),
|
||||
title: propTypes.string.def('Dialog'),
|
||||
fullscreen: propTypes.bool.def(true),
|
||||
|
||||
top: propTypes.string.def('8vh'),
|
||||
height: propTypes.oneOfType([String, Number]).def('500px'),
|
||||
width: propTypes.oneOfType([String, Number]).def('700px')
|
||||
@ -109,26 +108,30 @@ const dialogStyle = computed(() => {
|
||||
</template>
|
||||
|
||||
<style lang="less">
|
||||
// .@{elNamespace}-overlay-dialog {
|
||||
// display: flex;
|
||||
// justify-content: center;
|
||||
// align-items: center;
|
||||
// }
|
||||
.@{elNamespace}-overlay-dialog {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.@{elNamespace}-dialog {
|
||||
// margin: 0 !important;
|
||||
margin: 0 !important;
|
||||
|
||||
&__header {
|
||||
height: 54px;
|
||||
padding: 0;
|
||||
margin-right: 0 !important;
|
||||
border-bottom: 1px solid var(--el-border-color);
|
||||
padding: 0;
|
||||
height: 54px;
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: 15px !important;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
border-top: 1px solid var(--el-border-color);
|
||||
}
|
||||
|
||||
&__headerbtn {
|
||||
top: 0;
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import networkError from '@/assets/svgs/500.svg'
|
||||
import noPermission from '@/assets/svgs/403.svg'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElButton } from 'element-plus'
|
||||
|
||||
interface ErrorMap {
|
||||
url: string
|
||||
@ -51,7 +50,7 @@ const btnClick = () => {
|
||||
<img width="350" :src="errorMap[type].url" alt="" />
|
||||
<div class="text-14px text-[var(--el-color-info)]">{{ errorMap[type].message }}</div>
|
||||
<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>
|
||||
|
@ -9,14 +9,14 @@ const prefixCls = getPrefixCls('footer')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const title = computed(() => appStore.getTitle)
|
||||
const footerContent = computed(() => appStore.getFooterContent)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
: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>
|
||||
</template>
|
||||
|
@ -95,9 +95,6 @@ export default defineComponent({
|
||||
// element form 实例
|
||||
const elFormRef = ref<ComponentRef<typeof ElForm>>()
|
||||
|
||||
// useForm传入的props
|
||||
const outsideProps = ref<FormProps>({})
|
||||
|
||||
const mergeProps = ref<FormProps>({})
|
||||
|
||||
const getProps = computed(() => {
|
||||
@ -155,8 +152,6 @@ export default defineComponent({
|
||||
|
||||
const setProps = (props: FormProps = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
// @ts-ignore
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
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
|
||||
vModel={itemVal.value}
|
||||
ref={(el: any) => setComponentRefMap(el, item.field)}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={item.componentProps?.style || {}}
|
||||
style={
|
||||
item.componentProps?.style || {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
>
|
||||
{{ ...slotsMap }}
|
||||
</Com>
|
||||
@ -447,6 +460,10 @@ export default defineComponent({
|
||||
{...getFormBindValue()}
|
||||
model={unref(getProps).isCustom ? unref(getProps).model : formModel}
|
||||
class={prefixCls}
|
||||
// @ts-ignore
|
||||
onSubmit={(e: Event) => {
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
{{
|
||||
// 如果需要自定义,就什么都不渲染,而是提供默认插槽
|
||||
@ -466,4 +483,16 @@ export default defineComponent({
|
||||
margin-right: 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>
|
||||
|
@ -25,6 +25,7 @@ import { Editor } from '@/components/Editor'
|
||||
import { Text } from '@/components/Text'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { ComponentName } from '../types'
|
||||
import { IconPicker } from '@/components/IconPicker'
|
||||
|
||||
const componentMap: Recordable<Component, ComponentName> = {
|
||||
RadioGroup: ElRadioGroup,
|
||||
@ -51,6 +52,7 @@ const componentMap: Recordable<Component, ComponentName> = {
|
||||
TreeSelect: ElTreeSelect,
|
||||
Upload: ElUpload,
|
||||
JsonEditor: JsonEditor,
|
||||
IconPicker: IconPicker,
|
||||
Text: Text
|
||||
}
|
||||
|
||||
|
@ -56,6 +56,7 @@ export enum ComponentNameEnum {
|
||||
TREE_SELECT = 'TreeSelect',
|
||||
UPLOAD = 'Upload',
|
||||
JSON_EDITOR = 'JsonEditor',
|
||||
ICON_PICKER = 'IconPicker',
|
||||
Text = 'Text'
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,11 @@ const symbolId = computed(() => {
|
||||
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 { color, size } = props
|
||||
return {
|
||||
@ -40,7 +45,10 @@ const getIconifyStyle = computed(() => {
|
||||
<use :xlink:href="symbolId" />
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@ -49,11 +57,18 @@ const getIconifyStyle = computed(() => {
|
||||
|
||||
.@{prefix-cls},
|
||||
.iconify {
|
||||
&:hover {
|
||||
:deep(svg) {
|
||||
:deep(svg) {
|
||||
&:hover {
|
||||
// stylelint-disable-next-line
|
||||
color: v-bind(hoverColor) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.iconify {
|
||||
&:hover {
|
||||
// stylelint-disable-next-line
|
||||
color: v-bind(hoverColor) !important;
|
||||
}
|
||||
}
|
||||
</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;
|
||||
border-color: var(--el-color-white);
|
||||
border-style: solid;
|
||||
border-width: 0 5px 0 5px;
|
||||
border-width: 0 5px;
|
||||
content: '';
|
||||
}
|
||||
|
||||
|
@ -30,13 +30,7 @@ watch(
|
||||
show.value = true
|
||||
return
|
||||
}
|
||||
if (!collapse) {
|
||||
setTimeout(() => {
|
||||
show.value = !collapse
|
||||
}, 400)
|
||||
} else {
|
||||
show.value = !collapse
|
||||
}
|
||||
show.value = !collapse
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -93,7 +93,7 @@ export default defineComponent({
|
||||
>
|
||||
{{
|
||||
default: () => {
|
||||
const { renderMenuItem } = useRenderMenuItem(unref(menuMode))
|
||||
const { renderMenuItem } = useRenderMenuItem()
|
||||
return renderMenuItem(unref(routers))
|
||||
}
|
||||
}}
|
||||
@ -123,30 +123,10 @@ export default defineComponent({
|
||||
<style lang="less" scoped>
|
||||
@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} {
|
||||
position: relative;
|
||||
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) {
|
||||
width: 100% !important;
|
||||
border-right: none;
|
||||
@ -168,7 +148,6 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 设置选中时的高亮背景和高亮颜色
|
||||
.@{elNamespace}-sub-menu.is-active,
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
color: var(--left-menu-text-active-color) !important;
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
@ -180,10 +159,6 @@ export default defineComponent({
|
||||
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
|
||||
// 设置子菜单的背景颜色
|
||||
@ -203,16 +178,11 @@ export default defineComponent({
|
||||
& > .is-active > .@{elNamespace}-sub-menu__title {
|
||||
position: relative;
|
||||
background-color: var(--left-menu-collapse-bg-active-color) !important;
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
// 折叠动画的时候,就需要把文字给隐藏掉
|
||||
: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 {
|
||||
display: none;
|
||||
}
|
||||
@ -235,7 +205,7 @@ export default defineComponent({
|
||||
.@{elNamespace}-menu-item.is-active {
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
@ -254,16 +224,6 @@ export default defineComponent({
|
||||
<style lang="less">
|
||||
@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}--horizontal {
|
||||
// 设置选中时子标题的颜色
|
||||
@ -290,10 +250,6 @@ export default defineComponent({
|
||||
&:hover {
|
||||
background-color: var(--left-menu-bg-active-color) !important;
|
||||
}
|
||||
|
||||
// &:after {
|
||||
// .is-active--after;
|
||||
// }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,57 +2,49 @@ import { ElSubMenu, ElMenuItem } from 'element-plus'
|
||||
import { hasOneShowingChild } from '../helper'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
export const useRenderMenuItem = (
|
||||
export const useRenderMenuItem = () =>
|
||||
// allRouters: AppRouteRecordRaw[] = [],
|
||||
menuMode: 'vertical' | 'horizontal'
|
||||
) => {
|
||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers.map((v) => {
|
||||
const meta = v.meta ?? {}
|
||||
if (!meta.hidden) {
|
||||
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 renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers
|
||||
.filter((v) => !v.meta?.hidden)
|
||||
.map((v) => {
|
||||
const meta = v.meta ?? {}
|
||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow
|
||||
) {
|
||||
return (
|
||||
<ElMenuItem index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}>
|
||||
{{
|
||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||
}}
|
||||
</ElMenuItem>
|
||||
)
|
||||
} else {
|
||||
const { getPrefixCls } = useDesign()
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!(onlyOneChild?.children?.length !== 0) || onlyOneChild?.noShowingChildren) &&
|
||||
!meta?.alwaysShow
|
||||
) {
|
||||
return (
|
||||
<ElMenuItem
|
||||
index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}
|
||||
>
|
||||
{{
|
||||
default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)
|
||||
}}
|
||||
</ElMenuItem>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<ElSubMenu index={fullPath}>
|
||||
{{
|
||||
title: () => renderMenuTitle(meta),
|
||||
default: () => renderMenuItem(v.children!, fullPath)
|
||||
}}
|
||||
</ElSubMenu>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const preFixCls = getPrefixCls('menu-popper')
|
||||
return (
|
||||
<ElSubMenu
|
||||
index={fullPath}
|
||||
popperClass={
|
||||
menuMode === 'vertical' ? `${preFixCls}--vertical` : `${preFixCls}--horizontal`
|
||||
}
|
||||
>
|
||||
{{
|
||||
title: () => renderMenuTitle(meta),
|
||||
default: () => renderMenuItem(v.children!, fullPath)
|
||||
}}
|
||||
</ElSubMenu>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
return {
|
||||
renderMenuItem
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
renderMenuItem
|
||||
}
|
||||
}
|
||||
|
@ -10,10 +10,14 @@ export const useRenderMenuTitle = () => {
|
||||
return 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} {
|
||||
&--disabled {
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
background: rgb(255 255 255 / 95%);
|
||||
|
||||
& > div {
|
||||
transform: translate(-50%, -50%);
|
||||
|
@ -88,6 +88,9 @@ const newSchema = computed(() => {
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
label: () => {
|
||||
return <span> </span>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -117,11 +120,14 @@ const setProps = (props: SearchProps = {}) => {
|
||||
outsideProps.value = props
|
||||
}
|
||||
|
||||
const schemaRef = ref<FormSchema[]>([])
|
||||
|
||||
// 监听表单结构化数组,重新生成formModel
|
||||
watch(
|
||||
() => unref(newSchema),
|
||||
async (schema = []) => {
|
||||
formModel.value = initModel(schema, unref(formModel))
|
||||
schemaRef.value = schema
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
@ -241,7 +247,7 @@ const onFormValidate = (prop: FormItemProp, isValid: boolean, message: string) =
|
||||
hide-required-asterisk
|
||||
:inline="getProps.inline"
|
||||
:is-col="getProps.isCol"
|
||||
:schema="newSchema"
|
||||
:schema="schemaRef"
|
||||
@register="formRegister"
|
||||
@validate="onFormValidate"
|
||||
/>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
@ -31,7 +30,7 @@ const onExpand = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElButton
|
||||
<BaseButton
|
||||
v-if="showSearch"
|
||||
type="primary"
|
||||
:loading="searchLoading"
|
||||
@ -39,21 +38,21 @@ const onExpand = () => {
|
||||
@click="onSearch"
|
||||
>
|
||||
{{ t('common.query') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="showReset"
|
||||
:loading="resetLoading"
|
||||
:icon="useIcon({ icon: 'ep:refresh-right' })"
|
||||
@click="onReset"
|
||||
>
|
||||
{{ t('common.reset') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
v-if="showExpand"
|
||||
:icon="useIcon({ icon: visible ? 'ep:arrow-down' : 'ep:arrow-up' })"
|
||||
:icon="useIcon({ icon: visible ? 'ep:arrow-up' : 'ep:arrow-down' })"
|
||||
text
|
||||
@click="onExpand"
|
||||
>
|
||||
{{ t(visible ? 'common.shrink' : 'common.expand') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</template>
|
||||
|
@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||
import { ref, unref, computed, watch } from 'vue'
|
||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { trim, setCssVar } from '@/utils'
|
||||
import { trim, setCssVar, getCssVar } from '@/utils'
|
||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||
@ -14,7 +13,7 @@ import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
const { removeStorage } = useStorage()
|
||||
const { clear: storageClear } = useStorage('localStorage')
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -24,8 +23,6 @@ const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const drawer = ref(false)
|
||||
|
||||
// 主题色相关
|
||||
@ -42,70 +39,27 @@ const setSystemTheme = (color: string) => {
|
||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||
|
||||
const 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)
|
||||
appStore.setTheme({
|
||||
topHeaderBgColor: color,
|
||||
topHeaderTextColor: textColor,
|
||||
topHeaderHoverColor: textHoverColor,
|
||||
topToolBorderColor
|
||||
})
|
||||
if (unref(layout) === 'top') {
|
||||
setMenuTheme(color)
|
||||
}
|
||||
appStore.setHeaderTheme(color)
|
||||
}
|
||||
|
||||
// 菜单主题相关
|
||||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||||
|
||||
const 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'
|
||||
}
|
||||
appStore.setTheme(theme)
|
||||
appStore.setCssVarTheme()
|
||||
appStore.setMenuTheme(color)
|
||||
}
|
||||
|
||||
// 监听layout变化,重置一些主题色
|
||||
watch(
|
||||
() => layout.value,
|
||||
(n) => {
|
||||
if (n === 'top' && !appStore.getIsDark) {
|
||||
headerTheme.value = '#fff'
|
||||
setHeaderTheme('#fff')
|
||||
} else {
|
||||
setMenuTheme(unref(menuTheme))
|
||||
}
|
||||
}
|
||||
)
|
||||
// watch(
|
||||
// () => layout.value,
|
||||
// (n) => {
|
||||
// if (n === 'top' && !appStore.getIsDark) {
|
||||
// headerTheme.value = '#fff'
|
||||
// setHeaderTheme('#fff')
|
||||
// } else {
|
||||
// setMenuTheme(unref(menuTheme))
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
// 拷贝
|
||||
const copyConfig = async () => {
|
||||
@ -174,7 +128,8 @@ const copyConfig = async () => {
|
||||
// 头部边框颜色
|
||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||
}
|
||||
`
|
||||
`,
|
||||
legacy: true
|
||||
})
|
||||
if (!isSupported) {
|
||||
ElMessage.error(t('setting.copyFailed'))
|
||||
@ -188,11 +143,15 @@ const copyConfig = async () => {
|
||||
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
removeStorage('layout')
|
||||
removeStorage('theme')
|
||||
removeStorage('isDark')
|
||||
storageClear()
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const themeChange = () => {
|
||||
const color = getCssVar('--el-bg-color')
|
||||
setMenuTheme(color)
|
||||
setHeaderTheme(color)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -212,7 +171,7 @@ const clear = () => {
|
||||
<div class="text-center">
|
||||
<!-- 主题 -->
|
||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitch @change="themeChange" />
|
||||
|
||||
<!-- 布局 -->
|
||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||
@ -253,23 +212,21 @@ const clear = () => {
|
||||
/>
|
||||
|
||||
<!-- 菜单主题 -->
|
||||
<template v-if="layout !== 'top'">
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</template>
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 界面显示 -->
|
||||
@ -278,12 +235,14 @@ const clear = () => {
|
||||
|
||||
<ElDivider />
|
||||
<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 class="mt-5px">
|
||||
<ElButton type="danger" class="w-full" @click="clear">
|
||||
<BaseButton type="danger" class="w-full" @click="clear">
|
||||
{{ t('setting.clearAndReset') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</ElDrawer>
|
||||
</template>
|
||||
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -1,12 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDrawer, ElDivider, ElButton, ElMessage } from 'element-plus'
|
||||
import { ref, unref, computed, watch } from 'vue'
|
||||
import { ElDrawer, ElDivider, ElMessage } from 'element-plus'
|
||||
import { ref, unref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ThemeSwitch } from '@/components/ThemeSwitch'
|
||||
import { colorIsDark, lighten, hexToRGB } from '@/utils/color'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { trim, setCssVar } from '@/utils'
|
||||
import { trim, setCssVar, getCssVar } from '@/utils'
|
||||
import ColorRadioPicker from './components/ColorRadioPicker.vue'
|
||||
import InterfaceDisplay from './components/InterfaceDisplay.vue'
|
||||
import LayoutRadioPicker from './components/LayoutRadioPicker.vue'
|
||||
@ -15,7 +14,7 @@ import { useClipboard } from '@vueuse/core'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
const { removeStorage } = useStorage()
|
||||
const { clear: storageClear } = useStorage('localStorage')
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -29,8 +28,6 @@ const appStore = useAppStore()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const drawer = ref(false)
|
||||
|
||||
// 主题色相关
|
||||
@ -47,70 +44,27 @@ const setSystemTheme = (color: string) => {
|
||||
const headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')
|
||||
|
||||
const 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)
|
||||
appStore.setTheme({
|
||||
topHeaderBgColor: color,
|
||||
topHeaderTextColor: textColor,
|
||||
topHeaderHoverColor: textHoverColor,
|
||||
topToolBorderColor
|
||||
})
|
||||
if (unref(layout) === 'top') {
|
||||
setMenuTheme(color)
|
||||
}
|
||||
appStore.setHeaderTheme(color)
|
||||
}
|
||||
|
||||
// 菜单主题相关
|
||||
const menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')
|
||||
|
||||
const 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'
|
||||
}
|
||||
appStore.setTheme(theme)
|
||||
appStore.setCssVarTheme()
|
||||
appStore.setMenuTheme(color)
|
||||
}
|
||||
|
||||
// 监听layout变化,重置一些主题色
|
||||
watch(
|
||||
() => layout.value,
|
||||
(n) => {
|
||||
if (n === 'top' && !appStore.getIsDark) {
|
||||
headerTheme.value = '#fff'
|
||||
setHeaderTheme('#fff')
|
||||
} else {
|
||||
setMenuTheme(unref(menuTheme))
|
||||
}
|
||||
}
|
||||
)
|
||||
// watch(
|
||||
// () => layout.value,
|
||||
// (n) => {
|
||||
// if (n === 'top' && !appStore.getIsDark) {
|
||||
// headerTheme.value = '#fff'
|
||||
// setHeaderTheme('#fff')
|
||||
// } else {
|
||||
// setMenuTheme(unref(menuTheme))
|
||||
// }
|
||||
// }
|
||||
// )
|
||||
|
||||
// 拷贝
|
||||
const copyConfig = async () => {
|
||||
@ -179,7 +133,8 @@ const copyConfig = async () => {
|
||||
// 头部边框颜色
|
||||
topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'
|
||||
}
|
||||
`
|
||||
`,
|
||||
legacy: true
|
||||
})
|
||||
if (!isSupported) {
|
||||
ElMessage.error(t('setting.copyFailed'))
|
||||
@ -193,11 +148,15 @@ const copyConfig = async () => {
|
||||
|
||||
// 清空缓存
|
||||
const clear = () => {
|
||||
removeStorage('layout')
|
||||
removeStorage('theme')
|
||||
removeStorage('isDark')
|
||||
storageClear()
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const themeChange = () => {
|
||||
const color = getCssVar('--el-bg-color')
|
||||
setMenuTheme(color)
|
||||
setHeaderTheme(color)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -219,7 +178,7 @@ const clear = () => {
|
||||
<div class="text-center">
|
||||
<!-- 主题 -->
|
||||
<ElDivider>{{ t('setting.theme') }}</ElDivider>
|
||||
<ThemeSwitch />
|
||||
<ThemeSwitch @change="themeChange" />
|
||||
|
||||
<!-- 布局 -->
|
||||
<ElDivider>{{ t('setting.layout') }}</ElDivider>
|
||||
@ -260,23 +219,21 @@ const clear = () => {
|
||||
/>
|
||||
|
||||
<!-- 菜单主题 -->
|
||||
<template v-if="layout !== 'top'">
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</template>
|
||||
<ElDivider>{{ t('setting.menuTheme') }}</ElDivider>
|
||||
<ColorRadioPicker
|
||||
v-model="menuTheme"
|
||||
:schema="[
|
||||
'#fff',
|
||||
'#001529',
|
||||
'#212121',
|
||||
'#273352',
|
||||
'#191b24',
|
||||
'#383f45',
|
||||
'#001628',
|
||||
'#344058'
|
||||
]"
|
||||
@change="setMenuTheme"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 界面显示 -->
|
||||
@ -285,14 +242,14 @@ const clear = () => {
|
||||
|
||||
<ElDivider />
|
||||
<div>
|
||||
<ElButton type="primary" class="w-full" @click="copyConfig">{{
|
||||
<BaseButton type="primary" class="w-full" @click="copyConfig">{{
|
||||
t('setting.copy')
|
||||
}}</ElButton>
|
||||
}}</BaseButton>
|
||||
</div>
|
||||
<div class="mt-5px">
|
||||
<ElButton type="danger" class="w-full" @click="clear">
|
||||
<BaseButton type="danger" class="w-full" @click="clear">
|
||||
{{ t('setting.clearAndReset') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</ElDrawer>
|
||||
</div>
|
||||
|
@ -67,7 +67,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -79,7 +79,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -95,7 +95,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -107,7 +107,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -123,7 +123,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -140,7 +140,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 4px;
|
||||
|
||||
&:before {
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -152,7 +152,7 @@ const layout = computed(() => appStore.getLayout)
|
||||
content: '';
|
||||
}
|
||||
|
||||
&:after {
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -88,6 +88,9 @@ export default defineComponent({
|
||||
} else {
|
||||
showTitle.value = !collapse
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
@ -202,11 +205,12 @@ export default defineComponent({
|
||||
</div>
|
||||
<Menu
|
||||
class={[
|
||||
'!absolute top-0 z-4000',
|
||||
'!absolute top-0 z-3000',
|
||||
{
|
||||
'!left-[var(--tab-menu-min-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)
|
||||
}
|
||||
]}
|
||||
|
@ -5,7 +5,9 @@ import {
|
||||
ElPagination,
|
||||
ComponentSize,
|
||||
ElTooltipProps,
|
||||
ElImage
|
||||
ElImage,
|
||||
ElEmpty,
|
||||
ElCard
|
||||
} from 'element-plus'
|
||||
import { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
@ -15,9 +17,10 @@ import { set, get } from 'lodash-es'
|
||||
import { CSSProperties } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import TableActions from './components/TableActions.vue'
|
||||
// import Sortable from 'sortablejs'
|
||||
// import { Icon } from '@/components/Icon'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { createVideoViewer } from '@/components/VideoPlayer'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
@ -60,8 +63,13 @@ export default defineComponent({
|
||||
type: Array as PropType<Recordable[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否自动预览
|
||||
preview: {
|
||||
// 图片自动预览字段数组
|
||||
imagePreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 视频自动预览字段数组
|
||||
videoPreview: {
|
||||
type: Array as PropType<string[]>,
|
||||
default: () => []
|
||||
},
|
||||
@ -72,7 +80,7 @@ export default defineComponent({
|
||||
border: propTypes.bool.def(true),
|
||||
size: {
|
||||
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),
|
||||
showHeader: propTypes.bool.def(true),
|
||||
@ -191,9 +199,27 @@ export default defineComponent({
|
||||
default: 'fixed'
|
||||
},
|
||||
scrollbarAlwaysOn: propTypes.bool.def(false),
|
||||
flexible: propTypes.bool.def(false)
|
||||
flexible: propTypes.bool.def(false),
|
||||
// 自定义内容
|
||||
customContent: propTypes.bool.def(false),
|
||||
cardBodyStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardBodyClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
cardWrapStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => ({})
|
||||
},
|
||||
cardWrapClass: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh', 'sortable-change'],
|
||||
emits: ['update:pageSize', 'update:currentPage', 'register', 'refresh'],
|
||||
setup(props, { attrs, emit, slots, expose }) {
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
@ -218,33 +244,6 @@ export default defineComponent({
|
||||
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 = {}) => {
|
||||
mergeProps.value = Object.assign(unref(mergeProps), props)
|
||||
outsideProps.value = { ...props } as any
|
||||
@ -265,7 +264,7 @@ export default defineComponent({
|
||||
|
||||
const addColumn = (column: TableColumn, index?: number) => {
|
||||
const { columns } = unref(getProps)
|
||||
if (index) {
|
||||
if (index !== void 0) {
|
||||
columns.splice(index, 0, column)
|
||||
} else {
|
||||
columns.push(column)
|
||||
@ -350,11 +349,13 @@ export default defineComponent({
|
||||
const bindValue: Recordable = { ...attrs, ...unref(getProps) }
|
||||
delete bindValue.columns
|
||||
delete bindValue.data
|
||||
delete bindValue.align
|
||||
return bindValue
|
||||
})
|
||||
|
||||
const renderTreeTableColumn = (columnsChildren: TableColumn[]) => {
|
||||
const { align, headerAlign, showOverflowTooltip, preview } = unref(getProps)
|
||||
const { align, headerAlign, showOverflowTooltip, imagePreview, videoPreview } =
|
||||
unref(getProps)
|
||||
return columnsChildren.map((v) => {
|
||||
if (v.show === false) return null
|
||||
const props = { ...v } as any
|
||||
@ -365,20 +366,20 @@ export default defineComponent({
|
||||
const slots = {
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
@ -399,17 +400,32 @@ export default defineComponent({
|
||||
})
|
||||
}
|
||||
|
||||
const renderPreview = (url: string) => {
|
||||
const renderPreview = (url: string, field: string) => {
|
||||
const { imagePreview, videoPreview } = unref(getProps)
|
||||
return (
|
||||
<div class="flex items-center">
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%] h-100px"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
{imagePreview.includes(field) ? (
|
||||
<ElImage
|
||||
src={url}
|
||||
fit="cover"
|
||||
class="w-[100%]"
|
||||
lazy
|
||||
preview-src-list={[url]}
|
||||
preview-teleported
|
||||
/>
|
||||
) : videoPreview.includes(field) ? (
|
||||
<BaseButton
|
||||
type="primary"
|
||||
icon={<Icon icon="ep:video-play" />}
|
||||
onClick={() => {
|
||||
createVideoViewer({
|
||||
url
|
||||
})
|
||||
}}
|
||||
>
|
||||
预览
|
||||
</BaseButton>
|
||||
) : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -424,7 +440,8 @@ export default defineComponent({
|
||||
headerAlign,
|
||||
showOverflowTooltip,
|
||||
reserveSelection,
|
||||
preview
|
||||
imagePreview,
|
||||
videoPreview
|
||||
} = unref(getProps)
|
||||
|
||||
return (columnsChildren || columns).map((v) => {
|
||||
@ -450,6 +467,7 @@ export default defineComponent({
|
||||
reserveSelection={reserveSelection}
|
||||
align="center"
|
||||
headerAlign="center"
|
||||
selectable={v.selectable}
|
||||
width="50px"
|
||||
fixed="left"
|
||||
></ElTableColumn>
|
||||
@ -464,20 +482,20 @@ export default defineComponent({
|
||||
default: (...args: any[]) => {
|
||||
const data = args[0]
|
||||
|
||||
let isImageUrl = false
|
||||
if (preview.length) {
|
||||
isImageUrl = preview.some((item) => (item as string) === v.field)
|
||||
}
|
||||
let isPreview = false
|
||||
isPreview =
|
||||
imagePreview.some((item) => (item as string) === v.field) ||
|
||||
videoPreview.some((item) => (item as string) === v.field)
|
||||
|
||||
return children && children.length
|
||||
? renderTreeTableColumn(children)
|
||||
: props?.slots?.default
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isImageUrl
|
||||
? renderPreview(get(data.row, v.field))
|
||||
: get(data.row, v.field)
|
||||
? props.slots.default(...args)
|
||||
: v?.formatter
|
||||
? v?.formatter?.(data.row, data.column, get(data.row, v.field), data.$index)
|
||||
: isPreview
|
||||
? renderPreview(get(data.row, v.field), v.field)
|
||||
: get(data.row, v.field)
|
||||
}
|
||||
}
|
||||
if (props?.slots?.header) {
|
||||
@ -509,52 +527,79 @@ export default defineComponent({
|
||||
}
|
||||
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 (
|
||||
<div v-loading={unref(getProps).loading}>
|
||||
<div class="flex justify-between mb-1">
|
||||
<div>{toolbar}</div>
|
||||
<div class="pt-2">
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
activeUID={unref(getProps).activeUID}
|
||||
columns={unref(getProps).columns}
|
||||
el-table-ref={elTableRef}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
{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>
|
||||
) : (
|
||||
<>
|
||||
<div class="flex justify-between mb-1">
|
||||
<div>{toolbar}</div>
|
||||
<div class="pt-2">
|
||||
{unref(getProps).showAction ? (
|
||||
<TableActions
|
||||
activeUID={unref(getProps).activeUID}
|
||||
columns={unref(getProps).columns}
|
||||
el-table-ref={elTableRef}
|
||||
onChangSize={changSize}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
<ElTable
|
||||
ref={elTableRef}
|
||||
data={unref(getProps).data}
|
||||
{...unref(getBindValue)}
|
||||
header-cell-style={
|
||||
appStore.getIsDark
|
||||
? { color: '#CFD3DC', 'background-color': '#000' }
|
||||
: { color: '#000', 'background-color': '#f5f7fa' }
|
||||
}
|
||||
>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ElTable
|
||||
ref={elTableRef}
|
||||
data={unref(getProps).data}
|
||||
{...unref(getBindValue)}
|
||||
header-cell-style={
|
||||
appStore.getIsDark
|
||||
? { color: '#CFD3DC', 'background-color': '#000' }
|
||||
: { color: '#000', 'background-color': '#f5f7fa' }
|
||||
}
|
||||
>
|
||||
{{
|
||||
default: () => renderTableColumn(),
|
||||
...tableSlots
|
||||
}}
|
||||
</ElTable>
|
||||
{unref(getProps).pagination ? (
|
||||
<ElPagination
|
||||
v-model:pageSize={pageSizeRef.value}
|
||||
|
@ -9,7 +9,6 @@ import {
|
||||
ElPopover,
|
||||
ElCheckbox,
|
||||
ElScrollbar,
|
||||
ElButton,
|
||||
ElTable,
|
||||
ElDivider
|
||||
} from 'element-plus'
|
||||
@ -23,14 +22,10 @@ import { useStorage } from '@/hooks/web/useStorage'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { moveElementToIndex } from '@/utils/index'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { setStorage, getStorage, removeStorage } = useStorage()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TableActions',
|
||||
props: {
|
||||
@ -47,6 +42,10 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['refresh', 'changSize'],
|
||||
setup(props, { emit }) {
|
||||
const appStore = useAppStore()
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
const { t } = useI18n()
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
@ -263,9 +262,9 @@ export default defineComponent({
|
||||
{t('common.SerialNumberColumn')}
|
||||
</ElCheckbox>
|
||||
</div>
|
||||
<ElButton type="primary" link onClick={resetTableColumns}>
|
||||
<BaseButton type="primary" link onClick={resetTableColumns}>
|
||||
{t('common.reset')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<ElScrollbar max-height="400px">
|
||||
<VueDraggable
|
||||
|
@ -210,13 +210,14 @@ const isActive = (route: RouteLocationNormalizedLoaded): boolean => {
|
||||
// 所有右键菜单组件的元素
|
||||
const itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()
|
||||
|
||||
// 右键菜单装填改变的时候
|
||||
// 右键菜单状态改变的时候
|
||||
const visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {
|
||||
if (visible) {
|
||||
for (const v of unref(itemRefs)) {
|
||||
const elDropdownMenuRef = v.elDropdownMenuRef
|
||||
if (tagItem.fullPath !== v.tagItem.fullPath) {
|
||||
elDropdownMenuRef?.handleClose()
|
||||
setSelectTag(tagItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -582,4 +583,3 @@ watch(
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@/hooks/web/useTagsView
|
||||
|
@ -9,6 +9,8 @@ const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('theme-switch')
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const Sun = useIcon({ icon: 'emojione-monotone:sun', 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) => {
|
||||
appStore.setIsDark(val)
|
||||
emit('change', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
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 LockDialog from './components/LockDialog.vue'
|
||||
import { ref, computed } from 'vue'
|
||||
@ -14,7 +14,7 @@ const lockStore = useLockStore()
|
||||
|
||||
const getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -73,13 +73,13 @@ const user = computed(() => authStore.getUser)
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toHome" link>个人主页</ElButton>
|
||||
<BaseButton @click="toHome" link>个人主页</BaseButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toGitee" link>Gitee</ElButton>
|
||||
<BaseButton @click="toGitee" link>Gitee</BaseButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<ElButton @click="toGithub" link>Github</ElButton>
|
||||
<BaseButton @click="toGithub" link>Github</BaseButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem divided>
|
||||
<div @click="lockScreen">{{ t('lock.lockScreen') }}</div>
|
||||
|
@ -7,7 +7,6 @@ import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, computed } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
|
||||
@ -87,14 +86,14 @@ const handleLock = async () => {
|
||||
</div>
|
||||
<Form :is-col="false" :schema="schema" :rules="rules" @register="formRegister" />
|
||||
<template #footer>
|
||||
<ElButton type="primary" @click="handleLock">{{ t('lock.lock') }}</ElButton>
|
||||
<BaseButton type="primary" @click="handleLock">{{ t('lock.lock') }}</BaseButton>
|
||||
</template>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:global(.v-lock-dialog) {
|
||||
@media (max-width: 767px) {
|
||||
@media (width <= 767px) {
|
||||
max-width: calc(100vw - 16px);
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
import { ElInput, ElButton } from 'element-plus'
|
||||
import { ElInput } from 'element-plus'
|
||||
import { useLockStore } from '@/store/modules/lock'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useNow } from '@/hooks/web/useNow'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
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 loading = ref(false)
|
||||
@ -92,7 +92,7 @@ function handleShowForm(show = false) {
|
||||
{{ t('lock.message') }}
|
||||
</span>
|
||||
<div :class="`${prefixCls}-entry__footer enter-x`">
|
||||
<ElButton
|
||||
<BaseButton
|
||||
type="primary"
|
||||
size="small"
|
||||
class="mt-2 mr-2 enter-x"
|
||||
@ -101,8 +101,8 @@ function handleShowForm(show = false) {
|
||||
@click="handleShowForm(true)"
|
||||
>
|
||||
{{ t('common.back') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
type="primary"
|
||||
size="small"
|
||||
class="mt-2 mr-2 enter-x"
|
||||
@ -111,8 +111,8 @@ function handleShowForm(show = false) {
|
||||
@click="goLogin"
|
||||
>
|
||||
{{ t('lock.backToLogin') }}
|
||||
</ElButton>
|
||||
<ElButton
|
||||
</BaseButton>
|
||||
<BaseButton
|
||||
type="primary"
|
||||
class="mt-2"
|
||||
size="small"
|
||||
@ -121,7 +121,7 @@ function handleShowForm(show = false) {
|
||||
:disabled="loading"
|
||||
>
|
||||
{{ t('lock.entrySystem') }}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -190,6 +190,7 @@ function handleShowForm(show = false) {
|
||||
font-size: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: @screen-lg) {
|
||||
span:not(.meridiem) {
|
||||
font-size: 220px;
|
||||
@ -201,6 +202,7 @@ function handleShowForm(show = false) {
|
||||
font-size: 260px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: @screen-2xl) {
|
||||
span:not(.meridiem) {
|
||||
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 { Icon } from './Icon'
|
||||
import { Permission } from './Permission'
|
||||
import { BaseButton } from './Button'
|
||||
|
||||
export const setupGlobCom = (app: App<Element>): void => {
|
||||
app.component('Icon', Icon)
|
||||
app.component('Permission', Permission)
|
||||
app.component('BaseButton', BaseButton)
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
import axios, { AxiosInstance, InternalAxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import qs from 'qs'
|
||||
import { config } from './config'
|
||||
import { ElMessage } from 'element-plus'
|
||||
@ -9,8 +7,6 @@ import request from '@/config/axios'
|
||||
|
||||
const { result_code, unauthorized_code, request_timeout } = config
|
||||
|
||||
const { getStorage, setStorage } = useStorage()
|
||||
|
||||
// 创建axios实例
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: '/api', // api 的 base_url
|
||||
@ -21,10 +17,10 @@ const service: AxiosInstance = axios.create({
|
||||
// request拦截器
|
||||
service.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
const appStore = useAppStore()
|
||||
const token = getStorage(appStore.getToken)
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const token = authStore.getToken
|
||||
if (token !== '') {
|
||||
;(config.headers as any)['Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
;(config.headers as any)[authStore.getTokenKey ?? 'Authorization'] = token // 让每个请求携带自定义token 请根据实际情况自行修改
|
||||
}
|
||||
if (
|
||||
config.method === 'post' &&
|
||||
@ -87,18 +83,18 @@ service.interceptors.response.use(
|
||||
if (refresh === '1') {
|
||||
// 因token快过期,刷新token
|
||||
refreshToken().then((res) => {
|
||||
const appStore = useAppStore()
|
||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||
const authStore = useAuthStoreWithOut()
|
||||
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
||||
authStore.setRefreshToken(res.data.refresh_token)
|
||||
})
|
||||
}
|
||||
return response.data
|
||||
} else if (code === unauthorized_code) {
|
||||
// 因token无效,token过期导致
|
||||
refreshToken().then((res) => {
|
||||
const appStore = useAppStore()
|
||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||
const authStore = useAuthStoreWithOut()
|
||||
authStore.setToken(`${res.data.token_type} ${res.data.access_token}`)
|
||||
authStore.setRefreshToken(res.data.refresh_token)
|
||||
ElMessage.error('操作失败,请重试')
|
||||
})
|
||||
} else {
|
||||
@ -108,7 +104,7 @@ service.interceptors.response.use(
|
||||
(error: AxiosError) => {
|
||||
console.log('err', error)
|
||||
let { message } = error
|
||||
const authStore = useAuthStore()
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const status = error.response?.status
|
||||
switch (status) {
|
||||
case 400:
|
||||
@ -117,7 +113,7 @@ service.interceptors.response.use(
|
||||
case 401:
|
||||
// 强制要求重新登录,因账号已冻结,账号已过期,手机号码错误,刷新token无效等问题导致
|
||||
authStore.logout()
|
||||
message = '认证已过期,请重新登录'
|
||||
message = '认证已失效,请重新登录'
|
||||
break
|
||||
case 403:
|
||||
// 强制要求重新登录,因无系统权限,而进入到系统访问等问题导致
|
||||
@ -158,8 +154,8 @@ service.interceptors.response.use(
|
||||
|
||||
// 刷新Token
|
||||
const refreshToken = (): Promise<IResponse> => {
|
||||
const appStore = useAppStore()
|
||||
const data = getStorage(appStore.getRefreshToken)
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const data = authStore.getRefreshToken
|
||||
return request.post({ url: '/auth/token/refresh', data })
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,11 @@ import { isArray } from '@/utils/is'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
|
||||
const { t } = useI18n()
|
||||
const authStore = useAuthStoreWithOut()
|
||||
|
||||
// 全部权限
|
||||
const all_permission = ['*.*.*']
|
||||
const hasPermission = (value: string | string[]): boolean => {
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const permissions = authStore.getPermissions
|
||||
if (!value) {
|
||||
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,19 +79,14 @@ const filterSearchSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const schemaItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!schemaItem?.search?.hidden) {
|
||||
const searchSchemaItem = {
|
||||
component: schemaItem?.search?.component || 'Input',
|
||||
...schemaItem.search,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete searchSchemaItem.hidden
|
||||
|
||||
searchSchema.push(searchSchemaItem)
|
||||
const searchSchemaItem = {
|
||||
component: schemaItem?.search?.component || 'Input',
|
||||
...schemaItem.search,
|
||||
field: schemaItem.field,
|
||||
label: schemaItem.search?.label || schemaItem.label
|
||||
}
|
||||
|
||||
searchSchema.push(searchSchemaItem)
|
||||
}
|
||||
|
||||
return searchSchema
|
||||
@ -103,8 +98,8 @@ const filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {
|
||||
conversion: (schema: CrudSchema) => {
|
||||
if (!schema?.table?.hidden) {
|
||||
return {
|
||||
...schema.table,
|
||||
...schema
|
||||
...schema,
|
||||
...schema.table
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -127,19 +122,14 @@ const filterFormSchema = (crudSchema: CrudSchema[]): FormSchema[] => {
|
||||
for (let i = 0; i < length; i++) {
|
||||
const formItem = crudSchema[i]
|
||||
// 判断是否隐藏
|
||||
if (!formItem?.form?.hidden) {
|
||||
const formSchemaItem = {
|
||||
component: formItem?.form?.component || 'Input',
|
||||
...formItem.form,
|
||||
field: formItem.field,
|
||||
label: formItem.label
|
||||
}
|
||||
|
||||
// 删除不必要的字段
|
||||
delete formSchemaItem.hidden
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
const formSchemaItem = {
|
||||
component: formItem?.form?.component || 'Input',
|
||||
...formItem.form,
|
||||
field: formItem.field,
|
||||
label: formItem.form?.label || formItem.label
|
||||
}
|
||||
|
||||
formSchema.push(formSchemaItem)
|
||||
}
|
||||
|
||||
return formSchema
|
||||
|
@ -2,6 +2,7 @@ import type { Form, FormExpose } from '@/components/Form'
|
||||
import type { ElForm, ElFormItem } from 'element-plus'
|
||||
import { ref, unref, nextTick } from 'vue'
|
||||
import { FormSchema, FormSetProps, FormProps } from '@/components/Form'
|
||||
import { isEmptyVal, isObject } from '@/utils/is'
|
||||
|
||||
export const useForm = () => {
|
||||
// From实例
|
||||
@ -93,9 +94,27 @@ export const useForm = () => {
|
||||
* @description 获取表单数据
|
||||
* @returns form data
|
||||
*/
|
||||
getFormData: async <T = Recordable>(): Promise<T> => {
|
||||
getFormData: async <T = Recordable>(filterEmptyVal = true): Promise<T> => {
|
||||
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'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
export const usePageLoading = () => {
|
||||
const loadStart = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
appStore.setPageLoading(true)
|
||||
}
|
||||
|
||||
const loadDone = () => {
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
appStore.setPageLoading(false)
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,7 @@ const getValueType = (value: any) => {
|
||||
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 valueType = getValueType(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 { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
export const useTitle = (newTitle?: string) => {
|
||||
const { t } = useI18n()
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const title = ref(
|
||||
newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle
|
||||
)
|
||||
|
@ -71,8 +71,14 @@ export default defineComponent({
|
||||
|
||||
.@{prefix-cls} {
|
||||
background-color: var(--app-content-bg-color);
|
||||
:deep(.@{elNamespace}-scrollbar__view) {
|
||||
height: 100% !important;
|
||||
.@{prefix-cls}-content-scrollbar {
|
||||
& > :deep(.el-scrollbar__wrap) {
|
||||
& > .@{elNamespace}-scrollbar__view {
|
||||
display: flex;
|
||||
height: 100% !important;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -6,10 +6,6 @@ import { computed } from 'vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const fixedHeader = computed(() => appStore.getFixedHeader)
|
||||
|
||||
const footer = computed(() => appStore.getFooter)
|
||||
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
@ -17,39 +13,12 @@ const tagsViewStore = useTagsViewStore()
|
||||
const getCaches = computed((): string[] => {
|
||||
return tagsViewStore.getCachedViews
|
||||
})
|
||||
|
||||
const tagsView = computed(() => appStore.getTagsView)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section
|
||||
: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)]',
|
||||
{
|
||||
'!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
|
||||
}
|
||||
'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)]'
|
||||
]"
|
||||
>
|
||||
<router-view>
|
||||
|
@ -42,8 +42,7 @@ export default defineComponent({
|
||||
id={`${variables.namespace}-tool-header`}
|
||||
class={[
|
||||
prefixCls,
|
||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',
|
||||
'dark:bg-[var(--el-bg-color)]'
|
||||
'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between'
|
||||
]}
|
||||
>
|
||||
{layout.value !== 'top' ? (
|
||||
|
@ -195,8 +195,8 @@ export const useRenderLayout = () => {
|
||||
`${prefixCls}-content`,
|
||||
'w-full',
|
||||
{
|
||||
'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,
|
||||
'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value
|
||||
'h-[calc(100%-var(--top-tool-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',
|
||||
notSpecialCharacters: 'Special characters are not allowed',
|
||||
isEqual: 'The two are not equal',
|
||||
// 列设置
|
||||
setting: 'Setting',
|
||||
selectAll: 'Select all',
|
||||
SerialNumberColumn: 'Index column'
|
||||
},
|
||||
@ -186,7 +188,15 @@ export default {
|
||||
permission: 'Permission test page',
|
||||
function: 'Function',
|
||||
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: {
|
||||
hasPermission: 'Please set the operation permission value'
|
||||
@ -332,7 +342,8 @@ export default {
|
||||
lazyLoad: 'Lazy load',
|
||||
upload: 'Upload',
|
||||
// 用户头像
|
||||
userAvatar: 'User avatar'
|
||||
userAvatar: 'User avatar',
|
||||
iconPicker: 'Icon picker'
|
||||
},
|
||||
guideDemo: {
|
||||
guide: 'Guide',
|
||||
@ -461,7 +472,9 @@ export default {
|
||||
fixedHeaderOrAuto: 'Fixed header or auto',
|
||||
getSelections: 'Get selections',
|
||||
preview: 'Preview',
|
||||
showOrHiddenSortable: 'Show or hidden sortable'
|
||||
showOrHiddenSortable: 'Show or hidden sortable',
|
||||
videoPreview: 'Video preview',
|
||||
cardTable: 'Card table'
|
||||
},
|
||||
richText: {
|
||||
richText: 'Rich text',
|
||||
|
@ -50,6 +50,7 @@ export default {
|
||||
notSpace: '不能包含空格',
|
||||
notSpecialCharacters: '不能包含特殊字符',
|
||||
isEqual: '两次输入不一致',
|
||||
setting: '设置',
|
||||
selectAll: '全选',
|
||||
SerialNumberColumn: '序号列'
|
||||
},
|
||||
@ -84,7 +85,7 @@ export default {
|
||||
sizeIcon: '尺寸图标',
|
||||
localeIcon: '多语言图标',
|
||||
tagsView: '标签页',
|
||||
logo: '标志',
|
||||
logo: 'Logo',
|
||||
greyMode: '灰色模式',
|
||||
fixedHeader: '固定头部',
|
||||
headerTheme: '头部主题',
|
||||
@ -184,7 +185,14 @@ export default {
|
||||
permission: '权限测试页',
|
||||
function: '功能',
|
||||
multipleTabs: '多开标签页',
|
||||
details: '详情页'
|
||||
details: '详情页',
|
||||
iconPicker: '图标选择器',
|
||||
request: '请求',
|
||||
waterfall: '瀑布流',
|
||||
imageCropping: '图片裁剪',
|
||||
videoPlayer: '视频播放器',
|
||||
tableVideoPreview: '表格视频预览',
|
||||
cardTable: '卡片表格'
|
||||
},
|
||||
permission: {
|
||||
hasPermission: '请设置操作权限值'
|
||||
@ -327,7 +335,8 @@ export default {
|
||||
customContent: '自定义内容',
|
||||
lazyLoad: '懒加载',
|
||||
upload: '上传',
|
||||
userAvatar: '用户头像'
|
||||
userAvatar: '用户头像',
|
||||
iconPicker: '图标选择器'
|
||||
},
|
||||
guideDemo: {
|
||||
guide: '引导页',
|
||||
@ -454,7 +463,9 @@ export default {
|
||||
fixedHeaderOrAuto: '固定头部/自动',
|
||||
getSelections: '获取多选数据',
|
||||
preview: '封面',
|
||||
showOrHiddenSortable: '显示/隐藏排序'
|
||||
showOrHiddenSortable: '显示/隐藏排序',
|
||||
videoPreview: '视频预览',
|
||||
cardTable: '卡片表格'
|
||||
},
|
||||
richText: {
|
||||
richText: '富文本',
|
||||
@ -530,7 +541,7 @@ export default {
|
||||
menu: {
|
||||
menuName: '菜单名称',
|
||||
icon: '图标',
|
||||
permission: '权限标识',
|
||||
permission: '按钮权限',
|
||||
component: '组件',
|
||||
path: '路径',
|
||||
status: '状态',
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'vue/jsx'
|
||||
|
||||
// 引入windi css
|
||||
import '@/plugins/unocss'
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { hasRoute } 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 { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
@ -9,23 +8,19 @@ import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import { getRoleMenusApi } from '@/api/login'
|
||||
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
const authStore = useAuthStoreWithOut()
|
||||
|
||||
const { getStorage, setStorage } = useStorage()
|
||||
|
||||
const { start, done } = useNProgress()
|
||||
|
||||
const { loadStart, loadDone } = usePageLoading()
|
||||
|
||||
const whiteList = ['/login'] // 不重定向白名单
|
||||
const whiteList = ['/login', '/docs/privacy', '/docs/agreement'] // 不重定向白名单
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
start()
|
||||
loadStart()
|
||||
if (getStorage(appStore.getToken)) {
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
const authStore = useAuthStoreWithOut()
|
||||
|
||||
if (authStore.getToken) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else if (to.path === '/reset/password') {
|
||||
@ -35,6 +30,9 @@ router.beforeEach(async (to, from, next) => {
|
||||
await authStore.setUserInfo()
|
||||
}
|
||||
if (permissionStore.getIsAddRouters) {
|
||||
if (!hasRoute(to.path)) {
|
||||
authStore.logout('认证已过期,请重新登录!')
|
||||
}
|
||||
next()
|
||||
return
|
||||
}
|
||||
@ -42,7 +40,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
// 开发者可根据实际情况进行修改
|
||||
const res = await getRoleMenusApi()
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
router.addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
|
@ -12,7 +12,13 @@ export const setupElementPlus = (app: App<Element>) => {
|
||||
app.use(plugin)
|
||||
})
|
||||
|
||||
// 为了开发环境启动更快,一次性引入所有样式
|
||||
if (import.meta.env.VITE_USE_ALL_ELEMENT_PLUS_STYLE === 'true') {
|
||||
import('element-plus/dist/index.css')
|
||||
return
|
||||
}
|
||||
|
||||
components.forEach((component) => {
|
||||
app.component(component.name, component)
|
||||
app.component(component.name!, component)
|
||||
})
|
||||
}
|
||||
|
@ -1,3 +1 @@
|
||||
import 'virtual:svg-icons-register'
|
||||
|
||||
import '@purge-icons/generated'
|
||||
|
@ -70,6 +70,37 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||
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',
|
||||
component: () => import('@/views/Error/404.vue'),
|
||||
@ -115,7 +146,17 @@ const router = createRouter({
|
||||
})
|
||||
|
||||
export const resetRouter = (): void => {
|
||||
const resetWhiteNameList = ['Login', 'NoFind', 'Root']
|
||||
const resetWhiteNameList = [
|
||||
'Login',
|
||||
'NoFind',
|
||||
'Root',
|
||||
'ResetPassword',
|
||||
'Redirect',
|
||||
'Home',
|
||||
'Docs',
|
||||
'Privacy',
|
||||
'Agreement'
|
||||
]
|
||||
router.getRoutes().forEach((route) => {
|
||||
const { name } = route
|
||||
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>) => {
|
||||
app.use(router)
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import type { App } from 'vue'
|
||||
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()
|
||||
|
||||
store.use(piniaPersist)
|
||||
store.use(piniaPluginPersistedstate)
|
||||
|
||||
export const setupStore = (app: App<Element>) => {
|
||||
app.use(store)
|
||||
|
@ -2,9 +2,10 @@ import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { setCssVar, humpToUnderline } from '@/utils'
|
||||
import { ElMessage, ComponentSize } from 'element-plus'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
|
||||
const { getStorage, setStorage } = useStorage()
|
||||
import { colorIsDark, hexToRGB, lighten, mix } from '@/utils/color'
|
||||
import { useCssVar } from '@vueuse/core'
|
||||
import { unref } from 'vue'
|
||||
import { useDark } from '@vueuse/core'
|
||||
|
||||
interface AppState {
|
||||
breadcrumb: boolean
|
||||
@ -25,7 +26,6 @@ interface AppState {
|
||||
pageLoading: boolean
|
||||
layout: LayoutType
|
||||
title: string
|
||||
userInfo: string
|
||||
isDark: boolean
|
||||
currentSize: ComponentSize
|
||||
sizeMap: ComponentSize[]
|
||||
@ -34,8 +34,6 @@ interface AppState {
|
||||
theme: ThemeTypes
|
||||
fixedMenu: boolean
|
||||
|
||||
token: string
|
||||
refreshToken: string
|
||||
logoImage: string
|
||||
footerContent: string
|
||||
icpNumber: string
|
||||
@ -44,7 +42,6 @@ interface AppState {
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: (): AppState => {
|
||||
return {
|
||||
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其它项目冲突
|
||||
sizeMap: ['default', 'large', 'small'],
|
||||
mobile: false, // 是否是移动端
|
||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||
@ -63,14 +60,14 @@ export const useAppStore = defineStore('app', {
|
||||
fixedHeader: true, // 固定toolheader
|
||||
footer: true, // 显示页脚
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
|
||||
serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
|
||||
fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
|
||||
dynamicRouter: true, // 是否动态路由
|
||||
serverDynamicRouter: true, // 是否服务端渲染动态路由
|
||||
fixedMenu: false, // 是否固定菜单
|
||||
|
||||
layout: getStorage('layout') || 'classic', // layout布局
|
||||
isDark: getStorage('isDark'), // 是否是暗黑模式
|
||||
currentSize: getStorage('default') || 'default', // 组件尺寸
|
||||
theme: getStorage('theme') || {
|
||||
layout: 'classic', // layout布局
|
||||
isDark: false, // 是否是暗黑模式
|
||||
currentSize: 'default', // 组件尺寸
|
||||
theme: {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
// 左侧菜单边框颜色
|
||||
@ -101,8 +98,6 @@ export const useAppStore = defineStore('app', {
|
||||
topToolBorderColor: '#eee'
|
||||
},
|
||||
|
||||
token: 'Token', // 存储Token字段
|
||||
refreshToken: 'RefreshToken', // 存储刷新Token字段
|
||||
logoImage: '', // logo图片
|
||||
footerContent: '', // 页脚内容
|
||||
icpNumber: '' // 备案号
|
||||
@ -166,9 +161,6 @@ export const useAppStore = defineStore('app', {
|
||||
getTitle(): string {
|
||||
return this.title
|
||||
},
|
||||
getUserInfo(): string {
|
||||
return this.userInfo
|
||||
},
|
||||
getIsDark(): boolean {
|
||||
return this.isDark
|
||||
},
|
||||
@ -191,12 +183,6 @@ export const useAppStore = defineStore('app', {
|
||||
getLogoImage(): string {
|
||||
return this.logoImage
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token
|
||||
},
|
||||
getRefreshToken(): string {
|
||||
return this.refreshToken
|
||||
},
|
||||
getFooterContent(): string {
|
||||
return this.footerContent
|
||||
},
|
||||
@ -245,15 +231,12 @@ export const useAppStore = defineStore('app', {
|
||||
this.greyMode = greyMode
|
||||
},
|
||||
setDynamicRouter(dynamicRouter: boolean) {
|
||||
setStorage('dynamicRouter', dynamicRouter)
|
||||
this.dynamicRouter = dynamicRouter
|
||||
},
|
||||
setServerDynamicRouter(serverDynamicRouter: boolean) {
|
||||
setStorage('serverDynamicRouter', serverDynamicRouter)
|
||||
this.serverDynamicRouter = serverDynamicRouter
|
||||
},
|
||||
setFixedMenu(fixedMenu: boolean) {
|
||||
setStorage('fixedMenu', fixedMenu)
|
||||
this.fixedMenu = fixedMenu
|
||||
},
|
||||
setPageLoading(pageLoading: boolean) {
|
||||
@ -265,7 +248,6 @@ export const useAppStore = defineStore('app', {
|
||||
return
|
||||
}
|
||||
this.layout = layout
|
||||
setStorage('layout', this.layout)
|
||||
},
|
||||
setTitle(title: string) {
|
||||
this.title = title
|
||||
@ -279,23 +261,22 @@ export const useAppStore = defineStore('app', {
|
||||
document.documentElement.classList.add('light')
|
||||
document.documentElement.classList.remove('dark')
|
||||
}
|
||||
setStorage('isDark', this.isDark)
|
||||
this.setPrimaryLight()
|
||||
},
|
||||
setCurrentSize(currentSize: ComponentSize) {
|
||||
this.currentSize = currentSize
|
||||
setStorage('currentSize', this.currentSize)
|
||||
},
|
||||
setMobile(mobile: boolean) {
|
||||
this.mobile = mobile
|
||||
},
|
||||
setTheme(theme: ThemeTypes) {
|
||||
this.theme = Object.assign(this.theme, theme)
|
||||
setStorage('theme', this.theme)
|
||||
},
|
||||
setCssVarTheme() {
|
||||
for (const key in this.theme) {
|
||||
setCssVar(`--${humpToUnderline(key)}`, this.theme[key])
|
||||
}
|
||||
this.setPrimaryLight()
|
||||
},
|
||||
setFooter(footer: boolean) {
|
||||
this.footer = footer
|
||||
@ -309,8 +290,75 @@ export const useAppStore = defineStore('app', {
|
||||
},
|
||||
setIcpNumber(icpNumber: string) {
|
||||
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 = () => {
|
||||
|
@ -2,7 +2,6 @@ import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { UserLoginType } from '@/api/login/types'
|
||||
import { loginApi } from '@/api/login'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { getCurrentAdminUserInfo } from '@/api/vadmin/auth/user'
|
||||
import { resetRouter } from '@/router'
|
||||
@ -10,7 +9,7 @@ import { useTagsViewStore } from '@/store/modules/tagsView'
|
||||
import router from '@/router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const { setStorage, clear } = useStorage()
|
||||
const { clear } = useStorage()
|
||||
|
||||
export interface UserState {
|
||||
id?: number
|
||||
@ -32,6 +31,9 @@ export interface AuthState {
|
||||
isUser: boolean // 是否已经登录并获取到用户信息
|
||||
roles: string[] // 当前用户角色 role_key 列表
|
||||
permissions: string[] // 当前用户权限列表
|
||||
tokenKey: string // 提交认证请求时,设置的 header key
|
||||
token: string // 认证 token
|
||||
refreshToken: string // 刷新 token
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
@ -40,10 +42,22 @@ export const useAuthStore = defineStore('auth', {
|
||||
user: {},
|
||||
roles: [],
|
||||
permissions: [],
|
||||
isUser: false
|
||||
isUser: false,
|
||||
tokenKey: 'Authorization',
|
||||
token: '',
|
||||
refreshToken: ''
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getTokenKey(): string {
|
||||
return this.tokenKey
|
||||
},
|
||||
getToken(): string {
|
||||
return this.token
|
||||
},
|
||||
getRefreshToken(): string {
|
||||
return this.refreshToken
|
||||
},
|
||||
getUser(): UserState {
|
||||
return this.user
|
||||
},
|
||||
@ -58,24 +72,34 @@ export const useAuthStore = defineStore('auth', {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
},
|
||||
setRefreshToken(refreshToken: string) {
|
||||
this.refreshToken = refreshToken
|
||||
},
|
||||
async login(formData: UserLoginType) {
|
||||
formData.platform = '0'
|
||||
const res = await loginApi(formData)
|
||||
if (res) {
|
||||
const appStore = useAppStore()
|
||||
setStorage(appStore.getToken, `${res.data.token_type} ${res.data.access_token}`)
|
||||
setStorage(appStore.getRefreshToken, res.data.refresh_token)
|
||||
this.token = `${res.data.token_type} ${res.data.access_token}`
|
||||
this.refreshToken = res.data.refresh_token
|
||||
// 获取当前登录用户的信息
|
||||
await this.setUserInfo()
|
||||
}
|
||||
return res
|
||||
},
|
||||
logout(message?: string) {
|
||||
clear()
|
||||
reset() {
|
||||
this.user = {}
|
||||
this.roles = []
|
||||
this.permissions = []
|
||||
this.isUser = false
|
||||
this.token = ''
|
||||
this.refreshToken = ''
|
||||
},
|
||||
logout(message?: string) {
|
||||
clear()
|
||||
this.reset()
|
||||
const tagsViewStore = useTagsViewStore()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter()
|
||||
@ -105,7 +129,8 @@ export const useAuthStore = defineStore('auth', {
|
||||
})
|
||||
this.permissions = res.data.permissions
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useAuthStoreWithOut = () => {
|
||||
|
@ -10,7 +10,11 @@ export const useDictStore = defineStore('dict', {
|
||||
state: (): DictState => ({
|
||||
dictObj: {}
|
||||
}),
|
||||
getters: {},
|
||||
getters: {
|
||||
getDictObjData(): Recordable {
|
||||
return this.dictObj
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async getDictObj(dictTypes: string[]) {
|
||||
const result: Recordable = {}
|
||||
@ -34,7 +38,8 @@ export const useDictStore = defineStore('dict', {
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useDictStoreWithOut = () => {
|
||||
|
@ -5,7 +5,7 @@ import en from 'element-plus/es/locale/lang/en'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { LocaleDropdownType } from '@/components/LocaleDropdown'
|
||||
|
||||
const { getStorage, setStorage } = useStorage()
|
||||
const { getStorage, setStorage } = useStorage('localStorage')
|
||||
|
||||
const elLocaleMap = {
|
||||
'zh-CN': zhCn,
|
||||
@ -51,7 +51,8 @@ export const useLocaleStore = defineStore('locales', {
|
||||
this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]
|
||||
setStorage('lang', localeMap?.lang)
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useLocaleStoreWithOut = () => {
|
||||
|
@ -40,10 +40,7 @@ export const useLockStore = defineStore('lock', {
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
enabled: true,
|
||||
strategies: [{ key: 'lock', storage: localStorage }]
|
||||
}
|
||||
persist: true
|
||||
})
|
||||
|
||||
export const useLockStoreWithOut = () => {
|
||||
|
@ -60,6 +60,9 @@ export const usePermissionStore = defineStore('permission', {
|
||||
setMenuTabRouters(routers: AppRouteRecordRaw[]): void {
|
||||
this.menuTabRouters = routers
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
paths: ['routers', 'addRouters', 'menuTabRouters']
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -4,12 +4,7 @@ import { getRawRoute } from '@/utils/routerHelper'
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { findIndex } from '@/utils'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useAppStoreWithOut } from './app'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
import { useAuthStoreWithOut } from './auth'
|
||||
|
||||
export interface TagsViewState {
|
||||
visitedViews: RouteLocationNormalizedLoaded[]
|
||||
@ -95,8 +90,9 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
},
|
||||
// 删除所有tag
|
||||
delAllVisitedViews() {
|
||||
const authStore = useAuthStoreWithOut()
|
||||
// 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)
|
||||
: []
|
||||
},
|
||||
@ -157,7 +153,8 @@ export const useTagsViewStore = defineStore('tagsView', {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
persist: false
|
||||
})
|
||||
|
||||
export const useTagsViewStoreWithOut = () => {
|
||||
|
@ -151,3 +151,22 @@ const subtractLight = (color: string, amount: number) => {
|
||||
const c = cc < 0 ? 0 : cc
|
||||
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 需要注册的组件
|
||||
@ -47,6 +45,10 @@ export const setCssVar = (prop: string, val: any, dom = document.documentElement
|
||||
dom.style.setProperty(prop, val)
|
||||
}
|
||||
|
||||
export const getCssVar = (prop: string, dom = document.documentElement) => {
|
||||
return getComputedStyle(dom).getPropertyValue(prop)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找数组对象的某个下标
|
||||
* @param {Array} ary 查找的数组
|
||||
@ -123,6 +125,17 @@ export function firstUpperCase(str: string) {
|
||||
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 => {
|
||||
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'
|
||||
|
||||
// 自定义扩展vue-types
|
||||
type PropTypes = VueTypesInterface & {
|
||||
readonly style: VueTypeValidableDef<CSSProperties>
|
||||
}
|
||||
|
||||
const propTypes = createTypes({
|
||||
const newPropTypes = createTypes({
|
||||
func: undefined,
|
||||
bool: undefined,
|
||||
string: undefined,
|
||||
@ -15,15 +14,12 @@ const propTypes = createTypes({
|
||||
integer: undefined
|
||||
}) as PropTypes
|
||||
|
||||
// 需要自定义扩展的类型
|
||||
// see: https://dwightjack.github.io/vue-types/advanced/extending-vue-types.html#the-extend-method
|
||||
propTypes.extend([
|
||||
{
|
||||
name: 'style',
|
||||
getter: true,
|
||||
type: [String, Object],
|
||||
default: undefined
|
||||
class propTypes extends newPropTypes {
|
||||
static get style() {
|
||||
return toValidableType('style', {
|
||||
type: [String, Object]
|
||||
})
|
||||
}
|
||||
])
|
||||
}
|
||||
|
||||
export { propTypes }
|
||||
|
@ -3,6 +3,7 @@ import { ref } from 'vue'
|
||||
import Finance from './components/Finance.vue'
|
||||
import { ElTabs, ElTabPane } from 'element-plus'
|
||||
import User from './components/User.vue'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
|
||||
defineOptions({
|
||||
name: 'DashboardAnalysis'
|
||||
@ -12,7 +13,7 @@ const activeName = ref('user')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="p-5">
|
||||
<ContentWrap>
|
||||
<ElTabs v-model="activeName">
|
||||
<ElTabPane label="财务分析" name="finance" :lazy="true">
|
||||
<Finance />
|
||||
@ -21,7 +22,7 @@ const activeName = ref('user')
|
||||
<User />
|
||||
</ElTabPane>
|
||||
</ElTabs>
|
||||
</div>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
@ -155,12 +155,12 @@ initMap()
|
||||
background-color: #f13737;
|
||||
box-shadow: 0px 0px 15px #f61212;
|
||||
border-radius: 50%;
|
||||
-webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名,也就是我们前面keyframes定义的动画名*/
|
||||
-webkit-animation-duration: 1s; /*动画持续时间*/
|
||||
-webkit-animation-timing-function: ease; /*动画频率,和transition-timing-function是一样的*/
|
||||
-webkit-animation-delay: 0s; /*动画延迟时间*/
|
||||
-webkit-animation-iteration-count: infinite; /*定义循环资料,infinite为无限次*/
|
||||
-webkit-animation-direction: alternate; /*定义动画方式*/
|
||||
--webkit-animation-name: 'alarmDeviceBreath'; /*动画属性名,也就是我们前面keyframes定义的动画名*/
|
||||
--webkit-animation-duration: 1s; /*动画持续时间*/
|
||||
--webkit-animation-timing-function: ease; /*动画频率,和transition-timing-function是一样的*/
|
||||
--webkit-animation-delay: 0s; /*动画延迟时间*/
|
||||
--webkit-animation-iteration-count: infinite; /*定义循环资料,infinite为无限次*/
|
||||
--webkit-animation-direction: alternate; /*定义动画方式*/
|
||||
}
|
||||
</style>
|
||||
|
||||
|
@ -3,14 +3,14 @@ import { ElCard, ElRow, ElCol, ElTabs, ElTabPane, ElAvatar } from 'element-plus'
|
||||
import { computed, ref } from 'vue'
|
||||
import InfoWrite from './components/InfoWrite.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 { selectDictLabel, DictDetail } from '@/utils/dict'
|
||||
import { useDictStore } from '@/store/modules/dict'
|
||||
|
||||
const activeName = ref('info')
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
let genderOptions = ref<DictDetail[]>([])
|
||||
|
||||
|
@ -3,13 +3,14 @@ import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import { ElButton, ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postCurrentUserUpdateInfo } from '@/api/vadmin/auth/user'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { required, isTelephone } = useValidator()
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
@ -92,9 +93,9 @@ const formSchema = reactive<FormSchema[]>([
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
|
@ -3,13 +3,14 @@ import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive, ref } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import { ElButton, ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const { required } = useValidator()
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
@ -57,9 +58,9 @@ const formSchema = reactive<FormSchema[]>([
|
||||
return (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
@ -101,7 +102,8 @@ const save = async () => {
|
||||
const res = await postCurrentUserResetPassword(formData)
|
||||
if (res) {
|
||||
elForm?.resetFields()
|
||||
ElMessage.success('保存成功')
|
||||
authStore.logout()
|
||||
ElMessage.warning('请重新登录')
|
||||
}
|
||||
} finally {
|
||||
loading.value = false
|
||||
|
@ -9,6 +9,7 @@ import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ref } from 'vue'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { computed } from 'vue'
|
||||
import { ElButton } from 'element-plus'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -28,6 +29,11 @@ const toTelephoneLogin = () => {
|
||||
const toPasswordLogin = () => {
|
||||
isPasswordLogin.value = true
|
||||
}
|
||||
|
||||
const icpNumber = computed(() => appStore.getIcpNumber)
|
||||
const toICO = () => {
|
||||
window.open('https://beian.miit.gov.cn/#/Integrated/index')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -88,6 +94,9 @@ const toPasswordLogin = () => {
|
||||
/>
|
||||
</div>
|
||||
</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>
|
||||
</ElScrollbar>
|
||||
|
@ -2,18 +2,18 @@
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { Form } from '@/components/Form'
|
||||
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 { getRoleMenusApi } from '@/api/login'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
|
||||
import { UserLoginType } from '@/api/login/types'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { FormSchema } from '@/components/Form'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const emit = defineEmits(['to-telephone'])
|
||||
|
||||
@ -21,10 +21,9 @@ const { required } = useValidator()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
const { setStorage } = useStorage()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -122,14 +121,19 @@ const schema = reactive<FormSchema[]>([
|
||||
return (
|
||||
<>
|
||||
<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')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<ElButton class="w-[100%]" onClick={toTelephoneLogin}>
|
||||
<BaseButton class="w-[100%]" onClick={toTelephoneLogin}>
|
||||
{t('login.smsLogin')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
@ -241,7 +245,6 @@ const getMenu = async () => {
|
||||
const res = await getRoleMenusApi()
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
|
@ -3,16 +3,16 @@ import { Form } from '@/components/Form'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
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 { FormSchema } from '@/components/Form'
|
||||
import { postSMSCodeApi } from '@/api/login'
|
||||
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 { getRoleMenusApi } from '@/api/login'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { BaseButton } from '@/components/Button'
|
||||
|
||||
const emit = defineEmits(['to-password'])
|
||||
|
||||
@ -22,8 +22,7 @@ const { t } = useI18n()
|
||||
const { required } = useValidator()
|
||||
const { currentRoute, addRoute, push } = useRouter()
|
||||
const permissionStore = usePermissionStore()
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const { setStorage } = useStorage()
|
||||
const authStore = useAuthStore()
|
||||
|
||||
const schema = reactive<FormSchema[]>([
|
||||
{
|
||||
@ -74,13 +73,13 @@ const schema = reactive<FormSchema[]>([
|
||||
<>
|
||||
<ElDivider direction="vertical" />
|
||||
{SMSCodeStatus.value ? (
|
||||
<ElButton type="primary" link onClick={getSMSCode}>
|
||||
<BaseButton type="primary" link onClick={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')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
@ -110,19 +109,19 @@ const schema = reactive<FormSchema[]>([
|
||||
return (
|
||||
<div class="w-[100%]">
|
||||
<div class="w-[100%]">
|
||||
<ElButton
|
||||
<BaseButton
|
||||
type="primary"
|
||||
class="w-[100%]"
|
||||
loading={loading.value}
|
||||
onClick={telephoneCodeLogin}
|
||||
>
|
||||
{t('login.login')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
<div class="w-[100%] mt-15px">
|
||||
<ElButton class="w-[100%]" onClick={toPasswordLogin}>
|
||||
<BaseButton class="w-[100%]" onClick={toPasswordLogin}>
|
||||
{t('login.passwordLogin')}
|
||||
</ElButton>
|
||||
</BaseButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
@ -216,7 +215,6 @@ const getMenu = async () => {
|
||||
const res = await getRoleMenusApi()
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
|
@ -3,21 +3,19 @@ import { Form, FormSchema } from '@/components/Form'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useAuthStoreWithOut } from '@/store/modules/auth'
|
||||
import { ElButton, ElMessage } from 'element-plus'
|
||||
import { useAuthStore } from '@/store/modules/auth'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { postCurrentUserResetPassword } from '@/api/vadmin/auth/user'
|
||||
import { getRoleMenusApi } from '@/api/login'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { RouteLocationNormalizedLoaded, RouteRecordRaw, useRouter } from 'vue-router'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { Footer } from '@/components/Footer'
|
||||
|
||||
const { required } = useValidator()
|
||||
const { setStorage } = useStorage()
|
||||
const { addRoute, push, currentRoute } = useRouter()
|
||||
|
||||
const authStore = useAuthStoreWithOut()
|
||||
const authStore = useAuthStore()
|
||||
const appStore = useAppStore()
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
@ -112,7 +110,6 @@ const getMenu = async () => {
|
||||
const res = await getRoleMenusApi()
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
await permissionStore.generateRoutes(routers).catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
@ -137,9 +134,9 @@ const getMenu = async () => {
|
||||
class="dark:(border-1 border-[var(--el-border-color)] border-solid)"
|
||||
/>
|
||||
<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>
|
||||
|
||||
|
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