更新1.1.0
69
README.md
@ -18,12 +18,33 @@
|
||||
Kinit 是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
|
||||
|
||||
- 🧑🤝🧑前端采用 [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/),等主流技术开发。
|
||||
- 👭后端采用 Python 语言高性能 [FastAPI](https://fastapi.tiangolo.com/zh/) 框架以及强大的 Mysql 数据库。
|
||||
- 👭后端采用 Python 语言以及现代、快速(高性能) [FastAPI](https://fastapi.tiangolo.com/zh/) 框架。
|
||||
- 👫权限认证使用[(哈希)密码和 JWT Bearer 令牌的 OAuth2](https://fastapi.tiangolo.com/zh/tutorial/security/oauth2-jwt/),支持多终端认证系统。
|
||||
- 👬支持加载动态权限菜单,多方式轻松权限控制。
|
||||
- 💏特别鸣谢:[django-vue-admin](https://gitee.com/liqianglog/django-vue-admin) 、 [vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin)。
|
||||
- 开箱即用的中后台解决方案,可以用来作为新项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。
|
||||
|
||||
## 💏特别鸣谢
|
||||
|
||||
[ELADMIN](https://eladmin.vip/demo):项目基于 Spring Boot 2.1.0 、 Jpa、 Spring Security、redis、Vue的前后端分离的后台管理系统。
|
||||
|
||||
[django-vue-admin](https://gitee.com/liqianglog/django-vue-admin):基于RBAC模型的权限控制的一整套基础开发平台,前后端分离,后端采用 django+django-rest-framework,前端采用 vue+ElementUI。
|
||||
|
||||
[vue-element-plus-admin](https://gitee.com/kailong110120130/vue-element-plus-admin):一套基于vue3、element-plus、typescript4、vite3的后台集成方案
|
||||
|
||||
[Ant Design Pro](https://preview.pro.ant.design/dashboard/analysis):开箱即用的中台前端/设计解决方案
|
||||
|
||||
[Gin-Vue-Admin](https://demo.gin-vue-admin.com):基于vite+vue3+gin搭建的开发基础平台(支持TS,JS混用),集成jwt鉴权,权限管理,动态路由,显隐可控组件,分页封装,多点登录拦截,资源权限,上传下载,代码生成器,表单生成器等开发必备功能。
|
||||
|
||||
[Vben Admin (vvbin.cn)](https://vvbin.cn/next):Vue Vben Admin 是一个免费开源的中后台模版。使用了最新的`vue3`,`vite2`,`TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,也可用于学习参考。
|
||||
|
||||
[中华人民共和国行政区划 (github.com)](https://github.com/modood/Administrative-divisions-of-China):省级(省份)、 地级(城市)、 县级(区县)、 乡级(乡镇街道)、 村级(村委会居委会) ,中国省市区镇村二级三级四级五级联动地址数据。
|
||||
|
||||
[Vue Admin Plus](https://vue-admin-beautiful.com/admin-plus/#/index):vue-admin-better是github开源admin中最优秀的集成框架之一,它是国内首个基于vue3.0的开源admin项目,同时支持电脑,手机,平板,默认分支使用vue3.x+antdv开发,master分支使用的是vue2.x+element开发。
|
||||
|
||||
[小诺开源技术 (xiaonuo.vip)](https://www.xiaonuo.vip/):国内首个国密前后端分离快速开发平台
|
||||
|
||||
[my-web:](https://gitee.com/newgateway/my-web):MyWeb 是一个企业级中后台前端/设计解决方案的的项目工程模板,它可以帮助你快速搭建企业级中后台产品原型
|
||||
|
||||
## 在线体验
|
||||
|
||||
👩👧👦演示地址:http://kinit.ktianc.top/
|
||||
@ -49,11 +70,7 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
|
||||
- [x] 📚字典管理:对系统中经常使用的一些较为固定的数据进行维护。
|
||||
|
||||
- [ ] 📁附件管理:对平台上所有文件、图片等进行统一管理。
|
||||
|
||||
- [ ] 🗓️登录日志:用户登录日志记录和查询。
|
||||
|
||||
- [ ] 🗓️操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
|
||||
- [ ] 📁附件管理:对平台上所有文件、图片等进行统一管理,对接阿里云OSS。
|
||||
|
||||
- [x] 🔒登录认证:目前支持用户使用手机号+密码方式登录。
|
||||
|
||||
@ -61,6 +78,32 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
|
||||
说明:用户在第一次登录时,必须修改当前用户密码。
|
||||
|
||||
- [x] 系统配置:对本系统环境信息进行动态配置
|
||||
|
||||
网站标题,LOGO,描述,ICO,备案号,底部内容,百度统计代码,等等
|
||||
|
||||
- [ ] 数据分析:根据用户的登录用户地址分析出哪个地区的人最多
|
||||
|
||||
- [x] 🗓️登录日志:用户登录日志记录和查询。
|
||||
|
||||
- [x] 🗓️操作日志:系统用户每次操作功能时的详细记录。
|
||||
|
||||
- [ ] **🗓️异常日志:获取并展示接口异常日志**
|
||||
|
||||
- [x] 🧾接口文档:提供自动生成的交互式 API 文档,与 ReDoc 文档
|
||||
|
||||
- [x] 导入导出:灵活支持数据导入导出功能
|
||||
|
||||
- [x] 手机验证码登录功能’
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] 考虑支持多机部署方案,如果接口使用多机,那么用户是否支持统一认证
|
||||
- [ ] **自动化编排服务:使用docker-compose部署项目**
|
||||
- [ ] **数据库备份:自动备份数据库**
|
||||
- [ ] **接入数据大屏**
|
||||
- [ ] **可视化低代码表单:接入低代码表单,https://vform666.com/vform3.html?from=element_plus**
|
||||
|
||||
## 前序准备
|
||||
|
||||
- [FastAPI](https://fastapi.tiangolo.com/zh/) - 熟悉后台接口 Web 框架
|
||||
@ -73,6 +116,18 @@ github地址:https://github.com/vvandk/kinit 👩👦👦
|
||||
- [Element-Plus](https://gitee.com/link?target=https%3A%2F%2Felement-plus.org%2F) - element-plus 基本使用
|
||||
- [Mock.js](https://gitee.com/link?target=https%3A%2F%2Fgithub.com%2Fnuysoft%2FMock) - mockjs 基本语法
|
||||
|
||||
### 依赖包
|
||||
|
||||
#### 前端
|
||||
|
||||
- [vue3-json-viewer](https://gitee.com/isfive/vue3-json-viewer):简单易用的json内容展示组件,适配vue3和vite。
|
||||
- [vue3-slide-verify](https://github.com/monoplasty/vue3-slide-verify):滑块验证码插件 vue3 + typescript
|
||||
- [SortableJS/vue.draggable.next](https://github.com/SortableJS/vue.draggable.next):Vue 组件 (Vue.js 3.0) 允许拖放和与视图模型数组同步。
|
||||
|
||||
#### 后端
|
||||
|
||||
- [iP查询接口文档](https://user.ip138.com/ip/doc):IP查询第三方服务,有1000次的免费次数
|
||||
|
||||
## 安装和使用
|
||||
|
||||
获取代码
|
||||
|
BIN
images/1.png
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 57 KiB |
BIN
images/2.png
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 93 KiB |
BIN
images/3.png
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 865 KiB |
BIN
images/4.png
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 97 KiB |
BIN
images/5.png
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 61 KiB |
@ -1,11 +0,0 @@
|
||||
# 环境
|
||||
NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=base
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=Kinit
|
@ -1,11 +1,8 @@
|
||||
# 环境
|
||||
NODE_ENV=production
|
||||
NODE_ENV=development
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=dev
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-dev/
|
||||
# 访问基础路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=false
|
||||
@ -16,8 +13,8 @@ VITE_DROP_CONSOLE=false
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=true
|
||||
|
||||
# 输出路径
|
||||
# 打包输出路径
|
||||
VITE_OUT_DIR=dist-dev
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=Kinit
|
||||
VITE_APP_TITLE=后台系统-开发
|
||||
|
@ -1,23 +0,0 @@
|
||||
# 环境
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=pro
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/kinit/
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=true
|
||||
|
||||
# 是否删除console.log
|
||||
VITE_DROP_CONSOLE=true
|
||||
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=false
|
||||
|
||||
# 输出路径
|
||||
VITE_OUT_DIR=dist-pro
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=Kinit
|
@ -1,10 +1,7 @@
|
||||
# 环境
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=pro
|
||||
|
||||
# 打包路径
|
||||
# 访问基础路径
|
||||
VITE_BASE_PATH=/
|
||||
|
||||
# 是否删除debugger
|
||||
@ -16,8 +13,8 @@ VITE_DROP_CONSOLE=true
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=false
|
||||
|
||||
# 输出路径
|
||||
# 打包输出路径
|
||||
VITE_OUT_DIR=dist-pro
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=Kinit
|
||||
VITE_APP_TITLE=后台系统
|
||||
|
@ -1,23 +0,0 @@
|
||||
# 环境
|
||||
NODE_ENV=production
|
||||
|
||||
# 接口前缀
|
||||
VITE_API_BASEPATH=test
|
||||
|
||||
# 打包路径
|
||||
VITE_BASE_PATH=/dist-test/
|
||||
|
||||
# 是否删除debugger
|
||||
VITE_DROP_DEBUGGER=false
|
||||
|
||||
# 是否删除console.log
|
||||
VITE_DROP_CONSOLE=false
|
||||
|
||||
# 是否sourcemap
|
||||
VITE_SOURCEMAP=true
|
||||
|
||||
# 输出路径
|
||||
VITE_OUT_DIR=dist-test
|
||||
|
||||
# 标题
|
||||
VITE_APP_TITLE=Kinit
|
@ -2,6 +2,86 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [1.8.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.2...v1.8.3) (2022-10-28)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* 优化描述组件 ([73ecc98](https://github.com/kailong321200875/vue-element-plus-admin/commit/73ecc98671d430013920246d98ce9ab1752e56eb))
|
||||
|
||||
## [1.8.2](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.1...v1.8.2) (2022-10-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Correct spelling of words(aciton →action) ([eb405b2](https://github.com/kailong321200875/vue-element-plus-admin/commit/eb405b2a9041ca0ad4455db79bf617ec910dc485))
|
||||
* Correct spelling of words(tigger →trigger) ([c2ca2d7](https://github.com/kailong321200875/vue-element-plus-admin/commit/c2ca2d736c92e02380923a6741450844acb41a38))
|
||||
|
||||
## [1.8.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.8.0...v1.8.1) (2022-10-11)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修复cutMenu收起时 ([993af6b](https://github.com/kailong321200875/vue-element-plus-admin/commit/993af6bb6576249e66e0c0ea592ebf851f65ab8c))
|
||||
|
||||
|
||||
### Styling
|
||||
|
||||
* cutMenu层级样式 ([32d2408](https://github.com/kailong321200875/vue-element-plus-admin/commit/32d2408588c487cff2cf73e3cc132e5105ff4459))
|
||||
|
||||
## [1.8.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.7.1...v1.8.0) (2022-10-10)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* types优化 ([3351155](https://github.com/kailong321200875/vue-element-plus-admin/commit/33511553cd9055b036b2d7491f9c2eda123f8b22))
|
||||
|
||||
|
||||
### Styling
|
||||
|
||||
* 优化第四种布局 ([122fa62](https://github.com/kailong321200875/vue-element-plus-admin/commit/122fa62d859413d16175e0d97c7bf13f232dbb3a))
|
||||
|
||||
## [1.7.1](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.7.0...v1.7.1) (2022-10-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 修正types提示错误 ([ef3e006](https://github.com/kailong321200875/vue-element-plus-admin/commit/ef3e006859dcd8b93ffb7cffcaeae24cbb330f2a))
|
||||
|
||||
## [1.7.0](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.6...v1.7.0) (2022-10-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* type抽离 ([8b4fa1a](https://github.com/kailong321200875/vue-element-plus-admin/commit/8b4fa1aa21aa2c1379288315ccd64a6f3375be51))
|
||||
|
||||
## [1.6.6](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.5...v1.6.6) (2022-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* table search params ([a62929a](https://github.com/kailong321200875/vue-element-plus-admin/commit/a62929a8dac21028d3dd1cddf98189492c33b093))
|
||||
|
||||
## [1.6.5](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.4...v1.6.5) (2022-10-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* The attribute of option does not work ([d946920](https://github.com/kailong321200875/vue-element-plus-admin/commit/d946920e61ed81beacf9f1f8be7ee1f50505f64d))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* perf store ([d416178](https://github.com/kailong321200875/vue-element-plus-admin/commit/d416178d69ca6100be4b635922b1a22d27629f08))
|
||||
* token test ([b320e65](https://github.com/kailong321200875/vue-element-plus-admin/commit/b320e658d1a559a6eaebdf374d63649c223c2ecd))
|
||||
|
||||
## [1.6.4](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.3...v1.6.4) (2022-09-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix bug ([da39f3b](https://github.com/kailong321200875/vue-element-plus-admin/commit/da39f3bc904ca2d80f432a31709725f9a57deb19))
|
||||
|
||||
## [1.6.3](https://github.com/kailong321200875/vue-element-plus-admin/compare/v1.6.2...v1.6.3) (2022-08-20)
|
||||
|
||||
|
||||
|
@ -56,6 +56,12 @@ git clone https://github.com/kailong321200875/vue-element-plus-admin.git
|
||||
- 安装依赖
|
||||
|
||||
```bash
|
||||
临时修改
|
||||
pnpm --registry https://registry.npm.taobao.org install any-touch
|
||||
|
||||
持久使用
|
||||
pnpm config set registry https://registry.npm.taobao.org
|
||||
|
||||
cd vue-element-plus-admin
|
||||
|
||||
pnpm install
|
||||
|
@ -1,8 +1,8 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="icon" href="/static/system/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title><%= title %></title>
|
||||
</head>
|
||||
@ -126,10 +126,11 @@
|
||||
</style>
|
||||
<div class="app-loading">
|
||||
<div class="app-loading-wrap">
|
||||
<div class="app-loading-title">
|
||||
<!-- 设置页面加载时的Logo与标题 -->
|
||||
<!-- <div class="app-loading-title">
|
||||
<img src="/logo.png" class="app-loading-logo" alt="Logo" />
|
||||
<div class="app-loading-title"><%= title %></div>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="app-loading-item">
|
||||
<div class="app-loading-outter"></div>
|
||||
<div class="app-loading-inner"></div>
|
||||
|
@ -1,138 +0,0 @@
|
||||
import { config } from '@/config/axios/config'
|
||||
import { MockMethod } from 'vite-plugin-mock'
|
||||
|
||||
const { result_code } = config
|
||||
|
||||
const timeout = 1000
|
||||
|
||||
export default [
|
||||
// 获取统计
|
||||
{
|
||||
url: '/api/workplace/total',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
code: result_code,
|
||||
data: {
|
||||
project: 40,
|
||||
access: 2340,
|
||||
todo: 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取项目
|
||||
{
|
||||
url: '/api/workplace/project',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
code: result_code,
|
||||
data: [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite',
|
||||
message: 'workplace.introduction',
|
||||
personal: 'kinit',
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取动态
|
||||
{
|
||||
url: '/api/workplace/dynamic',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
code: result_code,
|
||||
data: [
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
},
|
||||
{
|
||||
keys: ['workplace.push', 'Github'],
|
||||
time: new Date()
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取团队信息
|
||||
{
|
||||
url: '/api/workplace/team',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
code: result_code,
|
||||
data: [
|
||||
{
|
||||
name: 'Github',
|
||||
icon: 'akar-icons:github-fill'
|
||||
},
|
||||
{
|
||||
name: 'Vue',
|
||||
icon: 'logos:vue'
|
||||
},
|
||||
{
|
||||
name: 'Angular',
|
||||
icon: 'logos:angular-icon'
|
||||
},
|
||||
{
|
||||
name: 'React',
|
||||
icon: 'logos:react'
|
||||
},
|
||||
{
|
||||
name: 'Webpack',
|
||||
icon: 'logos:webpack'
|
||||
},
|
||||
{
|
||||
name: 'Vite',
|
||||
icon: 'vscode-icons:file-type-vite'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
] as MockMethod[]
|
@ -1,20 +1,17 @@
|
||||
{
|
||||
"name": "vue-element-plus-admin",
|
||||
"version": "1.6.3",
|
||||
"version": "1.8.4",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite3的后台集成方案。",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"i": "pnpm install",
|
||||
"dev": "vite --mode base",
|
||||
"dev": "vite --mode dev",
|
||||
"ts:check": "vue-tsc --noEmit",
|
||||
"build:pro": "vite build --mode pro",
|
||||
"build:gitee": "vite build --mode gitee",
|
||||
"build:dev": "npm run ts:check && vite build --mode dev",
|
||||
"build:test": "npm run ts:check && vite build --mode test",
|
||||
"serve:pro": "vite preview --mode pro",
|
||||
"serve:dev": "vite preview --mode dev",
|
||||
"serve:test": "vite preview --mode test",
|
||||
"npm:check": "npx npm-check-updates",
|
||||
"clean": "npx rimraf node_modules",
|
||||
"clean:cache": "npx rimraf node_modules/.cache",
|
||||
@ -23,84 +20,88 @@
|
||||
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
||||
"prepare": "husky install",
|
||||
"p": "plop"
|
||||
"p": "plop",
|
||||
"analysis": "windicss-analysis"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/iconify": "^2.2.1",
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"@wangeditor/editor": "^5.1.14",
|
||||
"@iconify/iconify": "^3.0.0",
|
||||
"@vueuse/core": "^9.4.0",
|
||||
"@wangeditor/editor": "^5.1.22",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^2.0.4",
|
||||
"@zxcvbn-ts/core": "^2.1.0",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.27.2",
|
||||
"echarts": "^5.3.3",
|
||||
"axios": "^1.1.3",
|
||||
"clipboard": "^2.0.11",
|
||||
"echarts": "^5.4.0",
|
||||
"echarts-wordcloud": "^2.0.0",
|
||||
"element-plus": "2.2.15",
|
||||
"element-plus": "2.2.21",
|
||||
"intro.js": "^6.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.0.21",
|
||||
"pinia": "^2.0.23",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"qrcode": "^1.5.1",
|
||||
"qs": "^6.11.0",
|
||||
"url": "^0.11.0",
|
||||
"vue": "3.2.37",
|
||||
"vue": "3.2.41",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-router": "^4.1.5",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-types": "^4.2.1",
|
||||
"vue3-json-viewer": "^2.2.2",
|
||||
"vuedraggable": "4.1.0",
|
||||
"web-storage-cache": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.1.2",
|
||||
"@commitlint/config-conventional": "^17.1.0",
|
||||
"@iconify/json": "^2.1.100",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
||||
"@commitlint/cli": "^17.2.0",
|
||||
"@commitlint/config-conventional": "^17.2.0",
|
||||
"@iconify/json": "^2.1.134",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.3",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.0",
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/node": "^18.7.13",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.0",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@typescript-eslint/eslint-plugin": "^5.35.1",
|
||||
"@typescript-eslint/parser": "^5.35.1",
|
||||
"@vitejs/plugin-vue": "^3.0.3",
|
||||
"@vitejs/plugin-vue-jsx": "^2.0.0",
|
||||
"autoprefixer": "^10.4.8",
|
||||
"eslint": "^8.23.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.0",
|
||||
"@typescript-eslint/parser": "^5.42.0",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^2.1.0",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-define-config": "^1.6.0",
|
||||
"eslint-define-config": "^1.11.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"eslint-plugin-vue": "^9.7.0",
|
||||
"husky": "^8.0.1",
|
||||
"less": "^4.1.3",
|
||||
"lint-staged": "^13.0.3",
|
||||
"plop": "^3.1.1",
|
||||
"postcss": "^8.4.16",
|
||||
"postcss": "^8.4.18",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^2.7.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"rollup": "^2.78.1",
|
||||
"stylelint": "^14.11.0",
|
||||
"rollup": "^3.2.5",
|
||||
"stylelint": "^14.14.1",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-recommended": "^9.0.0",
|
||||
"stylelint-config-standard": "^28.0.0",
|
||||
"stylelint-config-standard": "^29.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"typescript": "4.8.2",
|
||||
"unplugin-vue-macros": "^0.11.0",
|
||||
"vite": "3.0.9",
|
||||
"typescript": "4.8.4",
|
||||
"unplugin-vue-macros": "^0.16.0",
|
||||
"vite": "3.2.2",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-html": "^3.2.0",
|
||||
"vite-plugin-mock": "^2.9.6",
|
||||
"vite-plugin-purge-icons": "^0.9.0",
|
||||
"vite-plugin-purge-icons": "^0.9.1",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vite-plugin-windicss": "^1.8.7",
|
||||
"vue-tsc": "^0.40.4",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
"vue-tsc": "^1.0.9",
|
||||
"windicss": "^3.5.6",
|
||||
"windicss-analysis": "^0.3.5"
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ import { ConfigGlobal } from '@/components/ConfigGlobal'
|
||||
import { isDark } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { getSystemSettingsClassifysApi } from '@/api/vadmin/system/settings'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -12,6 +13,36 @@ const prefixCls = getPrefixCls('app')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
// 手动添加mate标签
|
||||
const addMeta = (name: string, content: string) => {
|
||||
const meta = document.createElement('meta')
|
||||
meta.content = content
|
||||
meta.name = name
|
||||
document.getElementsByTagName('head')[0].appendChild(meta)
|
||||
}
|
||||
|
||||
// 获取并设置系统配置
|
||||
const setSystemConfig = async () => {
|
||||
const res = await getSystemSettingsClassifysApi({ classify: 'web' })
|
||||
if (res) {
|
||||
appStore.setTitle(res.data.web_basic.web_title || import.meta.env.VITE_APP_TITLE)
|
||||
appStore.setLogoImage(res.data.web_basic.web_logo || '/static/system/logo.png')
|
||||
appStore.setFooterContent(res.data.web_basic.web_copyright || 'Copyright ©2022-present K')
|
||||
appStore.setIcpNumber(res.data.web_basic.web_icp_number || '')
|
||||
addMeta(
|
||||
'description',
|
||||
res.data.web_basic.web_desc ||
|
||||
'Kinit 是一套开箱即用的中后台解决方案,可以作为新项目的启动模版。'
|
||||
)
|
||||
// 接入百度统计
|
||||
if (res.data.web_baidu.web_baidu) {
|
||||
eval(res.data.web_baidu.web_baidu)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSystemConfig()
|
||||
|
||||
const currentSize = computed(() => appStore.getCurrentSize)
|
||||
|
||||
const greyMode = computed(() => appStore.getGreyMode)
|
||||
@ -60,4 +91,14 @@ body {
|
||||
.@{prefix-cls}-grey-mode {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
ol {
|
||||
display: block;
|
||||
list-style-type: decimal;
|
||||
margin-block-start: 1em;
|
||||
margin-block-end: 1em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
padding-inline-start: 40px;
|
||||
}
|
||||
</style>
|
||||
|
23
kinit-admin/src/api/dashboard/analysis/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import request from '@/config/axios'
|
||||
import type {
|
||||
AnalysisBannersTypes,
|
||||
UserAccessSource,
|
||||
WeeklyUserActivity,
|
||||
MonthlySales
|
||||
} from './types'
|
||||
|
||||
export const getBannersApi = (): Promise<IResponse<AnalysisBannersTypes[]>> => {
|
||||
return request.get({ url: '/vadmin/analysis/banners/' })
|
||||
}
|
||||
|
||||
export const getUserAccessSourceApi = (): Promise<IResponse<UserAccessSource[]>> => {
|
||||
return request.get({ url: '/vadmin/analysis/user/access/source/' })
|
||||
}
|
||||
|
||||
export const getWeeklyUserActivityApi = (): Promise<IResponse<WeeklyUserActivity[]>> => {
|
||||
return request.get({ url: '/vadmin/analysis/weekly/user/activity/' })
|
||||
}
|
||||
|
||||
export const getMonthlySalesApi = (): Promise<IResponse<MonthlySales[]>> => {
|
||||
return request.get({ url: '/vadmin/analysis/monthly/sales/' })
|
||||
}
|
20
kinit-admin/src/api/dashboard/analysis/types.ts
Normal file
@ -0,0 +1,20 @@
|
||||
export type AnalysisBannersTypes = {
|
||||
id: number
|
||||
image: string
|
||||
}
|
||||
|
||||
export type UserAccessSource = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type WeeklyUserActivity = {
|
||||
value: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type MonthlySales = {
|
||||
name: string
|
||||
estimate: number
|
||||
actual: number
|
||||
}
|
@ -1,22 +1,26 @@
|
||||
import request from '@/config/axios'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData } from './types'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team, RadarData, Shortcuts } from './types'
|
||||
|
||||
export const getCountApi = (): Promise<IResponse<WorkplaceTotal>> => {
|
||||
return request.get({ url: '/workplace/total' })
|
||||
return request.get({ url: '/vadmin/workplace/total/' })
|
||||
}
|
||||
|
||||
export const getProjectApi = (): Promise<IResponse<Project>> => {
|
||||
return request.get({ url: '/workplace/project' })
|
||||
return request.get({ url: '/vadmin/workplace/project/' })
|
||||
}
|
||||
|
||||
export const getDynamicApi = (): Promise<IResponse<Dynamic[]>> => {
|
||||
return request.get({ url: '/workplace/dynamic' })
|
||||
return request.get({ url: '/vadmin/workplace/dynamic/' })
|
||||
}
|
||||
|
||||
export const getTeamApi = (): Promise<IResponse<Team[]>> => {
|
||||
return request.get({ url: '/workplace/team' })
|
||||
return request.get({ url: '/vadmin/workplace/team/' })
|
||||
}
|
||||
|
||||
export const getRadarApi = (): Promise<IResponse<RadarData[]>> => {
|
||||
return request.get({ url: '/workplace/radar' })
|
||||
return request.get({ url: '/vadmin/workplace/radar/' })
|
||||
}
|
||||
|
||||
export const getShortcutsApi = (): Promise<IResponse<Shortcuts[]>> => {
|
||||
return request.get({ url: '/vadmin/workplace/shortcuts/' })
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ export type Project = {
|
||||
icon: string
|
||||
message: string
|
||||
personal: string
|
||||
link: string
|
||||
time: Date | number | string
|
||||
}
|
||||
|
||||
@ -28,3 +29,8 @@ export type RadarData = {
|
||||
max: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export type Shortcuts = {
|
||||
name: string
|
||||
link: string
|
||||
}
|
||||
|
@ -8,3 +8,7 @@ export const loginApi = (data: UserLoginType): Promise<IResponse> => {
|
||||
export const getRoleMenusApi = (): Promise<IResponse<AppCustomRouteRecordRaw[]>> => {
|
||||
return request.get({ url: '/auth/getMenuList/' })
|
||||
}
|
||||
|
||||
export const postSMSCodeApi = (params: any): Promise<IResponse> => {
|
||||
return request.post({ url: '/vadmin/system/sms/send/', params })
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
export type UserLoginType = {
|
||||
telephone: string
|
||||
password: string
|
||||
method: string
|
||||
}
|
||||
|
||||
export type UserType = {
|
||||
|
@ -31,3 +31,26 @@ export const postCurrentUserUpdateInfo = (data: any): Promise<IResponse> => {
|
||||
export const getCurrentUserInfo = (): Promise<IResponse> => {
|
||||
return request.get({ url: `/vadmin/auth/user/current/info/` })
|
||||
}
|
||||
|
||||
export const postExportUserQueryListApi = (params: any, data: any): Promise<IResponse> => {
|
||||
return request.post({ url: `/vadmin/auth/user/export/query/list/to/excel/`, params, data })
|
||||
}
|
||||
|
||||
export const getImportTemplateApi = (): Promise<IResponse> => {
|
||||
return request.get({ url: `/vadmin/auth/user/download/import/template/` })
|
||||
}
|
||||
|
||||
export const postImportUserApi = (data: any): Promise<IResponse> => {
|
||||
return request.post({
|
||||
url: `/vadmin/auth/import/users/`,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
timeout: 5 * 60 * 1000,
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export const postUsersInitPasswordSendSMSApi = (data: any): Promise<IResponse> => {
|
||||
return request.post({ url: `/vadmin/auth/users/init/password/send/sms/`, data })
|
||||
}
|
||||
|
5
kinit-admin/src/api/vadmin/system/record/login.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getRecordLoginListApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/record/logins/', params })
|
||||
}
|
5
kinit-admin/src/api/vadmin/system/record/operation.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getRecordOperationListApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/record/operations/', params })
|
||||
}
|
17
kinit-admin/src/api/vadmin/system/settings.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const getSystemSettingsTabsApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/system/settings/tabs/', params })
|
||||
}
|
||||
|
||||
export const getSystemSettingsApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/system/settings/tabs/values/', params })
|
||||
}
|
||||
|
||||
export const putSystemSettingsApi = (data: any): Promise<IResponse> => {
|
||||
return request.put({ url: '/vadmin/system/settings/tabs/values/', data })
|
||||
}
|
||||
|
||||
export const getSystemSettingsClassifysApi = (params: any): Promise<IResponse> => {
|
||||
return request.get({ url: '/vadmin/system/settings/classifys/', params })
|
||||
}
|
@ -7,13 +7,14 @@ import { useWindowSize } from '@vueuse/core'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { setCssVar } from '@/utils'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const props = defineProps({
|
||||
size: propTypes.oneOf<ElememtPlusSize[]>(['default', 'small', 'large']).def('default')
|
||||
size: propTypes.oneOf<ElementPlusSize[]>(['default', 'small', 'large']).def('default')
|
||||
})
|
||||
|
||||
provide('configGlobal', props)
|
||||
|
@ -4,7 +4,7 @@ import { PropType, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
|
||||
import { contextMenuSchema } from '../../../types/contextMenu'
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
const prefixCls = getPrefixCls('context-menu')
|
||||
|
@ -2,8 +2,9 @@
|
||||
import { ElCollapseTransition, ElDescriptions, ElDescriptionsItem, ElTooltip } from 'element-plus'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ref, unref, PropType, computed, useAttrs } from 'vue'
|
||||
import { ref, unref, PropType, computed, useAttrs, useSlots } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { DescriptionsSchema } from '@/types/descriptions'
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
@ -11,6 +12,8 @@ const mobile = computed(() => appStore.getMobile)
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
title: propTypes.string.def(''),
|
||||
message: propTypes.string.def(''),
|
||||
@ -95,6 +98,9 @@ const toggleClick = () => {
|
||||
:direction="mobile ? 'vertical' : 'horizontal'"
|
||||
v-bind="getBindValue"
|
||||
>
|
||||
<template v-if="slots['extra']" #extra>
|
||||
<slot name="extra"></slot>
|
||||
</template>
|
||||
<ElDescriptionsItem
|
||||
v-for="item in schema"
|
||||
:key="item.field"
|
||||
|
@ -72,7 +72,7 @@ const dialogStyle = computed(() => {
|
||||
</slot>
|
||||
<Icon
|
||||
v-if="fullscreen"
|
||||
class="mr-18px cursor-pointer is-hover mt-2px"
|
||||
class="mr-18px cursor-pointer is-hover mt-2px z-10"
|
||||
:icon="isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'"
|
||||
color="var(--el-color-info)"
|
||||
@click="toggleFull"
|
||||
|
@ -116,7 +116,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="border-1 border-solid border-[var(--tags-view-border-color)]">
|
||||
<div class="border-1 border-solid border-[var(--tags-view-border-color)] z-3000">
|
||||
<!-- 工具栏 -->
|
||||
<Toolbar
|
||||
:editor="editorRef"
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { computed } from 'vue'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -9,7 +9,7 @@ const prefixCls = getPrefixCls('footer')
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const title = computed(() => appStore.getTitle)
|
||||
const footerContent = computed(() => appStore.getFooterContent)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -17,6 +17,6 @@ const title = computed(() => appStore.getTitle)
|
||||
: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)]"
|
||||
>
|
||||
Copyright ©2021-present {{ title }}
|
||||
{{ footerContent }}
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Form from './src/Form.vue'
|
||||
import { ElForm } from 'element-plus'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
|
||||
export interface FormExpose {
|
||||
setValues: (data: Recordable) => void
|
||||
|
@ -20,6 +20,7 @@ import { findIndex } from '@/utils'
|
||||
import { set } from 'lodash-es'
|
||||
import { FormProps } from './types'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -244,6 +245,11 @@ export default defineComponent({
|
||||
vModel={formModel.value[item.field]}
|
||||
{...(autoSetPlaceholder && setTextPlaceholder(item))}
|
||||
{...setComponentProps(item)}
|
||||
style={
|
||||
item?.component === 'Input'
|
||||
? { width: '100%', ...item.componentProps?.style }
|
||||
: { ...item.componentProps?.style }
|
||||
}
|
||||
{...(notRenderOptions.includes(item?.component as string) &&
|
||||
item?.componentProps?.options
|
||||
? { options: item?.componentProps?.options || [] }
|
||||
|
@ -22,6 +22,8 @@ import {
|
||||
} from 'element-plus'
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { Text } from '@/components/Text'
|
||||
import { ComponentName } from '@/types/components'
|
||||
|
||||
const componentMap: Recordable<Component, ComponentName> = {
|
||||
Radio: ElRadioGroup,
|
||||
@ -46,7 +48,8 @@ const componentMap: Recordable<Component, ComponentName> = {
|
||||
InputPassword: InputPassword,
|
||||
Editor: Editor,
|
||||
TreeSelect: ElTreeSelect,
|
||||
Tree: ElTree
|
||||
Tree: ElTree,
|
||||
Text: Text
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ElCheckbox, ElCheckboxButton } from 'element-plus'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
@ -11,6 +12,12 @@ export const useRenderCheckbox = () => {
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com>
|
||||
// const { value, ...other } = option
|
||||
// return (
|
||||
// <Com label={option[labelAlias || 'value']} {...other}>
|
||||
// {option[valueAlias || 'label']}
|
||||
// </Com>
|
||||
// )
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ElRadio, ElRadioButton } from 'element-plus'
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
@ -11,6 +12,12 @@ export const useRenderRadio = () => {
|
||||
>
|
||||
return item?.componentProps?.options?.map((option) => {
|
||||
return <Com label={option[labelAlias || 'value']}>{option[valueAlias || 'label']}</Com>
|
||||
// const { value, ...other } = option
|
||||
// return (
|
||||
// <Com label={option[labelAlias || 'value']} {...other}>
|
||||
// {option[valueAlias || 'label']}
|
||||
// </Com>
|
||||
// )
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { ElOption, ElOptionGroup } from 'element-plus'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { Slots } from 'vue'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ComponentOptions } from '@/types/components'
|
||||
|
||||
export const useRenderSelect = (slots: Slots) => {
|
||||
// 渲染 select options
|
||||
@ -29,11 +31,14 @@ export const useRenderSelect = (slots: Slots) => {
|
||||
// 如果有别名,就取别名
|
||||
const labelAlias = item?.componentProps?.optionsAlias?.labelField
|
||||
const valueAlias = item?.componentProps?.optionsAlias?.valueField
|
||||
|
||||
const { label, value, ...other } = option
|
||||
|
||||
return (
|
||||
<ElOption
|
||||
label={option[labelAlias || 'label']}
|
||||
value={option[valueAlias || 'value']}
|
||||
disabled={option.disabled}
|
||||
label={labelAlias ? option[labelAlias] : label}
|
||||
value={valueAlias ? option[valueAlias] : value}
|
||||
{...other}
|
||||
>
|
||||
{{
|
||||
default: () =>
|
||||
|
@ -2,6 +2,8 @@ import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { Slots } from 'vue'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import { PlaceholderMoel } from './types'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { ColProps } from '@/types/components'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
export interface PlaceholderMoel {
|
||||
placeholder?: string
|
||||
startPlaceholder?: string
|
||||
|
@ -1,8 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { PropType } from 'vue'
|
||||
import { Highlight } from '@//components/Highlight'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { TipSchema } from '@/types/infoTip'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -17,6 +17,8 @@ const layout = computed(() => appStore.getLayout)
|
||||
|
||||
const collapse = computed(() => appStore.getCollapse)
|
||||
|
||||
const logoImage = computed(() => appStore.getLogoImage)
|
||||
|
||||
onMounted(() => {
|
||||
if (unref(collapse)) show.value = false
|
||||
})
|
||||
@ -65,7 +67,7 @@ watch(
|
||||
to="/"
|
||||
>
|
||||
<img
|
||||
src="@/assets/imgs/logo.png"
|
||||
:src="logoImage"
|
||||
class="w-[calc(var(--logo-height)-10px)] h-[calc(var(--logo-height)-10px)]"
|
||||
/>
|
||||
<div
|
||||
|
@ -3,11 +3,11 @@ import { computed, defineComponent, unref, PropType } from 'vue'
|
||||
import { ElMenu, ElScrollbar } from 'element-plus'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import type { LayoutType } from '@/config/app'
|
||||
import { useRenderMenuItem } from './components/useRenderMenuItem'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { LayoutType } from '@/types/layout'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -6,6 +6,7 @@ import { cloneDeep } from 'lodash-es'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { isString } from '@/utils/is'
|
||||
import { QrcodeLogo } from '@/types/qrcode'
|
||||
|
||||
const props = defineProps({
|
||||
// img 或者 canvas,img不支持logo嵌套
|
||||
|
3
kinit-admin/src/components/RightToolbar/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import RightToolbar from './src/RightToolbar.vue'
|
||||
|
||||
export { RightToolbar }
|
112
kinit-admin/src/components/RightToolbar/src/RightToolbar.vue
Normal file
@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ElButton,
|
||||
ElRow,
|
||||
ElCol,
|
||||
ElDropdownItem,
|
||||
ElDropdownMenu,
|
||||
ElDropdown,
|
||||
ElPopover,
|
||||
ElCheckbox,
|
||||
ElScrollbar
|
||||
} from 'element-plus'
|
||||
import { useIcon } from '@/hooks/web/useIcon'
|
||||
import { ref, PropType } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
const emit = defineEmits(['getList', 'update:tableSize'])
|
||||
|
||||
const props = defineProps({
|
||||
tableSize: propTypes.string.def(''),
|
||||
columns: {
|
||||
type: Array as PropType<any[]>,
|
||||
default: () => []
|
||||
}
|
||||
})
|
||||
|
||||
const handleClickRefresh = () => {
|
||||
emit('getList')
|
||||
}
|
||||
|
||||
const refresh = useIcon({ icon: 'ic:outline-refresh' })
|
||||
const spacing = useIcon({ icon: 'mdi:format-paragraph-spacing' })
|
||||
const settings = useIcon({ icon: 'bytesize:settings' })
|
||||
|
||||
const handleCommand = (command: string) => {
|
||||
emit('update:tableSize', command)
|
||||
}
|
||||
|
||||
const checkAll = ref(false)
|
||||
const columns = ref(props.columns)
|
||||
const isIndeterminate = ref(true) // 如果为True,则表示为半选状态
|
||||
const handleCheckAllChange = (val: boolean) => {
|
||||
columns.value.forEach((item) => {
|
||||
if (item.disabled !== true) {
|
||||
item.show = val
|
||||
}
|
||||
})
|
||||
isIndeterminate.value = false
|
||||
}
|
||||
const handleCheckChange = () => {
|
||||
checkAll.value = columns.value.every((item) => item.show)
|
||||
if (checkAll.value) {
|
||||
isIndeterminate.value = false
|
||||
} else {
|
||||
isIndeterminate.value = columns.value.some((item) => item.show)
|
||||
}
|
||||
}
|
||||
handleCheckChange()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElRow :gutter="10">
|
||||
<ElCol :span="1.5">
|
||||
<ElButton link :icon="refresh" @click="handleClickRefresh">刷新数据</ElButton>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5" class="pt-4px">
|
||||
<ElDropdown trigger="click" @command="handleCommand">
|
||||
<ElButton link :icon="spacing">密度调整</ElButton>
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem command="default">默认</ElDropdownItem>
|
||||
<ElDropdownItem command="large">宽松</ElDropdownItem>
|
||||
<ElDropdownItem command="small">紧凑</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
</ElDropdown>
|
||||
</ElCol>
|
||||
<ElCol :span="1.5">
|
||||
<ElPopover placement="bottom" :width="200" trigger="click">
|
||||
<div style="border-bottom: 1px solid #d4d7de">
|
||||
<el-checkbox
|
||||
v-model="checkAll"
|
||||
:indeterminate="isIndeterminate"
|
||||
@change="handleCheckAllChange"
|
||||
>全选</el-checkbox
|
||||
>
|
||||
</div>
|
||||
<ElScrollbar max-height="400px">
|
||||
<draggable :list="columns" item-key="field">
|
||||
<template #item="{ element }">
|
||||
<div>
|
||||
<span class="cursor-move mr-10px">
|
||||
<Icon icon="akar-icons:drag-vertical" />
|
||||
</span>
|
||||
<ElCheckbox
|
||||
v-model="element.show"
|
||||
:disabled="element.disabled === true"
|
||||
@change="handleCheckChange"
|
||||
>{{ element.label }}</ElCheckbox
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</ElScrollbar>
|
||||
<template #reference>
|
||||
<ElButton link :icon="settings">字段设置</ElButton>
|
||||
</template>
|
||||
</ElPopover>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
@ -8,6 +8,7 @@ import { useForm } from '@/hooks/web/useForm'
|
||||
import { findIndex } from '@/utils'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { set } from 'lodash-es'
|
||||
import { FormSchema } from '@/types/form'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
|
@ -208,7 +208,7 @@ const clear = () => {
|
||||
<Icon icon="ant-design:setting-outlined" color="#fff" />
|
||||
</div>
|
||||
|
||||
<ElDrawer v-model="drawer" direction="rtl" size="350px">
|
||||
<ElDrawer v-model="drawer" direction="rtl" size="350px" :z-index="4000">
|
||||
<template #header>
|
||||
<span class="text-16px font-700">{{ t('setting.projectSetting') }}</span>
|
||||
</template>
|
||||
|
@ -115,6 +115,13 @@ const dynamicRouterChange = (show: boolean) => {
|
||||
appStore.setDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 固定菜单
|
||||
const fixedMenu = ref(appStore.getFixedMenu)
|
||||
|
||||
const fixedMenuChange = (show: boolean) => {
|
||||
appStore.setFixedMenu(show)
|
||||
}
|
||||
|
||||
const layout = computed(() => appStore.getLayout)
|
||||
|
||||
watch(
|
||||
@ -198,5 +205,10 @@ watch(
|
||||
<span class="text-14px">{{ t('setting.dynamicRouter') }}</span>
|
||||
<ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.fixedMenu') }}</span>
|
||||
<ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -5,6 +5,7 @@ import { useAppStore } from '@/store/modules/app'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
@ -20,7 +21,7 @@ const appStore = useAppStore()
|
||||
|
||||
const sizeMap = computed(() => appStore.sizeMap)
|
||||
|
||||
const setCurrentSize = (size: ElememtPlusSize) => {
|
||||
const setCurrentSize = (size: ElementPlusSize) => {
|
||||
appStore.setCurrentSize(size)
|
||||
}
|
||||
</script>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<script lang="tsx">
|
||||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
import { computed, unref, defineComponent, watch, ref } from 'vue'
|
||||
import { computed, unref, defineComponent, watch, ref, onMounted } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { Icon } from '@/components/Icon'
|
||||
@ -28,6 +28,8 @@ export default defineComponent({
|
||||
|
||||
const collapse = computed(() => appStore.getCollapse)
|
||||
|
||||
const fixedMenu = computed(() => appStore.getFixedMenu)
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
const routers = computed(() => permissionStore.getRouters)
|
||||
@ -38,6 +40,27 @@ export default defineComponent({
|
||||
appStore.setCollapse(!unref(collapse))
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (unref(fixedMenu)) {
|
||||
const path = `/${unref(currentRoute).path.split('/')[1]}`
|
||||
const children = unref(tabRouters).find(
|
||||
(v) =>
|
||||
(v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)) &&
|
||||
v.path === path
|
||||
)?.children
|
||||
|
||||
tabActive.value = path
|
||||
if (children) {
|
||||
permissionStore.setMenuTabRouters(
|
||||
cloneDeep(children).map((v) => {
|
||||
v.path = pathResolve(unref(tabActive), v.path)
|
||||
return v
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(
|
||||
() => routers.value,
|
||||
(routers: AppRouteRecordRaw[]) => {
|
||||
@ -66,7 +89,7 @@ export default defineComponent({
|
||||
)
|
||||
|
||||
// 是否显示菜单
|
||||
const showMenu = ref(false)
|
||||
const showMenu = ref(unref(fixedMenu) ? true : false)
|
||||
|
||||
// tab高亮
|
||||
const tabActive = ref('')
|
||||
@ -77,9 +100,13 @@ export default defineComponent({
|
||||
window.open(item.path)
|
||||
return
|
||||
}
|
||||
const newPath = item.children ? item.path : item.path.split('/')[0]
|
||||
const oldPath = unref(tabActive)
|
||||
tabActive.value = item.children ? item.path : item.path.split('/')[0]
|
||||
if (item.children) {
|
||||
showMenu.value = !unref(showMenu)
|
||||
if (newPath === oldPath || !unref(showMenu)) {
|
||||
showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
|
||||
}
|
||||
if (unref(showMenu)) {
|
||||
permissionStore.setMenuTabRouters(
|
||||
cloneDeep(item.children).map((v) => {
|
||||
@ -96,7 +123,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
// 设置高亮
|
||||
const isActice = (currentPath: string) => {
|
||||
const isActive = (currentPath: string) => {
|
||||
const { path } = unref(currentRoute)
|
||||
if (tabPathMap[currentPath].includes(path)) {
|
||||
return true
|
||||
@ -105,7 +132,7 @@ export default defineComponent({
|
||||
}
|
||||
|
||||
const mouseleave = () => {
|
||||
if (!unref(showMenu)) return
|
||||
if (!unref(showMenu) || unref(fixedMenu)) return
|
||||
showMenu.value = false
|
||||
}
|
||||
|
||||
@ -114,7 +141,7 @@ export default defineComponent({
|
||||
id={`${variables.namespace}-menu`}
|
||||
class={[
|
||||
prefixCls,
|
||||
'relative bg-[var(--left-menu-bg-color)] top-1px z-999',
|
||||
'relative bg-[var(--left-menu-bg-color)] top-1px z-3000',
|
||||
{
|
||||
'w-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
||||
@ -140,7 +167,7 @@ export default defineComponent({
|
||||
`${prefixCls}__item`,
|
||||
'text-center text-12px relative py-12px cursor-pointer',
|
||||
{
|
||||
'is-active': isActice(v.path)
|
||||
'is-active': isActive(v.path)
|
||||
}
|
||||
]}
|
||||
onClick={() => {
|
||||
@ -174,8 +201,8 @@ export default defineComponent({
|
||||
{
|
||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu),
|
||||
'!w-0': !unref(showMenu)
|
||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||
}
|
||||
]}
|
||||
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
||||
|
@ -28,9 +28,9 @@ export const filterMenusPath = (
|
||||
let data: Nullable<AppRouteRecordRaw> = null
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
if (!meta.hidden || meta.canTo) {
|
||||
const allParentPaht = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
|
||||
const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
|
||||
|
||||
const fullPath = isUrl(v.path) ? v.path : allParentPaht.join('/')
|
||||
const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/')
|
||||
|
||||
data = cloneDeep(v)
|
||||
data.path = fullPath
|
||||
@ -42,8 +42,8 @@ export const filterMenusPath = (
|
||||
res.push(data)
|
||||
}
|
||||
|
||||
if (allParentPaht.length && Reflect.has(tabPathMap, allParentPaht[0])) {
|
||||
tabPathMap[allParentPaht[0]].push(fullPath)
|
||||
if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) {
|
||||
tabPathMap[allParentPath[0]].push(fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Table from './src/Table.vue'
|
||||
import { ElTable } from 'element-plus'
|
||||
import { TableSetPropsType } from '@/types/table'
|
||||
|
||||
export interface TableExpose {
|
||||
setProps: (props: Recordable) => void
|
||||
|
@ -6,6 +6,7 @@ import { setIndex } from './helper'
|
||||
import { getSlot } from '@/utils/tsxHelper'
|
||||
import type { TableProps } from './types'
|
||||
import { set } from 'lodash-es'
|
||||
import { TableColumn, TableSlotDefault, Pagination, TableSetPropsType } from '../../../types/table'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Table',
|
||||
@ -23,6 +24,8 @@ export default defineComponent({
|
||||
},
|
||||
// 展开行
|
||||
expand: propTypes.bool.def(false),
|
||||
// 是否为斑马纹 table
|
||||
stripe: propTypes.bool.def(true),
|
||||
// 是否展示分页
|
||||
pagination: {
|
||||
type: Object as PropType<Pagination>,
|
||||
@ -49,6 +52,7 @@ export default defineComponent({
|
||||
},
|
||||
emits: ['update:limit', 'update:page', 'register'],
|
||||
setup(props, { attrs, slots, emit, expose }) {
|
||||
console.log('attrs', attrs)
|
||||
const elTableRef = ref<ComponentRef<typeof ElTable>>()
|
||||
|
||||
// 注册
|
||||
@ -230,6 +234,7 @@ export default defineComponent({
|
||||
} else {
|
||||
const props = { ...v }
|
||||
if (props.children) delete props.children
|
||||
if (props.show === false) return
|
||||
return (
|
||||
<ElTableColumn
|
||||
showOverflowTooltip={showOverflowTooltip}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Pagination, TableColumn } from '@/types/table'
|
||||
|
||||
export type TableProps = {
|
||||
limit?: number
|
||||
page?: number
|
||||
pageSize?: number
|
||||
currentPage?: number
|
||||
// 是否多选
|
||||
selection?: boolean
|
||||
// 是否所有的超出隐藏,优先级低于schema中的showOverflowTooltip,
|
||||
|
3
kinit-admin/src/components/Text/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import Text from './src/Text.vue'
|
||||
|
||||
export { Text }
|
14
kinit-admin/src/components/Text/src/Text.vue
Normal file
@ -0,0 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: propTypes.string.def('')
|
||||
})
|
||||
|
||||
const value = ref(props.modelValue)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<span v-html="value"></span>
|
||||
</template>
|
@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox } from 'element-plus'
|
||||
import { ElDropdown, ElDropdownMenu, ElDropdownItem, ElMessageBox, ElButton } from 'element-plus'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
@ -21,17 +21,13 @@ const loginOut = () => {
|
||||
cancelButtonText: t('common.cancel'),
|
||||
type: 'warning'
|
||||
})
|
||||
.then(async () => {
|
||||
.then(() => {
|
||||
authStore.logout()
|
||||
replace('/login')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
const toDocument = () => {
|
||||
window.open('https://element-plus-admin-doc.cn/')
|
||||
}
|
||||
|
||||
const toHome = () => {
|
||||
push('/system/home')
|
||||
}
|
||||
@ -54,13 +50,10 @@ const user = authStore.getUser
|
||||
<template #dropdown>
|
||||
<ElDropdownMenu>
|
||||
<ElDropdownItem>
|
||||
<div @click="toHome">个人主页</div>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem>
|
||||
<div @click="toDocument">前端项目文档</div>
|
||||
<ElButton @click="toHome" link>个人主页</ElButton>
|
||||
</ElDropdownItem>
|
||||
<ElDropdownItem divided>
|
||||
<div @click="loginOut">{{ t('common.loginOut') }}</div>
|
||||
<ElButton @click="loginOut" link>退出系统</ElButton>
|
||||
</ElDropdownItem>
|
||||
</ElDropdownMenu>
|
||||
</template>
|
||||
|
@ -1,108 +0,0 @@
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
|
||||
|
||||
export type ThemeTypes = {
|
||||
elColorPrimary?: string
|
||||
leftMenuBorderColor?: string
|
||||
leftMenuBgColor?: string
|
||||
leftMenuBgLightColor?: string
|
||||
leftMenuBgActiveColor?: string
|
||||
leftMenuCollapseBgActiveColor?: string
|
||||
leftMenuTextColor?: string
|
||||
leftMenuTextActiveColor?: string
|
||||
logoTitleTextColor?: string
|
||||
logoBorderColor?: string
|
||||
topHeaderBgColor?: string
|
||||
topHeaderTextColor?: string
|
||||
topHeaderHoverColor?: string
|
||||
topToolBorderColor?: string
|
||||
}
|
||||
export interface AppState {
|
||||
breadcrumb: boolean
|
||||
breadcrumbIcon: boolean
|
||||
collapse: boolean
|
||||
uniqueOpened: boolean
|
||||
hamburger: boolean
|
||||
screenfull: boolean
|
||||
size: boolean
|
||||
locale: boolean
|
||||
tagsView: boolean
|
||||
tagsViewIcon: boolean
|
||||
logo: boolean
|
||||
fixedHeader: boolean
|
||||
greyMode: boolean
|
||||
dynamicRouter: boolean
|
||||
pageLoading: boolean
|
||||
layout: LayoutType
|
||||
title: string
|
||||
userInfo: string
|
||||
token: string
|
||||
isDark: boolean
|
||||
currentSize: ElememtPlusSize
|
||||
sizeMap: ElememtPlusSize[]
|
||||
mobile: boolean
|
||||
footer: boolean
|
||||
theme: ThemeTypes
|
||||
}
|
||||
|
||||
export const appModules: AppState = {
|
||||
userInfo: 'UserInfo', // 登录信息保存的存储字段名称-建议每个项目换一个字段,避免与其他项目冲突,方便后面直接使用这个名称来获取对应的登录用户信息
|
||||
token: 'Token', // 存储Token字段
|
||||
sizeMap: ['default', 'large', 'small'],
|
||||
mobile: false, // 是否是移动端
|
||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||
pageLoading: true, // 路由跳转loading
|
||||
|
||||
breadcrumb: true, // 面包屑
|
||||
breadcrumbIcon: true, // 面包屑图标
|
||||
collapse: false, // 折叠菜单
|
||||
uniqueOpened: true, // 是否只保持一个子菜单的展开
|
||||
hamburger: true, // 折叠图标
|
||||
screenfull: true, // 全屏图标
|
||||
size: true, // 尺寸图标
|
||||
locale: true, // 多语言图标
|
||||
tagsView: true, // 标签页
|
||||
tagsViewIcon: true, // 是否显示标签图标
|
||||
logo: true, // logo
|
||||
fixedHeader: true, // 固定toolheader
|
||||
footer: true, // 显示页脚
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
dynamicRouter: wsCache.get('dynamicRouter') || true, // 是否动态路由
|
||||
|
||||
layout: wsCache.get('layout') || 'classic', // layout布局
|
||||
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
|
||||
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
||||
theme: wsCache.get('theme') || {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
// 左侧菜单边框颜色
|
||||
leftMenuBorderColor: 'inherit',
|
||||
// 左侧菜单背景颜色
|
||||
leftMenuBgColor: '#001529',
|
||||
// 左侧菜单浅色背景颜色
|
||||
leftMenuBgLightColor: '#0f2438',
|
||||
// 左侧菜单选中背景颜色
|
||||
leftMenuBgActiveColor: 'var(--el-color-primary)',
|
||||
// 左侧菜单收起选中背景颜色
|
||||
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
|
||||
// 左侧菜单字体颜色
|
||||
leftMenuTextColor: '#bfcbd9',
|
||||
// 左侧菜单选中字体颜色
|
||||
leftMenuTextActiveColor: '#fff',
|
||||
// logo字体颜色
|
||||
logoTitleTextColor: '#fff',
|
||||
// logo边框颜色
|
||||
logoBorderColor: 'inherit',
|
||||
// 头部背景颜色
|
||||
topHeaderBgColor: '#fff',
|
||||
// 头部字体颜色
|
||||
topHeaderTextColor: 'inherit',
|
||||
// 头部悬停颜色
|
||||
topHeaderHoverColor: '#f6f6f6',
|
||||
// 头部边框颜色
|
||||
topToolBorderColor: '#eee'
|
||||
}
|
||||
}
|
@ -1,31 +1,8 @@
|
||||
const config: {
|
||||
base_url: {
|
||||
base: string
|
||||
dev: string
|
||||
pro: string
|
||||
test: string
|
||||
}
|
||||
result_code: number | string
|
||||
default_headers: AxiosHeaders
|
||||
request_timeout: number
|
||||
} = {
|
||||
/**
|
||||
* api请求基础路径
|
||||
*/
|
||||
base_url: {
|
||||
// 开发环境接口前缀
|
||||
base: '/api',
|
||||
|
||||
// 打包开发环境接口前缀
|
||||
dev: '/api',
|
||||
|
||||
// 打包生产环境接口前缀
|
||||
pro: '/api',
|
||||
|
||||
// 打包测试环境接口前缀
|
||||
test: '/api'
|
||||
},
|
||||
|
||||
/**
|
||||
* 接口成功返回状态码
|
||||
*/
|
||||
|
@ -8,17 +8,15 @@ import { config } from './config'
|
||||
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
const { result_code, base_url } = config
|
||||
const { result_code, request_timeout } = config
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH]
|
||||
|
||||
// 创建axios实例
|
||||
const service: AxiosInstance = axios.create({
|
||||
baseURL: PATH_URL, // api 的 base_url
|
||||
timeout: config.request_timeout, // 请求超时时间
|
||||
baseURL: '/api', // api 的 base_url
|
||||
timeout: request_timeout, // 请求超时时间
|
||||
headers: {} // 请求头信息
|
||||
})
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import en from 'element-plus/es/locale/lang/en'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export const elLocaleMap = {
|
||||
'zh-CN': zhCn,
|
||||
en: en
|
||||
}
|
||||
export interface LocaleState {
|
||||
currentLocale: LocaleDropdownType
|
||||
localeMap: LocaleDropdownType[]
|
||||
}
|
||||
|
||||
export const localeModules: LocaleState = {
|
||||
currentLocale: {
|
||||
lang: wsCache.get('lang') || 'zh-CN',
|
||||
elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
|
||||
},
|
||||
// 多语言
|
||||
localeMap: [
|
||||
{
|
||||
lang: 'zh-CN',
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
lang: 'en',
|
||||
name: 'English'
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { ConfigGlobalTypes } from '@/types/configGlobal'
|
||||
import { inject } from 'vue'
|
||||
|
||||
export const useConfigGlobal = () => {
|
||||
|
@ -4,6 +4,9 @@ import { findIndex } from '@/utils'
|
||||
import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { AxiosPromise } from 'axios'
|
||||
import { FormSchema } from '@/types/form'
|
||||
import { TableColumn } from '@/types/table'
|
||||
import { DescriptionsSchema } from '@/types/descriptions'
|
||||
|
||||
export type CrudSchema = Omit<TableColumn, 'children'> & {
|
||||
search?: CrudSearchParams
|
||||
|
@ -2,6 +2,7 @@ import type { Form, FormExpose } from '@/components/Form'
|
||||
import type { ElForm } from 'element-plus'
|
||||
import { ref, unref, nextTick } from 'vue'
|
||||
import type { FormProps } from '@/components/Form/src/types'
|
||||
import { FormSchema, FormSetPropsType } from '@/types/form'
|
||||
|
||||
export const useForm = (props?: FormProps) => {
|
||||
// From实例
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { h } from 'vue'
|
||||
import type { VNode } from 'vue'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { IconTypes } from '@/types/icon'
|
||||
|
||||
export const useIcon = (props: IconTypes): VNode => {
|
||||
return h(Icon, props)
|
||||
|
@ -4,6 +4,8 @@ import { ref, reactive, watch, computed, unref, nextTick } from 'vue'
|
||||
import { get } from 'lodash-es'
|
||||
import type { TableProps } from '@/components/Table/src/types'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { TableSetPropsType } from '@/types/table'
|
||||
import { columns } from 'element-plus/es/components/table-v2/src/common'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -15,6 +17,7 @@ interface TableResponse<T = any> {
|
||||
interface UseTableConfig<T = any> {
|
||||
getListApi: (option: any) => Promise<IResponse<TableResponse<T>>>
|
||||
delListApi?: (option: any) => Promise<IResponse>
|
||||
exportQueryListApi?: (params: any, data: any) => Promise<IResponse>
|
||||
// 返回数据格式配置
|
||||
response: {
|
||||
data: string
|
||||
@ -136,6 +139,7 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
|
||||
},
|
||||
// 与Search组件结合
|
||||
setSearchParams: (data: Recordable) => {
|
||||
tableObject.page = 1
|
||||
tableObject.params = Object.assign(tableObject.params, {
|
||||
limit: tableObject.limit,
|
||||
page: tableObject.page,
|
||||
@ -168,6 +172,31 @@ export const useTable = <T = any>(config?: UseTableConfig<T>) => {
|
||||
} else {
|
||||
await delData(ids)
|
||||
}
|
||||
},
|
||||
// 导出筛选列表
|
||||
exportQueryList: async () => {
|
||||
if (config?.exportQueryListApi) {
|
||||
const header = config?.props?.columns
|
||||
?.filter((item) => item.show === true)
|
||||
.map((item) => {
|
||||
return { field: item.field, label: item.label }
|
||||
})
|
||||
tableObject.loading = true
|
||||
const res = await config?.exportQueryListApi(unref(paramsObj), header).finally(() => {
|
||||
tableObject.loading = false
|
||||
})
|
||||
if (res) {
|
||||
const a = document.createElement('a')
|
||||
a.style.display = 'none'
|
||||
a.href = res.data.url
|
||||
a.target = '_blank'
|
||||
a.download = res.data.filename
|
||||
const event = new MouseEvent('click')
|
||||
a.dispatchEvent(event)
|
||||
}
|
||||
} else {
|
||||
ElMessage.error('删除失败,请配置删除接口!')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,25 +48,25 @@ export default defineComponent({
|
||||
{layout.value !== 'top' ? (
|
||||
<div class="h-full flex items-center">
|
||||
{hamburger.value && layout.value !== 'cutMenu' ? (
|
||||
<Collapse class="hover-tigger" color="var(--top-header-text-color)"></Collapse>
|
||||
<Collapse class="hover-trigger" color="var(--top-header-text-color)"></Collapse>
|
||||
) : undefined}
|
||||
{breadcrumb.value ? <Breadcrumb class="<md:hidden"></Breadcrumb> : undefined}
|
||||
</div>
|
||||
) : undefined}
|
||||
<div class="h-full flex items-center">
|
||||
{screenfull.value ? (
|
||||
<Screenfull class="hover-tigger" color="var(--top-header-text-color)"></Screenfull>
|
||||
<Screenfull class="hover-trigger" color="var(--top-header-text-color)"></Screenfull>
|
||||
) : undefined}
|
||||
{size.value ? (
|
||||
<SizeDropdown class="hover-tigger" color="var(--top-header-text-color)"></SizeDropdown>
|
||||
<SizeDropdown class="hover-trigger" color="var(--top-header-text-color)"></SizeDropdown>
|
||||
) : undefined}
|
||||
{locale.value ? (
|
||||
<LocaleDropdown
|
||||
class="hover-tigger"
|
||||
class="hover-trigger"
|
||||
color="var(--top-header-text-color)"
|
||||
></LocaleDropdown>
|
||||
) : undefined}
|
||||
<UserInfo class="hover-tigger"></UserInfo>
|
||||
<UserInfo class="hover-trigger"></UserInfo>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -32,6 +32,9 @@ const fixedHeader = computed(() => appStore.getFixedHeader)
|
||||
// 是否是移动端
|
||||
const mobile = computed(() => appStore.getMobile)
|
||||
|
||||
// 固定菜单
|
||||
const fixedMenu = computed(() => appStore.getFixedMenu)
|
||||
|
||||
export const useRenderLayout = () => {
|
||||
const renderClassic = () => {
|
||||
return (
|
||||
@ -107,7 +110,7 @@ export const useRenderLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex items-center bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)] dark:border-[var(--el-border-color)]">
|
||||
{logo.value ? <Logo class="hover-tigger !pr-15px"></Logo> : undefined}
|
||||
{logo.value ? <Logo class="hover-trigger !pr-15px"></Logo> : undefined}
|
||||
|
||||
<ToolHeader class="flex-1"></ToolHeader>
|
||||
</div>
|
||||
@ -164,7 +167,7 @@ export const useRenderLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex items-center justify-between bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)] dark:border-[var(--el-border-color)]">
|
||||
{logo.value ? <Logo class="hover-tigger"></Logo> : undefined}
|
||||
{logo.value ? <Logo class="hover-trigger"></Logo> : undefined}
|
||||
<Menu class="flex-1 px-10px h-[var(--top-tool-height)]"></Menu>
|
||||
<ToolHeader></ToolHeader>
|
||||
</div>
|
||||
@ -201,7 +204,7 @@ export const useRenderLayout = () => {
|
||||
return (
|
||||
<>
|
||||
<div class="flex items-center bg-[var(--top-header-bg-color)] border-bottom-1 border-solid border-[var(--top-tool-border-color)] dark:border-[var(--el-border-color)]">
|
||||
{logo.value ? <Logo class="hover-tigger !pr-15px"></Logo> : undefined}
|
||||
{logo.value ? <Logo class="hover-trigger !pr-15px"></Logo> : undefined}
|
||||
|
||||
<ToolHeader class="flex-1"></ToolHeader>
|
||||
</div>
|
||||
@ -213,9 +216,13 @@ export const useRenderLayout = () => {
|
||||
'h-[100%]',
|
||||
{
|
||||
'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]':
|
||||
collapse.value,
|
||||
collapse.value && !fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)]':
|
||||
!collapse.value
|
||||
!collapse.value && !fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':
|
||||
collapse.value && fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':
|
||||
!collapse.value && fixedMenu.value
|
||||
}
|
||||
]}
|
||||
style="transition: all var(--transition-time-02);"
|
||||
@ -239,7 +246,13 @@ export const useRenderLayout = () => {
|
||||
'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':
|
||||
collapse.value && fixedHeader.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value
|
||||
!collapse.value && fixedHeader.value,
|
||||
'!fixed top-0 left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':
|
||||
fixedHeader.value && fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||
collapse.value && fixedHeader.value && fixedMenu.value,
|
||||
'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':
|
||||
!collapse.value && fixedHeader.value && fixedMenu.value
|
||||
}
|
||||
]}
|
||||
style="transition: width var(--transition-time-02), left var(--transition-time-02);"
|
||||
|
@ -76,7 +76,8 @@ export default {
|
||||
uniqueOpened: 'Unique opened',
|
||||
tagsViewIcon: 'Tags view icon',
|
||||
dynamicRouter: 'Dynamic router',
|
||||
reExperienced: 'Please exit the login experience again'
|
||||
reExperienced: 'Please exit the login experience again',
|
||||
fixedMenu: 'fixed menu'
|
||||
},
|
||||
size: {
|
||||
default: 'Default',
|
||||
|
@ -76,7 +76,8 @@ export default {
|
||||
uniqueOpened: '菜单手风琴',
|
||||
tagsViewIcon: '标签页图标',
|
||||
dynamicRouter: '动态路由',
|
||||
reExperienced: '请重新退出登录体验'
|
||||
reExperienced: '请重新退出登录体验',
|
||||
fixedMenu: '固定菜单'
|
||||
},
|
||||
size: {
|
||||
default: '默认',
|
||||
@ -186,7 +187,7 @@ export default {
|
||||
happyDay: '祝你开心每一天!',
|
||||
toady: '今日晴',
|
||||
project: '项目数',
|
||||
access: '项目访问',
|
||||
access: '系统登录',
|
||||
toDo: '待办',
|
||||
introduction: '一个正经的简介',
|
||||
more: '更多',
|
||||
|
@ -28,6 +28,8 @@ router.beforeEach(async (to, from, next) => {
|
||||
if (wsCache.get(appStore.getUserInfo)) {
|
||||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else if (to.path === '/reset/password') {
|
||||
next()
|
||||
} else {
|
||||
if (!authStore.getIsUser) {
|
||||
await authStore.getUserInfo()
|
||||
@ -37,16 +39,6 @@ router.beforeEach(async (to, from, next) => {
|
||||
return
|
||||
}
|
||||
|
||||
// 取消一次性获取所有字典,改为按需获取
|
||||
// if (!dictStore.getIsSetDict) {
|
||||
// 获取所有字典
|
||||
// const res = await getDictApi()
|
||||
// if (res) {
|
||||
// dictStore.setDictObj(res.data)
|
||||
// dictStore.setIsSetDict(true)
|
||||
// }
|
||||
// }
|
||||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const res = await getRoleMenusApi()
|
||||
const { wsCache } = useCache()
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router'
|
||||
import type { RouteRecordRaw } from 'vue-router'
|
||||
import type { App } from 'vue'
|
||||
import { Layout } from '@/utils/routerHelper'
|
||||
@ -13,7 +13,9 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||
redirect: '/dashboard',
|
||||
name: 'Root',
|
||||
meta: {
|
||||
hidden: true
|
||||
hidden: true,
|
||||
title: '首页',
|
||||
noTagsView: true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -29,7 +31,7 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||
{
|
||||
path: '/reset/password',
|
||||
component: () => import('@/views/Reset/Reset.vue'),
|
||||
name: 'Reset',
|
||||
name: 'ResetPassword',
|
||||
meta: {
|
||||
hidden: true,
|
||||
title: '重置密码',
|
||||
@ -48,80 +50,20 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
|
||||
}
|
||||
]
|
||||
|
||||
export const asyncRouterMap: AppRouteRecordRaw[] = [
|
||||
// {
|
||||
// path: '/dashboard',
|
||||
// component: Layout,
|
||||
// redirect: '/dashboard/analysis',
|
||||
// name: 'Dashboard',
|
||||
// meta: {
|
||||
// title: t('router.dashboard'),
|
||||
// icon: 'ant-design:dashboard-filled',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'analysis',
|
||||
// component: () => import('@/views/Dashboard/Analysis.vue'),
|
||||
// name: 'Analysis',
|
||||
// meta: {
|
||||
// title: t('router.analysis'),
|
||||
// noCache: true,
|
||||
// affix: true
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'workplace',
|
||||
// component: () => import('@/views/Dashboard/Workplace.vue'),
|
||||
// name: 'Workplace',
|
||||
// meta: {
|
||||
// title: t('router.workplace'),
|
||||
// noCache: true
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// {
|
||||
// path: '/authorization',
|
||||
// component: Layout,
|
||||
// redirect: '/authorization/user',
|
||||
// name: 'Authorization',
|
||||
// meta: {
|
||||
// title: t('router.authorization'),
|
||||
// icon: 'eos-icons:role-binding',
|
||||
// alwaysShow: true
|
||||
// },
|
||||
// children: [
|
||||
// {
|
||||
// path: 'user',
|
||||
// component: () => import('@/views/Authorization/User.vue'),
|
||||
// name: 'User',
|
||||
// meta: {
|
||||
// title: t('router.user')
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'role',
|
||||
// component: () => import('@/views/Authorization/Role.vue'),
|
||||
// name: 'Role',
|
||||
// meta: {
|
||||
// title: t('router.role')
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
]
|
||||
export const asyncRouterMap: AppRouteRecordRaw[] = []
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(), // HTML5 模式,https://router.vuejs.org/zh/guide/essentials/history-mode.html#hash-%E6%A8%A1%E5%BC%8F
|
||||
// history: createWebHashHistory(), // Hash 模式,https://router.vuejs.org/zh/guide/essentials/history-mode.html#hash-%E6%A8%A1%E5%BC%8F
|
||||
strict: true,
|
||||
routes: constantRouterMap as RouteRecordRaw[],
|
||||
scrollBehavior: () => ({ left: 0, top: 0 })
|
||||
})
|
||||
|
||||
export const resetRouter = (): void => {
|
||||
const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root', 'Reset']
|
||||
const resetWhiteNameList = ['Login', 'NoFind', 'Root', 'ResetPassword']
|
||||
router.getRoutes().forEach((route) => {
|
||||
// 切记 name 不能重复
|
||||
const { name } = route
|
||||
if (name && !resetWhiteNameList.includes(name as string)) {
|
||||
router.hasRoute(name) && router.removeRoute(name)
|
||||
|
@ -1,19 +1,110 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { appModules } from '@/config/app'
|
||||
import type { AppState, LayoutType, ThemeTypes } from '@/config/app'
|
||||
import { setCssVar, humpToUnderline } from '@/utils'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElementPlusSize } from '@/types/elementPlus'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { LayoutType } from '@/types/layout'
|
||||
import { ThemeTypes } from '@/types/theme'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export const useAppStore = defineStore({
|
||||
id: 'app',
|
||||
state: (): AppState => appModules,
|
||||
persist: {
|
||||
// 开启持久化存储
|
||||
enabled: true
|
||||
interface AppState {
|
||||
breadcrumb: boolean
|
||||
breadcrumbIcon: boolean
|
||||
collapse: boolean
|
||||
uniqueOpened: boolean
|
||||
hamburger: boolean
|
||||
screenfull: boolean
|
||||
size: boolean
|
||||
locale: boolean
|
||||
tagsView: boolean
|
||||
tagsViewIcon: boolean
|
||||
logo: boolean
|
||||
fixedHeader: boolean
|
||||
greyMode: boolean
|
||||
dynamicRouter: boolean
|
||||
pageLoading: boolean
|
||||
layout: LayoutType
|
||||
title: string
|
||||
userInfo: string
|
||||
isDark: boolean
|
||||
currentSize: ElementPlusSize
|
||||
sizeMap: ElementPlusSize[]
|
||||
mobile: boolean
|
||||
footer: boolean
|
||||
theme: ThemeTypes
|
||||
fixedMenu: boolean
|
||||
logoImage: string
|
||||
footerContent: string
|
||||
icpNumber: string
|
||||
token: string
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore('app', {
|
||||
state: (): AppState => {
|
||||
return {
|
||||
userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
|
||||
token: 'Token', // 存储Token字段
|
||||
sizeMap: ['default', 'large', 'small'],
|
||||
mobile: false, // 是否是移动端
|
||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||
pageLoading: false, // 路由跳转loading
|
||||
|
||||
breadcrumb: true, // 面包屑
|
||||
breadcrumbIcon: true, // 面包屑图标
|
||||
collapse: false, // 折叠菜单
|
||||
uniqueOpened: true, // 是否只保持一个子菜单的展开
|
||||
hamburger: true, // 折叠图标
|
||||
screenfull: true, // 全屏图标
|
||||
size: true, // 尺寸图标
|
||||
locale: true, // 多语言图标
|
||||
tagsView: true, // 标签页
|
||||
tagsViewIcon: true, // 是否显示标签图标
|
||||
logo: true, // 是否开启logo显示
|
||||
logoImage: '', // logo图片
|
||||
fixedHeader: true, // 固定toolheader
|
||||
footer: true, // 显示页脚
|
||||
footerContent: '', // 页脚内容
|
||||
icpNumber: '', // 备案号
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
dynamicRouter: wsCache.get('dynamicRouter') || true, // 是否动态路由
|
||||
fixedMenu: wsCache.get('fixedMenu') || true, // 是否固定菜单
|
||||
|
||||
layout: wsCache.get('layout') || 'classic', // layout布局
|
||||
isDark: wsCache.get('isDark') || false, // 是否是暗黑模式
|
||||
currentSize: wsCache.get('default') || 'default', // 组件尺寸
|
||||
theme: wsCache.get('theme') || {
|
||||
// 主题色
|
||||
elColorPrimary: '#409eff',
|
||||
// 左侧菜单边框颜色
|
||||
leftMenuBorderColor: 'inherit',
|
||||
// 左侧菜单背景颜色
|
||||
leftMenuBgColor: '#001529',
|
||||
// 左侧菜单浅色背景颜色
|
||||
leftMenuBgLightColor: '#0f2438',
|
||||
// 左侧菜单选中背景颜色
|
||||
leftMenuBgActiveColor: 'var(--el-color-primary)',
|
||||
// 左侧菜单收起选中背景颜色
|
||||
leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',
|
||||
// 左侧菜单字体颜色
|
||||
leftMenuTextColor: '#bfcbd9',
|
||||
// 左侧菜单选中字体颜色
|
||||
leftMenuTextActiveColor: '#fff',
|
||||
// logo字体颜色
|
||||
logoTitleTextColor: '#fff',
|
||||
// logo边框颜色
|
||||
logoBorderColor: 'inherit',
|
||||
// 头部背景颜色
|
||||
topHeaderBgColor: '#fff',
|
||||
// 头部字体颜色
|
||||
topHeaderTextColor: 'inherit',
|
||||
// 头部悬停颜色
|
||||
topHeaderHoverColor: '#f6f6f6',
|
||||
// 头部边框颜色
|
||||
topToolBorderColor: '#eee'
|
||||
}
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getBreadcrumb(): boolean {
|
||||
@ -49,6 +140,9 @@ export const useAppStore = defineStore({
|
||||
getLogo(): boolean {
|
||||
return this.logo
|
||||
},
|
||||
getLogoImage(): string {
|
||||
return this.logoImage
|
||||
},
|
||||
getFixedHeader(): boolean {
|
||||
return this.fixedHeader
|
||||
},
|
||||
@ -58,6 +152,9 @@ export const useAppStore = defineStore({
|
||||
getDynamicRouter(): boolean {
|
||||
return this.dynamicRouter
|
||||
},
|
||||
getFixedMenu(): boolean {
|
||||
return this.fixedMenu
|
||||
},
|
||||
getPageLoading(): boolean {
|
||||
return this.pageLoading
|
||||
},
|
||||
@ -76,10 +173,10 @@ export const useAppStore = defineStore({
|
||||
getIsDark(): boolean {
|
||||
return this.isDark
|
||||
},
|
||||
getCurrentSize(): ElememtPlusSize {
|
||||
getCurrentSize(): ElementPlusSize {
|
||||
return this.currentSize
|
||||
},
|
||||
getSizeMap(): ElememtPlusSize[] {
|
||||
getSizeMap(): ElementPlusSize[] {
|
||||
return this.sizeMap
|
||||
},
|
||||
getMobile(): boolean {
|
||||
@ -90,6 +187,12 @@ export const useAppStore = defineStore({
|
||||
},
|
||||
getFooter(): boolean {
|
||||
return this.footer
|
||||
},
|
||||
getFooterContent(): string {
|
||||
return this.footerContent
|
||||
},
|
||||
getIcpNumber(): string {
|
||||
return this.icpNumber
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
@ -126,6 +229,9 @@ export const useAppStore = defineStore({
|
||||
setLogo(logo: boolean) {
|
||||
this.logo = logo
|
||||
},
|
||||
setLogoImage(logoImage: string) {
|
||||
this.logoImage = logoImage
|
||||
},
|
||||
setFixedHeader(fixedHeader: boolean) {
|
||||
this.fixedHeader = fixedHeader
|
||||
},
|
||||
@ -136,6 +242,10 @@ export const useAppStore = defineStore({
|
||||
wsCache.set('dynamicRouter', dynamicRouter)
|
||||
this.dynamicRouter = dynamicRouter
|
||||
},
|
||||
setFixedMenu(fixedMenu: boolean) {
|
||||
wsCache.set('fixedMenu', fixedMenu)
|
||||
this.fixedMenu = fixedMenu
|
||||
},
|
||||
setPageLoading(pageLoading: boolean) {
|
||||
this.pageLoading = pageLoading
|
||||
},
|
||||
@ -161,7 +271,7 @@ export const useAppStore = defineStore({
|
||||
}
|
||||
wsCache.set('isDark', this.isDark)
|
||||
},
|
||||
setCurrentSize(currentSize: ElememtPlusSize) {
|
||||
setCurrentSize(currentSize: ElementPlusSize) {
|
||||
this.currentSize = currentSize
|
||||
wsCache.set('currentSize', this.currentSize)
|
||||
},
|
||||
@ -179,6 +289,12 @@ export const useAppStore = defineStore({
|
||||
},
|
||||
setFooter(footer: boolean) {
|
||||
this.footer = footer
|
||||
},
|
||||
setFooterContent(footerContent: string) {
|
||||
this.footerContent = footerContent
|
||||
},
|
||||
setIcpNumber(icpNumber: string) {
|
||||
this.icpNumber = icpNumber
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -28,8 +28,7 @@ export interface AuthState {
|
||||
isUser: boolean
|
||||
}
|
||||
|
||||
export const useAuthStore = defineStore({
|
||||
id: 'auth',
|
||||
export const useAuthStore = defineStore('auth', {
|
||||
state: (): AuthState => {
|
||||
return {
|
||||
user: {},
|
||||
@ -64,8 +63,8 @@ export const useAuthStore = defineStore({
|
||||
wsCache.clear()
|
||||
this.user = {}
|
||||
this.isUser = false
|
||||
resetRouter()
|
||||
tagsViewStore.delAllViews()
|
||||
resetRouter()
|
||||
},
|
||||
updateUser(data: UserState) {
|
||||
this.user.gender = data.gender
|
||||
|
@ -6,8 +6,7 @@ export interface DictState {
|
||||
dictObj: Recordable
|
||||
}
|
||||
|
||||
export const useDictStore = defineStore({
|
||||
id: 'dict',
|
||||
export const useDictStore = defineStore('dict', {
|
||||
state: (): DictState => ({
|
||||
dictObj: {}
|
||||
}),
|
||||
|
@ -1,17 +1,40 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import en from 'element-plus/es/locale/lang/en'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { localeModules, elLocaleMap } from '@/config/locale'
|
||||
import type { LocaleState } from '@/config/locale'
|
||||
import { LocaleDropdownType } from '@/types/localeDropdown'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export const useLocaleStore = defineStore({
|
||||
id: 'locales',
|
||||
state: (): LocaleState => localeModules,
|
||||
persist: {
|
||||
// 开启持久化存储
|
||||
enabled: true
|
||||
const elLocaleMap = {
|
||||
'zh-CN': zhCn,
|
||||
en: en
|
||||
}
|
||||
interface LocaleState {
|
||||
currentLocale: LocaleDropdownType
|
||||
localeMap: LocaleDropdownType[]
|
||||
}
|
||||
|
||||
export const useLocaleStore = defineStore('locales', {
|
||||
state: (): LocaleState => {
|
||||
return {
|
||||
currentLocale: {
|
||||
lang: wsCache.get('lang') || 'zh-CN',
|
||||
elLocale: elLocaleMap[wsCache.get('lang') || 'zh-CN']
|
||||
},
|
||||
// 多语言
|
||||
localeMap: [
|
||||
{
|
||||
lang: 'zh-CN',
|
||||
name: '简体中文'
|
||||
},
|
||||
{
|
||||
lang: 'en',
|
||||
name: 'English'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getCurrentLocale(): LocaleDropdownType {
|
||||
|
@ -11,8 +11,7 @@ export interface PermissionState {
|
||||
menuTabRouters: AppRouteRecordRaw[]
|
||||
}
|
||||
|
||||
export const usePermissionStore = defineStore({
|
||||
id: 'permission',
|
||||
export const usePermissionStore = defineStore('permission', {
|
||||
state: (): PermissionState => ({
|
||||
routers: [],
|
||||
addRouters: [],
|
||||
|
@ -10,8 +10,7 @@ export interface TagsViewState {
|
||||
cachedViews: Set<string>
|
||||
}
|
||||
|
||||
export const useTagsViewStore = defineStore({
|
||||
id: 'tagsView',
|
||||
export const useTagsViewStore = defineStore('tagsView', {
|
||||
state: (): TagsViewState => ({
|
||||
visitedViews: [],
|
||||
cachedViews: new Set()
|
||||
|
54
kinit-admin/src/types/components.d.ts
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
export type ComponentName =
|
||||
| 'Radio'
|
||||
| 'RadioButton'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxButton'
|
||||
| 'Input'
|
||||
| 'Autocomplete'
|
||||
| 'InputNumber'
|
||||
| 'Select'
|
||||
| 'Cascader'
|
||||
| 'Switch'
|
||||
| 'Slider'
|
||||
| 'TimePicker'
|
||||
| 'DatePicker'
|
||||
| 'Rate'
|
||||
| 'ColorPicker'
|
||||
| 'Transfer'
|
||||
| 'Divider'
|
||||
| 'TimeSelect'
|
||||
| 'SelectV2'
|
||||
| 'InputPassword'
|
||||
| 'TreeSelect'
|
||||
| 'Tree'
|
||||
| 'Text'
|
||||
|
||||
export type ColProps = {
|
||||
span?: number
|
||||
xs?: number
|
||||
sm?: number
|
||||
md?: number
|
||||
lg?: number
|
||||
xl?: number
|
||||
tag?: string
|
||||
}
|
||||
|
||||
export type ComponentOptions = {
|
||||
label?: string
|
||||
value?: FormValueType
|
||||
disabled?: boolean
|
||||
key?: string | number
|
||||
children?: ComponentOptions[]
|
||||
options?: ComponentOptions[]
|
||||
} & Recordable
|
||||
|
||||
export type ComponentOptionsAlias = {
|
||||
labelField?: string
|
||||
valueField?: string
|
||||
}
|
||||
|
||||
export type ComponentProps = {
|
||||
optionsAlias?: ComponentOptionsAlias
|
||||
options?: ComponentOptions[]
|
||||
optionsSlot?: boolean
|
||||
} & Recordable
|
4
kinit-admin/src/types/configGlobal.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
import { ElementPlusSize } from './elementPlus'
|
||||
export interface ConfigGlobalTypes {
|
||||
size?: ElementPlusSize
|
||||
}
|
7
kinit-admin/src/types/contextMenu.d.ts
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export type contextMenuSchema = {
|
||||
disabled?: boolean
|
||||
divided?: boolean
|
||||
icon?: string
|
||||
label: string
|
||||
command?: (item: contextMenuSchema) => void
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
declare interface DescriptionsSchema {
|
||||
export interface DescriptionsSchema {
|
||||
span?: number // 占多少分
|
||||
field: string // 字段名
|
||||
label?: string // label名
|
3
kinit-admin/src/types/elementPlus.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
export type ElementPlusSize = 'default' | 'small' | 'large'
|
||||
|
||||
export type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger'
|
47
kinit-admin/src/types/form.d.ts
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { ColProps, ComponentProps, ComponentName } from '@/types/components'
|
||||
import { FormValueType, FormValueType } from '@/types/form'
|
||||
import type { AxiosPromise } from 'axios'
|
||||
|
||||
export type FormSetPropsType = {
|
||||
field: string
|
||||
path: string
|
||||
value: any
|
||||
}
|
||||
|
||||
export type FormValueType = string | number | string[] | number[] | boolean | undefined | null
|
||||
|
||||
export type FormItemProps = {
|
||||
labelWidth?: string | number
|
||||
required?: boolean
|
||||
rules?: Recordable
|
||||
error?: string
|
||||
showMessage?: boolean
|
||||
inlineMessage?: boolean
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export type FormSchema = {
|
||||
// 唯一值
|
||||
field: string
|
||||
// 标题
|
||||
label?: string
|
||||
// 提示
|
||||
labelMessage?: string
|
||||
// col组件属性
|
||||
colProps?: ColProps
|
||||
// 表单组件属性,slots对应的是表单组件的插槽,规则:${field}-xxx,具体可以查看element-plus文档
|
||||
componentProps?: { slots?: Recordable } & ComponentProps
|
||||
// formItem组件属性
|
||||
formItemProps?: FormItemProps
|
||||
// 渲染的组件
|
||||
component?: ComponentName
|
||||
// 初始值
|
||||
value?: FormValueType
|
||||
// 是否隐藏,隐藏后就不会在formModel中显示了,不会获取到该字段的值
|
||||
hidden?: boolean
|
||||
// 是否显示,隐藏后还会在formModel中显示,可以获取到该字段的值
|
||||
ifshow?: (values: Recordable) => boolean
|
||||
// 远程加载下拉项
|
||||
api?: <T = any>() => AxiosPromise<T>
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
declare interface IconTypes {
|
||||
export interface IconTypes {
|
||||
size?: number
|
||||
color?: string
|
||||
icon: string
|
@ -1,4 +1,4 @@
|
||||
declare interface TipSchema {
|
||||
export interface TipSchema {
|
||||
label: string
|
||||
keys?: string[]
|
||||
}
|
1
kinit-admin/src/types/layout.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'
|
@ -1,9 +1,9 @@
|
||||
declare interface Language {
|
||||
export interface Language {
|
||||
el: Recordable
|
||||
name: string
|
||||
}
|
||||
|
||||
declare interface LocaleDropdownType {
|
||||
export interface LocaleDropdownType {
|
||||
lang: LocaleType
|
||||
name?: string
|
||||
elLocale?: Language
|
@ -1,4 +1,4 @@
|
||||
declare interface QrcodeLogo {
|
||||
export interface QrcodeLogo {
|
||||
src?: string
|
||||
logoSize?: number
|
||||
bgColor?: string
|
@ -1,16 +1,16 @@
|
||||
declare type TableColumn = {
|
||||
export type TableColumn = {
|
||||
field: string
|
||||
label?: string
|
||||
children?: TableColumn[]
|
||||
} & Recordable
|
||||
|
||||
declare type TableSlotDefault = {
|
||||
export type TableSlotDefault = {
|
||||
row: Recordable
|
||||
column: TableColumn
|
||||
$index: number
|
||||
} & Recordable
|
||||
|
||||
declare interface Pagination {
|
||||
export interface Pagination {
|
||||
small?: boolean
|
||||
background?: boolean
|
||||
pageSize?: number
|
||||
@ -29,7 +29,7 @@ declare interface Pagination {
|
||||
hideOnSinglePage?: boolean
|
||||
}
|
||||
|
||||
declare interface TableSetPropsType {
|
||||
export interface TableSetPropsType {
|
||||
field: string
|
||||
path: string
|
||||
value: any
|
16
kinit-admin/src/types/theme.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
export type ThemeTypes = {
|
||||
elColorPrimary?: string
|
||||
leftMenuBorderColor?: string
|
||||
leftMenuBgColor?: string
|
||||
leftMenuBgLightColor?: string
|
||||
leftMenuBgActiveColor?: string
|
||||
leftMenuCollapseBgActiveColor?: string
|
||||
leftMenuTextColor?: string
|
||||
leftMenuTextActiveColor?: string
|
||||
logoTitleTextColor?: string
|
||||
logoBorderColor?: string
|
||||
topHeaderBgColor?: string
|
||||
topHeaderTextColor?: string
|
||||
topHeaderHoverColor?: string
|
||||
topToolBorderColor?: string
|
||||
}
|
53
kinit-admin/src/views/Dashboard/Analysis.vue
Normal file
@ -0,0 +1,53 @@
|
||||
<script setup lang="ts">
|
||||
import { ElCarousel, ElCarouselItem, ElImage, ElCard } from 'element-plus'
|
||||
import Charts from './components/Charts.vue'
|
||||
import { getBannersApi } from '@/api/dashboard/analysis'
|
||||
import { ref } from 'vue'
|
||||
import { AnalysisBannersTypes } from '@/api/dashboard/analysis/types'
|
||||
|
||||
const banners = ref([] as AnalysisBannersTypes[])
|
||||
const loading = ref(false)
|
||||
|
||||
const getBanners = async () => {
|
||||
loading.value = true
|
||||
const res = await getBannersApi()
|
||||
if (res) {
|
||||
banners.value = res.data
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getBanners()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mb-20px" style="display: flex; justify-content: center; min-width: 1680px">
|
||||
<ElCard shadow="never" class="w-1680px">
|
||||
<!-- 添加 v-if 解决显示动态数据时,默认会显示一个空白元素 -->
|
||||
<ElCarousel v-loading="loading" v-if="banners.length > 0" height="500px" :interval="5000">
|
||||
<ElCarouselItem v-for="item in banners" :key="item.id">
|
||||
<ElImage :src="item.image" />
|
||||
</ElCarouselItem>
|
||||
</ElCarousel>
|
||||
</ElCard>
|
||||
</div>
|
||||
<Charts />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.el-carousel__item h3 {
|
||||
display: flex;
|
||||
color: #475669;
|
||||
opacity: 0.75;
|
||||
line-height: 300px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
</style>
|
@ -6,8 +6,20 @@ import { ref, reactive } from 'vue'
|
||||
import { CountTo } from '@/components/CountTo'
|
||||
import { formatTime } from '@/utils'
|
||||
import { Highlight } from '@/components/Highlight'
|
||||
import { getCountApi, getProjectApi, getDynamicApi, getTeamApi } from '@/api/dashboard/workplace'
|
||||
import type { WorkplaceTotal, Project, Dynamic, Team } from '@/api/dashboard/workplace/types'
|
||||
import {
|
||||
getCountApi,
|
||||
getProjectApi,
|
||||
getDynamicApi,
|
||||
getTeamApi,
|
||||
getShortcutsApi
|
||||
} from '@/api/dashboard/workplace'
|
||||
import type {
|
||||
WorkplaceTotal,
|
||||
Project,
|
||||
Dynamic,
|
||||
Team,
|
||||
Shortcuts
|
||||
} from '@/api/dashboard/workplace/types'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useAppStoreWithOut } from '@/store/modules/app'
|
||||
|
||||
@ -15,6 +27,10 @@ const { wsCache } = useCache()
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const toLink = (link: string) => {
|
||||
window.open(link)
|
||||
}
|
||||
|
||||
// 获取统计数
|
||||
let totalSate = reactive<WorkplaceTotal>({
|
||||
project: 0,
|
||||
@ -39,6 +55,18 @@ const getProject = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
let shortcuts = reactive<Shortcuts[]>([])
|
||||
|
||||
// 获取快捷操作
|
||||
const getShortcuts = async () => {
|
||||
const res = await getShortcutsApi().catch(() => {})
|
||||
if (res) {
|
||||
shortcuts = Object.assign(shortcuts, res.data)
|
||||
}
|
||||
}
|
||||
|
||||
getShortcuts()
|
||||
|
||||
// 获取动态
|
||||
let dynamics = reactive<Dynamic[]>([])
|
||||
|
||||
@ -154,6 +182,7 @@ const user = wsCache.get(appStore.getUserInfo)
|
||||
:xs="24"
|
||||
>
|
||||
<ElCard shadow="hover">
|
||||
<div class="cursor-pointer" @click="toLink(item.link)">
|
||||
<div class="flex items-center">
|
||||
<Icon :icon="item.icon" :size="25" class="mr-10px" />
|
||||
<span class="text-16px">{{ item.name }}</span>
|
||||
@ -163,6 +192,7 @@ const user = wsCache.get(appStore.getUserInfo)
|
||||
<span>{{ item.personal }}</span>
|
||||
<span>{{ formatTime(item.time, 'yyyy-MM-dd') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
@ -206,9 +236,18 @@ const user = wsCache.get(appStore.getUserInfo)
|
||||
<span>{{ t('workplace.shortcutOperation') }}</span>
|
||||
</template>
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<ElCol :xl="12" :lg="12" :md="12" :sm="24" :xs="24" class="mb-10px">
|
||||
<ElLink type="default" :underline="false">
|
||||
{{ t('workplace.operation') }}
|
||||
<ElCol
|
||||
v-for="(item, index) in shortcuts"
|
||||
:key="`card-${index}`"
|
||||
:xl="12"
|
||||
:lg="12"
|
||||
:md="12"
|
||||
:sm="12"
|
||||
:xs="12"
|
||||
class="mb-10px"
|
||||
>
|
||||
<ElLink type="primary" :href="item.link" target="_blank" :underline="false">
|
||||
{{ item.name }}
|
||||
</ElLink>
|
||||
</ElCol>
|
||||
</ElSkeleton>
|
||||
|
125
kinit-admin/src/views/Dashboard/components/Charts.vue
Normal file
@ -0,0 +1,125 @@
|
||||
<script setup lang="ts">
|
||||
import { ElRow, ElCol, ElCard, ElSkeleton } from 'element-plus'
|
||||
import { Echart } from '@/components/Echart'
|
||||
import { pieOptions, barOptions, lineOptions } from './echarts-data'
|
||||
import { ref, reactive } from 'vue'
|
||||
import {
|
||||
getUserAccessSourceApi,
|
||||
getWeeklyUserActivityApi,
|
||||
getMonthlySalesApi
|
||||
} from '@/api/dashboard/analysis'
|
||||
import { set } from 'lodash-es'
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const loading = ref(true)
|
||||
|
||||
const pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption
|
||||
|
||||
// 用户来源
|
||||
const getUserAccessSource = async () => {
|
||||
const res = await getUserAccessSourceApi().catch(() => {})
|
||||
if (res) {
|
||||
set(
|
||||
pieOptionsData,
|
||||
'legend.data',
|
||||
res.data.map((v) => t(v.name))
|
||||
)
|
||||
pieOptionsData!.series![0].data = res.data.map((v) => {
|
||||
return {
|
||||
name: t(v.name),
|
||||
value: v.value
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption
|
||||
|
||||
// 周活跃量
|
||||
const getWeeklyUserActivity = async () => {
|
||||
const res = await getWeeklyUserActivityApi().catch(() => {})
|
||||
if (res) {
|
||||
set(
|
||||
barOptionsData,
|
||||
'xAxis.data',
|
||||
res.data.map((v) => t(v.name))
|
||||
)
|
||||
set(barOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: res.data.map((v) => v.value),
|
||||
type: 'bar'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption
|
||||
|
||||
// 每月销售总额
|
||||
const getMonthlySales = async () => {
|
||||
const res = await getMonthlySalesApi().catch(() => {})
|
||||
if (res) {
|
||||
set(
|
||||
lineOptionsData,
|
||||
'xAxis.data',
|
||||
res.data.map((v) => t(v.name))
|
||||
)
|
||||
set(lineOptionsData, 'series', [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: res.data.map((v) => v.estimate),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: res.data.map((v) => v.actual),
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const getAllApi = async () => {
|
||||
await Promise.all([getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
getAllApi()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ElRow :gutter="20" justify="space-between">
|
||||
<ElCol :xl="10" :lg="10" :md="24" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<Echart :options="pieOptionsData" :height="300" />
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :xl="14" :lg="14" :md="24" :sm="24" :xs="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated>
|
||||
<Echart :options="barOptionsData" :height="300" />
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ElCard shadow="hover" class="mb-20px">
|
||||
<ElSkeleton :loading="loading" animated :rows="4">
|
||||
<Echart :options="lineOptionsData" :height="350" />
|
||||
</ElSkeleton>
|
||||
</ElCard>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</template>
|
310
kinit-admin/src/views/Dashboard/components/echarts-data.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const lineOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.monthlySales'),
|
||||
left: 'center'
|
||||
},
|
||||
xAxis: {
|
||||
data: [
|
||||
t('analysis.january'),
|
||||
t('analysis.february'),
|
||||
t('analysis.march'),
|
||||
t('analysis.april'),
|
||||
t('analysis.may'),
|
||||
t('analysis.june'),
|
||||
t('analysis.july'),
|
||||
t('analysis.august'),
|
||||
t('analysis.september'),
|
||||
t('analysis.october'),
|
||||
t('analysis.november'),
|
||||
t('analysis.december')
|
||||
],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 80,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: [t('analysis.estimate'), t('analysis.actual')],
|
||||
top: 50
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const pieOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.userAccessSource'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: [
|
||||
t('analysis.directAccess'),
|
||||
t('analysis.mailMarketing'),
|
||||
t('analysis.allianceAdvertising'),
|
||||
t('analysis.videoAdvertising'),
|
||||
t('analysis.searchEngines')
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.userAccessSource'),
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['50%', '60%'],
|
||||
data: [
|
||||
{ value: 335, name: t('analysis.directAccess') },
|
||||
{ value: 310, name: t('analysis.mailMarketing') },
|
||||
{ value: 234, name: t('analysis.allianceAdvertising') },
|
||||
{ value: 135, name: t('analysis.videoAdvertising') },
|
||||
{ value: 1548, name: t('analysis.searchEngines') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const barOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.weeklyUserActivity'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
t('analysis.monday'),
|
||||
t('analysis.tuesday'),
|
||||
t('analysis.wednesday'),
|
||||
t('analysis.thursday'),
|
||||
t('analysis.friday'),
|
||||
t('analysis.saturday'),
|
||||
t('analysis.sunday')
|
||||
],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const radarOption: EChartsOption = {
|
||||
legend: {
|
||||
data: [t('workplace.personal'), t('workplace.team')]
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: t('workplace.quote'), max: 65 },
|
||||
{ name: t('workplace.contribution'), max: 160 },
|
||||
{ name: t('workplace.hot'), max: 300 },
|
||||
{ name: t('workplace.yield'), max: 130 },
|
||||
{ name: t('workplace.follow'), max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [42, 30, 20, 35, 80],
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: [50, 140, 290, 100, 90],
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const wordOptions: EChartsWordOption = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
gridSize: 2,
|
||||
sizeRange: [12, 50],
|
||||
rotationRange: [-90, 90],
|
||||
shape: 'pentagon',
|
||||
width: 600,
|
||||
height: 400,
|
||||
drawOutOfBound: true,
|
||||
textStyle: {
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160)
|
||||
].join(',') +
|
||||
')'
|
||||
)
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#333'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Sam S Club',
|
||||
value: 10000,
|
||||
textStyle: {
|
||||
color: 'black'
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Macys',
|
||||
value: 6181
|
||||
},
|
||||
{
|
||||
name: 'Amy Schumer',
|
||||
value: 4386
|
||||
},
|
||||
{
|
||||
name: 'Jurassic World',
|
||||
value: 4055
|
||||
},
|
||||
{
|
||||
name: 'Charter Communications',
|
||||
value: 2467
|
||||
},
|
||||
{
|
||||
name: 'Chick Fil A',
|
||||
value: 2244
|
||||
},
|
||||
{
|
||||
name: 'Planet Fitness',
|
||||
value: 1898
|
||||
},
|
||||
{
|
||||
name: 'Pitch Perfect',
|
||||
value: 1484
|
||||
},
|
||||
{
|
||||
name: 'Express',
|
||||
value: 1112
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
value: 965
|
||||
},
|
||||
{
|
||||
name: 'Johnny Depp',
|
||||
value: 847
|
||||
},
|
||||
{
|
||||
name: 'Lena Dunham',
|
||||
value: 582
|
||||
},
|
||||
{
|
||||
name: 'Lewis Hamilton',
|
||||
value: 555
|
||||
},
|
||||
{
|
||||
name: 'KXAN',
|
||||
value: 550
|
||||
},
|
||||
{
|
||||
name: 'Mary Ellen Mark',
|
||||
value: 462
|
||||
},
|
||||
{
|
||||
name: 'Farrah Abraham',
|
||||
value: 366
|
||||
},
|
||||
{
|
||||
name: 'Rita Ora',
|
||||
value: 360
|
||||
},
|
||||
{
|
||||
name: 'Serena Williams',
|
||||
value: 282
|
||||
},
|
||||
{
|
||||
name: 'NCAA baseball tournament',
|
||||
value: 273
|
||||
},
|
||||
{
|
||||
name: 'Point Break',
|
||||
value: 265
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
310
kinit-admin/src/views/Dashboard/echarts-data.ts
Normal file
@ -0,0 +1,310 @@
|
||||
import { EChartsOption } from 'echarts'
|
||||
import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
export const lineOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.monthlySales'),
|
||||
left: 'center'
|
||||
},
|
||||
xAxis: {
|
||||
data: [
|
||||
t('analysis.january'),
|
||||
t('analysis.february'),
|
||||
t('analysis.march'),
|
||||
t('analysis.april'),
|
||||
t('analysis.may'),
|
||||
t('analysis.june'),
|
||||
t('analysis.july'),
|
||||
t('analysis.august'),
|
||||
t('analysis.september'),
|
||||
t('analysis.october'),
|
||||
t('analysis.november'),
|
||||
t('analysis.december')
|
||||
],
|
||||
boundaryGap: false,
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 20,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
top: 80,
|
||||
containLabel: true
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'cross'
|
||||
},
|
||||
padding: [5, 10]
|
||||
},
|
||||
yAxis: {
|
||||
axisTick: {
|
||||
show: false
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: [t('analysis.estimate'), t('analysis.actual')],
|
||||
top: 50
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.estimate'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'cubicInOut'
|
||||
},
|
||||
{
|
||||
name: t('analysis.actual'),
|
||||
smooth: true,
|
||||
type: 'line',
|
||||
itemStyle: {},
|
||||
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
|
||||
animationDuration: 2800,
|
||||
animationEasing: 'quadraticOut'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const pieOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.userAccessSource'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: '{a} <br/>{b} : {c} ({d}%)'
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
data: [
|
||||
t('analysis.directAccess'),
|
||||
t('analysis.mailMarketing'),
|
||||
t('analysis.allianceAdvertising'),
|
||||
t('analysis.videoAdvertising'),
|
||||
t('analysis.searchEngines')
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.userAccessSource'),
|
||||
type: 'pie',
|
||||
radius: '55%',
|
||||
center: ['50%', '60%'],
|
||||
data: [
|
||||
{ value: 335, name: t('analysis.directAccess') },
|
||||
{ value: 310, name: t('analysis.mailMarketing') },
|
||||
{ value: 234, name: t('analysis.allianceAdvertising') },
|
||||
{ value: 135, name: t('analysis.videoAdvertising') },
|
||||
{ value: 1548, name: t('analysis.searchEngines') }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const barOptions: EChartsOption = {
|
||||
title: {
|
||||
text: t('analysis.weeklyUserActivity'),
|
||||
left: 'center'
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: {
|
||||
type: 'shadow'
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
left: 50,
|
||||
right: 20,
|
||||
bottom: 20
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: [
|
||||
t('analysis.monday'),
|
||||
t('analysis.tuesday'),
|
||||
t('analysis.wednesday'),
|
||||
t('analysis.thursday'),
|
||||
t('analysis.friday'),
|
||||
t('analysis.saturday'),
|
||||
t('analysis.sunday')
|
||||
],
|
||||
axisTick: {
|
||||
alignWithLabel: true
|
||||
}
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: t('analysis.activeQuantity'),
|
||||
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
|
||||
type: 'bar'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const radarOption: EChartsOption = {
|
||||
legend: {
|
||||
data: [t('workplace.personal'), t('workplace.team')]
|
||||
},
|
||||
radar: {
|
||||
// shape: 'circle',
|
||||
indicator: [
|
||||
{ name: t('workplace.quote'), max: 65 },
|
||||
{ name: t('workplace.contribution'), max: 160 },
|
||||
{ name: t('workplace.hot'), max: 300 },
|
||||
{ name: t('workplace.yield'), max: 130 },
|
||||
{ name: t('workplace.follow'), max: 100 }
|
||||
]
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: `xxx${t('workplace.index')}`,
|
||||
type: 'radar',
|
||||
data: [
|
||||
{
|
||||
value: [42, 30, 20, 35, 80],
|
||||
name: t('workplace.personal')
|
||||
},
|
||||
{
|
||||
value: [50, 140, 290, 100, 90],
|
||||
name: t('workplace.team')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const wordOptions: EChartsWordOption = {
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
gridSize: 2,
|
||||
sizeRange: [12, 50],
|
||||
rotationRange: [-90, 90],
|
||||
shape: 'pentagon',
|
||||
width: 600,
|
||||
height: 400,
|
||||
drawOutOfBound: true,
|
||||
textStyle: {
|
||||
color: function () {
|
||||
return (
|
||||
'rgb(' +
|
||||
[
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160),
|
||||
Math.round(Math.random() * 160)
|
||||
].join(',') +
|
||||
')'
|
||||
)
|
||||
}
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 10,
|
||||
shadowColor: '#333'
|
||||
}
|
||||
},
|
||||
data: [
|
||||
{
|
||||
name: 'Sam S Club',
|
||||
value: 10000,
|
||||
textStyle: {
|
||||
color: 'black'
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Macys',
|
||||
value: 6181
|
||||
},
|
||||
{
|
||||
name: 'Amy Schumer',
|
||||
value: 4386
|
||||
},
|
||||
{
|
||||
name: 'Jurassic World',
|
||||
value: 4055
|
||||
},
|
||||
{
|
||||
name: 'Charter Communications',
|
||||
value: 2467
|
||||
},
|
||||
{
|
||||
name: 'Chick Fil A',
|
||||
value: 2244
|
||||
},
|
||||
{
|
||||
name: 'Planet Fitness',
|
||||
value: 1898
|
||||
},
|
||||
{
|
||||
name: 'Pitch Perfect',
|
||||
value: 1484
|
||||
},
|
||||
{
|
||||
name: 'Express',
|
||||
value: 1112
|
||||
},
|
||||
{
|
||||
name: 'Home',
|
||||
value: 965
|
||||
},
|
||||
{
|
||||
name: 'Johnny Depp',
|
||||
value: 847
|
||||
},
|
||||
{
|
||||
name: 'Lena Dunham',
|
||||
value: 582
|
||||
},
|
||||
{
|
||||
name: 'Lewis Hamilton',
|
||||
value: 555
|
||||
},
|
||||
{
|
||||
name: 'KXAN',
|
||||
value: 550
|
||||
},
|
||||
{
|
||||
name: 'Mary Ellen Mark',
|
||||
value: 462
|
||||
},
|
||||
{
|
||||
name: 'Farrah Abraham',
|
||||
value: 366
|
||||
},
|
||||
{
|
||||
name: 'Rita Ora',
|
||||
value: 360
|
||||
},
|
||||
{
|
||||
name: 'Serena Williams',
|
||||
value: 282
|
||||
},
|
||||
{
|
||||
name: 'NCAA baseball tournament',
|
||||
value: 273
|
||||
},
|
||||
{
|
||||
name: 'Point Break',
|
||||
value: 265
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
@ -81,6 +81,9 @@ const loading = ref(false)
|
||||
|
||||
// 提交
|
||||
const save = async () => {
|
||||
if (authStore.getUser.id === 1) {
|
||||
return ElMessage.warning('编辑账号为演示账号,无权限操作!')
|
||||
}
|
||||
const formRef = unref(elFormRef)
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
|
@ -75,6 +75,9 @@ const loading = ref(false)
|
||||
|
||||
// 提交
|
||||
const save = async () => {
|
||||
if (authStore.getUser.id === 1) {
|
||||
return ElMessage.warning('编辑账号为演示账号,无权限操作!')
|
||||
}
|
||||
const formRef = unref(elFormRef)
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
|