切换为vue3的前端版本

This commit is contained in:
AlanScipio
2024-01-30 14:15:52 +08:00
parent 78e61d89ba
commit c3de97c825
208 changed files with 18115 additions and 24357 deletions

View File

@@ -1,30 +1,14 @@
<p align="center"> # 平台简介
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.3</h1>
<h4 align="center">基于 Vue/Element UI 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4>
<p align="center">
<a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.3-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
</p>
## 平台简介 基于若依V3.6.3
若依是一套全部开源的快速开发平台,毫无保留给个人及企业免费使用。
* 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue))。 * 采用前后端分离的模式,微服务版本前端(基于 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue))。
* 后端采用Spring Boot、Spring Cloud & Alibaba。 * 后端采用Spring Boot、Spring Cloud & Alibaba。
* 注册中心、配置中心选型Nacos权限认证使用Redis。 * 注册中心、配置中心选型Nacos权限认证使用Redis。
* 流量控制框架选型Sentinel分布式事务选型Seata。 * 流量控制框架选型Sentinel分布式事务选型Seata。
* 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://github.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。 * 提供了技术栈([Vue3](https://v3.cn.vuejs.org) [Element Plus](https://element-plus.org/zh-CN) [Vite](https://cn.vitejs.dev))版本[RuoYi-Cloud-Vue3](https://github.com/yangzongzhuan/RuoYi-Cloud-Vue3),保持同步更新。
* 如需不分离应用,请移步 [RuoYi](https://gitee.com/y_project/RuoYi),如需分离应用,请移步 [RuoYi-Vue](https://gitee.com/y_project/RuoYi-Vue)
* 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
* 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
#### 友情链接 [若依/RuoYi-Cloud](https://gitee.com/zhangmrit/ruoyi-cloud) Ant Design版本。 # 系统模块
## 系统模块
~~~ ~~~
com.ruoyi com.ruoyi
@@ -52,11 +36,7 @@ com.ruoyi
├──pom.xml // 公共依赖 ├──pom.xml // 公共依赖
~~~ ~~~
## 架构图 # 内置功能
<img src="https://oscimg.oschina.net/oscnet/up-82e9722ecb846786405a904bafcf19f73f3.png"/>
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。 1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。 2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
@@ -75,57 +55,3 @@ com.ruoyi
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。 15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 在线构建器拖动表单元素生成相应的HTML代码。 16. 在线构建器拖动表单元素生成相应的HTML代码。
17. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。 17. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 在线体验
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址http://ruoyi.vip
文档地址http://doc.ruoyi.vip
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-4148b24f58660a9dc347761e4cf6162f28f.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ece3fd37a3d4bb75a3926e905a3c5629055.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-92ffb7f3835855cff100fa0f754a6be0d99.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ff9e3066561574aca73005c5730c6a41f15.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
## 若依微服务交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/已满-128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) [![加入QQ群](https://img.shields.io/badge/179219821-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=irnwcXhbLOQEv1g-TwGifjNTA_f4wZiA&authKey=4bpzEwhcUY%2FvsPDHvzYn6xfoS%2FtOArvZ%2BGXzfr7O0%2FEqLfkKA%2BuCDXlzHIFg8t93&noverify=0&group_code=179219821) 点击按钮入群。

View File

@@ -9,14 +9,13 @@
<version>3.6.3</version> <version>3.6.3</version>
<name>ruoyi</name> <name>ruoyi</name>
<url>http://www.ruoyi.vip</url>
<description>若依微服务系统</description>
<properties> <properties>
<ruoyi.version>3.6.3</ruoyi.version> <ruoyi.version>3.6.3</ruoyi.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version> <java.version>1.8</java.version>
<spring-boot.version>2.7.18</spring-boot.version> <spring-boot.version>2.7.18</spring-boot.version>
<spring-cloud.version>2021.0.8</spring-cloud.version> <spring-cloud.version>2021.0.8</spring-cloud.version>
<spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version> <spring-cloud-alibaba.version>2021.0.5.0</spring-cloud-alibaba.version>

View File

@@ -1,22 +0,0 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 结尾换行符可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配md结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@@ -1,11 +1,8 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 若依管理系统DEV
# 开发环境配置 # 开发环境配置
ENV = 'development' VITE_APP_ENV = 'development'
# 若依管理系统/开发环境 # 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api' VITE_APP_BASE_API = '/dev-api'
# 路由懒加载
VUE_CLI_BABEL_TRANSPILE_MODULES = true

View File

@@ -1,8 +1,11 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 若依管理系统PROD
# 生产环境配置 # 生产环境配置
ENV = 'production' VITE_APP_ENV = 'production'
# 若依管理系统/生产环境 # 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api' VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@@ -1,10 +1,11 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VITE_APP_TITLE = 若依管理系统STAGING
NODE_ENV = production # 生产环境配置
VITE_APP_ENV = 'staging'
# 测试环境配置
ENV = 'staging' # 若依管理系统/生产环境
VITE_APP_BASE_API = '/stage-api'
# 若依管理系统/测试环境
VUE_APP_BASE_API = '/stage-api' # 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

View File

@@ -1,10 +0,0 @@
# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略public目录下文件的语法检查
public
# 忽略当前目录下为js的文件的语法检查
*.js
# 忽略当前目录下为vue的文件的语法检查
*.vue

View File

@@ -1,199 +0,0 @@
// ESlint 检查配置
module.exports = {
root: true,
parserOptions: {
parser: 'babel-eslint',
sourceType: 'module'
},
env: {
browser: true,
node: true,
es6: true,
},
extends: ['plugin:vue/recommended', 'eslint:recommended'],
// add your custom rules here
//it is base on https://github.com/vuejs/eslint-config-vue
rules: {
"vue/max-attributes-per-line": [2, {
"singleline": 10,
"multiline": {
"max": 1,
"allowFirstLine": false
}
}],
"vue/singleline-html-element-content-newline": "off",
"vue/multiline-html-element-content-newline":"off",
"vue/name-property-casing": ["error", "PascalCase"],
"vue/no-v-html": "off",
'accessor-pairs': 2,
'arrow-spacing': [2, {
'before': true,
'after': true
}],
'block-spacing': [2, 'always'],
'brace-style': [2, '1tbs', {
'allowSingleLine': true
}],
'camelcase': [0, {
'properties': 'always'
}],
'comma-dangle': [2, 'never'],
'comma-spacing': [2, {
'before': false,
'after': true
}],
'comma-style': [2, 'last'],
'constructor-super': 2,
'curly': [2, 'multi-line'],
'dot-location': [2, 'property'],
'eol-last': 2,
'eqeqeq': ["error", "always", {"null": "ignore"}],
'generator-star-spacing': [2, {
'before': true,
'after': true
}],
'handle-callback-err': [2, '^(err|error)$'],
'indent': [2, 2, {
'SwitchCase': 1
}],
'jsx-quotes': [2, 'prefer-single'],
'key-spacing': [2, {
'beforeColon': false,
'afterColon': true
}],
'keyword-spacing': [2, {
'before': true,
'after': true
}],
'new-cap': [2, {
'newIsCap': true,
'capIsNew': false
}],
'new-parens': 2,
'no-array-constructor': 2,
'no-caller': 2,
'no-console': 'off',
'no-class-assign': 2,
'no-cond-assign': 2,
'no-const-assign': 2,
'no-control-regex': 0,
'no-delete-var': 2,
'no-dupe-args': 2,
'no-dupe-class-members': 2,
'no-dupe-keys': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-empty-pattern': 2,
'no-eval': 2,
'no-ex-assign': 2,
'no-extend-native': 2,
'no-extra-bind': 2,
'no-extra-boolean-cast': 2,
'no-extra-parens': [2, 'functions'],
'no-fallthrough': 2,
'no-floating-decimal': 2,
'no-func-assign': 2,
'no-implied-eval': 2,
'no-inner-declarations': [2, 'functions'],
'no-invalid-regexp': 2,
'no-irregular-whitespace': 2,
'no-iterator': 2,
'no-label-var': 2,
'no-labels': [2, {
'allowLoop': false,
'allowSwitch': false
}],
'no-lone-blocks': 2,
'no-mixed-spaces-and-tabs': 2,
'no-multi-spaces': 2,
'no-multi-str': 2,
'no-multiple-empty-lines': [2, {
'max': 1
}],
'no-native-reassign': 2,
'no-negated-in-lhs': 2,
'no-new-object': 2,
'no-new-require': 2,
'no-new-symbol': 2,
'no-new-wrappers': 2,
'no-obj-calls': 2,
'no-octal': 2,
'no-octal-escape': 2,
'no-path-concat': 2,
'no-proto': 2,
'no-redeclare': 2,
'no-regex-spaces': 2,
'no-return-assign': [2, 'except-parens'],
'no-self-assign': 2,
'no-self-compare': 2,
'no-sequences': 2,
'no-shadow-restricted-names': 2,
'no-spaced-func': 2,
'no-sparse-arrays': 2,
'no-this-before-super': 2,
'no-throw-literal': 2,
'no-trailing-spaces': 2,
'no-undef': 2,
'no-undef-init': 2,
'no-unexpected-multiline': 2,
'no-unmodified-loop-condition': 2,
'no-unneeded-ternary': [2, {
'defaultAssignment': false
}],
'no-unreachable': 2,
'no-unsafe-finally': 2,
'no-unused-vars': [2, {
'vars': 'all',
'args': 'none'
}],
'no-useless-call': 2,
'no-useless-computed-key': 2,
'no-useless-constructor': 2,
'no-useless-escape': 0,
'no-whitespace-before-property': 2,
'no-with': 2,
'one-var': [2, {
'initialized': 'never'
}],
'operator-linebreak': [2, 'after', {
'overrides': {
'?': 'before',
':': 'before'
}
}],
'padded-blocks': [2, 'never'],
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'semi-spacing': [2, {
'before': false,
'after': true
}],
'space-before-blocks': [2, 'always'],
'space-before-function-paren': [2, 'never'],
'space-in-parens': [2, 'never'],
'space-infix-ops': 2,
'space-unary-ops': [2, {
'words': true,
'nonwords': false
}],
'spaced-comment': [2, 'always', {
'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
}],
'template-curly-spacing': [2, 'never'],
'use-isnan': 2,
'valid-typeof': 2,
'wrap-iife': [2, 'any'],
'yield-star-spacing': [2, 'both'],
'yoda': [2, 'never'],
'prefer-const': 2,
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'object-curly-spacing': [2, 'always', {
objectsInObjects: false
}],
'array-bracket-spacing': [2, 'never']
}
}

46
ruoyi-ui/.gitignore vendored
View File

@@ -1,23 +1,23 @@
.DS_Store .DS_Store
node_modules/ node_modules/
dist/ dist/
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
**/*.log **/*.log
tests/**/coverage/ tests/**/coverage/
tests/e2e/reports tests/e2e/reports
selenium-debug.log selenium-debug.log
# Editor directories and files # Editor directories and files
.idea .idea
.vscode .vscode
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.local *.local
package-lock.json package-lock.json
yarn.lock yarn.lock

20
ruoyi-ui/LICENSE Normal file
View File

@@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 RuoYi
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,30 +1,143 @@
## 开发 <p align="center">
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-b99b286755aef70355a7084753f89cdb7c9.png">
```bash </p>
# 克隆项目 <h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.6.3</h1>
git clone https://gitee.com/y_project/RuoYi-Vue <h4 align="center">基于 Vue3/Element Plus 和 Spring Boot/Spring Cloud & Alibaba 前后端分离的分布式微服务架构</h4>
<p align="center">
# 进入项目目录 <a href="https://gitee.com/y_project/RuoYi-Cloud/stargazers"><img src="https://gitee.com/y_project/RuoYi-Cloud/badge/star.svg?theme=dark"></a>
cd ruoyi-ui <a href="https://gitee.com/y_project/RuoYi-Cloud"><img src="https://img.shields.io/badge/RuoYi-v3.6.3-brightgreen.svg"></a>
<a href="https://gitee.com/y_project/RuoYi-Cloud/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
# 安装依赖 </p>
npm install
## 平台简介
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npmmirror.com * 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
* 配套后端代码仓库地址[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud) 或 [RuoYi-Cloud-Oracle](https://github.com/yangzongzhuan/RuoYi-Cloud-Oracle) 版本。
# 启动服务 * 前端技术栈([Vue2](https://cn.vuejs.org) + [Element](https://github.com/ElemeFE/element) + [Vue CLI](https://cli.vuejs.org/zh)),请移步[RuoYi-Cloud](https://gitee.com/y_project/RuoYi-Cloud/tree/master/ruoyi-ui)。
npm run dev * 阿里云折扣场:[点我进入](http://aly.ruoyi.vip),腾讯云秒杀场:[点我进入](http://txy.ruoyi.vip)&nbsp;&nbsp;
``` * 阿里云优惠券:[点我领取](https://www.aliyun.com/minisite/goods?userCode=brki8iof&share_source=copy_link),腾讯云优惠券:[点我领取](https://cloud.tencent.com/redirect.php?redirect=1025&cps_key=198c8df2ed259157187173bc7f4f32fd&from=console)&nbsp;&nbsp;
浏览器访问 http://localhost:80 ## 前端运行
## 发布 ```bash
# 克隆项目
```bash git clone https://github.com/yangzongzhuan/RuoYi-Cloud-Vue3.git
# 构建测试环境
npm run build:stage # 进入项目目录
cd RuoYi-Cloud-Vue3
# 构建生产环境
npm run build:prod # 安装依赖
``` yarn --registry=https://registry.npmmirror.com
# 启动服务
yarn dev
# 构建测试环境 yarn build:stage
# 构建生产环境 yarn build:prod
# 前端访问地址 http://localhost:80
```
## 系统模块
~~~
com.ruoyi
├── ruoyi-ui // 前端框架 [80]
├── ruoyi-gateway // 网关模块 [8080]
├── ruoyi-auth // 认证中心 [9200]
├── ruoyi-api // 接口模块
│ └── ruoyi-api-system // 系统接口
├── ruoyi-common // 通用模块
│ └── ruoyi-common-core // 核心模块
│ └── ruoyi-common-datascope // 权限范围
│ └── ruoyi-common-datasource // 多数据源
│ └── ruoyi-common-log // 日志记录
│ └── ruoyi-common-redis // 缓存服务
│ └── ruoyi-common-security // 安全模块
│ └── ruoyi-common-swagger // 系统接口
├── ruoyi-modules // 业务模块
│ └── ruoyi-system // 系统模块 [9201]
│ └── ruoyi-gen // 代码生成 [9202]
│ └── ruoyi-job // 定时任务 [9203]
│ └── ruoyi-file // 文件服务 [9300]
├── ruoyi-visual // 图形化管理模块
│ └── ruoyi-visual-monitor // 监控中心 [9100]
├──pom.xml // 公共依赖
~~~
## 架构图
<img src="https://oscimg.oschina.net/oscnet/up-82e9722ecb846786405a904bafcf19f73f3.png"/>
## 内置功能
1. 用户管理:用户是系统操作者,该功能主要完成系统用户配置。
2. 部门管理:配置系统组织机构(公司、部门、小组),树结构展现支持数据权限。
3. 岗位管理:配置系统用户所属担任职务。
4. 菜单管理:配置系统菜单,操作权限,按钮权限标识等。
5. 角色管理:角色菜单权限分配、设置角色按机构进行数据范围权限划分。
6. 字典管理:对系统中经常使用的一些较为固定的数据进行维护。
7. 参数管理:对系统动态配置常用参数。
8. 通知公告:系统通知公告信息发布维护。
9. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询。
10. 登录日志:系统登录日志记录查询包含登录异常。
11. 在线用户:当前系统中活跃用户状态监控。
12. 定时任务:在线(添加、修改、删除)任务调度包含执行结果日志。
13. 代码生成前后端代码的生成java、html、xml、sql支持CRUD下载 。
14. 系统接口根据业务代码自动生成相关的api接口文档。
15. 服务监控监视当前系统CPU、内存、磁盘、堆栈等相关信息。
16. 在线构建器拖动表单元素生成相应的HTML代码。
17. 连接池监视监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈。
## 在线体验
- admin/admin123
- 陆陆续续收到一些打赏,为了更好的体验已用于演示服务器升级。谢谢各位小伙伴。
演示地址http://ruoyi.vip
文档地址http://doc.ruoyi.vip
## 演示图
<table>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/cd1f90be5f2684f4560c9519c0f2a232ee8.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/1cbcf0e6f257c7d3a063c0e3f2ff989e4b3.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8074972883b5ba0622e13246738ebba237a.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-9f88719cdfca9af2e58b352a20e23d43b12.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-39bf2584ec3a529b0d5a3b70d15c9b37646.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-4148b24f58660a9dc347761e4cf6162f28f.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-b2d62ceb95d2dd9b3fbe157bb70d26001e9.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-d67451d308b7a79ad6819723396f7c3d77a.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/5e8c387724954459291aafd5eb52b456f53.jpg"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/644e78da53c2e92a95dfda4f76e6d117c4b.jpg"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-8370a0d02977eebf6dbf854c8450293c937.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-49003ed83f60f633e7153609a53a2b644f7.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-d4fe726319ece268d4746602c39cffc0621.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-c195234bbcd30be6927f037a6755e6ab69c.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ece3fd37a3d4bb75a3926e905a3c5629055.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-92ffb7f3835855cff100fa0f754a6be0d99.png"/></td>
</tr>
<tr>
<td><img src="https://oscimg.oschina.net/oscnet/up-ff9e3066561574aca73005c5730c6a41f15.png"/></td>
<td><img src="https://oscimg.oschina.net/oscnet/up-5e4daac0bb59612c5038448acbcef235e3a.png"/></td>
</tr>
</table>
## 若依微服务交流群
QQ群 [![加入QQ群](https://img.shields.io/badge/已满-42799195-blue.svg)](https://jq.qq.com/?_wv=1027&k=yqInfq0S) [![加入QQ群](https://img.shields.io/badge/已满-170157040-blue.svg)](https://jq.qq.com/?_wv=1027&k=Oy1mb3p8) [![加入QQ群](https://img.shields.io/badge/已满-130643120-blue.svg)](https://jq.qq.com/?_wv=1027&k=rvxkJtXK) [![加入QQ群](https://img.shields.io/badge/已满-225920371-blue.svg)](https://jq.qq.com/?_wv=1027&k=0Ck3PvTe) [![加入QQ群](https://img.shields.io/badge/已满-201705537-blue.svg)](https://jq.qq.com/?_wv=1027&k=FnHHP4TT) [![加入QQ群](https://img.shields.io/badge/已满-236543183-blue.svg)](https://jq.qq.com/?_wv=1027&k=qdT1Ojpz) [![加入QQ群](https://img.shields.io/badge/已满-213618602-blue.svg)](https://jq.qq.com/?_wv=1027&k=nw3OiyXs) [![加入QQ群](https://img.shields.io/badge/已满-148794840-blue.svg)](https://jq.qq.com/?_wv=1027&k=kiU5WDls) [![加入QQ群](https://img.shields.io/badge/已满-118752664-blue.svg)](https://jq.qq.com/?_wv=1027&k=MtBy6YfT) [![加入QQ群](https://img.shields.io/badge/已满-101038945-blue.svg)](https://jq.qq.com/?_wv=1027&k=FqImHgH2) [![加入QQ群](https://img.shields.io/badge/已满-128355254-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G4jZ4EtdT50PhnMBudTnEwgonxkXOscJ&authKey=FkGHYfoTKlGE6wHdKdjH9bVoOgQjtLP9WM%2Fj7pqGY1msoqw9uxDiBo39E2mLgzYg&noverify=0&group_code=128355254) [![加入QQ群](https://img.shields.io/badge/179219821-blue.svg)](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=irnwcXhbLOQEv1g-TwGifjNTA_f4wZiA&authKey=4bpzEwhcUY%2FvsPDHvzYn6xfoS%2FtOArvZ%2BGXzfr7O0%2FEqLfkKA%2BuCDXlzHIFg8t93&noverify=0&group_code=179219821) 点击按钮入群。

View File

@@ -1,13 +0,0 @@
module.exports = {
presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset'
],
'env': {
'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
'plugins': ['dynamic-import-node']
}
}
}

View File

@@ -1,12 +1,12 @@
@echo off @echo off
echo. echo.
echo [信息] 打包Web工程生成dist文件。 echo [信息] 打包Web工程生成dist文件。
echo. echo.
%~d0 %~d0
cd %~dp0 cd %~dp0
cd .. cd ..
npm run build:prod yarn build:prod
pause pause

View File

@@ -1,12 +1,12 @@
@echo off @echo off
echo. echo.
echo [信息] 安装Web工程生成node_modules文件。 echo [信息] 安装Web工程生成node_modules文件。
echo. echo.
%~d0 %~d0
cd %~dp0 cd %~dp0
cd .. cd ..
npm install --registry=https://registry.npmmirror.com yarn --registry=https://registry.npmmirror.com
pause pause

View File

@@ -1,12 +1,12 @@
@echo off @echo off
echo. echo.
echo [信息] 使用 Vue CLI 命令运行 Web 工程。 echo [信息] 使用 Vite 命令运行 Web 工程。
echo. echo.
%~d0 %~d0
cd %~dp0 cd %~dp0
cd .. cd ..
npm run dev yarn dev
pause pause

View File

@@ -1,35 +0,0 @@
const { run } = require('runjs')
const chalk = require('chalk')
const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`)
const port = 9526
const publicPath = config.publicPath
var connect = require('connect')
var serveStatic = require('serve-static')
const app = connect()
app.use(
publicPath,
serveStatic('./dist', {
index: ['index.html', '/']
})
)
app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
}
})
} else {
run(`vue-cli-service build ${args}`)
}

File diff suppressed because one or more lines are too long

View File

@@ -1,208 +1,215 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head>
<meta charset="utf-8"> <head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta charset="utf-8">
<meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="renderer" content="webkit">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title><%= webpackConfig.name %></title> <link rel="icon" href="/favicon.ico">
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <title>若依管理系统</title>
<style> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
html, <style>
body, html,
#app { body,
height: 100%; #app {
margin: 0px; height: 100%;
padding: 0px; margin: 0px;
} padding: 0px;
.chromeframe { }
margin: 0.2em 0;
background: #ccc; .chromeframe {
color: #000; margin: 0.2em 0;
padding: 0.2em 0; background: #ccc;
} color: #000;
padding: 0.2em 0;
#loader-wrapper { }
position: fixed;
top: 0; #loader-wrapper {
left: 0; position: fixed;
width: 100%; top: 0;
height: 100%; left: 0;
z-index: 999999; width: 100%;
} height: 100%;
z-index: 999999;
#loader { }
display: block;
position: relative; #loader {
left: 50%; display: block;
top: 50%; position: relative;
width: 150px; left: 50%;
height: 150px; top: 50%;
margin: -75px 0 0 -75px; width: 150px;
border-radius: 50%; height: 150px;
border: 3px solid transparent; margin: -75px 0 0 -75px;
border-top-color: #FFF; border-radius: 50%;
-webkit-animation: spin 2s linear infinite; border: 3px solid transparent;
-ms-animation: spin 2s linear infinite; border-top-color: #FFF;
-moz-animation: spin 2s linear infinite; -webkit-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite; -ms-animation: spin 2s linear infinite;
animation: spin 2s linear infinite; -moz-animation: spin 2s linear infinite;
z-index: 1001; -o-animation: spin 2s linear infinite;
} animation: spin 2s linear infinite;
z-index: 1001;
#loader:before { }
content: "";
position: absolute; #loader:before {
top: 5px; content: "";
left: 5px; position: absolute;
right: 5px; top: 5px;
bottom: 5px; left: 5px;
border-radius: 50%; right: 5px;
border: 3px solid transparent; bottom: 5px;
border-top-color: #FFF; border-radius: 50%;
-webkit-animation: spin 3s linear infinite; border: 3px solid transparent;
-moz-animation: spin 3s linear infinite; border-top-color: #FFF;
-o-animation: spin 3s linear infinite; -webkit-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite; -moz-animation: spin 3s linear infinite;
animation: spin 3s linear infinite; -o-animation: spin 3s linear infinite;
} -ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite;
#loader:after { }
content: "";
position: absolute; #loader:after {
top: 15px; content: "";
left: 15px; position: absolute;
right: 15px; top: 15px;
bottom: 15px; left: 15px;
border-radius: 50%; right: 15px;
border: 3px solid transparent; bottom: 15px;
border-top-color: #FFF; border-radius: 50%;
-moz-animation: spin 1.5s linear infinite; border: 3px solid transparent;
-o-animation: spin 1.5s linear infinite; border-top-color: #FFF;
-ms-animation: spin 1.5s linear infinite; -moz-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite; -o-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite; -ms-animation: spin 1.5s linear infinite;
} -webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite;
}
@-webkit-keyframes spin {
0% {
-webkit-transform: rotate(0deg); @-webkit-keyframes spin {
-ms-transform: rotate(0deg); 0% {
transform: rotate(0deg); -webkit-transform: rotate(0deg);
} -ms-transform: rotate(0deg);
100% { transform: rotate(0deg);
-webkit-transform: rotate(360deg); }
-ms-transform: rotate(360deg);
transform: rotate(360deg); 100% {
} -webkit-transform: rotate(360deg);
} -ms-transform: rotate(360deg);
transform: rotate(360deg);
@keyframes spin { }
0% { }
-webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg); @keyframes spin {
transform: rotate(0deg); 0% {
} -webkit-transform: rotate(0deg);
100% { -ms-transform: rotate(0deg);
-webkit-transform: rotate(360deg); transform: rotate(0deg);
-ms-transform: rotate(360deg); }
transform: rotate(360deg);
} 100% {
} -webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg);
transform: rotate(360deg);
#loader-wrapper .loader-section { }
position: fixed; }
top: 0;
width: 51%;
height: 100%; #loader-wrapper .loader-section {
background: #7171C6; position: fixed;
z-index: 1000; top: 0;
-webkit-transform: translateX(0); width: 51%;
-ms-transform: translateX(0); height: 100%;
transform: translateX(0); background: #7171C6;
} z-index: 1000;
-webkit-transform: translateX(0);
#loader-wrapper .loader-section.section-left { -ms-transform: translateX(0);
left: 0; transform: translateX(0);
} }
#loader-wrapper .loader-section.section-right { #loader-wrapper .loader-section.section-left {
right: 0; left: 0;
} }
#loader-wrapper .loader-section.section-right {
.loaded #loader-wrapper .loader-section.section-left { right: 0;
-webkit-transform: translateX(-100%); }
-ms-transform: translateX(-100%);
transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); .loaded #loader-wrapper .loader-section.section-left {
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); -webkit-transform: translateX(-100%);
} -ms-transform: translateX(-100%);
transform: translateX(-100%);
.loaded #loader-wrapper .loader-section.section-right { -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-webkit-transform: translateX(100%); transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-ms-transform: translateX(100%); }
transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); .loaded #loader-wrapper .loader-section.section-right {
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); -webkit-transform: translateX(100%);
} -ms-transform: translateX(100%);
transform: translateX(100%);
.loaded #loader { -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
opacity: 0; transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
-webkit-transition: all 0.3s ease-out; }
transition: all 0.3s ease-out;
} .loaded #loader {
opacity: 0;
.loaded #loader-wrapper { -webkit-transition: all 0.3s ease-out;
visibility: hidden; transition: all 0.3s ease-out;
-webkit-transform: translateY(-100%); }
-ms-transform: translateY(-100%);
transform: translateY(-100%); .loaded #loader-wrapper {
-webkit-transition: all 0.3s 1s ease-out; visibility: hidden;
transition: all 0.3s 1s ease-out; -webkit-transform: translateY(-100%);
} -ms-transform: translateY(-100%);
transform: translateY(-100%);
.no-js #loader-wrapper { -webkit-transition: all 0.3s 1s ease-out;
display: none; transition: all 0.3s 1s ease-out;
} }
.no-js h1 { .no-js #loader-wrapper {
color: #222222; display: none;
} }
#loader-wrapper .load_title { .no-js h1 {
font-family: 'Open Sans'; color: #222222;
color: #FFF; }
font-size: 19px;
width: 100%; #loader-wrapper .load_title {
text-align: center; font-family: 'Open Sans';
z-index: 9999999999999; color: #FFF;
position: absolute; font-size: 19px;
top: 60%; width: 100%;
opacity: 1; text-align: center;
line-height: 30px; z-index: 9999999999999;
} position: absolute;
top: 60%;
#loader-wrapper .load_title span { opacity: 1;
font-weight: normal; line-height: 30px;
font-style: italic; }
font-size: 13px;
color: #FFF; #loader-wrapper .load_title span {
opacity: 0.5; font-weight: normal;
} font-style: italic;
</style> font-size: 13px;
</head> color: #FFF;
<body> opacity: 0.5;
<div id="app"> }
<div id="loader-wrapper"> </style>
<div id="loader"></div> </head>
<div class="loader-section section-left"></div>
<div class="loader-section section-right"></div> <body>
<div class="load_title">正在加载系统资源,请耐心等待</div> <div id="app">
</div> <div id="loader-wrapper">
</div> <div id="loader"></div>
</body> <div class="loader-section section-left"></div>
</html> <div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div>
</div>
</div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

View File

@@ -1,90 +1,45 @@
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.6.3", "version": "3.6.3",
"description": "若依管理系统", "description": "若依管理系统",
"author": "若依", "author": "若依",
"license": "MIT", "license": "MIT",
"scripts": { "type": "module",
"dev": "vue-cli-service serve", "scripts": {
"build:prod": "vue-cli-service build", "dev": "vite",
"build:stage": "vue-cli-service build --mode staging", "build:prod": "vite build",
"preview": "node build/index.js --preview", "build:stage": "vite build --mode staging",
"lint": "eslint --ext .js,.vue src" "preview": "vite preview"
}, },
"husky": { "repository": {
"hooks": { "type": "git",
"pre-commit": "lint-staged" "url": "https://gitee.com/y_project/RuoYi-Cloud.git"
} },
}, "dependencies": {
"lint-staged": { "@element-plus/icons-vue": "2.3.1",
"src/**/*.{js,vue}": [ "@vueup/vue-quill": "1.2.0",
"eslint --fix", "@vueuse/core": "10.6.1",
"git add" "axios": "0.27.2",
] "echarts": "5.4.3",
}, "element-plus": "2.4.3",
"keywords": [ "file-saver": "2.0.5",
"vue", "fuse.js": "6.6.2",
"admin", "js-cookie": "3.0.5",
"dashboard", "jsencrypt": "3.3.2",
"element-ui", "nprogress": "0.2.0",
"boilerplate", "pinia": "2.1.7",
"admin-template", "vue": "3.3.9",
"management-system" "vue-cropper": "1.1.1",
], "vue-router": "4.2.5"
"repository": { },
"type": "git", "devDependencies": {
"url": "https://gitee.com/y_project/RuoYi-Cloud.git" "@vitejs/plugin-vue": "4.5.0",
}, "@vue/compiler-sfc": "3.3.9",
"dependencies": { "sass": "1.69.5",
"@riophae/vue-treeselect": "0.4.0", "unplugin-auto-import": "0.17.1",
"axios": "0.24.0", "vite": "5.0.4",
"clipboard": "2.0.8", "vite-plugin-compression": "0.5.1",
"core-js": "3.25.3", "vite-plugin-svg-icons": "2.0.1",
"echarts": "5.4.0", "unplugin-vue-setup-extend-plus": "1.0.0"
"element-ui": "2.15.14", }
"file-saver": "2.0.5", }
"fuse.js": "6.4.3",
"highlight.js": "9.18.5",
"js-beautify": "1.13.0",
"js-cookie": "3.0.1",
"jsencrypt": "3.0.0-rc.1",
"nprogress": "0.2.0",
"quill": "1.3.7",
"screenfull": "5.0.2",
"sortablejs": "1.10.2",
"vue": "2.6.12",
"vue-count-to": "1.0.13",
"vue-cropper": "0.5.5",
"vue-meta": "2.4.0",
"vue-router": "3.4.9",
"vuedraggable": "2.24.3",
"vuex": "3.6.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-plugin-eslint": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-eslint": "10.1.0",
"babel-plugin-dynamic-import-node": "2.3.3",
"chalk": "4.1.0",
"compression-webpack-plugin": "5.0.2",
"connect": "3.6.6",
"eslint": "7.15.0",
"eslint-plugin-vue": "7.2.0",
"lint-staged": "10.5.3",
"runjs": "4.4.2",
"sass": "1.32.13",
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-template-compiler": "2.6.12"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
},
"browserslist": [
"> 1%",
"last 2 versions"
]
}

View File

@@ -1,2 +0,0 @@
User-agent: *
Disallow: /

View File

@@ -1,28 +1,15 @@
<template> <template>
<div id="app"> <router-view />
<router-view /> </template>
<theme-picker />
</div> <script setup>
</template> import useSettingsStore from '@/store/modules/settings'
import { handleThemeStyle } from '@/utils/theme'
<script>
import ThemePicker from "@/components/ThemePicker"; onMounted(() => {
nextTick(() => {
export default { // ³õʼ»¯Ö÷ÌâÑùʽ
name: "App", handleThemeStyle(useSettingsStore().theme)
components: { ThemePicker }, })
metaInfo() { })
return { </script>
title: this.$store.state.settings.dynamicTitle && this.$store.state.settings.title,
titleTemplate: title => {
return title ? `${title} - ${process.env.VUE_APP_TITLE}` : process.env.VUE_APP_TITLE
}
}
}
};
</script>
<style scoped>
#app .theme-picker {
display: none;
}
</style>

View File

@@ -1,62 +1,62 @@
import request from '@/utils/request' import request from '@/utils/request'
// 登录方法 // 登录方法
export function login(username, password, code, uuid) { export function login(username, password, code, uuid) {
return request({ return request({
url: '/auth/login', url: '/auth/login',
headers: { headers: {
isToken: false, isToken: false,
repeatSubmit: false repeatSubmit: false
}, },
method: 'post', method: 'post',
data: { username, password, code, uuid } data: { username, password, code, uuid }
}) })
} }
// 注册方法 // 注册方法
export function register(data) { export function register(data) {
return request({ return request({
url: '/auth/register', url: '/auth/register',
headers: { headers: {
isToken: false isToken: false
}, },
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 刷新方法 // 刷新方法
export function refreshToken() { export function refreshToken() {
return request({ return request({
url: '/auth/refresh', url: '/auth/refresh',
method: 'post' method: 'post'
}) })
} }
// 获取用户详细信息 // 获取用户详细信息
export function getInfo() { export function getInfo() {
return request({ return request({
url: '/system/user/getInfo', url: '/system/user/getInfo',
method: 'get' method: 'get'
}) })
} }
// 退出方法 // 退出方法
export function logout() { export function logout() {
return request({ return request({
url: '/auth/logout', url: '/auth/logout',
method: 'delete' method: 'delete'
}) })
} }
// 获取验证码 // 获取验证码
export function getCodeImg() { export function getCodeImg() {
return request({ return request({
url: '/code', url: '/code',
headers: { headers: {
isToken: false isToken: false
}, },
method: 'get', method: 'get',
timeout: 20000 timeout: 20000
}) })
} }

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request' import request from '@/utils/request'
// 获取路由 // 获取路由
export const getRouters = () => { export const getRouters = () => {
return request({ return request({
url: '/system/menu/getRouters', url: '/system/menu/getRouters',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,71 +1,71 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询定时任务调度列表 // 查询定时任务调度列表
export function listJob(query) { export function listJob(query) {
return request({ return request({
url: '/schedule/job/list', url: '/schedule/job/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询定时任务调度详细 // 查询定时任务调度详细
export function getJob(jobId) { export function getJob(jobId) {
return request({ return request({
url: '/schedule/job/' + jobId, url: '/schedule/job/' + jobId,
method: 'get' method: 'get'
}) })
} }
// 新增定时任务调度 // 新增定时任务调度
export function addJob(data) { export function addJob(data) {
return request({ return request({
url: '/schedule/job', url: '/schedule/job',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改定时任务调度 // 修改定时任务调度
export function updateJob(data) { export function updateJob(data) {
return request({ return request({
url: '/schedule/job', url: '/schedule/job',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除定时任务调度 // 删除定时任务调度
export function delJob(jobId) { export function delJob(jobId) {
return request({ return request({
url: '/schedule/job/' + jobId, url: '/schedule/job/' + jobId,
method: 'delete' method: 'delete'
}) })
} }
// 任务状态修改 // 任务状态修改
export function changeJobStatus(jobId, status) { export function changeJobStatus(jobId, status) {
const data = { const data = {
jobId, jobId,
status status
} }
return request({ return request({
url: '/schedule/job/changeStatus', url: '/schedule/job/changeStatus',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 定时任务立即执行一次 // 定时任务立即执行一次
export function runJob(jobId, jobGroup) { export function runJob(jobId, jobGroup) {
const data = { const data = {
jobId, jobId,
jobGroup jobGroup
} }
return request({ return request({
url: '/schedule/job/run', url: '/schedule/job/run',
method: 'put', method: 'put',
data: data data: data
}) })
} }

View File

@@ -1,26 +1,26 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询调度日志列表 // 查询调度日志列表
export function listJobLog(query) { export function listJobLog(query) {
return request({ return request({
url: '/schedule/job/log/list', url: '/schedule/job/log/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 删除调度日志 // 删除调度日志
export function delJobLog(jobLogId) { export function delJobLog(jobLogId) {
return request({ return request({
url: '/schedule/job/log/' + jobLogId, url: '/schedule/job/log/' + jobLogId,
method: 'delete' method: 'delete'
}) })
} }
// 清空调度日志 // 清空调度日志
export function cleanJobLog() { export function cleanJobLog() {
return request({ return request({
url: '/schedule/job/log/clean', url: '/schedule/job/log/clean',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,18 +1,18 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询在线用户列表 // 查询在线用户列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/system/online/list', url: '/system/online/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 强退用户 // 强退用户
export function forceLogout(tokenId) { export function forceLogout(tokenId) {
return request({ return request({
url: '/system/online/' + tokenId, url: '/system/online/' + tokenId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,60 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询参数列表 // 查询参数列表
export function listConfig(query) { export function listConfig(query) {
return request({ return request({
url: '/system/config/list', url: '/system/config/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询参数详细 // 查询参数详细
export function getConfig(configId) { export function getConfig(configId) {
return request({ return request({
url: '/system/config/' + configId, url: '/system/config/' + configId,
method: 'get' method: 'get'
}) })
} }
// 根据参数键名查询参数值 // 根据参数键名查询参数值
export function getConfigKey(configKey) { export function getConfigKey(configKey) {
return request({ return request({
url: '/system/config/configKey/' + configKey, url: '/system/config/configKey/' + configKey,
method: 'get' method: 'get'
}) })
} }
// 新增参数配置 // 新增参数配置
export function addConfig(data) { export function addConfig(data) {
return request({ return request({
url: '/system/config', url: '/system/config',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改参数配置 // 修改参数配置
export function updateConfig(data) { export function updateConfig(data) {
return request({ return request({
url: '/system/config', url: '/system/config',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除参数配置 // 删除参数配置
export function delConfig(configId) { export function delConfig(configId) {
return request({ return request({
url: '/system/config/' + configId, url: '/system/config/' + configId,
method: 'delete' method: 'delete'
}) })
} }
// 刷新参数缓存 // 刷新参数缓存
export function refreshCache() { export function refreshCache() {
return request({ return request({
url: '/system/config/refreshCache', url: '/system/config/refreshCache',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,52 +1,52 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询部门列表 // 查询部门列表
export function listDept(query) { export function listDept(query) {
return request({ return request({
url: '/system/dept/list', url: '/system/dept/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询部门列表(排除节点) // 查询部门列表(排除节点)
export function listDeptExcludeChild(deptId) { export function listDeptExcludeChild(deptId) {
return request({ return request({
url: '/system/dept/list/exclude/' + deptId, url: '/system/dept/list/exclude/' + deptId,
method: 'get' method: 'get'
}) })
} }
// 查询部门详细 // 查询部门详细
export function getDept(deptId) { export function getDept(deptId) {
return request({ return request({
url: '/system/dept/' + deptId, url: '/system/dept/' + deptId,
method: 'get' method: 'get'
}) })
} }
// 新增部门 // 新增部门
export function addDept(data) { export function addDept(data) {
return request({ return request({
url: '/system/dept', url: '/system/dept',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改部门 // 修改部门
export function updateDept(data) { export function updateDept(data) {
return request({ return request({
url: '/system/dept', url: '/system/dept',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除部门 // 删除部门
export function delDept(deptId) { export function delDept(deptId) {
return request({ return request({
url: '/system/dept/' + deptId, url: '/system/dept/' + deptId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,52 +1,52 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询字典数据列表 // 查询字典数据列表
export function listData(query) { export function listData(query) {
return request({ return request({
url: '/system/dict/data/list', url: '/system/dict/data/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询字典数据详细 // 查询字典数据详细
export function getData(dictCode) { export function getData(dictCode) {
return request({ return request({
url: '/system/dict/data/' + dictCode, url: '/system/dict/data/' + dictCode,
method: 'get' method: 'get'
}) })
} }
// 根据字典类型查询字典数据信息 // 根据字典类型查询字典数据信息
export function getDicts(dictType) { export function getDicts(dictType) {
return request({ return request({
url: '/system/dict/data/type/' + dictType, url: '/system/dict/data/type/' + dictType,
method: 'get' method: 'get'
}) })
} }
// 新增字典数据 // 新增字典数据
export function addData(data) { export function addData(data) {
return request({ return request({
url: '/system/dict/data', url: '/system/dict/data',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改字典数据 // 修改字典数据
export function updateData(data) { export function updateData(data) {
return request({ return request({
url: '/system/dict/data', url: '/system/dict/data',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除字典数据 // 删除字典数据
export function delData(dictCode) { export function delData(dictCode) {
return request({ return request({
url: '/system/dict/data/' + dictCode, url: '/system/dict/data/' + dictCode,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,60 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询字典类型列表 // 查询字典类型列表
export function listType(query) { export function listType(query) {
return request({ return request({
url: '/system/dict/type/list', url: '/system/dict/type/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询字典类型详细 // 查询字典类型详细
export function getType(dictId) { export function getType(dictId) {
return request({ return request({
url: '/system/dict/type/' + dictId, url: '/system/dict/type/' + dictId,
method: 'get' method: 'get'
}) })
} }
// 新增字典类型 // 新增字典类型
export function addType(data) { export function addType(data) {
return request({ return request({
url: '/system/dict/type', url: '/system/dict/type',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改字典类型 // 修改字典类型
export function updateType(data) { export function updateType(data) {
return request({ return request({
url: '/system/dict/type', url: '/system/dict/type',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除字典类型 // 删除字典类型
export function delType(dictId) { export function delType(dictId) {
return request({ return request({
url: '/system/dict/type/' + dictId, url: '/system/dict/type/' + dictId,
method: 'delete' method: 'delete'
}) })
} }
// 刷新字典缓存 // 刷新字典缓存
export function refreshCache() { export function refreshCache() {
return request({ return request({
url: '/system/dict/type/refreshCache', url: '/system/dict/type/refreshCache',
method: 'delete' method: 'delete'
}) })
} }
// 获取字典选择框列表 // 获取字典选择框列表
export function optionselect() { export function optionselect() {
return request({ return request({
url: '/system/dict/type/optionselect', url: '/system/dict/type/optionselect',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,33 +1,33 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询登录日志列表 // 查询登录日志列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/system/logininfor/list', url: '/system/logininfor/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 删除登录日志 // 删除登录日志
export function delLogininfor(infoId) { export function delLogininfor(infoId) {
return request({ return request({
url: '/system/logininfor/' + infoId, url: '/system/logininfor/' + infoId,
method: 'delete' method: 'delete'
}) })
} }
// 解锁用户登录状态 // 解锁用户登录状态
export function unlockLogininfor(userName) { export function unlockLogininfor(userName) {
return request({ return request({
url: '/system/logininfor/unlock/' + userName, url: '/system/logininfor/unlock/' + userName,
method: 'get' method: 'get'
}) })
} }
// 清空登录日志 // 清空登录日志
export function cleanLogininfor() { export function cleanLogininfor() {
return request({ return request({
url: '/system/logininfor/clean', url: '/system/logininfor/clean',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,60 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询菜单列表 // 查询菜单列表
export function listMenu(query) { export function listMenu(query) {
return request({ return request({
url: '/system/menu/list', url: '/system/menu/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询菜单详细 // 查询菜单详细
export function getMenu(menuId) { export function getMenu(menuId) {
return request({ return request({
url: '/system/menu/' + menuId, url: '/system/menu/' + menuId,
method: 'get' method: 'get'
}) })
} }
// 查询菜单下拉树结构 // 查询菜单下拉树结构
export function treeselect() { export function treeselect() {
return request({ return request({
url: '/system/menu/treeselect', url: '/system/menu/treeselect',
method: 'get' method: 'get'
}) })
} }
// 根据角色ID查询菜单下拉树结构 // 根据角色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId) { export function roleMenuTreeselect(roleId) {
return request({ return request({
url: '/system/menu/roleMenuTreeselect/' + roleId, url: '/system/menu/roleMenuTreeselect/' + roleId,
method: 'get' method: 'get'
}) })
} }
// 新增菜单 // 新增菜单
export function addMenu(data) { export function addMenu(data) {
return request({ return request({
url: '/system/menu', url: '/system/menu',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改菜单 // 修改菜单
export function updateMenu(data) { export function updateMenu(data) {
return request({ return request({
url: '/system/menu', url: '/system/menu',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除菜单 // 删除菜单
export function delMenu(menuId) { export function delMenu(menuId) {
return request({ return request({
url: '/system/menu/' + menuId, url: '/system/menu/' + menuId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,44 +1,44 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询公告列表 // 查询公告列表
export function listNotice(query) { export function listNotice(query) {
return request({ return request({
url: '/system/notice/list', url: '/system/notice/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询公告详细 // 查询公告详细
export function getNotice(noticeId) { export function getNotice(noticeId) {
return request({ return request({
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'get' method: 'get'
}) })
} }
// 新增公告 // 新增公告
export function addNotice(data) { export function addNotice(data) {
return request({ return request({
url: '/system/notice', url: '/system/notice',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改公告 // 修改公告
export function updateNotice(data) { export function updateNotice(data) {
return request({ return request({
url: '/system/notice', url: '/system/notice',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除公告 // 删除公告
export function delNotice(noticeId) { export function delNotice(noticeId) {
return request({ return request({
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,26 +1,26 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询操作日志列表 // 查询操作日志列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/system/operlog/list', url: '/system/operlog/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 删除操作日志 // 删除操作日志
export function delOperlog(operId) { export function delOperlog(operId) {
return request({ return request({
url: '/system/operlog/' + operId, url: '/system/operlog/' + operId,
method: 'delete' method: 'delete'
}) })
} }
// 清空操作日志 // 清空操作日志
export function cleanOperlog() { export function cleanOperlog() {
return request({ return request({
url: '/system/operlog/clean', url: '/system/operlog/clean',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,44 +1,44 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询岗位列表 // 查询岗位列表
export function listPost(query) { export function listPost(query) {
return request({ return request({
url: '/system/post/list', url: '/system/post/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询岗位详细 // 查询岗位详细
export function getPost(postId) { export function getPost(postId) {
return request({ return request({
url: '/system/post/' + postId, url: '/system/post/' + postId,
method: 'get' method: 'get'
}) })
} }
// 新增岗位 // 新增岗位
export function addPost(data) { export function addPost(data) {
return request({ return request({
url: '/system/post', url: '/system/post',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改岗位 // 修改岗位
export function updatePost(data) { export function updatePost(data) {
return request({ return request({
url: '/system/post', url: '/system/post',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除岗位 // 删除岗位
export function delPost(postId) { export function delPost(postId) {
return request({ return request({
url: '/system/post/' + postId, url: '/system/post/' + postId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,119 +1,119 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询角色列表 // 查询角色列表
export function listRole(query) { export function listRole(query) {
return request({ return request({
url: '/system/role/list', url: '/system/role/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询角色详细 // 查询角色详细
export function getRole(roleId) { export function getRole(roleId) {
return request({ return request({
url: '/system/role/' + roleId, url: '/system/role/' + roleId,
method: 'get' method: 'get'
}) })
} }
// 新增角色 // 新增角色
export function addRole(data) { export function addRole(data) {
return request({ return request({
url: '/system/role', url: '/system/role',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改角色 // 修改角色
export function updateRole(data) { export function updateRole(data) {
return request({ return request({
url: '/system/role', url: '/system/role',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 角色数据权限 // 角色数据权限
export function dataScope(data) { export function dataScope(data) {
return request({ return request({
url: '/system/role/dataScope', url: '/system/role/dataScope',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 角色状态修改 // 角色状态修改
export function changeRoleStatus(roleId, status) { export function changeRoleStatus(roleId, status) {
const data = { const data = {
roleId, roleId,
status status
} }
return request({ return request({
url: '/system/role/changeStatus', url: '/system/role/changeStatus',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除角色 // 删除角色
export function delRole(roleId) { export function delRole(roleId) {
return request({ return request({
url: '/system/role/' + roleId, url: '/system/role/' + roleId,
method: 'delete' method: 'delete'
}) })
} }
// 查询角色已授权用户列表 // 查询角色已授权用户列表
export function allocatedUserList(query) { export function allocatedUserList(query) {
return request({ return request({
url: '/system/role/authUser/allocatedList', url: '/system/role/authUser/allocatedList',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询角色未授权用户列表 // 查询角色未授权用户列表
export function unallocatedUserList(query) { export function unallocatedUserList(query) {
return request({ return request({
url: '/system/role/authUser/unallocatedList', url: '/system/role/authUser/unallocatedList',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 取消用户授权角色 // 取消用户授权角色
export function authUserCancel(data) { export function authUserCancel(data) {
return request({ return request({
url: '/system/role/authUser/cancel', url: '/system/role/authUser/cancel',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 批量取消用户授权角色 // 批量取消用户授权角色
export function authUserCancelAll(data) { export function authUserCancelAll(data) {
return request({ return request({
url: '/system/role/authUser/cancelAll', url: '/system/role/authUser/cancelAll',
method: 'put', method: 'put',
params: data params: data
}) })
} }
// 授权用户选择 // 授权用户选择
export function authUserSelectAll(data) { export function authUserSelectAll(data) {
return request({ return request({
url: '/system/role/authUser/selectAll', url: '/system/role/authUser/selectAll',
method: 'put', method: 'put',
params: data params: data
}) })
} }
// 根据角色ID查询部门树结构 // 根据角色ID查询部门树结构
export function deptTreeSelect(roleId) { export function deptTreeSelect(roleId) {
return request({ return request({
url: '/system/role/deptTree/' + roleId, url: '/system/role/deptTree/' + roleId,
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,76 +1,76 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询生成表数据 // 查询生成表数据
export function listTable(query) { export function listTable(query) {
return request({ return request({
url: '/code/gen/list', url: '/code/gen/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询db数据库列表 // 查询db数据库列表
export function listDbTable(query) { export function listDbTable(query) {
return request({ return request({
url: '/code/gen/db/list', url: '/code/gen/db/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询表详细信息 // 查询表详细信息
export function getGenTable(tableId) { export function getGenTable(tableId) {
return request({ return request({
url: '/code/gen/' + tableId, url: '/code/gen/' + tableId,
method: 'get' method: 'get'
}) })
} }
// 修改代码生成信息 // 修改代码生成信息
export function updateGenTable(data) { export function updateGenTable(data) {
return request({ return request({
url: '/code/gen', url: '/code/gen',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 导入表 // 导入表
export function importTable(data) { export function importTable(data) {
return request({ return request({
url: '/code/gen/importTable', url: '/code/gen/importTable',
method: 'post', method: 'post',
params: data params: data
}) })
} }
// 预览生成代码 // 预览生成代码
export function previewTable(tableId) { export function previewTable(tableId) {
return request({ return request({
url: '/code/gen/preview/' + tableId, url: '/code/gen/preview/' + tableId,
method: 'get' method: 'get'
}) })
} }
// 删除表数据 // 删除表数据
export function delTable(tableId) { export function delTable(tableId) {
return request({ return request({
url: '/code/gen/' + tableId, url: '/code/gen/' + tableId,
method: 'delete' method: 'delete'
}) })
} }
// 生成代码(自定义路径) // 生成代码(自定义路径)
export function genCode(tableName) { export function genCode(tableName) {
return request({ return request({
url: '/code/gen/genCode/' + tableName, url: '/code/gen/genCode/' + tableName,
method: 'get' method: 'get'
}) })
} }
// 同步数据库 // 同步数据库
export function synchDb(tableName) { export function synchDb(tableName) {
return request({ return request({
url: '/code/gen/synchDb/' + tableName, url: '/code/gen/synchDb/' + tableName,
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,9 +0,0 @@
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component
// register globally
Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req)

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg> </style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 883 B

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1605865043777" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="856" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css"></style></defs><path d="M1023.786667 611.84c-0.426667 9.770667-13.354667 20.693333-39.893334 34.56-54.613333 28.458667-337.749333 144.896-397.994666 176.298667-60.288 31.402667-93.738667 31.104-141.354667 8.32-47.616-22.741333-348.842667-144.469333-403.114667-170.368-27.093333-12.970667-40.917333-23.893333-41.386666-34.218667v103.509333c0 10.325333 14.250667 21.290667 41.386666 34.261334 54.272 25.941333 355.541333 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.362667 60.245333-31.402667 343.338667-147.797333 397.994666-176.298667 27.776-14.464 40.106667-25.728 40.106667-35.925333v-102.058667l-0.213333-0.085333z m0-168.746667c-0.512 9.770667-13.397333 20.650667-39.893334 34.517334-54.613333 28.458667-337.749333 144.896-397.994666 176.298666-60.288 31.402667-93.738667 31.104-141.354667 8.362667-47.616-22.741333-348.842667-144.469333-403.114667-170.410667-27.093333-12.928-40.917333-23.893333-41.386666-34.176v103.509334c0 10.325333 14.250667 21.248 41.386666 34.218666 54.272 25.941333 355.498667 147.626667 403.114667 170.368 47.616 22.784 81.066667 23.082667 141.354667-8.32 60.245333-31.402667 343.338667-147.84 397.994666-176.298666 27.776-14.506667 40.106667-25.770667 40.106667-35.968v-102.058667l-0.256-0.042667z m0-175.018666c0.469333-10.410667-13.141333-19.541333-40.533334-29.610667-53.248-19.498667-334.634667-131.498667-388.522666-151.253333-53.888-19.712-75.818667-18.901333-139.093334 3.84C392.234667 113.706667 92.629333 231.253333 39.338667 252.074667c-26.666667 10.496-39.68 20.181333-39.253334 30.506666V386.133333c0 10.325333 14.250667 21.248 41.386667 34.218667 54.272 25.941333 355.498667 147.669333 403.114667 170.410667 47.616 22.741333 81.066667 23.04 141.354666-8.362667 60.245333-31.402667 343.338667-147.84 397.994667-176.298667 27.776-14.506667 40.106667-25.770667 40.106667-35.968V268.074667h-0.341334zM366.677333 366.08l237.269334-36.437333-71.68 105.088-165.546667-68.650667z m524.8-94.634667l-140.330666 55.466667-15.232 5.973333-140.245334-55.466666 155.392-61.44 140.373334 55.466666z m-411.989333-101.674666l-22.954667-42.325334 71.594667 27.989334 67.498667-22.101334-18.261334 43.733334 68.778667 25.770666-88.704 9.216-19.882667 47.786667-32.085333-53.290667-102.4-9.216 76.416-27.562666z m-176.768 59.733333c70.058667 0 126.805333 21.973333 126.805333 49.109333s-56.746667 49.152-126.805333 49.152-126.848-22.058667-126.848-49.152c0-27.136 56.789333-49.152 126.848-49.152z" p-id="857"></path></svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg> </style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,22 +0,0 @@
# replace default config
# multipass: true
# full: true
plugins:
# - name
#
# or:
# - name: false
# - name: true
#
# or:
# - name:
# param1: 1
# param2: 2
- removeAttrs:
attrs:
- 'fill'
- 'fill-rule'

View File

@@ -1,39 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge> <feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode> <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge> </feMerge>
</filter> </filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter> </filter>
</defs> </defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)"> <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)"> <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)"> <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white"> <mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use> <use xlink:href="#path-2"></use>
</mask> </mask>
<g id="Rectangle-18"> <g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g> </g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,39 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge> <feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode> <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge> </feMerge>
</filter> </filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter> </filter>
</defs> </defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)"> <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)"> <g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)"> <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white"> <mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use> <use xlink:href="#path-2"></use>
</mask> </mask>
<g id="Rectangle-18"> <g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g> </g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

@@ -1,99 +1,99 @@
@import './variables.scss'; @import './variables.module.scss';
@mixin colorBtn($color) { @mixin colorBtn($color) {
background: $color; background: $color;
&:hover { &:hover {
color: $color; color: $color;
&:before, &:before,
&:after { &:after {
background: $color; background: $color;
} }
} }
} }
.blue-btn { .blue-btn {
@include colorBtn($blue) @include colorBtn($blue)
} }
.light-blue-btn { .light-blue-btn {
@include colorBtn($light-blue) @include colorBtn($light-blue)
} }
.red-btn { .red-btn {
@include colorBtn($red) @include colorBtn($red)
} }
.pink-btn { .pink-btn {
@include colorBtn($pink) @include colorBtn($pink)
} }
.green-btn { .green-btn {
@include colorBtn($green) @include colorBtn($green)
} }
.tiffany-btn { .tiffany-btn {
@include colorBtn($tiffany) @include colorBtn($tiffany)
} }
.yellow-btn { .yellow-btn {
@include colorBtn($yellow) @include colorBtn($yellow)
} }
.pan-btn { .pan-btn {
font-size: 14px; font-size: 14px;
color: #fff; color: #fff;
padding: 14px 36px; padding: 14px 36px;
border-radius: 8px; border-radius: 8px;
border: none; border: none;
outline: none; outline: none;
transition: 600ms ease all; transition: 600ms ease all;
position: relative; position: relative;
display: inline-block; display: inline-block;
&:hover { &:hover {
background: #fff; background: #fff;
&:before, &:before,
&:after { &:after {
width: 100%; width: 100%;
transition: 600ms ease all; transition: 600ms ease all;
} }
} }
&:before, &:before,
&:after { &:after {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
height: 2px; height: 2px;
width: 0; width: 0;
transition: 400ms ease all; transition: 400ms ease all;
} }
&::after { &::after {
right: inherit; right: inherit;
top: inherit; top: inherit;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
} }
.custom-button { .custom-button {
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
background: #fff; background: #fff;
color: #fff; color: #fff;
-webkit-appearance: none; -webkit-appearance: none;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
outline: 0; outline: 0;
margin: 0; margin: 0;
padding: 10px 15px; padding: 10px 15px;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
} }

View File

@@ -1,84 +1,96 @@
// cover some element-ui styles // cover some element-ui styles
.el-breadcrumb__inner, .el-breadcrumb__inner,
.el-breadcrumb__inner a { .el-breadcrumb__inner a {
font-weight: 400 !important; font-weight: 400 !important;
} }
.el-upload { .el-upload {
input[type="file"] { input[type="file"] {
display: none !important; display: none !important;
} }
} }
.el-upload__input { .el-upload__input {
display: none; display: none;
} }
.cell { .cell {
.el-tag { .el-tag {
margin-right: 0px; margin-right: 0px;
} }
} }
.small-padding { .small-padding {
.cell { .cell {
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
} }
.fixed-width { .fixed-width {
.el-button--mini { .el-button--mini {
padding: 7px 10px; padding: 7px 10px;
width: 60px; width: 60px;
} }
} }
.status-col { .status-col {
.cell { .cell {
padding: 0 10px; padding: 0 10px;
text-align: center; text-align: center;
.el-tag { .el-tag {
margin-right: 0px; margin-right: 0px;
} }
} }
} }
// to fixed https://github.com/ElemeFE/element/issues/2461 // to fixed https://github.com/ElemeFE/element/issues/2461
.el-dialog { .el-dialog {
transform: none; transform: none;
left: 0; left: 0;
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
} }
// refine element ui upload // refine element ui upload
.upload-container { .upload-container {
.el-upload { .el-upload {
width: 100%; width: 100%;
.el-upload-dragger { .el-upload-dragger {
width: 100%; width: 100%;
height: 200px; height: 200px;
} }
} }
} }
// dropdown // dropdown
.el-dropdown-menu { .el-dropdown-menu {
a { a {
display: block display: block
} }
} }
// fix date-picker ui bug in filter-item // fix date-picker ui bug in filter-item
.el-range-editor.el-input__inner { .el-range-editor.el-input__inner {
display: inline-flex !important; display: inline-flex !important;
} }
// to fix el-date-picker css style // to fix el-date-picker css style
.el-range-separator { .el-range-separator {
box-sizing: content-box; box-sizing: content-box;
} }
.el-menu--collapse
> div
> .el-submenu
> .el-submenu__title
.el-submenu__icon-arrow {
display: none;
}
.el-dropdown .el-dropdown-link{
color: var(--el-color-primary) !important;
}

View File

@@ -1,31 +0,0 @@
/**
* I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking.
**/
/* theme color */
$--color-primary: #1890ff;
$--color-success: #13ce66;
$--color-warning: #ffba00;
$--color-danger: #ff4949;
// $--color-info: #1E1E1E;
$--button-font-weight: 400;
// $--color-text-regular: #1f2d3d;
$--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5;
$--table-border:1px solid#dfe6ec;
/* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
theme: $--color-primary;
}

View File

@@ -1,182 +1,184 @@
@import './variables.scss'; @import './variables.module.scss';
@import './mixin.scss'; @import './mixin.scss';
@import './transition.scss'; @import './transition.scss';
@import './element-ui.scss'; @import './element-ui.scss';
@import './sidebar.scss'; @import './sidebar.scss';
@import './btn.scss'; @import './btn.scss';
@import './ruoyi.scss';
body {
height: 100%; body {
-moz-osx-font-smoothing: grayscale; height: 100%;
-webkit-font-smoothing: antialiased; margin: 0;
text-rendering: optimizeLegibility; -moz-osx-font-smoothing: grayscale;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif; -webkit-font-smoothing: antialiased;
} text-rendering: optimizeLegibility;
font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
label { }
font-weight: 700;
} label {
font-weight: 700;
html { }
height: 100%;
box-sizing: border-box; html {
} height: 100%;
box-sizing: border-box;
#app { }
height: 100%;
} #app {
height: 100%;
*, }
*:before,
*:after { *,
box-sizing: inherit; *:before,
} *:after {
box-sizing: inherit;
.no-padding { }
padding: 0px !important;
} .no-padding {
padding: 0px !important;
.padding-content { }
padding: 4px 0;
} .padding-content {
padding: 4px 0;
a:focus, }
a:active {
outline: none; a:focus,
} a:active {
outline: none;
a, }
a:focus,
a:hover { a,
cursor: pointer; a:focus,
color: inherit; a:hover {
text-decoration: none; cursor: pointer;
} color: inherit;
text-decoration: none;
div:focus { }
outline: none;
} div:focus {
outline: none;
.fr { }
float: right;
} .fr {
float: right;
.fl { }
float: left;
} .fl {
float: left;
.pr-5 { }
padding-right: 5px;
} .pr-5 {
padding-right: 5px;
.pl-5 { }
padding-left: 5px;
} .pl-5 {
padding-left: 5px;
.block { }
display: block;
} .block {
display: block;
.pointer { }
cursor: pointer;
} .pointer {
cursor: pointer;
.inlineBlock { }
display: block;
} .inlineBlock {
display: block;
.clearfix { }
&:after {
visibility: hidden; .clearfix {
display: block; &:after {
font-size: 0; visibility: hidden;
content: " "; display: block;
clear: both; font-size: 0;
height: 0; content: " ";
} clear: both;
} height: 0;
}
aside { }
background: #eef1f6;
padding: 8px 24px; aside {
margin-bottom: 20px; background: #eef1f6;
border-radius: 2px; padding: 8px 24px;
display: block; margin-bottom: 20px;
line-height: 32px; border-radius: 2px;
font-size: 16px; display: block;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; line-height: 32px;
color: #2c3e50; font-size: 16px;
-webkit-font-smoothing: antialiased; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
-moz-osx-font-smoothing: grayscale; color: #2c3e50;
-webkit-font-smoothing: antialiased;
a { -moz-osx-font-smoothing: grayscale;
color: #337ab7;
cursor: pointer; a {
color: #337ab7;
&:hover { cursor: pointer;
color: rgb(32, 160, 255);
} &:hover {
} color: rgb(32, 160, 255);
} }
}
//main-container全局样式 }
.app-container {
padding: 20px; //main-container全局样式
} .app-container {
padding: 20px;
.components-container { }
margin: 30px 50px;
position: relative; .components-container {
} margin: 30px 50px;
position: relative;
.pagination-container { }
margin-top: 30px;
} .pagination-container {
margin-top: 30px;
.text-center { }
text-align: center
} .text-center {
text-align: center
.sub-navbar { }
height: 50px;
line-height: 50px; .sub-navbar {
position: relative; height: 50px;
width: 100%; line-height: 50px;
text-align: right; position: relative;
padding-right: 20px; width: 100%;
transition: 600ms ease position; text-align: right;
background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%); padding-right: 20px;
transition: 600ms ease position;
.subtitle { background: linear-gradient(90deg, rgba(32, 182, 249, 1) 0%, rgba(32, 182, 249, 1) 0%, rgba(33, 120, 241, 1) 100%, rgba(33, 120, 241, 1) 100%);
font-size: 20px;
color: #fff; .subtitle {
} font-size: 20px;
color: #fff;
&.draft { }
background: #d0d0d0;
} &.draft {
background: #d0d0d0;
&.deleted { }
background: #d0d0d0;
} &.deleted {
} background: #d0d0d0;
}
.link-type, }
.link-type:focus {
color: #337ab7; .link-type,
cursor: pointer; .link-type:focus {
color: #337ab7;
&:hover { cursor: pointer;
color: rgb(32, 160, 255);
} &:hover {
} color: rgb(32, 160, 255);
}
.filter-container { }
padding-bottom: 10px;
.filter-container {
.filter-item { padding-bottom: 10px;
display: inline-block;
vertical-align: middle; .filter-item {
margin-bottom: 10px; display: inline-block;
} vertical-align: middle;
} margin-bottom: 10px;
}
}

View File

@@ -1,66 +1,66 @@
@mixin clearfix { @mixin clearfix {
&:after { &:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
} }
@mixin scrollBar { @mixin scrollBar {
&::-webkit-scrollbar-track-piece { &::-webkit-scrollbar-track-piece {
background: #d3dce6; background: #d3dce6;
} }
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #99a9bf; background: #99a9bf;
border-radius: 20px; border-radius: 20px;
} }
} }
@mixin relative { @mixin relative {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@mixin pct($pct) { @mixin pct($pct) {
width: #{$pct}; width: #{$pct};
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
} }
@mixin triangle($width, $height, $color, $direction) { @mixin triangle($width, $height, $color, $direction) {
$width: $width/2; $width: $width/2;
$color-border-style: $height solid $color; $color-border-style: $height solid $color;
$transparent-border-style: $width solid transparent; $transparent-border-style: $width solid transparent;
height: 0; height: 0;
width: 0; width: 0;
@if $direction==up { @if $direction==up {
border-bottom: $color-border-style; border-bottom: $color-border-style;
border-left: $transparent-border-style; border-left: $transparent-border-style;
border-right: $transparent-border-style; border-right: $transparent-border-style;
} }
@else if $direction==right { @else if $direction==right {
border-left: $color-border-style; border-left: $color-border-style;
border-top: $transparent-border-style; border-top: $transparent-border-style;
border-bottom: $transparent-border-style; border-bottom: $transparent-border-style;
} }
@else if $direction==down { @else if $direction==down {
border-top: $color-border-style; border-top: $color-border-style;
border-left: $transparent-border-style; border-left: $transparent-border-style;
border-right: $transparent-border-style; border-right: $transparent-border-style;
} }
@else if $direction==left { @else if $direction==left {
border-right: $color-border-style; border-right: $color-border-style;
border-top: $transparent-border-style; border-top: $transparent-border-style;
border-bottom: $transparent-border-style; border-bottom: $transparent-border-style;
} }
} }

View File

@@ -1,291 +1,281 @@
/** /**
* 通用css样式布局处理 * 通用css样式布局处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
/** 基础通用 **/ /** 基础通用 **/
.pt5 { .pt5 {
padding-top: 5px; padding-top: 5px;
} }
.pr5 {
.pr5 { padding-right: 5px;
padding-right: 5px; }
} .pb5 {
padding-bottom: 5px;
.pb5 { }
padding-bottom: 5px; .mt5 {
} margin-top: 5px;
}
.mt5 { .mr5 {
margin-top: 5px; margin-right: 5px;
} }
.mb5 {
.mr5 { margin-bottom: 5px;
margin-right: 5px; }
} .mb8 {
margin-bottom: 8px;
.mb5 { }
margin-bottom: 5px; .ml5 {
} margin-left: 5px;
}
.mb8 { .mt10 {
margin-bottom: 8px; margin-top: 10px;
} }
.mr10 {
.ml5 { margin-right: 10px;
margin-left: 5px; }
} .mb10 {
margin-bottom: 10px;
.mt10 { }
margin-top: 10px; .ml10 {
} margin-left: 10px;
}
.mr10 { .mt20 {
margin-right: 10px; margin-top: 20px;
} }
.mr20 {
.mb10 { margin-right: 20px;
margin-bottom: 10px; }
} .mb20 {
.ml10 { margin-bottom: 20px;
margin-left: 10px; }
} .ml20 {
margin-left: 20px;
.mt20 { }
margin-top: 20px;
} .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
.mr20 { font-weight: 500;
margin-right: 20px; line-height: 1.1;
} color: inherit;
}
.mb20 {
margin-bottom: 20px; .el-form .el-form-item__label {
} font-weight: 700;
.ml20 { }
margin-left: 20px; .el-dialog:not(.is-fullscreen) {
} margin-top: 6vh !important;
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit; .el-dialog.scrollbar .el-dialog__body {
font-weight: 500; overflow: auto;
line-height: 1.1; overflow-x: hidden;
color: inherit; max-height: 70vh;
} padding: 10px 20px 0;
}
.el-message-box__status + .el-message-box__message{
word-break: break-word; .el-table {
} .el-table__header-wrapper, .el-table__fixed-header-wrapper {
th {
.el-dialog:not(.is-fullscreen) { word-break: break-word;
margin-top: 6vh !important; background-color: #f8f8f9 !important;
} color: #515a6e;
height: 40px !important;
.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { font-size: 13px;
overflow: auto; }
overflow-x: hidden; }
max-height: 70vh; .el-table__body-wrapper {
padding: 10px 20px 0; .el-button [class*="el-icon-"] + span {
} margin-left: 1px;
}
.el-table { }
.el-table__header-wrapper, .el-table__fixed-header-wrapper { }
th {
word-break: break-word; /** 表单布局 **/
background-color: #f8f8f9; .form-header {
color: #515a6e; font-size:15px;
height: 40px; color:#6379bb;
font-size: 13px; border-bottom:1px solid #ddd;
} margin:8px 10px 25px 10px;
} padding-bottom:5px
}
.el-table__body-wrapper {
.el-button [class*="el-icon-"] + span { /** 表格布局 **/
margin-left: 1px; .pagination-container {
} position: relative;
} height: 25px;
} margin-bottom: 10px;
margin-top: 15px;
/** 表单布局 **/ padding: 10px 20px !important;
.form-header { }
font-size: 15px;
color: #6379bb; .el-dialog .pagination-container {
border-bottom: 1px solid #ddd; position: static !important;
margin: 8px 10px 25px 10px; }
padding-bottom: 5px
} /* tree border */
.tree-border {
/** 表格布局 **/ margin-top: 5px;
.pagination-container { border: 1px solid #e5e6e7;
position: relative; background: #FFFFFF none;
height: 25px; border-radius:4px;
margin-bottom: 10px; width: 100%;
margin-top: 15px; }
padding: 10px 20px !important;
} .pagination-container .el-pagination {
right: 0;
/* tree border */ position: absolute;
.tree-border { }
margin-top: 5px;
border: 1px solid #e5e6e7; @media ( max-width : 768px) {
background: #FFFFFF none; .pagination-container .el-pagination > .el-pagination__jump {
border-radius: 4px; display: none !important;
} }
.pagination-container .el-pagination > .el-pagination__sizes {
.pagination-container .el-pagination { display: none !important;
right: 0; }
position: absolute; }
}
.el-table .fixed-width .el-button--small {
@media (max-width: 768px) { padding-left: 0;
.pagination-container .el-pagination > .el-pagination__jump { padding-right: 0;
display: none !important; width: inherit;
} }
.pagination-container .el-pagination > .el-pagination__sizes {
display: none !important; /** 表格更多操作下拉样式 */
} .el-table .el-dropdown-link {
} cursor: pointer;
color: #409EFF;
.el-table .fixed-width .el-button--mini { margin-left: 10px;
padding-left: 0; }
padding-right: 0;
width: inherit; .el-table .el-dropdown, .el-icon-arrow-down {
} font-size: 12px;
}
/** 表格更多操作下拉样式 */
.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine { .el-tree-node__content > .el-checkbox {
cursor: pointer; margin-right: 8px;
margin-left: 5px; }
}
.list-group-striped > .list-group-item {
.el-table .el-dropdown, .el-icon-arrow-down { border-left: 0;
font-size: 12px; border-right: 0;
} border-radius: 0;
padding-left: 0;
.el-tree-node__content > .el-checkbox { padding-right: 0;
margin-right: 8px; }
}
.list-group {
.list-group-striped > .list-group-item { padding-left: 0px;
border-left: 0; list-style: none;
border-right: 0; }
border-radius: 0;
padding-left: 0; .list-group-item {
padding-right: 0; border-bottom: 1px solid #e7eaec;
} border-top: 1px solid #e7eaec;
margin-bottom: -1px;
.list-group { padding: 11px 0px;
padding-left: 0px; font-size: 13px;
list-style: none; }
}
.pull-right {
.list-group-item { float: right !important;
border-bottom: 1px solid #e7eaec; }
border-top: 1px solid #e7eaec;
margin-bottom: -1px; .el-card__header {
padding: 11px 0px; padding: 14px 15px 7px !important;
font-size: 13px; min-height: 40px;
} }
.pull-right { .el-card__body {
float: right !important; padding: 15px 20px 20px 20px !important;
} }
.el-card__header { .card-box {
padding: 14px 15px 7px; padding-right: 15px;
min-height: 40px; padding-left: 15px;
} margin-bottom: 10px;
}
.el-card__body {
padding: 15px 20px 20px 20px; /* button color */
} .el-button--cyan.is-active,
.el-button--cyan:active {
.card-box { background: #20B2AA;
padding-right: 15px; border-color: #20B2AA;
padding-left: 15px; color: #FFFFFF;
margin-bottom: 10px; }
}
.el-button--cyan:focus,
/* button color */ .el-button--cyan:hover {
.el-button--cyan.is-active, background: #48D1CC;
.el-button--cyan:active { border-color: #48D1CC;
background: #20B2AA; color: #FFFFFF;
border-color: #20B2AA; }
color: #FFFFFF;
} .el-button--cyan {
background-color: #20B2AA;
.el-button--cyan:focus, border-color: #20B2AA;
.el-button--cyan:hover { color: #FFFFFF;
background: #48D1CC; }
border-color: #48D1CC;
color: #FFFFFF; /* text color */
} .text-navy {
color: #1ab394;
.el-button--cyan { }
background-color: #20B2AA;
border-color: #20B2AA; .text-primary {
color: #FFFFFF; color: inherit;
} }
/* text color */ .text-success {
.text-navy { color: #1c84c6;
color: #1ab394; }
}
.text-info {
.text-primary { color: #23c6c8;
color: inherit; }
}
.text-warning {
.text-success { color: #f8ac59;
color: #1c84c6; }
}
.text-danger {
.text-info { color: #ed5565;
color: #23c6c8; }
}
.text-muted {
.text-warning { color: #888888;
color: #f8ac59; }
}
/* image */
.text-danger { .img-circle {
color: #ed5565; border-radius: 50%;
} }
.text-muted { .img-lg {
color: #888888; width: 120px;
} height: 120px;
}
/* image */
.img-circle { .avatar-upload-preview {
border-radius: 50%; position: absolute;
} top: 50%;
transform: translate(50%, -50%);
.img-lg { width: 200px;
width: 120px; height: 200px;
height: 120px; border-radius: 50%;
} box-shadow: 0 0 4px #ccc;
overflow: hidden;
.avatar-upload-preview { }
position: relative;
top: 50%; /* 拖拽列样式 */
left: 50%; .sortable-ghost{
transform: translate(-50%, -50%); opacity: .8;
width: 200px; color: #fff!important;
height: 200px; background: #42b983!important;
border-radius: 50%; }
box-shadow: 0 0 4px #ccc;
overflow: hidden; /* 表格右侧工具栏样式 */
} .top-right-btn {
margin-left: auto;
/* 拖拽列样式 */ }
.sortable-ghost {
opacity: .8;
color: #fff !important;
background: #42b983 !important;
}
.top-right-btn {
position: relative;
float: right;
}

View File

@@ -1,227 +1,238 @@
#app { #app {
.main-container { .main-container {
height: 100%; height: 100%;
transition: margin-left .28s; transition: margin-left .28s;
margin-left: $base-sidebar-width; margin-left: $base-sidebar-width;
position: relative; position: relative;
} }
.sidebarHide { .sidebarHide {
margin-left: 0!important; margin-left: 0!important;
} }
.sidebar-container { .sidebar-container {
-webkit-transition: width .28s; -webkit-transition: width .28s;
transition: width 0.28s; transition: width 0.28s;
width: $base-sidebar-width !important; width: $base-sidebar-width !important;
background-color: $base-menu-background; background-color: $base-menu-background;
height: 100%; height: 100%;
position: fixed; position: fixed;
font-size: 0px; font-size: 0px;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 1001; z-index: 1001;
overflow: hidden; overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 2px 0 6px rgba(0,21,41,.35); box-shadow: 2px 0 6px rgba(0,21,41,.35);
// reset element-ui css // reset element-ui css
.horizontal-collapse-transition { .horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
} }
.scrollbar-wrapper { .scrollbar-wrapper {
overflow-x: hidden !important; overflow-x: hidden !important;
} }
.el-scrollbar__bar.is-vertical { .el-scrollbar__bar.is-vertical {
right: 0px; right: 0px;
} }
.el-scrollbar { .el-scrollbar {
height: 100%; height: 100%;
} }
&.has-logo { &.has-logo {
.el-scrollbar { .el-scrollbar {
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }
.is-horizontal { .is-horizontal {
display: none; display: none;
} }
a { a {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
} }
.svg-icon { .svg-icon {
margin-right: 16px; margin-right: 16px;
} }
.el-menu { .el-menu {
border: none; border: none;
height: 100%; height: 100%;
width: 100% !important; width: 100% !important;
} }
.el-menu-item, .el-submenu__title { .el-menu-item, .menu-title {
overflow: hidden !important; overflow: hidden !important;
text-overflow: ellipsis !important; text-overflow: ellipsis !important;
white-space: nowrap !important; white-space: nowrap !important;
} }
// menu hover .el-menu-item .el-menu-tooltip__trigger {
.submenu-title-noDropdown, display: inline-block !important;
.el-submenu__title { }
&:hover {
background-color: rgba(0, 0, 0, 0.06) !important; // menu hover
} .sub-menu-title-noDropdown,
} .el-sub-menu__title {
&:hover {
& .theme-dark .is-active > .el-submenu__title { background-color: rgba(0, 0, 0, 0.06) !important;
color: $base-menu-color-active !important; }
} }
& .nest-menu .el-submenu>.el-submenu__title, & .theme-dark .is-active > .el-sub-menu__title {
& .el-submenu .el-menu-item { color: $base-menu-color-active !important;
min-width: $base-sidebar-width !important; }
&:hover { & .nest-menu .el-sub-menu>.el-sub-menu__title,
background-color: rgba(0, 0, 0, 0.06) !important; & .el-sub-menu .el-menu-item {
} min-width: $base-sidebar-width !important;
}
&:hover {
& .theme-dark .nest-menu .el-submenu>.el-submenu__title, background-color: rgba(0, 0, 0, 0.06) !important;
& .theme-dark .el-submenu .el-menu-item { }
background-color: $base-sub-menu-background !important; }
&:hover { & .theme-dark .nest-menu .el-sub-menu>.el-sub-menu__title,
background-color: $base-sub-menu-hover !important; & .theme-dark .el-sub-menu .el-menu-item {
} background-color: $base-sub-menu-background !important;
}
} &:hover {
background-color: $base-sub-menu-hover !important;
.hideSidebar { }
.sidebar-container { }
width: 54px !important; }
}
.hideSidebar {
.main-container { .sidebar-container {
margin-left: 54px; width: 54px !important;
} }
.submenu-title-noDropdown { .main-container {
padding: 0 !important; margin-left: 54px;
position: relative; }
.el-tooltip { .sub-menu-title-noDropdown {
padding: 0 !important; padding: 0 !important;
position: relative;
.svg-icon {
margin-left: 20px; .el-tooltip {
} padding: 0 !important;
}
} .svg-icon {
margin-left: 20px;
.el-submenu { }
overflow: hidden; }
}
&>.el-submenu__title {
padding: 0 !important; .el-sub-menu {
overflow: hidden;
.svg-icon {
margin-left: 20px; &>.el-sub-menu__title {
} padding: 0 !important;
} .svg-icon {
} margin-left: 20px;
}
.el-menu--collapse {
.el-submenu { }
&>.el-submenu__title { }
&>span {
height: 0; .el-menu--collapse {
width: 0; .el-sub-menu {
overflow: hidden; &>.el-sub-menu__title {
visibility: hidden; &>span {
display: inline-block; height: 0;
} width: 0;
} overflow: hidden;
} visibility: hidden;
} display: inline-block;
} }
&>i {
.el-menu--collapse .el-menu .el-submenu { height: 0;
min-width: $base-sidebar-width !important; width: 0;
} overflow: hidden;
visibility: hidden;
// mobile responsive display: inline-block;
.mobile { }
.main-container { }
margin-left: 0px; }
} }
}
.sidebar-container {
transition: transform .28s; .el-menu--collapse .el-menu .el-sub-menu {
width: $base-sidebar-width !important; min-width: $base-sidebar-width !important;
} }
&.hideSidebar { // mobile responsive
.sidebar-container { .mobile {
pointer-events: none; .main-container {
transition-duration: 0.3s; margin-left: 0px;
transform: translate3d(-$base-sidebar-width, 0, 0); }
}
} .sidebar-container {
} transition: transform .28s;
width: $base-sidebar-width !important;
.withoutAnimation { }
.main-container, &.hideSidebar {
.sidebar-container { .sidebar-container {
transition: none; pointer-events: none;
} transition-duration: 0.3s;
} transform: translate3d(-$base-sidebar-width, 0, 0);
} }
}
// when menu collapsed }
.el-menu--vertical {
&>.el-menu { .withoutAnimation {
.svg-icon {
margin-right: 16px; .main-container,
} .sidebar-container {
} transition: none;
}
.nest-menu .el-submenu>.el-submenu__title, }
.el-menu-item { }
&:hover {
// you can use $subMenuHover // when menu collapsed
background-color: rgba(0, 0, 0, 0.06) !important; .el-menu--vertical {
} &>.el-menu {
} .svg-icon {
margin-right: 16px;
// the scroll bar appears when the subMenu is too long }
>.el-menu--popup { }
max-height: 100vh;
overflow-y: auto; .nest-menu .el-sub-menu>.el-sub-menu__title,
.el-menu-item {
&::-webkit-scrollbar-track-piece { &:hover {
background: #d3dce6; // you can use $sub-menuHover
} background-color: rgba(0, 0, 0, 0.06) !important;
}
&::-webkit-scrollbar { }
width: 6px;
} // the scroll bar appears when the sub-menu is too long
>.el-menu--popup {
&::-webkit-scrollbar-thumb { max-height: 100vh;
background: #99a9bf; overflow-y: auto;
border-radius: 20px;
} &::-webkit-scrollbar-track-piece {
} background: #d3dce6;
} }
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@@ -1,49 +1,49 @@
// global transition css // global transition css
/* fade */ /* fade */
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.28s; transition: opacity 0.28s;
} }
.fade-enter, .fade-enter,
.fade-leave-active { .fade-leave-active {
opacity: 0; opacity: 0;
} }
/* fade-transform */ /* fade-transform */
.fade-transform--move, .fade-transform--move,
.fade-transform-leave-active, .fade-transform-leave-active,
.fade-transform-enter-active { .fade-transform-enter-active {
transition: all .5s; transition: all .5s;
} }
.fade-transform-enter { .fade-transform-enter {
opacity: 0; opacity: 0;
transform: translateX(-30px); transform: translateX(-30px);
} }
.fade-transform-leave-to { .fade-transform-leave-to {
opacity: 0; opacity: 0;
transform: translateX(30px); transform: translateX(30px);
} }
/* breadcrumb transition */ /* breadcrumb transition */
.breadcrumb-enter-active, .breadcrumb-enter-active,
.breadcrumb-leave-active { .breadcrumb-leave-active {
transition: all .5s; transition: all .5s;
} }
.breadcrumb-enter, .breadcrumb-enter,
.breadcrumb-leave-active { .breadcrumb-leave-active {
opacity: 0; opacity: 0;
transform: translateX(20px); transform: translateX(20px);
} }
.breadcrumb-move { .breadcrumb-move {
transition: all .5s; transition: all .5s;
} }
.breadcrumb-leave-active { .breadcrumb-leave-active {
position: absolute; position: absolute;
} }

View File

@@ -1,54 +1,65 @@
// base color // base color
$blue:#324157; $blue: #324157;
$light-blue:#3A71A8; $light-blue: #3A71A8;
$red:#C03639; $red: #C03639;
$pink: #E65D6E; $pink: #E65D6E;
$green: #30B08F; $green: #30B08F;
$tiffany: #4AB7BD; $tiffany: #4AB7BD;
$yellow:#FEC171; $yellow: #FEC171;
$panGreen: #30B08F; $panGreen: #30B08F;
// 默认菜单主题风格 // 默认菜单主题风格
$base-menu-color:#bfcbd9; $base-menu-color: #bfcbd9;
$base-menu-color-active:#f4f4f5; $base-menu-color-active: #f4f4f5;
$base-menu-background:#304156; $base-menu-background: #304156;
$base-logo-title-color: #ffffff; $base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-color: rgba(0, 0, 0, 0.7);
$base-menu-light-background:#ffffff; $base-menu-light-background: #ffffff;
$base-logo-light-title-color: #001529; $base-logo-light-title-color: #001529;
$base-sub-menu-background:#1f2d3d; $base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover:#001528; $base-sub-menu-hover: #001528;
// 自定义暗色菜单风格 // 自定义暗色菜单风格
/** /**
$base-menu-color:hsla(0,0%,100%,.65); $base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff; $base-menu-color-active:#fff;
$base-menu-background:#001529; $base-menu-background:#001529;
$base-logo-title-color: #ffffff; $base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff; $base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529; $base-logo-light-title-color: #001529;
$base-sub-menu-background:#000c17; $base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528; $base-sub-menu-hover:#001528;
*/ */
$base-sidebar-width: 200px; $--color-primary: #409EFF;
$--color-success: #67C23A;
// the :export directive is the magic sauce for webpack $--color-warning: #E6A23C;
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass $--color-danger: #F56C6C;
:export { $--color-info: #909399;
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color; $base-sidebar-width: 200px;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background; // the :export directive is the magic sauce for webpack
menuLightBackground: $base-menu-light-background; // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
subMenuBackground: $base-sub-menu-background; :export {
subMenuHover: $base-sub-menu-hover; menuColor: $base-menu-color;
sideBarWidth: $base-sidebar-width; menuLightColor: $base-menu-light-color;
logoTitleColor: $base-logo-title-color; menuColorActive: $base-menu-color-active;
logoLightTitleColor: $base-logo-light-title-color menuBackground: $base-menu-background;
} menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color;
primaryColor: $--color-primary;
successColor: $--color-success;
dangerColor: $--color-danger;
infoColor: $--color-info;
warningColor: $--color-warning;
}

View File

@@ -1,74 +1,66 @@
<template> <template>
<el-breadcrumb class="app-breadcrumb" separator="/"> <el-breadcrumb class="app-breadcrumb" separator="/">
<transition-group name="breadcrumb"> <transition-group name="breadcrumb">
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path"> <el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span> <span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a> <a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item> </el-breadcrumb-item>
</transition-group> </transition-group>
</el-breadcrumb> </el-breadcrumb>
</template> </template>
<script> <script setup>
export default { const route = useRoute();
data() { const router = useRouter();
return { const levelList = ref([])
levelList: null
} function getBreadcrumb() {
}, // only show routes with meta.title
watch: { let matched = route.matched.filter(item => item.meta && item.meta.title);
$route(route) { const first = matched[0]
// if you go to the redirect page, do not update the breadcrumbs // 判断是否为首页
if (route.path.startsWith('/redirect/')) { if (!isDashboard(first)) {
return matched = [{ path: '/index', meta: { title: '首页' } }].concat(matched)
} }
this.getBreadcrumb()
} levelList.value = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
}, }
created() { function isDashboard(route) {
this.getBreadcrumb() const name = route && route.name
}, if (!name) {
methods: { return false
getBreadcrumb() { }
// only show routes with meta.title return name.trim() === 'Index'
let matched = this.$route.matched.filter(item => item.meta && item.meta.title) }
const first = matched[0] function handleLink(item) {
const { redirect, path } = item
if (!this.isDashboard(first)) { if (redirect) {
matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched) router.push(redirect)
} return
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false) router.push(path)
}, }
isDashboard(route) {
const name = route && route.name watchEffect(() => {
if (!name) { // if you go to the redirect page, do not update the breadcrumbs
return false if (route.path.startsWith('/redirect/')) {
} return
return name.trim() === 'Index' }
}, getBreadcrumb()
handleLink(item) { })
const { redirect, path } = item getBreadcrumb();
if (redirect) { </script>
this.$router.push(redirect)
return <style lang='scss' scoped>
} .app-breadcrumb.el-breadcrumb {
this.$router.push(path) display: inline-block;
} font-size: 14px;
} line-height: 50px;
} margin-left: 8px;
</script>
.no-redirect {
<style lang="scss" scoped> color: #97a8be;
.app-breadcrumb.el-breadcrumb { cursor: text;
display: inline-block; }
font-size: 14px; }
line-height: 50px; </style>
margin-left: 8px;
.no-redirect {
color: #97a8be;
cursor: text;
}
}
</style>

View File

@@ -1,161 +1,174 @@
<template> <template>
<el-form size="small"> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L W] 允许的通配符[, - * ? / L W]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
不指定 不指定
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
周期从 周期从
<el-input-number v-model='cycle01' :min="1" :max="30" /> - <el-input-number v-model='cycle01' :min="1" :max="30" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="31" /> <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="31" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="30" /> 号开始 <el-input-number v-model='average01' :min="1" :max="30" /> 号开始
<el-input-number v-model='average02' :min="1" :max="31 - average01 || 1" /> 日执行一次 <el-input-number v-model='average02' :min="1" :max="31 - average01" /> 日执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="5"> <el-radio v-model='radioValue' :label="5">
每月 每月
<el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日 <el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="6"> <el-radio v-model='radioValue' :label="6">
本月最后一天 本月最后一天
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="7"> <el-radio v-model='radioValue' :label="7">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option> <el-option v-for="item in 31" :key="item" :label="item" :value="item" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script setup>
<script> const emit = defineEmits(['update'])
export default { const props = defineProps({
data() { cron: {
return { type: Object,
radioValue: 1, default: {
workday: 1, second: "*",
cycle01: 1, min: "*",
cycle02: 2, hour: "*",
average01: 1, day: "*",
average02: 1, month: "*",
checkboxList: [], week: "?",
checkNum: this.$options.propsData.check year: "",
} }
}, },
name: 'crontab-day', check: {
props: ['check', 'cron'], type: Function,
methods: { default: () => {
// 单选按钮值变化时 }
radioChange() { }
('day rachange'); })
if (this.radioValue !== 2 && this.cron.week !== '?') { const radioValue = ref(1)
this.$emit('update', 'week', '?', 'day') const cycle01 = ref(1)
} const cycle02 = ref(2)
const average01 = ref(1)
switch (this.radioValue) { const average02 = ref(1)
case 1: const workday = ref(1)
this.$emit('update', 'day', '*'); const checkboxList = ref([])
break; const checkCopy = ref([1])
case 2: const cycleTotal = computed(() => {
this.$emit('update', 'day', '?'); cycle01.value = props.check(cycle01.value, 1, 30)
break; cycle02.value = props.check(cycle02.value, cycle01.value + 1, 31)
case 3: return cycle01.value + '-' + cycle02.value
this.$emit('update', 'day', this.cycleTotal); })
break; const averageTotal = computed(() => {
case 4: average01.value = props.check(average01.value, 1, 30)
this.$emit('update', 'day', this.averageTotal); average02.value = props.check(average02.value, 1, 31 - average01.value)
break; return average01.value + '/' + average02.value
case 5: })
this.$emit('update', 'day', this.workday + 'W'); const workdayTotal = computed(() => {
break; workday.value = props.check(workday.value, 1, 31)
case 6: return workday.value + 'W'
this.$emit('update', 'day', 'L'); })
break; const checkboxString = computed(() => {
case 7: return checkboxList.value.join(',')
this.$emit('update', 'day', this.checkboxString); })
break; watch(() => props.cron.day, value => changeRadioValue(value))
} watch([radioValue, cycleTotal, averageTotal, workdayTotal, checkboxString], () => onRadioChange())
('day rachange end'); function changeRadioValue(value) {
}, if (value === "*") {
// 周期两个值变化时 radioValue.value = 1
cycleChange() { } else if (value === "?") {
if (this.radioValue == '3') { radioValue.value = 2
this.$emit('update', 'day', this.cycleTotal); } else if (value.indexOf("-") > -1) {
} const indexArr = value.split('-')
}, cycle01.value = Number(indexArr[0])
// 平均两个值变化时 cycle02.value = Number(indexArr[1])
averageChange() { radioValue.value = 3
if (this.radioValue == '4') { } else if (value.indexOf("/") > -1) {
this.$emit('update', 'day', this.averageTotal); const indexArr = value.split('/')
} average01.value = Number(indexArr[0])
}, average02.value = Number(indexArr[1])
// 最近工作日值变化时 radioValue.value = 4
workdayChange() { } else if (value.indexOf("W") > -1) {
if (this.radioValue == '5') { const indexArr = value.split("W")
this.$emit('update', 'day', this.workdayCheck + 'W'); workday.value = Number(indexArr[0])
} radioValue.value = 5
}, } else if (value === "L") {
// checkbox值变化时 radioValue.value = 6
checkboxChange() { } else {
if (this.radioValue == '7') { checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
this.$emit('update', 'day', this.checkboxString); radioValue.value = 7
} }
} }
}, // 单选按钮值变化时
watch: { function onRadioChange() {
'radioValue': 'radioChange', if (radioValue.value === 2 && props.cron.week === '?') {
'cycleTotal': 'cycleChange', emit('update', 'week', '*', 'day')
'averageTotal': 'averageChange', }
'workdayCheck': 'workdayChange', if (radioValue.value !== 2 && props.cron.week !== '?') {
'checkboxString': 'checkboxChange', emit('update', 'week', '?', 'day')
}, }
computed: { switch (radioValue.value) {
// 计算两个周期值 case 1:
cycleTotal: function () { emit('update', 'day', '*', 'day')
const cycle01 = this.checkNum(this.cycle01, 1, 30) break
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31) case 2:
return cycle01 + '-' + cycle02; emit('update', 'day', '?', 'day')
}, break
// 计算平均用到的值 case 3:
averageTotal: function () { emit('update', 'day', cycleTotal.value, 'day')
const average01 = this.checkNum(this.average01, 1, 30) break
const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0) case 4:
return average01 + '/' + average02; emit('update', 'day', averageTotal.value, 'day')
}, break
// 计算工作日格式 case 5:
workdayCheck: function () { emit('update', 'day', workdayTotal.value, 'day')
const workday = this.checkNum(this.workday, 1, 31) break
return workday; case 6:
}, emit('update', 'day', 'L', 'day')
// 计算勾选的checkbox值合集 break
checkboxString: function () { case 7:
let str = this.checkboxList.join(); if (checkboxList.value.length === 0) {
return str == '' ? '*' : str; checkboxList.value.push(checkCopy.value[0])
} } else {
} checkCopy.value = checkboxList.value
}
emit('update', 'day', checkboxString.value, 'day')
break
}
} }
</script> </script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 18.8rem;
}
</style>

View File

@@ -1,114 +1,127 @@
<template> <template>
<el-form size="small"> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
小时允许的通配符[, - * /] 小时允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="22" /> - <el-input-number v-model='cycle01' :min="0" :max="22" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="23" /> <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="23" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="22" /> 时开始 <el-input-number v-model='average01' :min="0" :max="22" /> 时开始
<el-input-number v-model='average02' :min="1" :max="23 - average01 || 0" /> 小时执行一次 <el-input-number v-model='average02' :min="1" :max="23 - average01" /> 小时执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 24" :key="item" :value="item-1">{{item-1}}</el-option> <el-option v-for="item in 24" :key="item" :label="item - 1" :value="item - 1" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script setup>
export default { const emit = defineEmits(['update'])
data() { const props = defineProps({
return { cron: {
radioValue: 1, type: Object,
cycle01: 0, default: {
cycle02: 1, second: "*",
average01: 0, min: "*",
average02: 1, hour: "*",
checkboxList: [], day: "*",
checkNum: this.$options.propsData.check month: "*",
} week: "?",
}, year: "",
name: 'crontab-hour', }
props: ['check', 'cron'], },
methods: { check: {
// 单选按钮值变化时 type: Function,
radioChange() { default: () => {
switch (this.radioValue) { }
case 1: }
this.$emit('update', 'hour', '*') })
break; const radioValue = ref(1)
case 2: const cycle01 = ref(0)
this.$emit('update', 'hour', this.cycleTotal); const cycle02 = ref(1)
break; const average01 = ref(0)
case 3: const average02 = ref(1)
this.$emit('update', 'hour', this.averageTotal); const checkboxList = ref([])
break; const checkCopy = ref([0])
case 4: const cycleTotal = computed(() => {
this.$emit('update', 'hour', this.checkboxString); cycle01.value = props.check(cycle01.value, 0, 22)
break; cycle02.value = props.check(cycle02.value, cycle01.value + 1, 23)
} return cycle01.value + '-' + cycle02.value
}, })
// 周期两个值变化时 const averageTotal = computed(() => {
cycleChange() { average01.value = props.check(average01.value, 0, 22)
if (this.radioValue == '2') { average02.value = props.check(average02.value, 1, 23 - average01.value)
this.$emit('update', 'hour', this.cycleTotal); return average01.value + '/' + average02.value
} })
}, const checkboxString = computed(() => {
// 平均两个值变化时 return checkboxList.value.join(',')
averageChange() { })
if (this.radioValue == '3') { watch(() => props.cron.hour, value => changeRadioValue(value))
this.$emit('update', 'hour', this.averageTotal); watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
} function changeRadioValue(value) {
}, if (value === '*') {
// checkbox值变化时 radioValue.value = 1
checkboxChange() { } else if (value.indexOf('-') > -1) {
if (this.radioValue == '4') { const indexArr = value.split('-')
this.$emit('update', 'hour', this.checkboxString); cycle01.value = Number(indexArr[0])
} cycle02.value = Number(indexArr[1])
} radioValue.value = 2
}, } else if (value.indexOf('/') > -1) {
watch: { const indexArr = value.split('/')
'radioValue': 'radioChange', average01.value = Number(indexArr[0])
'cycleTotal': 'cycleChange', average02.value = Number(indexArr[1])
'averageTotal': 'averageChange', radioValue.value = 3
'checkboxString': 'checkboxChange' } else {
}, checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
computed: { radioValue.value = 4
// 计算两个周期值 }
cycleTotal: function () { }
const cycle01 = this.checkNum(this.cycle01, 0, 22) function onRadioChange() {
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23) switch (radioValue.value) {
return cycle01 + '-' + cycle02; case 1:
}, emit('update', 'hour', '*', 'hour')
// 计算平均用到的值 break
averageTotal: function () { case 2:
const average01 = this.checkNum(this.average01, 0, 22) emit('update', 'hour', cycleTotal.value, 'hour')
const average02 = this.checkNum(this.average02, 1, 23 - average01 || 0) break
return average01 + '/' + average02; case 3:
}, emit('update', 'hour', averageTotal.value, 'hour')
// 计算勾选的checkbox值合集 break
checkboxString: function () { case 4:
let str = this.checkboxList.join(); if (checkboxList.value.length === 0) {
return str == '' ? '*' : str; checkboxList.value.push(checkCopy.value[0])
} } else {
} checkCopy.value = checkboxList.value
}
emit('update', 'hour', checkboxString.value, 'hour')
break
}
} }
</script> </script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 18.8rem;
}
</style>

View File

@@ -1,320 +1,233 @@
<template> <template>
<div> <div>
<el-tabs type="border-card"> <el-tabs type="border-card">
<el-tab-pane label="秒" v-if="shouldHide('second')"> <el-tab-pane label="秒" v-if="shouldHide('second')">
<CrontabSecond <CrontabSecond
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronsecond" ref="cronsecond"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="分钟" v-if="shouldHide('min')"> <el-tab-pane label="分钟" v-if="shouldHide('min')">
<CrontabMin <CrontabMin
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronmin" ref="cronmin"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="小时" v-if="shouldHide('hour')"> <el-tab-pane label="小时" v-if="shouldHide('hour')">
<CrontabHour <CrontabHour
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronhour" ref="cronhour"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="日" v-if="shouldHide('day')"> <el-tab-pane label="日" v-if="shouldHide('day')">
<CrontabDay <CrontabDay
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronday" ref="cronday"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="月" v-if="shouldHide('month')"> <el-tab-pane label="月" v-if="shouldHide('month')">
<CrontabMonth <CrontabMonth
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronmonth" ref="cronmonth"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="周" v-if="shouldHide('week')"> <el-tab-pane label="周" v-if="shouldHide('week')">
<CrontabWeek <CrontabWeek
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronweek" ref="cronweek"
/> />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="年" v-if="shouldHide('year')"> <el-tab-pane label="年" v-if="shouldHide('year')">
<CrontabYear <CrontabYear
@update="updateCrontabValue" @update="updateCrontabValue"
:check="checkNumber" :check="checkNumber"
:cron="crontabValueObj" :cron="crontabValueObj"
ref="cronyear" ref="cronyear"
/> />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
<div class="popup-main"> <div class="popup-main">
<div class="popup-result"> <div class="popup-result">
<p class="title">时间表达式</p> <p class="title">时间表达式</p>
<table> <table>
<thead> <thead>
<th v-for="item of tabTitles" width="40" :key="item">{{item}}</th> <th v-for="item of tabTitles" :key="item">{{item}}</th>
<th>Cron 表达式</th> <th>Cron 表达式</th>
</thead> </thead>
<tbody> <tbody>
<td> <td>
<span>{{crontabValueObj.second}}</span> <span v-if="crontabValueObj.second.length < 10">{{crontabValueObj.second}}</span>
</td> <el-tooltip v-else :content="crontabValueObj.second" placement="top"><span>{{crontabValueObj.second}}</span></el-tooltip>
<td> </td>
<span>{{crontabValueObj.min}}</span> <td>
</td> <span v-if="crontabValueObj.min.length < 10">{{crontabValueObj.min}}</span>
<td> <el-tooltip v-else :content="crontabValueObj.min" placement="top"><span>{{crontabValueObj.min}}</span></el-tooltip>
<span>{{crontabValueObj.hour}}</span> </td>
</td> <td>
<td> <span v-if="crontabValueObj.hour.length < 10">{{crontabValueObj.hour}}</span>
<span>{{crontabValueObj.day}}</span> <el-tooltip v-else :content="crontabValueObj.hour" placement="top"><span>{{crontabValueObj.hour}}</span></el-tooltip>
</td> </td>
<td> <td>
<span>{{crontabValueObj.month}}</span> <span v-if="crontabValueObj.day.length < 10">{{crontabValueObj.day}}</span>
</td> <el-tooltip v-else :content="crontabValueObj.day" placement="top"><span>{{crontabValueObj.day}}</span></el-tooltip>
<td> </td>
<span>{{crontabValueObj.week}}</span> <td>
</td> <span v-if="crontabValueObj.month.length < 10">{{crontabValueObj.month}}</span>
<td> <el-tooltip v-else :content="crontabValueObj.month" placement="top"><span>{{crontabValueObj.month}}</span></el-tooltip>
<span>{{crontabValueObj.year}}</span> </td>
</td> <td>
<td> <span v-if="crontabValueObj.week.length < 10">{{crontabValueObj.week}}</span>
<span>{{crontabValueString}}</span> <el-tooltip v-else :content="crontabValueObj.week" placement="top"><span>{{crontabValueObj.week}}</span></el-tooltip>
</td> </td>
</tbody> <td>
</table> <span v-if="crontabValueObj.year.length < 10">{{crontabValueObj.year}}</span>
</div> <el-tooltip v-else :content="crontabValueObj.year" placement="top"><span>{{crontabValueObj.year}}</span></el-tooltip>
<CrontabResult :ex="crontabValueString"></CrontabResult> </td>
<td class="result">
<span v-if="crontabValueString.length < 90">{{crontabValueString}}</span>
<el-tooltip v-else :content="crontabValueString" placement="top"><span>{{crontabValueString}}</span></el-tooltip>
</td>
</tbody>
</table>
</div>
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn"> <div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill">确定</el-button> <el-button type="primary" @click="submitFill">确定</el-button>
<el-button size="small" type="warning" @click="clearCron">重置</el-button> <el-button type="warning" @click="clearCron">重置</el-button>
<el-button size="small" @click="hidePopup">取消</el-button> <el-button @click="hidePopup">取消</el-button>
</div> </div>
</div>
</div> </div>
</div>
</template> </template>
<script> <script setup>
import CrontabSecond from "./second.vue"; import CrontabSecond from "./second.vue"
import CrontabMin from "./min.vue"; import CrontabMin from "./min.vue"
import CrontabHour from "./hour.vue"; import CrontabHour from "./hour.vue"
import CrontabDay from "./day.vue"; import CrontabDay from "./day.vue"
import CrontabMonth from "./month.vue"; import CrontabMonth from "./month.vue"
import CrontabWeek from "./week.vue"; import CrontabWeek from "./week.vue"
import CrontabYear from "./year.vue"; import CrontabYear from "./year.vue"
import CrontabResult from "./result.vue"; import CrontabResult from "./result.vue"
const { proxy } = getCurrentInstance()
export default { const emit = defineEmits(['hide', 'fill'])
data() { const props = defineProps({
return { hideComponent: {
tabTitles: ["秒", "分钟", "小时", "日", "月", "周", "年"], type: Array,
tabActive: 0, default: () => [],
myindex: 0,
crontabValueObj: {
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
},
};
},
name: "vcrontab",
props: ["expression", "hideComponent"],
methods: {
shouldHide(key) {
if (this.hideComponent && this.hideComponent.includes(key)) return false;
return true;
}, },
resolveExp() { expression: {
// 反解析 表达式 type: String,
if (this.expression) { default: ""
let arr = this.expression.split(" "); }
})
const tabTitles = ref(["秒", "分钟", "小时", "日", "月", "周", "年"])
const tabActive = ref(0)
const hideComponent = ref([])
const expression = ref('')
const crontabValueObj = ref({
second: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
})
const crontabValueString = computed(() => {
const obj = crontabValueObj.value
return obj.second
+ " "
+ obj.min
+ " "
+ obj.hour
+ " "
+ obj.day
+ " "
+ obj.month
+ " "
+ obj.week
+ (obj.year === "" ? "" : " " + obj.year)
})
watch(expression, () => resolveExp())
function shouldHide(key) {
return !(hideComponent.value && hideComponent.value.includes(key))
}
function resolveExp() {
// 反解析 表达式
if (expression.value) {
const arr = expression.value.split(/\s+/)
if (arr.length >= 6) { if (arr.length >= 6) {
//6 位以上是合法表达式 //6 位以上是合法表达式
let obj = { let obj = {
second: arr[0], second: arr[0],
min: arr[1], min: arr[1],
hour: arr[2], hour: arr[2],
day: arr[3], day: arr[3],
month: arr[4], month: arr[4],
week: arr[5], week: arr[5],
year: arr[6] ? arr[6] : "", year: arr[6] ? arr[6] : ""
}; }
this.crontabValueObj = { crontabValueObj.value = {
...obj, ...obj,
}; }
for (let i in obj) {
if (obj[i]) this.changeRadio(i, obj[i]);
}
} }
} else { } else {
// 没有传入的表达式 则还原 // 没有传入的表达式 则还原
this.clearCron(); clearCron()
} }
}, }
// tab切换值 // tab切换值
tabCheck(index) { function tabCheck(index) {
this.tabActive = index; tabActive.value = index
}, }
// 由子组件触发,更改表达式组成的字段值 // 由子组件触发,更改表达式组成的字段值
updateCrontabValue(name, value, from) { function updateCrontabValue(name, value, from) {
"updateCrontabValue", name, value, from; crontabValueObj.value[name] = value
this.crontabValueObj[name] = value; }
if (from && from !== name) { // 表单选项的子组件校验数字格式(通过-props传递
console.log(`来自组件 ${from} 改变了 ${name} ${value}`); function checkNumber(value, minLimit, maxLimit) {
this.changeRadio(name, value); // 检查必须为整数
} value = Math.floor(value)
}, if (value < minLimit) {
// 赋值到组件 value = minLimit
changeRadio(name, value) { } else if (value > maxLimit) {
let arr = ["second", "min", "hour", "month"], value = maxLimit
refName = "cron" + name, }
insValue; return value
}
if (!this.$refs[refName]) return; // 隐藏弹窗
function hidePopup() {
if (arr.includes(name)) { emit("hide")
if (value === "*") { }
insValue = 1; // 填充表达式
} else if (value.indexOf("-") > -1) { function submitFill() {
let indexArr = value.split("-"); emit("fill", crontabValueString.value)
isNaN(indexArr[0]) hidePopup()
? (this.$refs[refName].cycle01 = 0) }
: (this.$refs[refName].cycle01 = indexArr[0]); function clearCron() {
this.$refs[refName].cycle02 = indexArr[1]; // 还原选择项
insValue = 2; crontabValueObj.value = {
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 0)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 3;
} else {
insValue = 4;
this.$refs[refName].checkboxList = value.split(",");
}
} else if (name == "day") {
if (value === "*") {
insValue = 1;
} else if (value == "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (this.$refs[refName].cycle01 = 0)
: (this.$refs[refName].cycle01 = indexArr[0]);
this.$refs[refName].cycle02 = indexArr[1];
insValue = 3;
} else if (value.indexOf("/") > -1) {
let indexArr = value.split("/");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 0)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 4;
} else if (value.indexOf("W") > -1) {
let indexArr = value.split("W");
isNaN(indexArr[0])
? (this.$refs[refName].workday = 0)
: (this.$refs[refName].workday = indexArr[0]);
insValue = 5;
} else if (value === "L") {
insValue = 6;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 7;
}
} else if (name == "week") {
if (value === "*") {
insValue = 1;
} else if (value == "?") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
let indexArr = value.split("-");
isNaN(indexArr[0])
? (this.$refs[refName].cycle01 = 0)
: (this.$refs[refName].cycle01 = indexArr[0]);
this.$refs[refName].cycle02 = indexArr[1];
insValue = 3;
} else if (value.indexOf("#") > -1) {
let indexArr = value.split("#");
isNaN(indexArr[0])
? (this.$refs[refName].average01 = 1)
: (this.$refs[refName].average01 = indexArr[0]);
this.$refs[refName].average02 = indexArr[1];
insValue = 4;
} else if (value.indexOf("L") > -1) {
let indexArr = value.split("L");
isNaN(indexArr[0])
? (this.$refs[refName].weekday = 1)
: (this.$refs[refName].weekday = indexArr[0]);
insValue = 5;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 6;
}
} else if (name == "year") {
if (value == "") {
insValue = 1;
} else if (value == "*") {
insValue = 2;
} else if (value.indexOf("-") > -1) {
insValue = 3;
} else if (value.indexOf("/") > -1) {
insValue = 4;
} else {
this.$refs[refName].checkboxList = value.split(",");
insValue = 5;
}
}
this.$refs[refName].radioValue = insValue;
},
// 表单选项的子组件校验数字格式(通过-props传递
checkNumber(value, minLimit, maxLimit) {
// 检查必须为整数
value = Math.floor(value);
if (value < minLimit) {
value = minLimit;
} else if (value > maxLimit) {
value = maxLimit;
}
return value;
},
// 隐藏弹窗
hidePopup() {
this.$emit("hide");
},
// 填充表达式
submitFill() {
this.$emit("fill", this.crontabValueString);
this.hidePopup();
},
clearCron() {
// 还原选择项
("准备还原");
this.crontabValueObj = {
second: "*", second: "*",
min: "*", min: "*",
hour: "*", hour: "*",
@@ -322,109 +235,76 @@ export default {
month: "*", month: "*",
week: "?", week: "?",
year: "", year: "",
}; }
for (let j in this.crontabValueObj) { }
this.changeRadio(j, this.crontabValueObj[j]); onMounted(() => {
} expression.value = props.expression
}, hideComponent.value = props.hideComponent
}, })
computed: {
crontabValueString: function() {
let obj = this.crontabValueObj;
let str =
obj.second +
" " +
obj.min +
" " +
obj.hour +
" " +
obj.day +
" " +
obj.month +
" " +
obj.week +
(obj.year == "" ? "" : " " + obj.year);
return str;
},
},
components: {
CrontabSecond,
CrontabMin,
CrontabHour,
CrontabDay,
CrontabMonth,
CrontabWeek,
CrontabYear,
CrontabResult,
},
watch: {
expression: "resolveExp",
hideComponent(value) {
// 隐藏部分组件
},
},
mounted: function() {
this.resolveExp();
},
};
</script> </script>
<style scoped>
<style lang="scss" scoped>
.pop_btn { .pop_btn {
text-align: center; text-align: center;
margin-top: 20px; margin-top: 20px;
} }
.popup-main { .popup-main {
position: relative; position: relative;
margin: 10px auto; margin: 10px auto;
background: #fff; background: #fff;
border-radius: 5px; border-radius: 5px;
font-size: 12px; font-size: 12px;
overflow: hidden; overflow: hidden;
} }
.popup-title { .popup-title {
overflow: hidden; overflow: hidden;
line-height: 34px; line-height: 34px;
padding-top: 6px; padding-top: 6px;
background: #f2f2f2; background: #f2f2f2;
} }
.popup-result { .popup-result {
box-sizing: border-box; box-sizing: border-box;
line-height: 24px; line-height: 24px;
margin: 25px auto; margin: 25px auto;
padding: 15px 10px 10px; padding: 15px 10px 10px;
border: 1px solid #ccc; border: 1px solid #ccc;
position: relative; position: relative;
} }
.popup-result .title { .popup-result .title {
position: absolute; position: absolute;
top: -28px; top: -28px;
left: 50%; left: 50%;
width: 140px; width: 140px;
font-size: 14px; font-size: 14px;
margin-left: -70px; margin-left: -70px;
text-align: center; text-align: center;
line-height: 30px; line-height: 30px;
background: #fff; background: #fff;
} }
.popup-result table { .popup-result table {
text-align: center; text-align: center;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
}
.popup-result table td:not(.result) {
width: 3.5rem;
min-width: 3.5rem;
max-width: 3.5rem;
} }
.popup-result table span { .popup-result table span {
display: block; display: block;
width: 100%; width: 100%;
font-family: arial; font-family: arial;
line-height: 30px; line-height: 30px;
height: 30px; height: 30px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
border: 1px solid #e8e8e8; border: 1px solid #e8e8e8;
} }
.popup-result-scroll { .popup-result-scroll {
font-size: 12px; font-size: 12px;
line-height: 24px; line-height: 24px;
height: 10em; height: 10em;
overflow-y: auto; overflow-y: auto;
} }
</style> </style>

View File

@@ -1,116 +1,126 @@
<template> <template>
<el-form size="small"> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
分钟允许的通配符[, - * /] 分钟允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> - <el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟 <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" /> 分钟
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始 <el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次 <el-input-number v-model='average02' :min="1" :max="59 - average01" /> 分钟执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template> </template>
<script setup>
<script> const emit = defineEmits(['update'])
export default { const props = defineProps({
data() { cron: {
return { type: Object,
radioValue: 1, default: {
cycle01: 1, second: "*",
cycle02: 2, min: "*",
average01: 0, hour: "*",
average02: 1, day: "*",
checkboxList: [], month: "*",
checkNum: this.$options.propsData.check week: "?",
} year: "",
}, }
name: 'crontab-min', },
props: ['check', 'cron'], check: {
methods: { type: Function,
// 单选按钮值变化时 default: () => {
radioChange() { }
switch (this.radioValue) { }
case 1: })
this.$emit('update', 'min', '*', 'min'); const radioValue = ref(1)
break; const cycle01 = ref(0)
case 2: const cycle02 = ref(1)
this.$emit('update', 'min', this.cycleTotal, 'min'); const average01 = ref(0)
break; const average02 = ref(1)
case 3: const checkboxList = ref([])
this.$emit('update', 'min', this.averageTotal, 'min'); const checkCopy = ref([0])
break; const cycleTotal = computed(() => {
case 4: cycle01.value = props.check(cycle01.value, 0, 58)
this.$emit('update', 'min', this.checkboxString, 'min'); cycle02.value = props.check(cycle02.value, cycle01.value + 1, 59)
break; return cycle01.value + '-' + cycle02.value
} })
}, const averageTotal = computed(() => {
// 周期两个值变化时 average01.value = props.check(average01.value, 0, 58)
cycleChange() { average02.value = props.check(average02.value, 1, 59 - average01.value)
if (this.radioValue == '2') { return average01.value + '/' + average02.value
this.$emit('update', 'min', this.cycleTotal, 'min'); })
} const checkboxString = computed(() => {
}, return checkboxList.value.join(',')
// 平均两个值变化时 })
averageChange() { watch(() => props.cron.min, value => changeRadioValue(value))
if (this.radioValue == '3') { watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
this.$emit('update', 'min', this.averageTotal, 'min'); function changeRadioValue(value) {
} if (value === '*') {
}, radioValue.value = 1
// checkbox值变化时 } else if (value.indexOf('-') > -1) {
checkboxChange() { const indexArr = value.split('-')
if (this.radioValue == '4') { cycle01.value = Number(indexArr[0])
this.$emit('update', 'min', this.checkboxString, 'min'); cycle02.value = Number(indexArr[1])
} radioValue.value = 2
}, } else if (value.indexOf('/') > -1) {
const indexArr = value.split('/')
}, average01.value = Number(indexArr[0])
watch: { average02.value = Number(indexArr[1])
'radioValue': 'radioChange', radioValue.value = 3
'cycleTotal': 'cycleChange', } else {
'averageTotal': 'averageChange', checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
'checkboxString': 'checkboxChange', radioValue.value = 4
}, }
computed: {
// 计算两个周期值
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 58)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return cycle01 + '-' + cycle02;
},
// 计算平均用到的值
averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 58)
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return average01 + '/' + average02;
},
// 计算勾选的checkbox值合集
checkboxString: function () {
let str = this.checkboxList.join();
return str == '' ? '*' : str;
}
}
} }
</script> function onRadioChange() {
switch (radioValue.value) {
case 1:
emit('update', 'min', '*', 'min')
break
case 2:
emit('update', 'min', cycleTotal.value, 'min')
break
case 3:
emit('update', 'min', averageTotal.value, 'min')
break
case 4:
if (checkboxList.value.length === 0) {
checkboxList.value.push(checkCopy.value[0])
} else {
checkCopy.value = checkboxList.value
}
emit('update', 'min', checkboxString.value, 'min')
break
}
}
</script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 19.8rem;
}
</style>

View File

@@ -1,114 +1,141 @@
<template> <template>
<el-form size='small'> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /] 允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="1" :max="11" /> - <el-input-number v-model='cycle01' :min="1" :max="11" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="12" /> <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="12" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="1" :max="11" /> 月开始 <el-input-number v-model='average01' :min="1" :max="11" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12 - average01 || 0" /> 月月执行一次 <el-input-number v-model='average02' :min="1" :max="12 - average01" /> 月月执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8">
<el-option v-for="item in 12" :key="item" :value="item">{{item}}</el-option> <el-option v-for="item in monthList" :key="item.key" :label="item.value" :value="item.key" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script setup>
export default { const emit = defineEmits(['update'])
data() { const props = defineProps({
return { cron: {
radioValue: 1, type: Object,
cycle01: 1, default: {
cycle02: 2, second: "*",
average01: 1, min: "*",
average02: 1, hour: "*",
checkboxList: [], day: "*",
checkNum: this.check month: "*",
} week: "?",
}, year: "",
name: 'crontab-month', }
props: ['check', 'cron'], },
methods: { check: {
// 单选按钮值变化时 type: Function,
radioChange() { default: () => {
switch (this.radioValue) { }
case 1: }
this.$emit('update', 'month', '*'); })
break; const radioValue = ref(1)
case 2: const cycle01 = ref(1)
this.$emit('update', 'month', this.cycleTotal); const cycle02 = ref(2)
break; const average01 = ref(1)
case 3: const average02 = ref(1)
this.$emit('update', 'month', this.averageTotal); const checkboxList = ref([])
break; const checkCopy = ref([1])
case 4: const monthList = ref([
this.$emit('update', 'month', this.checkboxString); {key: 1, value: '一月'},
break; {key: 2, value: '二月'},
} {key: 3, value: '三月'},
}, {key: 4, value: '四月'},
// 周期两个值变化时 {key: 5, value: '五月'},
cycleChange() { {key: 6, value: '六月'},
if (this.radioValue == '2') { {key: 7, value: '七月'},
this.$emit('update', 'month', this.cycleTotal); {key: 8, value: '八月'},
} {key: 9, value: '九月'},
}, {key: 10, value: '十月'},
// 平均两个值变化时 {key: 11, value: '十一月'},
averageChange() { {key: 12, value: '十二月'}
if (this.radioValue == '3') { ])
this.$emit('update', 'month', this.averageTotal); const cycleTotal = computed(() => {
} cycle01.value = props.check(cycle01.value, 1, 11)
}, cycle02.value = props.check(cycle02.value, cycle01.value + 1, 12)
// checkbox值变化时 return cycle01.value + '-' + cycle02.value
checkboxChange() { })
if (this.radioValue == '4') { const averageTotal = computed(() => {
this.$emit('update', 'month', this.checkboxString); average01.value = props.check(average01.value, 1, 11)
} average02.value = props.check(average02.value, 1, 12 - average01.value)
} return average01.value + '/' + average02.value
}, })
watch: { const checkboxString = computed(() => {
'radioValue': 'radioChange', return checkboxList.value.join(',')
'cycleTotal': 'cycleChange', })
'averageTotal': 'averageChange', watch(() => props.cron.month, value => changeRadioValue(value))
'checkboxString': 'checkboxChange' watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
}, function changeRadioValue(value) {
computed: { if (value === '*') {
// 计算两个周期值 radioValue.value = 1
cycleTotal: function () { } else if (value.indexOf('-') > -1) {
const cycle01 = this.checkNum(this.cycle01, 1, 11) const indexArr = value.split('-')
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12) cycle01.value = Number(indexArr[0])
return cycle01 + '-' + cycle02; cycle02.value = Number(indexArr[1])
}, radioValue.value = 2
// 计算平均用到的值 } else if (value.indexOf('/') > -1) {
averageTotal: function () { const indexArr = value.split('/')
const average01 = this.checkNum(this.average01, 1, 11) average01.value = Number(indexArr[0])
const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0) average02.value = Number(indexArr[1])
return average01 + '/' + average02; radioValue.value = 3
}, } else {
// 计算勾选的checkbox值合集 checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
checkboxString: function () { radioValue.value = 4
let str = this.checkboxList.join(); }
return str == '' ? '*' : str; }
} function onRadioChange() {
} switch (radioValue.value) {
} case 1:
</script> emit('update', 'month', '*', 'month')
break
case 2:
emit('update', 'month', cycleTotal.value, 'month')
break
case 3:
emit('update', 'month', averageTotal.value, 'month')
break
case 4:
if (checkboxList.value.length === 0) {
checkboxList.value.push(checkCopy.value[0])
} else {
checkCopy.value = checkboxList.value
}
emit('update', 'month', checkboxString.value, 'month')
break
}
}
</script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 18.8rem;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +1,128 @@
<template> <template>
<el-form size="small"> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /] 允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> - <el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> <el-input-number v-model='cycle02' :min="cycle01 + 1" :max="59" />
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始 <el-input-number v-model='average01' :min="0" :max="58" /> 秒开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次 <el-input-number v-model='average02' :min="1" :max="59 - average01" /> 秒执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="10">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option> <el-option v-for="item in 60" :key="item" :label="item - 1" :value="item - 1" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script setup>
export default { const emit = defineEmits(['update'])
data() { const props = defineProps({
return { cron: {
radioValue: 1, type: Object,
cycle01: 1, default: {
cycle02: 2, second: "*",
average01: 0, min: "*",
average02: 1, hour: "*",
checkboxList: [], day: "*",
checkNum: this.$options.propsData.check month: "*",
} week: "?",
}, year: "",
name: 'crontab-second', }
props: ['check', 'radioParent'], },
methods: { check: {
// 单选按钮值变化时 type: Function,
radioChange() { default: () => {
switch (this.radioValue) { }
case 1: }
this.$emit('update', 'second', '*', 'second'); })
break; const radioValue = ref(1)
case 2: const cycle01 = ref(0)
this.$emit('update', 'second', this.cycleTotal); const cycle02 = ref(1)
break; const average01 = ref(0)
case 3: const average02 = ref(1)
this.$emit('update', 'second', this.averageTotal); const checkboxList = ref([])
break; const checkCopy = ref([0])
case 4: const cycleTotal = computed(() => {
this.$emit('update', 'second', this.checkboxString); cycle01.value = props.check(cycle01.value, 0, 58)
break; cycle02.value = props.check(cycle02.value, cycle01.value + 1, 59)
} return cycle01.value + '-' + cycle02.value
}, })
// 周期两个值变化时 const averageTotal = computed(() => {
cycleChange() { average01.value = props.check(average01.value, 0, 58)
if (this.radioValue == '2') { average02.value = props.check(average02.value, 1, 59 - average01.value)
this.$emit('update', 'second', this.cycleTotal); return average01.value + '/' + average02.value
} })
}, const checkboxString = computed(() => {
// 平均两个值变化时 return checkboxList.value.join(',')
averageChange() { })
if (this.radioValue == '3') { watch(() => props.cron.second, value => changeRadioValue(value))
this.$emit('update', 'second', this.averageTotal); watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
} function changeRadioValue(value) {
}, if (value === '*') {
// checkbox值变化时 radioValue.value = 1
checkboxChange() { } else if (value.indexOf('-') > -1) {
if (this.radioValue == '4') { const indexArr = value.split('-')
this.$emit('update', 'second', this.checkboxString); cycle01.value = Number(indexArr[0])
} cycle02.value = Number(indexArr[1])
} radioValue.value = 2
}, } else if (value.indexOf('/') > -1) {
watch: { const indexArr = value.split('/')
'radioValue': 'radioChange', average01.value = Number(indexArr[0])
'cycleTotal': 'cycleChange', average02.value = Number(indexArr[1])
'averageTotal': 'averageChange', radioValue.value = 3
'checkboxString': 'checkboxChange', } else {
radioParent() { checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
this.radioValue = this.radioParent radioValue.value = 4
} }
}, }
computed: { // 单选按钮值变化时
// 计算两个周期值 function onRadioChange() {
cycleTotal: function () { switch (radioValue.value) {
const cycle01 = this.checkNum(this.cycle01, 0, 58) case 1:
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) emit('update', 'second', '*', 'second')
return cycle01 + '-' + cycle02; break
}, case 2:
// 计算平均用到的值 emit('update', 'second', cycleTotal.value, 'second')
averageTotal: function () { break
const average01 = this.checkNum(this.average01, 0, 58) case 3:
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) emit('update', 'second', averageTotal.value, 'second')
return average01 + '/' + average02; break
}, case 4:
// 计算勾选的checkbox值合集 if (checkboxList.value.length === 0) {
checkboxString: function () { checkboxList.value.push(checkCopy.value[0])
let str = this.checkboxList.join(); } else {
return str == '' ? '*' : str; checkCopy.value = checkboxList.value
} }
} emit('update', 'second', checkboxString.value, 'second')
break
}
} }
</script> </script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 18.8rem;
}
</style>

View File

@@ -1,202 +1,197 @@
<template> <template>
<el-form size='small'> <el-form>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L #] 允许的通配符[, - * ? / L #]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
不指定 不指定
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
周期从星期 周期从
<el-select clearable v-model="cycle01"> <el-select clearable v-model="cycle01">
<el-option <el-option
v-for="(item,index) of weekList" v-for="(item,index) of weekList"
:key="index" :key="index"
:label="item.value" :label="item.value"
:value="item.key" :value="item.key"
:disabled="item.key === 1" :disabled="item.key === 7"
>{{item.value}}</el-option> >{{item.value}}</el-option>
</el-select> </el-select>
- -
<el-select clearable v-model="cycle02"> <el-select clearable v-model="cycle02">
<el-option <el-option
v-for="(item,index) of weekList" v-for="(item,index) of weekList"
:key="index" :key="index"
:label="item.value" :label="item.value"
:value="item.key" :value="item.key"
:disabled="item.key < cycle01 && item.key !== 1" :disabled="item.key <= cycle01"
>{{item.value}}</el-option> >{{item.value}}</el-option>
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的星期 <el-input-number v-model='average01' :min="1" :max="4" /> 周的
<el-select clearable v-model="average02"> <el-select clearable v-model="average02">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option> <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="5"> <el-radio v-model='radioValue' :label="5">
本月最后一个星期 本月最后一个
<el-select clearable v-model="weekday"> <el-select clearable v-model="weekday">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="item.key">{{item.value}}</el-option> <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="6"> <el-radio v-model='radioValue' :label="6">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select class="multiselect" clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="6">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="String(item.key)">{{item.value}}</el-option> <el-option v-for="item in weekList" :key="item.key" :label="item.value" :value="item.key" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script setup>
export default { const emit = defineEmits(['update'])
data() { const props = defineProps({
return { cron: {
radioValue: 2, type: Object,
weekday: 2, default: {
cycle01: 2, second: "*",
cycle02: 3, min: "*",
average01: 1, hour: "*",
average02: 2, day: "*",
checkboxList: [], month: "*",
weekList: [ week: "?",
{ year: ""
key: 2, }
value: '星期一' },
}, check: {
{ type: Function,
key: 3, default: () => {
value: '星期二' }
}, }
{ })
key: 4, const radioValue = ref(2)
value: '星期三' const cycle01 = ref(2)
}, const cycle02 = ref(3)
{ const average01 = ref(1)
key: 5, const average02 = ref(2)
value: '星期四' const weekday = ref(2)
}, const checkboxList = ref([])
{ const checkCopy = ref([2])
key: 6, const weekList = ref([
value: '星期五' {key: 1, value: '星期日'},
}, {key: 2, value: '星期一'},
{ {key: 3, value: '星期二'},
key: 7, {key: 4, value: '星期三'},
value: '星期六' {key: 5, value: '星期四'},
}, {key: 6, value: '星期五'},
{ {key: 7, value: '星期六'}
key: 1, ])
value: '星期日' const cycleTotal = computed(() => {
} cycle01.value = props.check(cycle01.value, 1, 6)
], cycle02.value = props.check(cycle02.value, cycle01.value + 1, 7)
checkNum: this.$options.propsData.check return cycle01.value + '-' + cycle02.value
} })
}, const averageTotal = computed(() => {
name: 'crontab-week', average01.value = props.check(average01.value, 1, 4)
props: ['check', 'cron'], average02.value = props.check(average02.value, 1, 7)
methods: { return average02.value + '#' + average01.value
// 单选按钮值变化时 })
radioChange() { const weekdayTotal = computed(() => {
if (this.radioValue !== 2 && this.cron.day !== '?') { weekday.value = props.check(weekday.value, 1, 7)
this.$emit('update', 'day', '?', 'week'); return weekday.value + 'L'
} })
switch (this.radioValue) { const checkboxString = computed(() => {
case 1: return checkboxList.value.join(',')
this.$emit('update', 'week', '*'); })
break; watch(() => props.cron.week, value => changeRadioValue(value))
case 2: watch([radioValue, cycleTotal, averageTotal, weekdayTotal, checkboxString], () => onRadioChange())
this.$emit('update', 'week', '?'); function changeRadioValue(value) {
break; if (value === "*") {
case 3: radioValue.value = 1
this.$emit('update', 'week', this.cycleTotal); } else if (value === "?") {
break; radioValue.value = 2
case 4: } else if (value.indexOf("-") > -1) {
this.$emit('update', 'week', this.averageTotal); const indexArr = value.split('-')
break; cycle01.value = Number(indexArr[0])
case 5: cycle02.value = Number(indexArr[1])
this.$emit('update', 'week', this.weekdayCheck + 'L'); radioValue.value = 3
break; } else if (value.indexOf("#") > -1) {
case 6: const indexArr = value.split('#')
this.$emit('update', 'week', this.checkboxString); average01.value = Number(indexArr[1])
break; average02.value = Number(indexArr[0])
} radioValue.value = 4
}, } else if (value.indexOf("L") > -1) {
const indexArr = value.split("L")
// 周期两个值变化时 weekday.value = Number(indexArr[0])
cycleChange() { radioValue.value = 5
if (this.radioValue == '3') { } else {
this.$emit('update', 'week', this.cycleTotal); checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
} radioValue.value = 6
}, }
// 平均两个值变化时 }
averageChange() { function onRadioChange() {
if (this.radioValue == '4') { if (radioValue.value === 2 && props.cron.day === '?') {
this.$emit('update', 'week', this.averageTotal); emit('update', 'day', '*', 'week')
} }
}, if (radioValue.value !== 2 && props.cron.day !== '?') {
// 最近工作日值变化时 emit('update', 'day', '?', 'week')
weekdayChange() { }
if (this.radioValue == '5') { switch (radioValue.value) {
this.$emit('update', 'week', this.weekday + 'L'); case 1:
} emit('update', 'week', '*', 'week')
}, break
// checkbox值变化时 case 2:
checkboxChange() { emit('update', 'week', '?', 'week')
if (this.radioValue == '6') { break
this.$emit('update', 'week', this.checkboxString); case 3:
} emit('update', 'week', cycleTotal.value, 'week')
}, break
}, case 4:
watch: { emit('update', 'week', averageTotal.value, 'week')
'radioValue': 'radioChange', break
'cycleTotal': 'cycleChange', case 5:
'averageTotal': 'averageChange', emit('update', 'week', weekdayTotal.value, 'week')
'weekdayCheck': 'weekdayChange', break
'checkboxString': 'checkboxChange', case 6:
}, if (checkboxList.value.length === 0) {
computed: { checkboxList.value.push(checkCopy.value[0])
// 计算两个周期值 } else {
cycleTotal: function () { checkCopy.value = checkboxList.value
this.cycle01 = this.checkNum(this.cycle01, 1, 7) }
this.cycle02 = this.checkNum(this.cycle02, 1, 7) emit('update', 'week', checkboxString.value, 'week')
return this.cycle01 + '-' + this.cycle02; break
}, }
// 计算平均用到的值
averageTotal: function () {
this.average01 = this.checkNum(this.average01, 1, 4)
this.average02 = this.checkNum(this.average02, 1, 7)
return this.average02 + '#' + this.average01;
},
// 最近的工作日(格式)
weekdayCheck: function () {
this.weekday = this.checkNum(this.weekday, 1, 7)
return this.weekday;
},
// 计算勾选的checkbox值合集
checkboxString: function () {
let str = this.checkboxList.join();
return str == '' ? '*' : str;
}
}
} }
</script> </script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.5rem;
}
.el-select, .el-select--small {
width: 8rem;
}
.el-select.multiselect, .el-select--small.multiselect {
width: 17.8rem;
}
</style>

View File

@@ -1,131 +1,149 @@
<template> <template>
<el-form size="small"> <el-form>
<el-form-item> <el-form-item>
<el-radio :label="1" v-model='radioValue'> <el-radio :label="1" v-model='radioValue'>
不填允许的通配符[, - * /] 不填允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio :label="2" v-model='radioValue'> <el-radio :label="2" v-model='radioValue'>
每年 每年
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio :label="3" v-model='radioValue'> <el-radio :label="3" v-model='radioValue'>
周期从 周期从
<el-input-number v-model='cycle01' :min='fullYear' :max="2098" /> - <el-input-number v-model='cycle01' :min='fullYear' :max="2098"/> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099" /> <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : fullYear + 1" :max="2099"/>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio :label="4" v-model='radioValue'> <el-radio :label="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始 <el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始
<el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear" /> 年执行一次 <el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear"/> 年执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio :label="5" v-model='radioValue'> <el-radio :label="5" v-model='radioValue'>
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple :multiple-limit="8">
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" /> <el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script setup>
export default { const emit = defineEmits(['update'])
data() { const props = defineProps({
return { cron: {
fullYear: 0, type: Object,
radioValue: 1, default: {
cycle01: 0, second: "*",
cycle02: 0, min: "*",
average01: 0, hour: "*",
average02: 1, day: "*",
checkboxList: [], month: "*",
checkNum: this.$options.propsData.check week: "?",
} year: ""
}, }
name: 'crontab-year', },
props: ['check', 'month', 'cron'], check: {
methods: { type: Function,
// 单选按钮值变化时 default: () => {
radioChange() { }
switch (this.radioValue) { }
case 1: })
this.$emit('update', 'year', ''); const fullYear = ref(0)
break; const maxFullYear = ref(0)
case 2: const radioValue = ref(1)
this.$emit('update', 'year', '*'); const cycle01 = ref(0)
break; const cycle02 = ref(0)
case 3: const average01 = ref(0)
this.$emit('update', 'year', this.cycleTotal); const average02 = ref(1)
break; const checkboxList = ref([])
case 4: const checkCopy = ref([])
this.$emit('update', 'year', this.averageTotal); const cycleTotal = computed(() => {
break; cycle01.value = props.check(cycle01.value, fullYear.value, maxFullYear.value - 1)
case 5: cycle02.value = props.check(cycle02.value, cycle01.value + 1, maxFullYear.value)
this.$emit('update', 'year', this.checkboxString); return cycle01.value + '-' + cycle02.value
break; })
} const averageTotal = computed(() => {
}, average01.value = props.check(average01.value, fullYear.value, maxFullYear.value - 1)
// 周期两个值变化时 average02.value = props.check(average02.value, 1, 10)
cycleChange() { return average01.value + '/' + average02.value
if (this.radioValue == '3') { })
this.$emit('update', 'year', this.cycleTotal); const checkboxString = computed(() => {
} return checkboxList.value.join(',')
}, })
// 平均两个值变化时 watch(() => props.cron.year, value => changeRadioValue(value))
averageChange() { watch([radioValue, cycleTotal, averageTotal, checkboxString], () => onRadioChange())
if (this.radioValue == '4') { function changeRadioValue(value) {
this.$emit('update', 'year', this.averageTotal); if (value === '') {
} radioValue.value = 1
}, } else if (value === "*") {
// checkbox值变化时 radioValue.value = 2
checkboxChange() { } else if (value.indexOf("-") > -1) {
if (this.radioValue == '5') { const indexArr = value.split('-')
this.$emit('update', 'year', this.checkboxString); cycle01.value = Number(indexArr[0])
} cycle02.value = Number(indexArr[1])
} radioValue.value = 3
}, } else if (value.indexOf("/") > -1) {
watch: { const indexArr = value.split('/')
'radioValue': 'radioChange', average01.value = Number(indexArr[1])
'cycleTotal': 'cycleChange', average02.value = Number(indexArr[0])
'averageTotal': 'averageChange', radioValue.value = 4
'checkboxString': 'checkboxChange' } else {
}, checkboxList.value = [...new Set(value.split(',').map(item => Number(item)))]
computed: { radioValue.value = 5
// 计算两个周期值 }
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, this.fullYear, 2098)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : this.fullYear + 1, 2099)
return cycle01 + '-' + cycle02;
},
// 计算平均用到的值
averageTotal: function () {
const average01 = this.checkNum(this.average01, this.fullYear, 2098)
const average02 = this.checkNum(this.average02, 1, 2099 - average01 || this.fullYear)
return average01 + '/' + average02;
},
// 计算勾选的checkbox值合集
checkboxString: function () {
let str = this.checkboxList.join();
return str;
}
},
mounted: function () {
// 仅获取当前年份
this.fullYear = Number(new Date().getFullYear());
this.cycle01 = this.fullYear
this.average01 = this.fullYear
}
} }
function onRadioChange() {
switch (radioValue.value) {
case 1:
emit('update', 'year', '', 'year')
break
case 2:
emit('update', 'year', '*', 'year')
break
case 3:
emit('update', 'year', cycleTotal.value, 'year')
break
case 4:
emit('update', 'year', averageTotal.value, 'year')
break
case 5:
if (checkboxList.value.length === 0) {
checkboxList.value.push(checkCopy.value[0])
} else {
checkCopy.value = checkboxList.value
}
emit('update', 'year', checkboxString.value, 'year')
break
}
}
onMounted(() => {
fullYear.value = Number(new Date().getFullYear())
maxFullYear.value = fullYear.value + 10
cycle01.value = fullYear.value
cycle02.value = cycle01.value + 1
average01.value = fullYear.value
checkCopy.value = [fullYear.value]
})
</script> </script>
<style lang="scss" scoped>
.el-input-number--small, .el-select, .el-select--small {
margin: 0 0.2rem;
}
.el-select, .el-select--small {
width: 18.8rem;
}
</style>

View File

@@ -1,49 +0,0 @@
import Vue from 'vue'
import store from '@/store'
import DataDict from '@/utils/dict'
import { getDicts as getDicts } from '@/api/system/dict/data'
function searchDictByKey(dict, key) {
if (key == null && key == "") {
return null
}
try {
for (let i = 0; i < dict.length; i++) {
if (dict[i].key == key) {
return dict[i].value
}
}
} catch (e) {
return null
}
}
function install() {
Vue.use(DataDict, {
metas: {
'*': {
labelField: 'dictLabel',
valueField: 'dictValue',
request(dictMeta) {
const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
if (storeDict) {
return new Promise(resolve => { resolve(storeDict) })
} else {
return new Promise((resolve, reject) => {
getDicts(dictMeta.type).then(res => {
store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
resolve(res.data)
}).catch(error => {
reject(error)
})
})
}
},
},
},
})
}
export default {
install,
}

View File

@@ -1,89 +1,82 @@
<template> <template>
<div> <div>
<template v-for="(item, index) in options"> <template v-for="(item, index) in options">
<template v-if="values.includes(item.value)"> <template v-if="values.includes(item.value)">
<span <span
v-if="(item.raw.listClass == 'default' || item.raw.listClass == '') && (item.raw.cssClass == '' || item.raw.cssClass == null)" v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
:key="item.value" :key="item.value"
:index="index" :index="index"
:class="item.raw.cssClass" :class="item.elTagClass"
>{{ item.label + ' ' }}</span >{{ item.label + " " }}</span>
> <el-tag
<el-tag v-else
v-else :disable-transitions="true"
:disable-transitions="true" :key="item.value + ''"
:key="item.value" :index="index"
:index="index" :type="item.elTagType === 'primary' ? '' : item.elTagType"
:type="item.raw.listClass == 'primary' ? '' : item.raw.listClass" :class="item.elTagClass"
:class="item.raw.cssClass" >{{ item.label + " " }}</el-tag>
> </template>
{{ item.label + ' ' }} </template>
</el-tag> <template v-if="unmatch && showValue">
</template> {{ unmatchArray | handleArray }}
</template> </template>
<template v-if="unmatch && showValue"> </div>
{{ unmatchArray | handleArray }} </template>
</template>
</div> <script setup>
</template> // 记录未匹配的项
const unmatchArray = ref([]);
<script>
export default { const props = defineProps({
name: "DictTag", // 数据
props: { options: {
options: { type: Array,
type: Array, default: null,
default: null, },
}, // 当前的值
value: [Number, String, Array], value: [Number, String, Array],
// 当未找到匹配的数据时显示value // 当未找到匹配的数据时显示value
showValue: { showValue: {
type: Boolean, type: Boolean,
default: true, default: true,
}, },
separator: { separator: {
type: String, type: String,
default: "," default: ",",
} }
}, });
data() {
return { const values = computed(() => {
unmatchArray: [], // 记录未匹配的项 if (props.value === null || typeof props.value === 'undefined' || props.value === '') return [];
} return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator);
}, });
computed: {
values() { const unmatch = computed(() => {
if (this.value === null || typeof this.value === 'undefined' || this.value === '') return [] unmatchArray.value = [];
return Array.isArray(this.value) ? this.value.map(item => '' + item) : String(this.value).split(this.separator) // 没有value不显示
}, if (props.value === null || typeof props.value === 'undefined' || props.value === '' || props.options.length === 0) return false
unmatch() { // 传入值为数组
this.unmatchArray = [] let unmatch = false // 添加一个标志来判断是否有未匹配项
// 没有value不显示 values.value.forEach(item => {
if (this.value === null || typeof this.value === 'undefined' || this.value === '' || this.options.length === 0) return false if (!props.options.some(v => v.value === item)) {
// 传入值为数组 unmatchArray.value.push(item)
let unmatch = false // 添加一个标志来判断是否有未匹配项 unmatch = true // 如果有未匹配项将标志设置为true
this.values.forEach(item => { }
if (!this.options.some(v => v.value === item)) { })
this.unmatchArray.push(item) return unmatch // 返回标志的值
unmatch = true // 如果有未匹配项将标志设置为true });
}
}) function handleArray(array) {
return unmatch // 返回标志的值 if (array.length === 0) return "";
}, return array.reduce((pre, cur) => {
return pre + " " + cur;
}, });
filters: { }
handleArray(array) { </script>
if (array.length === 0) return '';
return array.reduce((pre, cur) => { <style scoped>
return pre + ' ' + cur; .el-tag + .el-tag {
}) margin-left: 10px;
}, }
} </style>
};
</script>
<style scoped>
.el-tag + .el-tag {
margin-left: 10px;
}
</style>

View File

@@ -1,274 +1,251 @@
<template> <template>
<div> <div>
<el-upload <el-upload
:action="uploadUrl" :action="uploadUrl"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:on-error="handleUploadError" :on-error="handleUploadError"
name="file" name="file"
:show-file-list="false" :show-file-list="false"
:headers="headers" :headers="headers"
style="display: none" class="editor-img-uploader"
ref="upload" v-if="type == 'url'"
v-if="this.type == 'url'" >
> <i ref="uploadRef" class="editor-img-uploader"></i>
</el-upload> </el-upload>
<div class="editor" ref="editor" :style="styles"></div> </div>
</div> <div class="editor">
</template> <quill-editor
ref="quillEditorRef"
<script> v-model:content="content"
import Quill from "quill"; contentType="html"
import "quill/dist/quill.core.css"; @textChange="(e) => $emit('update:modelValue', content)"
import "quill/dist/quill.snow.css"; :options="options"
import "quill/dist/quill.bubble.css"; :style="styles"
import { getToken } from "@/utils/auth"; />
</div>
export default { </template>
name: "Editor",
props: { <script setup>
/* 编辑器的内容 */ import { QuillEditor } from "@vueup/vue-quill";
value: { import "@vueup/vue-quill/dist/vue-quill.snow.css";
type: String, import { getToken } from "@/utils/auth";
default: "",
}, const { proxy } = getCurrentInstance();
/* 高度 */
height: { const quillEditorRef = ref();
type: Number, const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传的图片服务器地址
default: null, const headers = ref({
}, Authorization: "Bearer " + getToken()
/* 最小高度 */ });
minHeight: {
type: Number, const props = defineProps({
default: null, /* 编辑器的内容 */
}, modelValue: {
/* 只读 */ type: String,
readOnly: { },
type: Boolean, /* 高度 */
default: false, height: {
}, type: Number,
/* 上传文件大小限制(MB) */ default: null,
fileSize: { },
type: Number, /* 最小高度 */
default: 5, minHeight: {
}, type: Number,
/* 类型base64格式、url格式 */ default: null,
type: { },
type: String, /* 只读 */
default: "url", readOnly: {
} type: Boolean,
}, default: false,
data() { },
return { /* 上传文件大小限制(MB) */
uploadUrl: process.env.VUE_APP_BASE_API + "/file/upload", // 上传的图片服务器地址 fileSize: {
headers: { type: Number,
Authorization: "Bearer " + getToken() default: 5,
}, },
Quill: null, /* 类型base64格式、url格式 */
currentValue: "", type: {
options: { type: String,
theme: "snow", default: "base64",
bounds: document.body, }
debug: "warn", });
modules: {
// 工具栏配置 const options = ref({
toolbar: [ theme: "snow",
["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线 bounds: document.body,
["blockquote", "code-block"], // 引用 代码块 debug: "warn",
[{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表 modules: {
[{ indent: "-1" }, { indent: "+1" }], // 缩进 // 工具栏配置
[{ size: ["small", false, "large", "huge"] }], // 字体大小 toolbar: [
[{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题 ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
[{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色 ["blockquote", "code-block"], // 引用 代码块
[{ align: [] }], // 对齐方式 [{ list: "ordered" }, { list: "bullet" }], // 有序、无序列表
["clean"], // 清除文本格式 [{ indent: "-1" }, { indent: "+1" }], // 缩进
["link", "image", "video"] // 链接、图片、视频 [{ size: ["small", false, "large", "huge"] }], // 字体大小
], [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
}, [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
placeholder: "请输入内容", [{ align: [] }], // 对齐方式
readOnly: this.readOnly, ["clean"], // 清除文本格式
}, ["link", "image", "video"] // 链接、图片、视频
}; ],
}, },
computed: { placeholder: "请输入内容",
styles() { readOnly: props.readOnly
let style = {}; });
if (this.minHeight) {
style.minHeight = `${this.minHeight}px`; const styles = computed(() => {
} let style = {};
if (this.height) { if (props.minHeight) {
style.height = `${this.height}px`; style.minHeight = `${props.minHeight}px`;
} }
return style; if (props.height) {
}, style.height = `${props.height}px`;
}, }
watch: { return style;
value: { });
handler(val) {
if (val !== this.currentValue) { const content = ref("");
this.currentValue = val === null ? "" : val; watch(() => props.modelValue, (v) => {
if (this.Quill) { if (v !== content.value) {
this.Quill.pasteHTML(this.currentValue); content.value = v === undefined ? "<p></p>" : v;
} }
} }, { immediate: true });
},
immediate: true, // 如果设置了上传地址则自定义图片上传事件
}, onMounted(() => {
}, if (props.type == 'url') {
mounted() { let quill = quillEditorRef.value.getQuill();
this.init(); let toolbar = quill.getModule("toolbar");
}, toolbar.addHandler("image", (value) => {
beforeDestroy() { if (value) {
this.Quill = null; proxy.$refs.uploadRef.click();
}, } else {
methods: { quill.format("image", false);
init() { }
const editor = this.$refs.editor; });
this.Quill = new Quill(editor, this.options); }
// 如果设置了上传地址则自定义图片上传事件 });
if (this.type == 'url') {
let toolbar = this.Quill.getModule("toolbar"); // 上传前校检格式和大小
toolbar.addHandler("image", (value) => { function handleBeforeUpload(file) {
if (value) { const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"];
this.$refs.upload.$children[0].$refs.input.click(); const isJPG = type.includes(file.type);
} else { //检验文件格式
this.quill.format("image", false); if (!isJPG) {
} proxy.$modal.msgError(`图片格式错误!`);
}); return false;
} }
this.Quill.pasteHTML(this.currentValue); // 校检文件大小
this.Quill.on("text-change", (delta, oldDelta, source) => { if (props.fileSize) {
const html = this.$refs.editor.children[0].innerHTML; const isLt = file.size / 1024 / 1024 < props.fileSize;
const text = this.Quill.getText(); if (!isLt) {
const quill = this.Quill; proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
this.currentValue = html; return false;
this.$emit("input", html); }
this.$emit("on-change", { html, text, quill }); }
}); return true;
this.Quill.on("text-change", (delta, oldDelta, source) => { }
this.$emit("on-text-change", delta, oldDelta, source);
}); // 上传成功处理
this.Quill.on("selection-change", (range, oldRange, source) => { function handleUploadSuccess(res, file) {
this.$emit("on-selection-change", range, oldRange, source); // 如果上传成功
}); if (res.code == 200) {
this.Quill.on("editor-change", (eventName, ...args) => { // 获取富文本实例
this.$emit("on-editor-change", eventName, ...args); let quill = toRaw(quillEditorRef.value).getQuill();
}); // 获取光标位置
}, let length = quill.selection.savedRange.index;
// 上传前校检格式和大小 // 插入图片res.url为服务器返回的图片链接地址
handleBeforeUpload(file) { quill.insertEmbed(length, "image", res.data.url);
const type = ["image/jpeg", "image/jpg", "image/png", "image/svg"]; // 调整光标到最后
const isJPG = type.includes(file.type); quill.setSelection(length + 1);
// 检验文件格式 } else {
if (!isJPG) { proxy.$modal.msgError("图片插入失败");
this.$message.error(`图片格式错误!`); }
return false; }
}
// 校检文件大小 // 上传失败处理
if (this.fileSize) { function handleUploadError() {
const isLt = file.size / 1024 / 1024 < this.fileSize; proxy.$modal.msgError("图片插入失败");
if (!isLt) { }
this.$message.error(`上传文件大小不能超过 ${this.fileSize} MB!`); </script>
return false;
} <style>
} .editor-img-uploader {
return true; display: none;
}, }
handleUploadSuccess(res, file) { .editor, .ql-toolbar {
// 如果上传成功 white-space: pre-wrap !important;
if (res.code == 200) { line-height: normal !important;
// 获取富文本组件实例 }
let quill = this.Quill; .quill-img {
// 获取光标所在位置 display: none;
let length = quill.getSelection().index; }
// 插入图片 res.url为服务器返回的图片地址 .ql-snow .ql-tooltip[data-mode="link"]::before {
quill.insertEmbed(length, "image", res.data.url); content: "请输入链接地址:";
// 调整光标到最后 }
quill.setSelection(length + 1); .ql-snow .ql-tooltip.ql-editing a.ql-action::after {
} else { border-right: 0px;
this.$message.error("图片插入失败"); content: "保存";
} padding-right: 0px;
}, }
handleUploadError() { .ql-snow .ql-tooltip[data-mode="video"]::before {
this.$message.error("图片插入失败"); content: "请输入视频地址:";
}, }
}, .ql-snow .ql-picker.ql-size .ql-picker-label::before,
}; .ql-snow .ql-picker.ql-size .ql-picker-item::before {
</script> content: "14px";
}
<style> .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before,
.editor, .ql-toolbar { .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before {
white-space: pre-wrap !important; content: "10px";
line-height: normal !important; }
} .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before,
.quill-img { .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before {
display: none; content: "18px";
} }
.ql-snow .ql-tooltip[data-mode="link"]::before { .ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before,
content: "请输入链接地址:"; .ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before {
} content: "32px";
.ql-snow .ql-tooltip.ql-editing a.ql-action::after { }
border-right: 0px; .ql-snow .ql-picker.ql-header .ql-picker-label::before,
content: "保存"; .ql-snow .ql-picker.ql-header .ql-picker-item::before {
padding-right: 0px; content: "文本";
} }
.ql-snow .ql-tooltip[data-mode="video"]::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before,
content: "请输入视频地址:"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before {
} content: "标题1";
.ql-snow .ql-picker.ql-size .ql-picker-label::before, }
.ql-snow .ql-picker.ql-size .ql-picker-item::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before,
content: "14px"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before {
} content: "标题2";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="small"]::before, }
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="small"]::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before,
content: "10px"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before {
} content: "标题3";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="large"]::before, }
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="large"]::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before,
content: "18px"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before {
} content: "标题4";
.ql-snow .ql-picker.ql-size .ql-picker-label[data-value="huge"]::before, }
.ql-snow .ql-picker.ql-size .ql-picker-item[data-value="huge"]::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
content: "32px"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
} content: "标题5";
.ql-snow .ql-picker.ql-header .ql-picker-label::before, }
.ql-snow .ql-picker.ql-header .ql-picker-item::before { .ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
content: "文本"; .ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
} content: "标题6";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="1"]::before, }
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="1"]::before { .ql-snow .ql-picker.ql-font .ql-picker-label::before,
content: "标题1"; .ql-snow .ql-picker.ql-font .ql-picker-item::before {
} content: "标准字体";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="2"]::before, }
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="2"]::before { .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
content: "标题2"; .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
} content: "衬线字体";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="3"]::before, }
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="3"]::before { .ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
content: "标题3"; .ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
} content: "等宽字体";
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="4"]::before, }
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="4"]::before { </style>
content: "标题4";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="5"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="5"]::before {
content: "标题5";
}
.ql-snow .ql-picker.ql-header .ql-picker-label[data-value="6"]::before,
.ql-snow .ql-picker.ql-header .ql-picker-item[data-value="6"]::before {
content: "标题6";
}
.ql-snow .ql-picker.ql-font .ql-picker-label::before,
.ql-snow .ql-picker.ql-font .ql-picker-item::before {
content: "标准字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="serif"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="serif"]::before {
content: "衬线字体";
}
.ql-snow .ql-picker.ql-font .ql-picker-label[data-value="monospace"]::before,
.ql-snow .ql-picker.ql-font .ql-picker-item[data-value="monospace"]::before {
content: "等宽字体";
}
</style>

View File

@@ -1,215 +1,206 @@
<template> <template>
<div class="upload-file"> <div class="upload-file">
<el-upload <el-upload
multiple multiple
:action="uploadFileUrl" :action="uploadFileUrl"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:file-list="fileList" :file-list="fileList"
:limit="limit" :limit="limit"
:on-error="handleUploadError" :on-error="handleUploadError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:show-file-list="false" :show-file-list="false"
:headers="headers" :headers="headers"
class="upload-file-uploader" class="upload-file-uploader"
ref="fileUpload" ref="fileUpload"
> >
<!-- 上传按钮 --> <!-- 上传按钮 -->
<el-button size="mini" type="primary">选取文件</el-button> <el-button type="primary">选取文件</el-button>
<!-- 上传提示 --> </el-upload>
<div class="el-upload__tip" slot="tip" v-if="showTip"> <!-- 上传提示 -->
请上传 <div class="el-upload__tip" v-if="showTip">
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> 请上传
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
的文件 <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
</div> 的文件
</el-upload> </div>
<!-- 文件列表 -->
<!-- 文件列表 --> <transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul">
<transition-group class="upload-file-list el-upload-list el-upload-list--text" name="el-fade-in-linear" tag="ul"> <li :key="file.uid" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList">
<li :key="file.url" class="el-upload-list__item ele-upload-list__item-content" v-for="(file, index) in fileList"> <el-link :href="file.url" :underline="false" target="_blank">
<el-link :href="file.url" :underline="false" target="_blank"> <span class="el-icon-document"> {{ getFileName(file.name) }} </span>
<span class="el-icon-document"> {{ getFileName(file.name) }} </span> </el-link>
</el-link> <div class="ele-upload-list__item-content-action">
<div class="ele-upload-list__item-content-action"> <el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link>
<el-link :underline="false" @click="handleDelete(index)" type="danger">删除</el-link> </div>
</div> </li>
</li> </transition-group>
</transition-group> </div>
</div> </template>
</template>
<script setup>
<script> import { getToken } from "@/utils/auth";
import { getToken } from "@/utils/auth";
const props = defineProps({
export default { modelValue: [String, Object, Array],
name: "FileUpload", // 数量限制
props: { limit: {
// 值 type: Number,
value: [String, Object, Array], default: 5,
// 数量限制 },
limit: { // 大小限制(MB)
type: Number, fileSize: {
default: 5, type: Number,
}, default: 5,
// 大小限制(MB) },
fileSize: { // 文件类型, 例如['png', 'jpg', 'jpeg']
type: Number, fileType: {
default: 5, type: Array,
}, default: () => ["doc", "xls", "ppt", "txt", "pdf"],
// 文件类型, 例如['png', 'jpg', 'jpeg'] },
fileType: { // 是否显示提示
type: Array, isShowTip: {
default: () => ["doc", "xls", "ppt", "txt", "pdf"], type: Boolean,
}, default: true
// 是否显示提示 }
isShowTip: { });
type: Boolean,
default: true const { proxy } = getCurrentInstance();
} const emit = defineEmits();
}, const number = ref(0);
data() { const uploadList = ref([]);
return { const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传文件服务器地址
number: 0, const headers = ref({ Authorization: "Bearer " + getToken() });
uploadList: [], const fileList = ref([]);
uploadFileUrl: process.env.VUE_APP_BASE_API + "/file/upload", // 上传文件服务器地址 const showTip = computed(
headers: { () => props.isShowTip && (props.fileType || props.fileSize)
Authorization: "Bearer " + getToken(), );
},
fileList: [], watch(() => props.modelValue, val => {
}; if (val) {
}, let temp = 1;
watch: { // 首先将值转为数组
value: { const list = Array.isArray(val) ? val : props.modelValue.split(',');
handler(val) { // 然后将数组转为对象数组
if (val) { fileList.value = list.map(item => {
let temp = 1; if (typeof item === "string") {
// 首先将值转为数组 item = { name: item, url: item };
const list = Array.isArray(val) ? val : this.value.split(','); }
// 然后将数组转为对象数组 item.uid = item.uid || new Date().getTime() + temp++;
this.fileList = list.map(item => { return item;
if (typeof item === "string") { });
item = { name: item, url: item }; } else {
} fileList.value = [];
item.uid = item.uid || new Date().getTime() + temp++; return [];
return item; }
}); },{ deep: true, immediate: true });
} else {
this.fileList = []; // 上传前校检格式和大小
return []; function handleBeforeUpload(file) {
} // 校检文件类型
}, if (props.fileType.length) {
deep: true, const fileName = file.name.split('.');
immediate: true const fileExt = fileName[fileName.length - 1];
} const isTypeOk = props.fileType.indexOf(fileExt) >= 0;
}, if (!isTypeOk) {
computed: { proxy.$modal.msgError(`文件格式不正确, 请上传${props.fileType.join("/")}格式文件!`);
// 是否显示提示 return false;
showTip() { }
return this.isShowTip && (this.fileType || this.fileSize); }
}, // 校检文件大小
}, if (props.fileSize) {
methods: { const isLt = file.size / 1024 / 1024 < props.fileSize;
// 上传前校检格式和大小 if (!isLt) {
handleBeforeUpload(file) { proxy.$modal.msgError(`上传文件大小不能超过 ${props.fileSize} MB!`);
// 校检文件类型 return false;
if (this.fileType) { }
const fileName = file.name.split('.'); }
const fileExt = fileName[fileName.length - 1]; proxy.$modal.loading("正在上传文件,请稍候...");
const isTypeOk = this.fileType.indexOf(fileExt) >= 0; number.value++;
if (!isTypeOk) { return true;
this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}格式文件!`); }
return false;
} // 文件个数超出
} function handleExceed() {
// 校检文件大小 proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
if (this.fileSize) { }
const isLt = file.size / 1024 / 1024 < this.fileSize;
if (!isLt) { // 上传失败
this.$modal.msgError(`上传文件大小不能超过 ${this.fileSize} MB!`); function handleUploadError(err) {
return false; proxy.$modal.msgError("上传文件失败");
} }
}
this.$modal.loading("正在上传文件,请稍候..."); // 上传成功回调
this.number++; function handleUploadSuccess(res, file) {
return true; if (res.code === 200) {
}, uploadList.value.push({ name: res.data.url, url: res.data.url });
// 文件个数超出 uploadedSuccessfully();
handleExceed() { } else {
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`); number.value--;
}, proxy.$modal.closeLoading();
// 上传失败 proxy.$modal.msgError(res.msg);
handleUploadError(err) { proxy.$refs.fileUpload.handleRemove(file);
this.$modal.msgError("上传文件失败,请重试"); uploadedSuccessfully();
this.$modal.closeLoading() }
}, }
// 上传成功回调
handleUploadSuccess(res, file) { // 删除文件
if (res.code === 200) { function handleDelete(index) {
this.uploadList.push({ name: res.data.url, url: res.data.url }); fileList.value.splice(index, 1);
this.uploadedSuccessfully(); emit("update:modelValue", listToString(fileList.value));
} else { }
this.number--;
this.$modal.closeLoading(); // 上传结束处理
this.$modal.msgError(res.msg); function uploadedSuccessfully() {
this.$refs.fileUpload.handleRemove(file); if (number.value > 0 && uploadList.value.length === number.value) {
this.uploadedSuccessfully(); fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
} uploadList.value = [];
}, number.value = 0;
// 删除文件 emit("update:modelValue", listToString(fileList.value));
handleDelete(index) { proxy.$modal.closeLoading();
this.fileList.splice(index, 1); }
this.$emit("input", this.listToString(this.fileList)); }
},
// 上传结束处理 // 获取文件名称
uploadedSuccessfully() { function getFileName(name) {
if (this.number > 0 && this.uploadList.length === this.number) { // 如果是url那么取最后的名字 如果不是直接返回
this.fileList = this.fileList.concat(this.uploadList); if (name.lastIndexOf("/") > -1) {
this.uploadList = []; return name.slice(name.lastIndexOf("/") + 1);
this.number = 0; } else {
this.$emit("input", this.listToString(this.fileList)); return name;
this.$modal.closeLoading(); }
} }
},
// 获取文件名称 // 对象转成指定字符串分隔
getFileName(name) { function listToString(list, separator) {
// 如果是url那么取最后的名字 如果不是直接返回 let strs = "";
if (name.lastIndexOf("/") > -1) { separator = separator || ",";
return name.slice(name.lastIndexOf("/") + 1); for (let i in list) {
} else { if (list[i].url) {
return name; strs += list[i].url + separator;
} }
}, }
// 对象转成指定字符串分隔 return strs != '' ? strs.substr(0, strs.length - 1) : '';
listToString(list, separator) { }
let strs = ""; </script>
separator = separator || ",";
for (let i in list) { <style scoped lang="scss">
strs += list[i].url + separator; .upload-file-uploader {
} margin-bottom: 5px;
return strs != '' ? strs.substr(0, strs.length - 1) : ''; }
} .upload-file-list .el-upload-list__item {
} border: 1px solid #e4e7ed;
}; line-height: 2;
</script> margin-bottom: 10px;
position: relative;
<style scoped lang="scss"> }
.upload-file-uploader { .upload-file-list .ele-upload-list__item-content {
margin-bottom: 5px; display: flex;
} justify-content: space-between;
.upload-file-list .el-upload-list__item { align-items: center;
border: 1px solid #e4e7ed; color: inherit;
line-height: 2; }
margin-bottom: 10px; .ele-upload-list__item-content-action .el-link {
position: relative; margin-right: 10px;
} }
.upload-file-list .ele-upload-list__item-content { </style>
display: flex;
justify-content: space-between;
align-items: center;
color: inherit;
}
.ele-upload-list__item-content-action .el-link {
margin-right: 10px;
}
</style>

View File

@@ -1,44 +1,41 @@
<template> <template>
<div style="padding: 0 15px;" @click="toggleClick"> <div style="padding: 0 15px;" @click="toggleClick">
<svg <svg
:class="{'is-active':isActive}" :class="{'is-active':isActive}"
class="hamburger" class="hamburger"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="64" width="64"
height="64" height="64"
> >
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" /> <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg> </svg>
</div> </div>
</template> </template>
<script> <script setup>
export default { defineProps({
name: 'Hamburger', isActive: {
props: { type: Boolean,
isActive: { default: false
type: Boolean, }
default: false })
}
}, const emit = defineEmits()
methods: { const toggleClick = () => {
toggleClick() { emit('toggleClick');
this.$emit('toggleClick') }
} </script>
}
} <style scoped>
</script> .hamburger {
display: inline-block;
<style scoped> vertical-align: middle;
.hamburger { width: 20px;
display: inline-block; height: 20px;
vertical-align: middle; }
width: 20px;
height: 20px; .hamburger.is-active {
} transform: rotate(180deg);
}
.hamburger.is-active { </style>
transform: rotate(180deg);
}
</style>

View File

@@ -1,198 +1,187 @@
<template> <template>
<div :class="{'show':show}" class="header-search"> <div :class="{ 'show': show }" class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-select <el-select
ref="headerSearchSelect" ref="headerSearchSelectRef"
v-model="search" v-model="search"
:remote-method="querySearch" :remote-method="querySearch"
filterable filterable
default-first-option default-first-option
remote remote
placeholder="Search" placeholder="Search"
class="header-search-select" class="header-search-select"
@change="change" @change="change"
> >
<el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" /> <el-option v-for="option in options" :key="option.item.path" :value="option.item" :label="option.item.title.join(' > ')" />
</el-select> </el-select>
</div> </div>
</template> </template>
<script> <script setup>
// fuse is a lightweight fuzzy-search module import Fuse from 'fuse.js'
// make search results more in line with expectations import { getNormalPath } from '@/utils/ruoyi'
import Fuse from 'fuse.js/dist/fuse.min.js' import { isHttp } from '@/utils/validate'
import path from 'path' import usePermissionStore from '@/store/modules/permission'
export default { const search = ref('');
name: 'HeaderSearch', const options = ref([]);
data() { const searchPool = ref([]);
return { const show = ref(false);
search: '', const fuse = ref(undefined);
options: [], const headerSearchSelectRef = ref(null);
searchPool: [], const router = useRouter();
show: false, const routes = computed(() => usePermissionStore().routes);
fuse: undefined
} function click() {
}, show.value = !show.value
computed: { if (show.value) {
routes() { headerSearchSelectRef.value && headerSearchSelectRef.value.focus()
return this.$store.getters.permission_routes }
} };
}, function close() {
watch: { headerSearchSelectRef.value && headerSearchSelectRef.value.blur()
routes() { options.value = []
this.searchPool = this.generateRoutes(this.routes) show.value = false
}, }
searchPool(list) { function change(val) {
this.initFuse(list) const path = val.path;
}, const query = val.query;
show(value) { if (isHttp(path)) {
if (value) { // http(s):// 路径新窗口打开
document.body.addEventListener('click', this.close) const pindex = path.indexOf("http");
} else { window.open(path.substr(pindex, path.length), "_blank");
document.body.removeEventListener('click', this.close) } else {
} if (query) {
} router.push({ path: path, query: JSON.parse(query) });
}, } else {
mounted() { router.push(path)
this.searchPool = this.generateRoutes(this.routes) }
}, }
methods: {
click() { search.value = ''
this.show = !this.show options.value = []
if (this.show) { nextTick(() => {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus() show.value = false
} })
}, }
close() { function initFuse(list) {
this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur() fuse.value = new Fuse(list, {
this.options = [] shouldSort: true,
this.show = false threshold: 0.4,
}, location: 0,
change(val) { distance: 100,
const path = val.path; minMatchCharLength: 1,
const query = val.query; keys: [{
if(this.ishttp(val.path)) { name: 'title',
// http(s):// 路径新窗口打开 weight: 0.7
const pindex = path.indexOf("http"); }, {
window.open(path.substr(pindex, path.length), "_blank"); name: 'path',
} else { weight: 0.3
if (query) { }]
this.$router.push({ path: path, query: JSON.parse(query) }); })
} else { }
this.$router.push(path) // Filter out the routes that can be displayed in the sidebar
} // And generate the internationalized title
} function generateRoutes(routes, basePath = '', prefixTitle = []) {
this.search = '' let res = []
this.options = []
this.$nextTick(() => { for (const r of routes) {
this.show = false // skip hidden router
}) if (r.hidden) { continue }
}, const p = r.path.length > 0 && r.path[0] === '/' ? r.path : '/' + r.path;
initFuse(list) { const data = {
this.fuse = new Fuse(list, { path: !isHttp(r.path) ? getNormalPath(basePath + p) : r.path,
shouldSort: true, title: [...prefixTitle]
threshold: 0.4, }
location: 0,
distance: 100, if (r.meta && r.meta.title) {
minMatchCharLength: 1, data.title = [...data.title, r.meta.title]
keys: [{
name: 'title', if (r.redirect !== 'noRedirect') {
weight: 0.7 // only push the routes with title
}, { // special case: need to exclude parent router without redirect
name: 'path', res.push(data)
weight: 0.3 }
}] }
}) if (r.query) {
}, data.query = r.query
// Filter out the routes that can be displayed in the sidebar }
// And generate the internationalized title
generateRoutes(routes, basePath = '/', prefixTitle = []) { // recursive child routes
let res = [] if (r.children) {
const tempRoutes = generateRoutes(r.children, data.path, data.title)
for (const router of routes) { if (tempRoutes.length >= 1) {
// skip hidden router res = [...res, ...tempRoutes]
if (router.hidden) { continue } }
}
const data = { }
path: !this.ishttp(router.path) ? path.resolve(basePath, router.path) : router.path, return res
title: [...prefixTitle] }
} function querySearch(query) {
if (query !== '') {
if (router.meta && router.meta.title) { options.value = fuse.value.search(query)
data.title = [...data.title, router.meta.title] } else {
options.value = []
if (router.redirect !== 'noRedirect') { }
// only push the routes with title }
// special case: need to exclude parent router without redirect
res.push(data) onMounted(() => {
} searchPool.value = generateRoutes(routes.value);
} })
if (router.query) { watchEffect(() => {
data.query = router.query searchPool.value = generateRoutes(routes.value)
} })
// recursive child routes watch(show, (value) => {
if (router.children) { if (value) {
const tempRoutes = this.generateRoutes(router.children, data.path, data.title) document.body.addEventListener('click', close)
if (tempRoutes.length >= 1) { } else {
res = [...res, ...tempRoutes] document.body.removeEventListener('click', close)
} }
} })
}
return res watch(searchPool, (list) => {
}, initFuse(list)
querySearch(query) { })
if (query !== '') { </script>
this.options = this.fuse.search(query)
} else { <style lang='scss' scoped>
this.options = [] .header-search {
} font-size: 0 !important;
},
ishttp(url) { .search-icon {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1 cursor: pointer;
} font-size: 18px;
} vertical-align: middle;
} }
</script>
.header-search-select {
<style lang="scss" scoped> font-size: 18px;
.header-search { transition: width 0.2s;
font-size: 0 !important; width: 0;
overflow: hidden;
.search-icon { background: transparent;
cursor: pointer; border-radius: 0;
font-size: 18px; display: inline-block;
vertical-align: middle; vertical-align: middle;
}
:deep(.el-input__inner) {
.header-search-select { border-radius: 0;
font-size: 18px; border: 0;
transition: width 0.2s; padding-left: 0;
width: 0; padding-right: 0;
overflow: hidden; box-shadow: none !important;
background: transparent; border-bottom: 1px solid #d9d9d9;
border-radius: 0; vertical-align: middle;
display: inline-block; }
vertical-align: middle; }
::v-deep .el-input__inner { &.show {
border-radius: 0; .header-search-select {
border: 0; width: 210px;
padding-left: 0; margin-left: 10px;
padding-right: 0; }
box-shadow: none !important; }
border-bottom: 1px solid #d9d9d9; }
vertical-align: middle; </style>
}
}
&.show {
.header-search-select {
width: 210px;
margin-left: 10px;
}
}
}
</style>

View File

@@ -1,104 +1,111 @@
<!-- @author zhengjie --> <template>
<template> <div class="icon-body">
<div class="icon-body"> <el-input
<el-input v-model="name" class="icon-search" clearable placeholder="请输入图标名称" @clear="filterIcons" @input="filterIcons"> v-model="iconName"
<i slot="suffix" class="el-icon-search el-input__icon" /> class="icon-search"
</el-input> clearable
<div class="icon-list"> placeholder="请输入图标名称"
<div class="list-container"> @clear="filterIcons"
<div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)"> @input="filterIcons"
<div :class="['icon-item', { active: activeIcon === item }]"> >
<svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/> <template #suffix><i class="el-icon-search el-input__icon" /></template>
<span>{{ item }}</span> </el-input>
</div> <div class="icon-list">
</div> <div class="list-container">
</div> <div v-for="(item, index) in iconList" class="icon-item-wrapper" :key="index" @click="selectedIcon(item)">
</div> <div :class="['icon-item', { active: activeIcon === item }]">
</div> <svg-icon :icon-class="item" class-name="icon" style="height: 25px;width: 16px;"/>
</template> <span>{{ item }}</span>
</div>
<script> </div>
import icons from './requireIcons' </div>
export default { </div>
name: 'IconSelect', </div>
props: { </template>
activeIcon: {
type: String <script setup>
} import icons from './requireIcons'
},
data() { const props = defineProps({
return { activeIcon: {
name: '', type: String
iconList: icons }
} });
},
methods: { const iconName = ref('');
filterIcons() { const iconList = ref(icons);
this.iconList = icons const emit = defineEmits(['selected']);
if (this.name) {
this.iconList = this.iconList.filter(item => item.includes(this.name)) function filterIcons() {
} iconList.value = icons
}, if (iconName.value) {
selectedIcon(name) { iconList.value = icons.filter(item => item.indexOf(iconName.value) !== -1)
this.$emit('selected', name) }
document.body.click() }
},
reset() { function selectedIcon(name) {
this.name = '' emit('selected', name)
this.iconList = icons document.body.click()
} }
}
} function reset() {
</script> iconName.value = ''
iconList.value = icons
<style rel="stylesheet/scss" lang="scss" scoped> }
.icon-body {
width: 100%; defineExpose({
padding: 10px; reset
.icon-search { })
position: relative; </script>
margin-bottom: 5px;
} <style lang='scss' scoped>
.icon-list { .icon-body {
height: 200px; width: 100%;
overflow: auto; padding: 10px;
.list-container { .icon-search {
display: flex; position: relative;
flex-wrap: wrap; margin-bottom: 5px;
.icon-item-wrapper { }
width: calc(100% / 3); .icon-list {
height: 25px; height: 200px;
line-height: 25px; overflow: auto;
cursor: pointer; .list-container {
display: flex; display: flex;
.icon-item { flex-wrap: wrap;
display: flex; .icon-item-wrapper {
max-width: 100%; width: calc(100% / 3);
height: 100%; height: 25px;
padding: 0 5px; line-height: 25px;
&:hover { cursor: pointer;
background: #ececec; display: flex;
border-radius: 5px; .icon-item {
} display: flex;
.icon { max-width: 100%;
flex-shrink: 0; height: 100%;
} padding: 0 5px;
span { &:hover {
display: inline-block; background: #ececec;
vertical-align: -0.15em; border-radius: 5px;
fill: currentColor; }
padding-left: 2px; .icon {
overflow: hidden; flex-shrink: 0;
text-overflow: ellipsis; }
white-space: nowrap; span {
} display: inline-block;
} vertical-align: -0.15em;
.icon-item.active { fill: currentColor;
background: #ececec; padding-left: 2px;
border-radius: 5px; overflow: hidden;
} text-overflow: ellipsis;
} white-space: nowrap;
} }
} }
} .icon-item.active {
</style> background: #ececec;
border-radius: 5px;
}
}
}
}
}
</style>

View File

@@ -1,11 +1,8 @@
let icons = []
const req = require.context('../../assets/icons/svg', false, /\.svg$/) const modules = import.meta.glob('./../../assets/icons/svg/*.svg');
const requireAll = requireContext => requireContext.keys() for (const path in modules) {
const p = path.split('assets/icons/svg/')[1].split('.svg')[0];
const re = /\.\/(.*)\.svg/ icons.push(p);
}
const icons = requireAll(req).map(i => {
return i.match(re)[1] export default icons
})
export default icons

View File

@@ -4,57 +4,59 @@
fit="cover" fit="cover"
:style="`width:${realWidth};height:${realHeight};`" :style="`width:${realWidth};height:${realHeight};`"
:preview-src-list="realSrcList" :preview-src-list="realSrcList"
preview-teleported
> >
<div slot="error" class="image-slot"> <template #error>
<i class="el-icon-picture-outline"></i> <div class="image-slot">
</div> <el-icon><picture-filled /></el-icon>
</div>
</template>
</el-image> </el-image>
</template> </template>
<script> <script setup>
export default { const props = defineProps({
name: "ImagePreview", src: {
props: { type: String,
src: { default: ""
type: String,
default: ""
},
width: {
type: [Number, String],
default: ""
},
height: {
type: [Number, String],
default: ""
}
}, },
computed: { width: {
realSrc() { type: [Number, String],
if (!this.src) { default: ""
return;
}
let real_src = this.src.split(",")[0];
return real_src;
},
realSrcList() {
if (!this.src) {
return;
}
let real_src_list = this.src.split(",");
let srcList = [];
real_src_list.forEach(item => {
return srcList.push(item);
});
return srcList;
},
realWidth() {
return typeof this.width == "string" ? this.width : `${this.width}px`;
},
realHeight() {
return typeof this.height == "string" ? this.height : `${this.height}px`;
}
}, },
}; height: {
type: [Number, String],
default: ""
}
});
const realSrc = computed(() => {
if (!props.src) {
return;
}
let real_src = props.src.split(",")[0];
return real_src;
});
const realSrcList = computed(() => {
if (!props.src) {
return;
}
let real_src_list = props.src.split(",");
let srcList = [];
real_src_list.forEach(item => {
return srcList.push(item);
});
return srcList;
});
const realWidth = computed(() =>
typeof props.width == "string" ? props.width : `${props.width}px`
);
const realHeight = computed(() =>
typeof props.height == "string" ? props.height : `${props.height}px`
);
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -62,14 +64,14 @@ export default {
border-radius: 5px; border-radius: 5px;
background-color: #ebeef5; background-color: #ebeef5;
box-shadow: 0 0 5px 1px #ccc; box-shadow: 0 0 5px 1px #ccc;
::v-deep .el-image__inner { :deep(.el-image__inner) {
transition: all 0.3s; transition: all 0.3s;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
transform: scale(1.2); transform: scale(1.2);
} }
} }
::v-deep .image-slot { :deep(.image-slot) {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;

View File

@@ -1,221 +1,208 @@
<template> <template>
<div class="component-upload-image"> <div class="component-upload-image">
<el-upload <el-upload
multiple multiple
:action="uploadImgUrl" :action="uploadImgUrl"
list-type="picture-card" list-type="picture-card"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:limit="limit" :limit="limit"
:on-error="handleUploadError" :on-error="handleUploadError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
ref="imageUpload" ref="imageUpload"
:on-remove="handleDelete" :before-remove="handleDelete"
:show-file-list="true" :show-file-list="true"
:headers="headers" :headers="headers"
:file-list="fileList" :file-list="fileList"
:on-preview="handlePictureCardPreview" :on-preview="handlePictureCardPreview"
:class="{hide: this.fileList.length >= this.limit}" :class="{ hide: fileList.length >= limit }"
> >
<i class="el-icon-plus"></i> <el-icon class="avatar-uploader-icon"><plus /></el-icon>
</el-upload> </el-upload>
<!-- 上传提示 -->
<!-- 上传提示 --> <div class="el-upload__tip" v-if="showTip">
<div class="el-upload__tip" slot="tip" v-if="showTip"> 请上传
请上传 <template v-if="fileSize">
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> </template>
的文件 <template v-if="fileType">
</div> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b>
</template>
<el-dialog 的文件
:visible.sync="dialogVisible" </div>
title="预览"
width="800" <el-dialog
append-to-body v-model="dialogVisible"
> title="预览"
<img width="800px"
:src="dialogImageUrl" append-to-body
style="display: block; max-width: 100%; margin: 0 auto" >
/> <img
</el-dialog> :src="dialogImageUrl"
</div> style="display: block; max-width: 100%; margin: 0 auto"
</template> />
</el-dialog>
<script> </div>
import { getToken } from "@/utils/auth"; </template>
export default { <script setup>
props: { import { getToken } from "@/utils/auth";
value: [String, Object, Array],
// 图片数量限制 const props = defineProps({
limit: { modelValue: [String, Object, Array],
type: Number, // 图片数量限制
default: 5, limit: {
}, type: Number,
// 大小限制(MB) default: 5,
fileSize: { },
type: Number, // 大小限制(MB)
default: 5, fileSize: {
}, type: Number,
// 文件类型, 例如['png', 'jpg', 'jpeg'] default: 5,
fileType: { },
type: Array, // 文件类型, 例如['png', 'jpg', 'jpeg']
default: () => ["png", "jpg", "jpeg"], fileType: {
}, type: Array,
// 是否显示提示 default: () => ["png", "jpg", "jpeg"],
isShowTip: { },
type: Boolean, // 是否显示提示
default: true isShowTip: {
} type: Boolean,
}, default: true
data() { },
return { });
number: 0,
uploadList: [], const { proxy } = getCurrentInstance();
dialogImageUrl: "", const emit = defineEmits();
dialogVisible: false, const number = ref(0);
hideUpload: false, const uploadList = ref([]);
uploadImgUrl: process.env.VUE_APP_BASE_API + "/file/upload", // 上传的图片服务器地址 const dialogImageUrl = ref("");
headers: { const dialogVisible = ref(false);
Authorization: "Bearer " + getToken(), const baseUrl = import.meta.env.VITE_APP_BASE_API;
}, const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + "/file/upload"); // 上传的图片服务器地址
fileList: [] const headers = ref({ Authorization: "Bearer " + getToken() });
}; const fileList = ref([]);
}, const showTip = computed(
watch: { () => props.isShowTip && (props.fileType || props.fileSize)
value: { );
handler(val) {
if (val) { watch(() => props.modelValue, val => {
// 首先将值转为数组 if (val) {
const list = Array.isArray(val) ? val : this.value.split(','); // 首先将值转为数组
// 然后将数组转为对象数组 const list = Array.isArray(val) ? val : props.modelValue.split(",");
this.fileList = list.map(item => { // 然后将数组转为对象数组
if (typeof item === "string") { fileList.value = list.map(item => {
item = { name: item, url: item }; if (typeof item === "string") {
} item = { name: item, url: item };
return item; }
}); return item;
} else { });
this.fileList = []; } else {
return []; fileList.value = [];
} return [];
}, }
deep: true, },{ deep: true, immediate: true });
immediate: true
} // 上传前loading加载
}, function handleBeforeUpload(file) {
computed: { let isImg = false;
// 是否显示提示 if (props.fileType.length) {
showTip() { let fileExtension = "";
return this.isShowTip && (this.fileType || this.fileSize); if (file.name.lastIndexOf(".") > -1) {
}, fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1);
}, }
methods: { isImg = props.fileType.some(type => {
// 上传前loading加载 if (file.type.indexOf(type) > -1) return true;
handleBeforeUpload(file) { if (fileExtension && fileExtension.indexOf(type) > -1) return true;
let isImg = false; return false;
if (this.fileType.length) { });
let fileExtension = ""; } else {
if (file.name.lastIndexOf(".") > -1) { isImg = file.type.indexOf("image") > -1;
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1); }
} if (!isImg) {
isImg = this.fileType.some(type => { proxy.$modal.msgError(
if (file.type.indexOf(type) > -1) return true; `文件格式不正确, 请上传${props.fileType.join("/")}图片格式文件!`
if (fileExtension && fileExtension.indexOf(type) > -1) return true; );
return false; return false;
}); }
} else { if (props.fileSize) {
isImg = file.type.indexOf("image") > -1; const isLt = file.size / 1024 / 1024 < props.fileSize;
} if (!isLt) {
proxy.$modal.msgError(`上传头像图片大小不能超过 ${props.fileSize} MB!`);
if (!isImg) { return false;
this.$modal.msgError(`文件格式不正确, 请上传${this.fileType.join("/")}图片格式文件!`); }
return false; }
} proxy.$modal.loading("正在上传图片,请稍候...");
if (this.fileSize) { number.value++;
const isLt = file.size / 1024 / 1024 < this.fileSize; }
if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`); // 文件个数超出
return false; function handleExceed() {
} proxy.$modal.msgError(`上传文件数量不能超过 ${props.limit} 个!`);
} }
this.$modal.loading("正在上传图片,请稍候...");
this.number++; // 上传成功回调
}, function handleUploadSuccess(res, file) {
// 文件个数超出 if (res.code === 200) {
handleExceed() { uploadList.value.push({ name: res.data.url, url: res.data.url });
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`); uploadedSuccessfully();
}, } else {
// 上传成功回调 number.value--;
handleUploadSuccess(res, file) { proxy.$modal.closeLoading();
if (res.code === 200) { proxy.$modal.msgError(res.msg);
this.uploadList.push({ name: res.data.url, url: res.data.url }); proxy.$refs.imageUpload.handleRemove(file);
this.uploadedSuccessfully(); uploadedSuccessfully();
} else { }
this.number--; }
this.$modal.closeLoading();
this.$modal.msgError(res.msg); // 删除图片
this.$refs.imageUpload.handleRemove(file); function handleDelete(file) {
this.uploadedSuccessfully(); const findex = fileList.value.map(f => f.name).indexOf(file.name);
} if (findex > -1 && uploadList.value.length === number.value) {
}, fileList.value.splice(findex, 1);
// 删除图片 emit("update:modelValue", listToString(fileList.value));
handleDelete(file) { return false;
const findex = this.fileList.map(f => f.name).indexOf(file.name); }
if (findex > -1) { }
this.fileList.splice(findex, 1);
this.$emit("input", this.listToString(this.fileList)); // 上传结束处理
} function uploadedSuccessfully() {
}, if (number.value > 0 && uploadList.value.length === number.value) {
// 上传失败 fileList.value = fileList.value.filter(f => f.url !== undefined).concat(uploadList.value);
handleUploadError() { uploadList.value = [];
this.$modal.msgError("上传图片失败,请重试"); number.value = 0;
this.$modal.closeLoading(); emit("update:modelValue", listToString(fileList.value));
}, proxy.$modal.closeLoading();
// 上传结束处理 }
uploadedSuccessfully() { }
if (this.number > 0 && this.uploadList.length === this.number) { // 上传失败
this.fileList = this.fileList.concat(this.uploadList); function handleUploadError() {
this.uploadList = []; proxy.$modal.msgError("上传图片失败");
this.number = 0; proxy.$modal.closeLoading();
this.$emit("input", this.listToString(this.fileList)); }
this.$modal.closeLoading();
} // 预览
}, function handlePictureCardPreview(file) {
// 预览 dialogImageUrl.value = file.url;
handlePictureCardPreview(file) { dialogVisible.value = true;
this.dialogImageUrl = file.url; }
this.dialogVisible = true;
}, // 对象转成指定字符串分隔
// 对象转成指定字符串分隔 function listToString(list, separator) {
listToString(list, separator) { let strs = "";
let strs = ""; separator = separator || ",";
separator = separator || ","; for (let i in list) {
for (let i in list) { if (undefined !== list[i].url && list[i].url.indexOf("blob:") !== 0) {
if (list[i].url) { strs += list[i].url.replace(baseUrl, "") + separator;
strs += list[i].url.replace(this.baseUrl, "") + separator; }
} }
} return strs != "" ? strs.substr(0, strs.length - 1) : "";
return strs != '' ? strs.substr(0, strs.length - 1) : ''; }
} </script>
}
}; <style scoped lang="scss">
</script> // .el-upload--picture-card 控制加号部分
<style scoped lang="scss"> :deep(.hide .el-upload--picture-card) {
// .el-upload--picture-card 控制加号部分 display: none;
::v-deep.hide .el-upload--picture-card { }
display: none; </style>
}
// 去掉动画效果
::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active {
transition: all 0s;
}
::v-deep .el-list-enter, .el-list-leave-active {
opacity: 0;
transform: translateY(0);
}
</style>

View File

@@ -1,114 +1,105 @@
<template> <template>
<div :class="{'hidden':hidden}" class="pagination-container"> <div :class="{ 'hidden': hidden }" class="pagination-container">
<el-pagination <el-pagination
:background="background" :background="background"
:current-page.sync="currentPage" v-model:current-page="currentPage"
:page-size.sync="pageSize" v-model:page-size="pageSize"
:layout="layout" :layout="layout"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:pager-count="pagerCount" :pager-count="pagerCount"
:total="total" :total="total"
v-bind="$attrs" @size-change="handleSizeChange"
@size-change="handleSizeChange" @current-change="handleCurrentChange"
@current-change="handleCurrentChange" />
/> </div>
</div> </template>
</template>
<script setup>
<script> import { scrollTo } from '@/utils/scroll-to'
import { scrollTo } from '@/utils/scroll-to'
const props = defineProps({
export default { total: {
name: 'Pagination', required: true,
props: { type: Number
total: { },
required: true, page: {
type: Number type: Number,
}, default: 1
page: { },
type: Number, limit: {
default: 1 type: Number,
}, default: 20
limit: { },
type: Number, pageSizes: {
default: 20 type: Array,
}, default() {
pageSizes: { return [10, 20, 30, 50]
type: Array, }
default() { },
return [10, 20, 30, 50] // 移动端页码按钮的数量端默认值5
} pagerCount: {
}, type: Number,
// 移动端页码按钮的数量端默认值5 default: document.body.clientWidth < 992 ? 5 : 7
pagerCount: { },
type: Number, layout: {
default: document.body.clientWidth < 992 ? 5 : 7 type: String,
}, default: 'total, sizes, prev, pager, next, jumper'
layout: { },
type: String, background: {
default: 'total, sizes, prev, pager, next, jumper' type: Boolean,
}, default: true
background: { },
type: Boolean, autoScroll: {
default: true type: Boolean,
}, default: true
autoScroll: { },
type: Boolean, hidden: {
default: true type: Boolean,
}, default: false
hidden: { }
type: Boolean, })
default: false
} const emit = defineEmits();
}, const currentPage = computed({
data() { get() {
return { return props.page
}; },
}, set(val) {
computed: { emit('update:page', val)
currentPage: { }
get() { })
return this.page const pageSize = computed({
}, get() {
set(val) { return props.limit
this.$emit('update:page', val) },
} set(val){
}, emit('update:limit', val)
pageSize: { }
get() { })
return this.limit function handleSizeChange(val) {
}, if (currentPage.value * val > props.total) {
set(val) { currentPage.value = 1
this.$emit('update:limit', val) }
} emit('pagination', { page: currentPage.value, limit: val })
} if (props.autoScroll) {
}, scrollTo(0, 800)
methods: { }
handleSizeChange(val) { }
if (this.currentPage * val > this.total) { function handleCurrentChange(val) {
this.currentPage = 1 emit('pagination', { page: val, limit: pageSize.value })
} if (props.autoScroll) {
this.$emit('pagination', { page: this.currentPage, limit: val }) scrollTo(0, 800)
if (this.autoScroll) { }
scrollTo(0, 800) }
}
}, </script>
handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize }) <style scoped>
if (this.autoScroll) { .pagination-container {
scrollTo(0, 800) background: #fff;
} padding: 32px 16px;
} }
} .pagination-container.hidden {
} display: none;
</script> }
</style>
<style scoped>
.pagination-container {
background: #fff;
padding: 32px 16px;
}
.pagination-container.hidden {
display: none;
}
</style>

View File

@@ -1,142 +0,0 @@
<template>
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
<div class="pan-info">
<div class="pan-info-roles-container">
<slot />
</div>
</div>
<!-- eslint-disable-next-line -->
<div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
</div>
</template>
<script>
export default {
name: 'PanThumb',
props: {
image: {
type: String,
required: true
},
zIndex: {
type: Number,
default: 1
},
width: {
type: String,
default: '150px'
},
height: {
type: String,
default: '150px'
}
}
}
</script>
<style scoped>
.pan-item {
width: 200px;
height: 200px;
border-radius: 50%;
display: inline-block;
position: relative;
cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.pan-info-roles-container {
padding: 20px;
text-align: center;
}
.pan-thumb {
width: 100%;
height: 100%;
background-position: center center;
background-size: cover;
border-radius: 50%;
overflow: hidden;
position: absolute;
transform-origin: 95% 40%;
transition: all 0.3s ease-in-out;
}
/* .pan-thumb:after {
content: '';
width: 8px;
height: 8px;
position: absolute;
border-radius: 50%;
top: 40%;
left: 95%;
margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
} */
.pan-info {
position: absolute;
width: inherit;
height: inherit;
border-radius: 50%;
overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
}
.pan-info h3 {
color: #fff;
text-transform: uppercase;
position: relative;
letter-spacing: 2px;
font-size: 18px;
margin: 0 60px;
padding: 22px 0 0 0;
height: 85px;
font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
}
.pan-info p {
color: #fff;
padding: 10px 5px;
font-style: italic;
margin: 0 30px;
font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5);
}
.pan-info p a {
display: block;
color: #333;
width: 80px;
height: 80px;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
color: #fff;
font-style: normal;
font-weight: 700;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 1px;
padding-top: 24px;
margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif;
opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
transform: translateX(60px) rotate(90deg);
}
.pan-info p a:hover {
background: rgba(255, 255, 255, 0.5);
}
.pan-item:hover .pan-thumb {
transform: rotate(-110deg);
}
.pan-item:hover .pan-info p a {
opacity: 1;
transform: translateX(0px) rotate(0deg);
}
</style>

View File

@@ -1,106 +0,0 @@
<template>
<div ref="rightPanel" class="rightPanel-container">
<div class="rightPanel-background" />
<div class="rightPanel">
<div class="rightPanel-items">
<slot />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'RightPanel',
props: {
clickNotClose: {
default: false,
type: Boolean
}
},
computed: {
show: {
get() {
return this.$store.state.settings.showSettings
},
set(val) {
this.$store.dispatch('settings/changeSetting', {
key: 'showSettings',
value: val
})
}
}
},
watch: {
show(value) {
if (value && !this.clickNotClose) {
this.addEventClick()
}
}
},
mounted() {
this.addEventClick()
},
beforeDestroy() {
const elx = this.$refs.rightPanel
elx.remove()
},
methods: {
addEventClick() {
window.addEventListener('click', this.closeSidebar)
},
closeSidebar(evt) {
const parent = evt.target.closest('.el-drawer__body')
if (!parent) {
this.show = false
window.removeEventListener('click', this.closeSidebar)
}
}
}
}
</script>
<style lang="scss" scoped>
.rightPanel-background {
position: fixed;
top: 0;
left: 0;
opacity: 0;
transition: opacity .3s cubic-bezier(.7, .3, .1, 1);
background: rgba(0, 0, 0, .2);
z-index: -1;
}
.rightPanel {
width: 100%;
max-width: 260px;
height: 100vh;
position: fixed;
top: 0;
right: 0;
box-shadow: 0px 0px 15px 0px rgba(0, 0, 0, .05);
transition: all .25s cubic-bezier(.7, .3, .1, 1);
transform: translate(100%);
background: #fff;
z-index: 40000;
}
.handle-button {
width: 48px;
height: 48px;
position: absolute;
left: -48px;
text-align: center;
font-size: 24px;
border-radius: 6px 0 0 6px !important;
z-index: 0;
pointer-events: auto;
cursor: pointer;
color: #fff;
line-height: 48px;
i {
font-size: 24px;
line-height: 48px;
}
}
</style>

View File

@@ -2,26 +2,28 @@
<div class="top-right-btn" :style="style"> <div class="top-right-btn" :style="style">
<el-row> <el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search"> <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" /> <el-button circle icon="Search" @click="toggleSearch()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top"> <el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" /> <el-button circle icon="Refresh" @click="refresh()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns"> <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="columns">
<el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/> <el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'"> <el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button size="mini" circle icon="el-icon-menu" /> <el-button circle icon="Menu" />
<el-dropdown-menu slot="dropdown"> <template #dropdown>
<template v-for="item in columns"> <el-dropdown-menu>
<el-dropdown-item :key="item.key"> <template v-for="item in columns" :key="item.key">
<el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" /> <el-dropdown-item>
</el-dropdown-item> <el-checkbox :checked="item.visible" @change="checkboxChange($event, item.label)" :label="item.label" />
</template> </el-dropdown-item>
</el-dropdown-menu> </template>
</el-dropdown-menu>
</template>
</el-dropdown> </el-dropdown>
</el-tooltip> </el-tooltip>
</el-row> </el-row>
<el-dialog :title="title" :visible.sync="open" append-to-body> <el-dialog :title="title" v-model="open" append-to-body>
<el-transfer <el-transfer
:titles="['显示', '隐藏']" :titles="['显示', '隐藏']"
v-model="value" v-model="value"
@@ -31,99 +33,102 @@
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script>
export default { <script setup>
name: "RightToolbar", const props = defineProps({
data() { /* 是否显示检索条件 */
return { showSearch: {
// 显隐数据 type: Boolean,
value: [], default: true,
// 弹出层标题
title: "显示/隐藏",
// 是否显示弹出层
open: false,
};
}, },
props: { /* 显隐列信息 */
/* 是否显示检索条件 */ columns: {
showSearch: { type: Array,
type: Boolean,
default: true,
},
/* 显隐列信息 */
columns: {
type: Array,
},
/* 是否显示检索图标 */
search: {
type: Boolean,
default: true,
},
/* 显隐列类型transfer穿梭框、checkbox复选框 */
showColumnsType: {
type: String,
default: "checkbox",
},
/* 右外边距 */
gutter: {
type: Number,
default: 10,
},
}, },
computed: { /* 是否显示检索图标 */
style() { search: {
const ret = {}; type: Boolean,
if (this.gutter) { default: true,
ret.marginRight = `${this.gutter / 2}px`; },
} /* 显隐列类型transfer穿梭框、checkbox复选框 */
return ret; showColumnsType: {
type: String,
default: "checkbox",
},
/* 右外边距 */
gutter: {
type: Number,
default: 10,
},
})
const emits = defineEmits(['update:showSearch', 'queryTable']);
// 显隐数据
const value = ref([]);
// 弹出层标题
const title = ref("显示/隐藏");
// 是否显示弹出层
const open = ref(false);
const style = computed(() => {
const ret = {};
if (props.gutter) {
ret.marginRight = `${props.gutter / 2}px`;
}
return ret;
});
// 搜索
function toggleSearch() {
emits("update:showSearch", !props.showSearch);
}
// 刷新
function refresh() {
emits("queryTable");
}
// 右侧列表元素变化
function dataChange(data) {
for (let item in props.columns) {
const key = props.columns[item].key;
props.columns[item].visible = !data.includes(key);
}
}
// 打开显隐列dialog
function showColumn() {
open.value = true;
}
if (props.showColumnsType == 'transfer') {
// 显隐列初始默认隐藏列
for (let item in props.columns) {
if (props.columns[item].visible === false) {
value.value.push(parseInt(item));
} }
}, }
created() { }
if (this.showColumnsType == 'transfer') {
// 显隐列初始默认隐藏列 // 勾选
for (let item in this.columns) { function checkboxChange(event, label) {
if (this.columns[item].visible === false) { props.columns.filter(item => item.label == label)[0].visible = event;
this.value.push(parseInt(item)); }
}
}
}
},
methods: {
// 搜索
toggleSearch() {
this.$emit("update:showSearch", !this.showSearch);
},
// 刷新
refresh() {
this.$emit("queryTable");
},
// 右侧列表元素变化
dataChange(data) {
for (let item in this.columns) {
const key = this.columns[item].key;
this.columns[item].visible = !data.includes(key);
}
},
// 打开显隐列dialog
showColumn() {
this.open = true;
},
// 勾选
checkboxChange(event, label) {
this.columns.filter(item => item.label == label)[0].visible = event;
}
},
};
</script> </script>
<style lang="scss" scoped>
::v-deep .el-transfer__button { <style lang='scss' scoped>
:deep(.el-transfer__button) {
border-radius: 50%; border-radius: 50%;
padding: 12px;
display: block; display: block;
margin-left: 0px; margin-left: 0px;
} }
::v-deep .el-transfer__button:first-child { :deep(.el-transfer__button:first-child) {
margin-bottom: 10px; margin-bottom: 10px;
} }
:deep(.el-dropdown-menu__item) {
line-height: 30px;
padding: 0 17px;
}
</style> </style>

View File

@@ -4,18 +4,10 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { const url = ref('http://doc.ruoyi.vip/ruoyi-cloud');
name: 'RuoYiDoc',
data() { function goto() {
return { window.open(url.value)
url: 'http://doc.ruoyi.vip/ruoyi-cloud'
}
},
methods: {
goto() {
window.open(this.url)
}
}
} }
</script> </script>

View File

@@ -4,18 +4,10 @@
</div> </div>
</template> </template>
<script> <script setup>
export default { const url = ref('https://gitee.com/y_project/RuoYi-Cloud');
name: 'RuoYiGit',
data() { function goto() {
return { window.open(url.value)
url: 'https://gitee.com/y_project/RuoYi-Cloud'
}
},
methods: {
goto() {
window.open(this.url)
}
}
} }
</script> </script>

View File

@@ -1,57 +1,22 @@
<template> <template>
<div> <div>
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" /> <svg-icon :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'" @click="toggle" />
</div> </div>
</template> </template>
<script> <script setup>
import screenfull from 'screenfull' import { useFullscreen } from '@vueuse/core'
export default { const { isFullscreen, enter, exit, toggle } = useFullscreen();
name: 'Screenfull', </script>
data() {
return { <style lang='scss' scoped>
isFullscreen: false .screenfull-svg {
} display: inline-block;
}, cursor: pointer;
mounted() { fill: #5a5e66;
this.init() width: 20px;
}, height: 20px;
beforeDestroy() { vertical-align: 10px;
this.destroy() }
}, </style>
methods: {
click() {
if (!screenfull.isEnabled) {
this.$message({ message: '你的浏览器不支持全屏', type: 'warning' })
return false
}
screenfull.toggle()
},
change() {
this.isFullscreen = screenfull.isFullscreen
},
init() {
if (screenfull.isEnabled) {
screenfull.on('change', this.change)
}
},
destroy() {
if (screenfull.isEnabled) {
screenfull.off('change', this.change)
}
}
}
}
</script>
<style scoped>
.screenfull-svg {
display: inline-block;
cursor: pointer;
fill: #5a5e66;;
width: 20px;
height: 20px;
vertical-align: 10px;
}
</style>

View File

@@ -1,56 +1,45 @@
<template> <template>
<el-dropdown trigger="click" @command="handleSetSize"> <div>
<div> <el-dropdown trigger="click" @command="handleSetSize">
<svg-icon class-name="size-icon" icon-class="size" /> <div class="size-icon--style">
</div> <svg-icon class-name="size-icon" icon-class="size" />
<el-dropdown-menu slot="dropdown"> </div>
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value"> <template #dropdown>
{{ item.label }} <el-dropdown-menu>
</el-dropdown-item> <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size === item.value" :command="item.value">
</el-dropdown-menu> {{ item.label }}
</el-dropdown> </el-dropdown-item>
</template> </el-dropdown-menu>
</template>
<script> </el-dropdown>
export default { </div>
data() { </template>
return {
sizeOptions: [ <script setup>
{ label: 'Default', value: 'default' }, import useAppStore from "@/store/modules/app";
{ label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' }, const appStore = useAppStore();
{ label: 'Mini', value: 'mini' } const size = computed(() => appStore.size);
] const route = useRoute();
} const router = useRouter();
}, const { proxy } = getCurrentInstance();
computed: { const sizeOptions = ref([
size() { { label: "较大", value: "large" },
return this.$store.getters.size { label: "默认", value: "default" },
} { label: "稍小", value: "small" },
}, ]);
methods: {
handleSetSize(size) { function handleSetSize(size) {
this.$ELEMENT.size = size proxy.$modal.loading("正在设置布局大小,请稍候...");
this.$store.dispatch('app/setSize', size) appStore.setSize(size);
this.refreshView() setTimeout("window.location.reload()", 1000);
this.$message({ }
message: 'Switch Size Success', </script>
type: 'success'
}) <style lang='scss' scoped>
}, .size-icon--style {
refreshView() { font-size: 18px;
// In order to make the cached page re-rendered line-height: 50px;
this.$store.dispatch('tagsView/delAllCachedViews', this.$route) padding-right: 7px;
}
const { fullPath } = this.$route </style>
this.$nextTick(() => {
this.$router.replace({
path: '/redirect' + fullPath
})
})
}
}
}
</script>

View File

@@ -1,61 +1,53 @@
<template> <template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" /> <svg :class="svgClass" aria-hidden="true">
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners"> <use :xlink:href="iconName" :fill="color" />
<use :xlink:href="iconName" /> </svg>
</svg> </template>
</template>
<script>
<script> export default defineComponent({
import { isExternal } from '@/utils/validate' props: {
iconClass: {
export default { type: String,
name: 'SvgIcon', required: true
props: { },
iconClass: { className: {
type: String, type: String,
required: true default: ''
}, },
className: { color: {
type: String, type: String,
default: '' default: ''
} },
}, },
computed: { setup(props) {
isExternal() { return {
return isExternal(this.iconClass) iconName: computed(() => `#icon-${props.iconClass}`),
}, svgClass: computed(() => {
iconName() { if (props.className) {
return `#icon-${this.iconClass}` return `svg-icon ${props.className}`
}, }
svgClass() { return 'svg-icon'
if (this.className) { })
return 'svg-icon ' + this.className }
} else { }
return 'svg-icon' })
} </script>
},
styleExternalIcon() { <style scope lang="scss">
return { .sub-el-icon,
mask: `url(${this.iconClass}) no-repeat 50% 50%`, .nav-icon {
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%` display: inline-block;
} font-size: 15px;
} margin-right: 12px;
} position: relative;
} }
</script>
.svg-icon {
<style scoped> width: 1em;
.svg-icon { height: 1em;
width: 1em; position: relative;
height: 1em; fill: currentColor;
vertical-align: -0.15em; vertical-align: -2px;
fill: currentColor; }
overflow: hidden; </style>
}
.svg-external-icon {
background-color: currentColor;
mask-size: cover!important;
display: inline-block;
}
</style>

View File

@@ -0,0 +1,10 @@
import * as components from '@element-plus/icons-vue'
export default {
install: (app) => {
for (const key in components) {
const componentConfig = components[key];
app.component(componentConfig.name, componentConfig);
}
},
};

View File

@@ -1,173 +0,0 @@
<template>
<el-color-picker
v-model="theme"
:predefine="['#409EFF', '#1890ff', '#304156','#212121','#11a983', '#13c2c2', '#6959CD', '#f5222d', ]"
class="theme-picker"
popper-class="theme-picker-dropdown"
/>
</template>
<script>
const version = require('element-ui/package.json').version // element-ui version from node_modules
const ORIGINAL_THEME = '#409EFF' // default color
export default {
data() {
return {
chalk: '', // content of theme-chalk css
theme: ''
}
},
computed: {
defaultTheme() {
return this.$store.state.settings.theme
}
},
watch: {
defaultTheme: {
handler: function(val, oldVal) {
this.theme = val
},
immediate: true
},
async theme(val) {
await this.setTheme(val)
}
},
created() {
if(this.defaultTheme !== ORIGINAL_THEME) {
this.setTheme(this.defaultTheme)
}
},
methods: {
async setTheme(val) {
const oldVal = this.chalk ? this.theme : ORIGINAL_THEME
if (typeof val !== 'string') return
const themeCluster = this.getThemeCluster(val.replace('#', ''))
const originalCluster = this.getThemeCluster(oldVal.replace('#', ''))
const getHandler = (variable, id) => {
return () => {
const originalCluster = this.getThemeCluster(ORIGINAL_THEME.replace('#', ''))
const newStyle = this.updateStyle(this[variable], originalCluster, themeCluster)
let styleTag = document.getElementById(id)
if (!styleTag) {
styleTag = document.createElement('style')
styleTag.setAttribute('id', id)
document.head.appendChild(styleTag)
}
styleTag.innerText = newStyle
}
}
if (!this.chalk) {
const url = `https://unpkg.com/element-ui@${version}/lib/theme-chalk/index.css`
await this.getCSSString(url, 'chalk')
}
const chalkHandler = getHandler('chalk', 'chalk-style')
chalkHandler()
const styles = [].slice.call(document.querySelectorAll('style'))
.filter(style => {
const text = style.innerText
return new RegExp(oldVal, 'i').test(text) && !/Chalk Variables/.test(text)
})
styles.forEach(style => {
const { innerText } = style
if (typeof innerText !== 'string') return
style.innerText = this.updateStyle(innerText, originalCluster, themeCluster)
})
this.$emit('change', val)
},
updateStyle(style, oldCluster, newCluster) {
let newStyle = style
oldCluster.forEach((color, index) => {
newStyle = newStyle.replace(new RegExp(color, 'ig'), newCluster[index])
})
return newStyle
},
getCSSString(url, variable) {
return new Promise(resolve => {
const xhr = new XMLHttpRequest()
xhr.onreadystatechange = () => {
if (xhr.readyState === 4 && xhr.status === 200) {
this[variable] = xhr.responseText.replace(/@font-face{[^}]+}/, '')
resolve()
}
}
xhr.open('GET', url)
xhr.send()
})
},
getThemeCluster(theme) {
const tintColor = (color, tint) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
if (tint === 0) { // when primary color is in its rgb space
return [red, green, blue].join(',')
} else {
red += Math.round(tint * (255 - red))
green += Math.round(tint * (255 - green))
blue += Math.round(tint * (255 - blue))
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
}
const shadeColor = (color, shade) => {
let red = parseInt(color.slice(0, 2), 16)
let green = parseInt(color.slice(2, 4), 16)
let blue = parseInt(color.slice(4, 6), 16)
red = Math.round((1 - shade) * red)
green = Math.round((1 - shade) * green)
blue = Math.round((1 - shade) * blue)
red = red.toString(16)
green = green.toString(16)
blue = blue.toString(16)
return `#${red}${green}${blue}`
}
const clusters = [theme]
for (let i = 0; i <= 9; i++) {
clusters.push(tintColor(theme, Number((i / 10).toFixed(2))))
}
clusters.push(shadeColor(theme, 0.1))
return clusters
}
}
}
</script>
<style>
.theme-message,
.theme-picker-dropdown {
z-index: 99999 !important;
}
.theme-picker .el-color-picker__trigger {
height: 26px !important;
width: 26px !important;
padding: 2px;
}
.theme-picker-dropdown .el-color-dropdown__link-btn {
display: none;
}
</style>

View File

@@ -3,6 +3,7 @@
:default-active="activeMenu" :default-active="activeMenu"
mode="horizontal" mode="horizontal"
@select="handleSelect" @select="handleSelect"
:ellipsis="false"
> >
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber"> <el-menu-item :style="{'--theme': theme}" :index="item.path" :key="index" v-if="index < visibleNumber">
@@ -14,158 +15,158 @@
</template> </template>
<!-- 顶部菜单超出数量折叠 --> <!-- 顶部菜单超出数量折叠 -->
<el-submenu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber"> <el-sub-menu :style="{'--theme': theme}" index="more" v-if="topMenus.length > visibleNumber">
<template slot="title">更多菜单</template> <template #title>更多菜单</template>
<template v-for="(item, index) in topMenus"> <template v-for="(item, index) in topMenus">
<el-menu-item <el-menu-item
:index="item.path" :index="item.path"
:key="index" :key="index"
v-if="index >= visibleNumber"> v-if="index >= visibleNumber">
<svg-icon <svg-icon
v-if="item.meta && item.meta.icon && item.meta.icon !== '#'" v-if="item.meta && item.meta.icon && item.meta.icon !== '#'"
:icon-class="item.meta.icon"/> :icon-class="item.meta.icon"/>
{{ item.meta.title }} {{ item.meta.title }}
</el-menu-item> </el-menu-item>
</template> </template>
</el-submenu> </el-sub-menu>
</el-menu> </el-menu>
</template> </template>
<script> <script setup>
import { constantRoutes } from "@/router"; import { constantRoutes } from "@/router"
import { isHttp } from '@/utils/validate'
import useAppStore from '@/store/modules/app'
import useSettingsStore from '@/store/modules/settings'
import usePermissionStore from '@/store/modules/permission'
// 顶部栏初始数
const visibleNumber = ref(null);
// 当前激活菜单的 index
const currentIndex = ref(null);
// 隐藏侧边栏路由 // 隐藏侧边栏路由
const hideList = ['/index', '/user/profile']; const hideList = ['/index', '/user/profile'];
export default { const appStore = useAppStore()
data() { const settingsStore = useSettingsStore()
return { const permissionStore = usePermissionStore()
// 顶部栏初始数 const route = useRoute();
visibleNumber: 5, const router = useRouter();
// 当前激活菜单的 index
currentIndex: undefined // 主题颜色
}; const theme = computed(() => settingsStore.theme);
}, // 所有的路由信息
computed: { const routers = computed(() => permissionStore.topbarRouters);
theme() {
return this.$store.state.settings.theme; // 顶部显示菜单
}, const topMenus = computed(() => {
// 顶部显示菜单 let topMenus = [];
topMenus() { routers.value.map((menu) => {
let topMenus = []; if (menu.hidden !== true) {
this.routers.map((menu) => { // 兼容顶部栏一级菜单内部跳转
if (menu.hidden !== true) { if (menu.path === "/") {
// 兼容顶部栏一级菜单内部跳转 topMenus.push(menu.children[0]);
if (menu.path === "/") {
topMenus.push(menu.children[0]);
} else {
topMenus.push(menu);
}
}
});
return topMenus;
},
// 所有的路由信息
routers() {
return this.$store.state.permission.topbarRouters;
},
// 设置子路由
childrenMenus() {
var childrenMenus = [];
this.routers.map((router) => {
for (var item in router.children) {
if (router.children[item].parentPath === undefined) {
if(router.path === "/") {
router.children[item].path = "/" + router.children[item].path;
} else {
if(!this.ishttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path;
}
}
router.children[item].parentPath = router.path;
}
childrenMenus.push(router.children[item]);
}
});
return constantRoutes.concat(childrenMenus);
},
// 默认激活的菜单
activeMenu() {
const path = this.$route.path;
let activePath = path;
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
const tmpPath = path.substring(1, path.length);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
if (!this.$route.meta.link) {
this.$store.dispatch('app/toggleSideBarHide', false);
}
} else if(!this.$route.children) {
activePath = path;
this.$store.dispatch('app/toggleSideBarHide', true);
}
this.activeRoutes(activePath);
return activePath;
},
},
beforeMount() {
window.addEventListener('resize', this.setVisibleNumber)
},
beforeDestroy() {
window.removeEventListener('resize', this.setVisibleNumber)
},
mounted() {
this.setVisibleNumber();
},
methods: {
// 根据宽度计算设置显示栏数
setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3;
this.visibleNumber = parseInt(width / 85);
},
// 菜单选择事件
handleSelect(key, keyPath) {
this.currentIndex = key;
const route = this.routers.find(item => item.path === key);
if (this.ishttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank");
} else if (!route || !route.children) {
// 没有子路由路径内部打开
const routeMenu = this.childrenMenus.find(item => item.path === key);
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
this.$router.push({ path: key, query: query });
} else {
this.$router.push({ path: key });
}
this.$store.dispatch('app/toggleSideBarHide', true);
} else { } else {
// 显示左侧联动菜单 topMenus.push(menu);
this.activeRoutes(key);
this.$store.dispatch('app/toggleSideBarHide', false);
} }
},
// 当前激活的路由
activeRoutes(key) {
var routes = [];
if (this.childrenMenus && this.childrenMenus.length > 0) {
this.childrenMenus.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
routes.push(item);
}
});
}
if(routes.length > 0) {
this.$store.commit("SET_SIDEBAR_ROUTERS", routes);
} else {
this.$store.dispatch('app/toggleSideBarHide', true);
}
},
ishttp(url) {
return url.indexOf('http://') !== -1 || url.indexOf('https://') !== -1
} }
}, })
}; return topMenus;
})
// 设置子路由
const childrenMenus = computed(() => {
let childrenMenus = [];
routers.value.map((router) => {
for (let item in router.children) {
if (router.children[item].parentPath === undefined) {
if(router.path === "/") {
router.children[item].path = "/" + router.children[item].path;
} else {
if(!isHttp(router.children[item].path)) {
router.children[item].path = router.path + "/" + router.children[item].path;
}
}
router.children[item].parentPath = router.path;
}
childrenMenus.push(router.children[item]);
}
})
return constantRoutes.concat(childrenMenus);
})
// 默认激活的菜单
const activeMenu = computed(() => {
const path = route.path;
let activePath = path;
if (path !== undefined && path.lastIndexOf("/") > 0 && hideList.indexOf(path) === -1) {
const tmpPath = path.substring(1, path.length);
activePath = "/" + tmpPath.substring(0, tmpPath.indexOf("/"));
if (!route.meta.link) {
appStore.toggleSideBarHide(false);
}
} else if(!route.children) {
activePath = path;
appStore.toggleSideBarHide(true);
}
activeRoutes(activePath);
return activePath;
})
function setVisibleNumber() {
const width = document.body.getBoundingClientRect().width / 3;
visibleNumber.value = parseInt(width / 85);
}
function handleSelect(key, keyPath) {
currentIndex.value = key;
const route = routers.value.find(item => item.path === key);
if (isHttp(key)) {
// http(s):// 路径新窗口打开
window.open(key, "_blank");
} else if (!route || !route.children) {
// 没有子路由路径内部打开
const routeMenu = childrenMenus.value.find(item => item.path === key);
if (routeMenu && routeMenu.query) {
let query = JSON.parse(routeMenu.query);
router.push({ path: key, query: query });
} else {
router.push({ path: key });
}
appStore.toggleSideBarHide(true);
} else {
// 显示左侧联动菜单
activeRoutes(key);
appStore.toggleSideBarHide(false);
}
}
function activeRoutes(key) {
let routes = [];
if (childrenMenus.value && childrenMenus.value.length > 0) {
childrenMenus.value.map((item) => {
if (key == item.parentPath || (key == "index" && "" == item.path)) {
routes.push(item);
}
});
}
if(routes.length > 0) {
permissionStore.setSidebarRouters(routes);
} else {
appStore.toggleSideBarHide(true);
}
return routes;
}
onMounted(() => {
window.addEventListener('resize', setVisibleNumber)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', setVisibleNumber)
})
onMounted(() => {
setVisibleNumber()
})
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -178,13 +179,13 @@ export default {
margin: 0 10px !important; margin: 0 10px !important;
} }
.topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-submenu.is-active .el-submenu__title { .topmenu-container.el-menu--horizontal > .el-menu-item.is-active, .el-menu--horizontal > .el-sub-menu.is-active .el-submenu__title {
border-bottom: 2px solid #{'var(--theme)'} !important; border-bottom: 2px solid #{'var(--theme)'} !important;
color: #303133; color: #303133;
} }
/* submenu item */ /* sub-menu item */
.topmenu-container.el-menu--horizontal > .el-submenu .el-submenu__title { .topmenu-container.el-menu--horizontal > .el-sub-menu .el-sub-menu__title {
float: left; float: left;
height: 50px !important; height: 50px !important;
line-height: 50px !important; line-height: 50px !important;
@@ -192,4 +193,22 @@ export default {
padding: 0 5px !important; padding: 0 5px !important;
margin: 0 10px !important; margin: 0 10px !important;
} }
/* 背景色隐藏 */
.topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):focus, .topmenu-container.el-menu--horizontal>.el-menu-item:not(.is-disabled):hover, .topmenu-container.el-menu--horizontal>.el-submenu .el-submenu__title:hover {
background-color: #ffffff !important;
}
/* 图标右间距 */
.topmenu-container .svg-icon {
margin-right: 4px;
}
/* topmenu more arrow */
.topmenu-container .el-sub-menu .el-sub-menu__icon-arrow {
position: static;
vertical-align: middle;
margin-left: 8px;
margin-top: 0px;
}
</style> </style>

View File

@@ -0,0 +1,156 @@
<template>
<div class="el-tree-select">
<el-select
style="width: 100%"
v-model="valueId"
ref="treeSelect"
:filterable="true"
:clearable="true"
@clear="clearHandle"
:filter-method="selectFilterData"
:placeholder="placeholder"
>
<el-option :value="valueId" :label="valueTitle">
<el-tree
id="tree-option"
ref="selectTree"
:accordion="accordion"
:data="options"
:props="objMap"
:node-key="objMap.value"
:expand-on-click-node="false"
:default-expanded-keys="defaultExpandedKey"
:filter-node-method="filterNode"
@node-click="handleNodeClick"
></el-tree>
</el-option>
</el-select>
</div>
</template>
<script setup>
const { proxy } = getCurrentInstance();
const props = defineProps({
/* 配置项 */
objMap: {
type: Object,
default: () => {
return {
value: 'id', // ID字段名
label: 'label', // 显示名称
children: 'children' // 子级字段名
}
}
},
/* 自动收起 */
accordion: {
type: Boolean,
default: () => {
return false
}
},
/**当前双向数据绑定的值 */
value: {
type: [String, Number],
default: ''
},
/**当前的数据 */
options: {
type: Array,
default: () => []
},
/**输入框内部的文字 */
placeholder: {
type: String,
default: ''
}
})
const emit = defineEmits(['update:value']);
const valueId = computed({
get: () => props.value,
set: (val) => {
emit('update:value', val)
}
});
const valueTitle = ref('');
const defaultExpandedKey = ref([]);
function initHandle() {
nextTick(() => {
const selectedValue = valueId.value;
if(selectedValue !== null && typeof (selectedValue) !== 'undefined') {
const node = proxy.$refs.selectTree.getNode(selectedValue)
if (node) {
valueTitle.value = node.data[props.objMap.label]
proxy.$refs.selectTree.setCurrentKey(selectedValue) // 设置默认选中
defaultExpandedKey.value = [selectedValue] // 设置默认展开
}
} else {
clearHandle()
}
})
}
function handleNodeClick(node) {
valueTitle.value = node[props.objMap.label]
valueId.value = node[props.objMap.value];
defaultExpandedKey.value = [];
proxy.$refs.treeSelect.blur()
selectFilterData('')
}
function selectFilterData(val) {
proxy.$refs.selectTree.filter(val)
}
function filterNode(value, data) {
if (!value) return true
return data[props.objMap['label']].indexOf(value) !== -1
}
function clearHandle() {
valueTitle.value = ''
valueId.value = ''
defaultExpandedKey.value = [];
clearSelected()
}
function clearSelected() {
const allNode = document.querySelectorAll('#tree-option .el-tree-node')
allNode.forEach((element) => element.classList.remove('is-current'))
}
onMounted(() => {
initHandle()
})
watch(valueId, () => {
initHandle();
})
</script>
<style lang='scss' scoped>
@import "@/assets/styles/variables.module.scss";
.el-scrollbar .el-scrollbar__view .el-select-dropdown__item {
padding: 0;
background-color: #fff;
height: auto;
}
.el-select-dropdown__item.selected {
font-weight: normal;
}
ul li .el-tree .el-tree-node__content {
height: auto;
padding: 0 20px;
box-sizing: border-box;
}
:deep(.el-tree-node__content:hover),
:deep(.el-tree-node__content:active),
:deep(.is-current > div:first-child),
:deep(.el-tree-node__content:focus) {
background-color: mix(#fff, $--color-primary, 90%);
color: $--color-primary;
}
</style>

View File

@@ -1,36 +1,31 @@
<template> <template>
<div v-loading="loading" :style="'height:' + height"> <div v-loading="loading" :style="'height:' + height">
<iframe <iframe
:src="src" :src="url"
frameborder="no" frameborder="no"
style="width: 100%; height: 100%" style="width: 100%; height: 100%"
scrolling="auto" scrolling="auto" />
/>
</div> </div>
</template> </template>
<script>
export default { <script setup>
props: { const props = defineProps({
src: { src: {
type: String, type: String,
required: true required: true
},
},
data() {
return {
height: document.documentElement.clientHeight - 94.5 + "px;",
loading: true,
url: this.src
};
},
mounted: function () {
setTimeout(() => {
this.loading = false;
}, 300);
const that = this;
window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + "px;";
};
} }
}; })
const height = ref(document.documentElement.clientHeight - 94.5 + "px;")
const loading = ref(true)
const url = computed(() => props.src)
onMounted(() => {
setTimeout(() => {
loading.value = false;
}, 300);
window.onresize = function temp() {
height.value = document.documentElement.clientHeight - 94.5 + "px;";
};
})
</script> </script>

View File

@@ -0,0 +1,66 @@
/**
* v-copyText 复制文本内容
* Copyright (c) 2022 ruoyi
*/
export default {
beforeMount(el, { value, arg }) {
if (arg === "callback") {
el.$copyCallback = value;
} else {
el.$copyValue = value;
const handler = () => {
copyTextToClipboard(el.$copyValue);
if (el.$copyCallback) {
el.$copyCallback(el.$copyValue);
}
};
el.addEventListener("click", handler);
el.$destroyCopy = () => el.removeEventListener("click", handler);
}
}
}
function copyTextToClipboard(input, { target = document.body } = {}) {
const element = document.createElement('textarea');
const previouslyFocusedElement = document.activeElement;
element.value = input;
// Prevent keyboard from showing on mobile
element.setAttribute('readonly', '');
element.style.contain = 'strict';
element.style.position = 'absolute';
element.style.left = '-9999px';
element.style.fontSize = '12pt'; // Prevent zooming on iOS
const selection = document.getSelection();
const originalRange = selection.rangeCount > 0 && selection.getRangeAt(0);
target.append(element);
element.select();
// Explicit selection workaround for iOS
element.selectionStart = 0;
element.selectionEnd = input.length;
let isSuccess = false;
try {
isSuccess = document.execCommand('copy');
} catch { }
element.remove();
if (originalRange) {
selection.removeAllRanges();
selection.addRange(originalRange);
}
// Get the focus back on the previously focused element, if any
if (previouslyFocusedElement) {
previouslyFocusedElement.focus();
}
return isSuccess;
}

View File

@@ -1,64 +0,0 @@
/**
* v-dialogDrag 弹窗拖拽
* Copyright (c) 2019 ruoyi
*/
export default {
bind(el, binding, vnode, oldVnode) {
const value = binding.value
if (value == false) return
// 获取拖拽内容头部
const dialogHeaderEl = el.querySelector('.el-dialog__header');
const dragDom = el.querySelector('.el-dialog');
dialogHeaderEl.style.cursor = 'move';
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
dragDom.style.position = 'absolute';
dragDom.style.marginTop = 0;
let width = dragDom.style.width;
if (width.includes('%')) {
width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
} else {
width = +width.replace(/\px/g, '');
}
dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
// 鼠标按下事件
dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
const disX = e.clientX - dialogHeaderEl.offsetLeft;
const disY = e.clientY - dialogHeaderEl.offsetTop;
// 获取到的值带px 正则匹配替换
let styL, styT;
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
} else {
styL = +sty.left.replace(/\px/g, '');
styT = +sty.top.replace(/\px/g, '');
};
// 鼠标拖拽事件
document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
const l = e.clientX - disX;
const t = e.clientY - disY;
let finallyL = l + styL
let finallyT = t + styT
// 移动当前元素
dragDom.style.left = `${finallyL}px`;
dragDom.style.top = `${finallyT}px`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}
}
};

View File

@@ -1,34 +0,0 @@
/**
* v-dialogDragWidth 可拖动弹窗高度(右下角)
* Copyright (c) 2019 ruoyi
*/
export default {
bind(el) {
const dragDom = el.querySelector('.el-dialog');
const lineEl = document.createElement('div');
lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;';
lineEl.addEventListener('mousedown',
function(e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
const disY = e.clientY - el.offsetTop;
// 当前宽度 高度
const curWidth = dragDom.offsetWidth;
const curHeight = dragDom.offsetHeight;
document.onmousemove = function(e) {
e.preventDefault(); // 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const xl = e.clientX - disX;
const yl = e.clientY - disY
dragDom.style.width = `${curWidth + xl}px`;
dragDom.style.height = `${curHeight + yl}px`;
};
document.onmouseup = function(e) {
document.onmousemove = null;
document.onmouseup = null;
};
}, false);
dragDom.appendChild(lineEl);
}
}

View File

@@ -1,30 +0,0 @@
/**
* v-dialogDragWidth 可拖动弹窗宽度(右侧边)
* Copyright (c) 2019 ruoyi
*/
export default {
bind(el) {
const dragDom = el.querySelector('.el-dialog');
const lineEl = document.createElement('div');
lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;';
lineEl.addEventListener('mousedown',
function (e) {
// 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft;
// 当前宽度
const curWidth = dragDom.offsetWidth;
document.onmousemove = function (e) {
e.preventDefault(); // 移动时禁用默认事件
// 通过事件委托,计算移动的距离
const l = e.clientX - disX;
dragDom.style.width = `${curWidth + l}px`;
};
document.onmouseup = function (e) {
document.onmousemove = null;
document.onmouseup = null;
};
}, false);
dragDom.appendChild(lineEl);
}
}

View File

@@ -1,23 +1,9 @@
import hasRole from './permission/hasRole' import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi' import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag' import copyText from './common/copyText'
import dialogDragWidth from './dialog/dragWidth'
import dialogDragHeight from './dialog/dragHeight'
import clipboard from './module/clipboard'
const install = function(Vue) { export default function directive(app){
Vue.directive('hasRole', hasRole) app.directive('hasRole', hasRole)
Vue.directive('hasPermi', hasPermi) app.directive('hasPermi', hasPermi)
Vue.directive('clipboard', clipboard) app.directive('copyText', copyText)
Vue.directive('dialogDrag', dialogDrag) }
Vue.directive('dialogDragWidth', dialogDragWidth)
Vue.directive('dialogDragHeight', dialogDragHeight)
}
if (window.Vue) {
window['hasRole'] = hasRole
window['hasPermi'] = hasPermi
Vue.use(install); // eslint-disable-line
}
export default install

View File

@@ -1,54 +0,0 @@
/**
* v-clipboard 文字复制剪贴
* Copyright (c) 2021 ruoyi
*/
import Clipboard from 'clipboard'
export default {
bind(el, binding, vnode) {
switch (binding.arg) {
case 'success':
el._vClipBoard_success = binding.value;
break;
case 'error':
el._vClipBoard_error = binding.value;
break;
default: {
const clipboard = new Clipboard(el, {
text: () => binding.value,
action: () => binding.arg === 'cut' ? 'cut' : 'copy'
});
clipboard.on('success', e => {
const callback = el._vClipBoard_success;
callback && callback(e);
});
clipboard.on('error', e => {
const callback = el._vClipBoard_error;
callback && callback(e);
});
el._vClipBoard = clipboard;
}
}
},
update(el, binding) {
if (binding.arg === 'success') {
el._vClipBoard_success = binding.value;
} else if (binding.arg === 'error') {
el._vClipBoard_error = binding.value;
} else {
el._vClipBoard.text = function () { return binding.value; };
el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy';
}
},
unbind(el, binding) {
if (!el._vClipboard) return
if (binding.arg === 'success') {
delete el._vClipBoard_success;
} else if (binding.arg === 'error') {
delete el._vClipBoard_error;
} else {
el._vClipBoard.destroy();
delete el._vClipBoard;
}
}
}

View File

@@ -1,28 +1,28 @@
/** /**
* v-hasPermi 操作权限处理 * v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
import store from '@/store' import useUserStore from '@/store/modules/user'
export default { export default {
inserted(el, binding, vnode) { mounted(el, binding, vnode) {
const { value } = binding const { value } = binding
const all_permission = "*:*:*"; const all_permission = "*:*:*";
const permissions = store.getters && store.getters.permissions const permissions = useUserStore().permissions
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value const permissionFlag = value
const hasPermissions = permissions.some(permission => { const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission) return all_permission === permission || permissionFlag.includes(permission)
}) })
if (!hasPermissions) { if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el) el.parentNode && el.parentNode.removeChild(el)
} }
} else { } else {
throw new Error(`请设置操作权限标签值`) throw new Error(`请设置操作权限标签值`)
} }
} }
} }

View File

@@ -1,28 +1,28 @@
/** /**
* v-hasRole 角色权限处理 * v-hasRole 角色权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
import store from '@/store' import useUserStore from '@/store/modules/user'
export default { export default {
inserted(el, binding, vnode) { mounted(el, binding, vnode) {
const { value } = binding const { value } = binding
const super_admin = "admin"; const super_admin = "admin";
const roles = store.getters && store.getters.roles const roles = useUserStore().roles
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const roleFlag = value const roleFlag = value
const hasRole = roles.some(role => { const hasRole = roles.some(role => {
return super_admin === role || roleFlag.includes(role) return super_admin === role || roleFlag.includes(role)
}) })
if (!hasRole) { if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el) el.parentNode && el.parentNode.removeChild(el)
} }
} else { } else {
throw new Error(`请设置角色权限标签值"`) throw new Error(`请设置角色权限标签值`)
} }
} }
} }

View File

@@ -1,75 +1,68 @@
<template> <template>
<section class="app-main"> <section class="app-main">
<transition name="fade-transform" mode="out-in"> <router-view v-slot="{ Component, route }">
<keep-alive :include="cachedViews"> <transition name="fade-transform" mode="out-in">
<router-view v-if="!$route.meta.link" :key="key" /> <keep-alive :include="tagsViewStore.cachedViews">
</keep-alive> <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
</transition> </keep-alive>
<iframe-toggle /> </transition>
</section> </router-view>
</template> <iframe-toggle />
</section>
<script> </template>
import iframeToggle from "./IframeToggle/index"
<script setup>
export default { import iframeToggle from "./IframeToggle/index"
name: 'AppMain', import useTagsViewStore from '@/store/modules/tagsView'
components: { iframeToggle },
computed: { const tagsViewStore = useTagsViewStore()
cachedViews() { </script>
return this.$store.state.tagsView.cachedViews
}, <style lang="scss" scoped>
key() { .app-main {
return this.$route.path /* 50= navbar 50 */
} min-height: calc(100vh - 50px);
} width: 100%;
} position: relative;
</script> overflow: hidden;
}
<style lang="scss" scoped>
.app-main { .fixed-header + .app-main {
/* 50= navbar 50 */ padding-top: 50px;
min-height: calc(100vh - 50px); }
width: 100%;
position: relative; .hasTagsView {
overflow: hidden; .app-main {
} /* 84 = navbar + tags-view = 50 + 34 */
min-height: calc(100vh - 84px);
.fixed-header + .app-main { }
padding-top: 50px;
} .fixed-header + .app-main {
padding-top: 84px;
.hasTagsView { }
.app-main { }
/* 84 = navbar + tags-view = 50 + 34 */ </style>
min-height: calc(100vh - 84px);
} <style lang="scss">
// fix css style bug in open el-dialog
.fixed-header + .app-main { .el-popup-parent--hidden {
padding-top: 84px; .fixed-header {
} padding-right: 6px;
} }
</style> }
<style lang="scss"> ::-webkit-scrollbar {
// fix css style bug in open el-dialog width: 6px;
.el-popup-parent--hidden { height: 6px;
.fixed-header { }
padding-right: 6px;
} ::-webkit-scrollbar-track {
} background-color: #f1f1f1;
}
::-webkit-scrollbar {
width: 6px; ::-webkit-scrollbar-thumb {
height: 6px; background-color: #c0c0c0;
} border-radius: 3px;
}
::-webkit-scrollbar-track { </style>
background-color: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background-color: #c0c0c0;
border-radius: 3px;
}
</style>

View File

@@ -1,33 +1,25 @@
<template> <template>
<transition-group name="fade-transform" mode="out-in"> <inner-link
<inner-link v-for="(item, index) in tagsViewStore.iframeViews"
v-for="(item, index) in iframeViews" :key="item.path"
:key="item.path" :iframeId="'iframe' + index"
:iframeId="'iframe' + index" v-show="route.path === item.path"
v-show="$route.path === item.path" :src="iframeUrl(item.meta.link, item.query)"
:src="iframeUrl(item.meta.link, item.query)" ></inner-link>
></inner-link> </template>
</transition-group>
</template> <script setup>
import InnerLink from "../InnerLink/index";
<script> import useTagsViewStore from "@/store/modules/tagsView";
import InnerLink from "../InnerLink/index";
const route = useRoute();
export default { const tagsViewStore = useTagsViewStore();
components: { InnerLink },
computed: { function iframeUrl(url, query) {
iframeViews() { if (Object.keys(query).length > 0) {
return this.$store.state.tagsView.iframeViews; let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
} return url + "?" + params;
}, }
methods: { return url;
iframeUrl(url, query) { }
if (Object.keys(query).length > 0) { </script>
let params = Object.keys(query).map((key) => key + "=" + query[key]).join("&");
return url + "?" + params;
}
return url;
}
}
}
</script>

View File

@@ -1,47 +1,24 @@
<template> <template>
<div :style="'height:' + height" v-loading="loading" element-loading-text="正在加载页面,请稍候!"> <div :style="'height:' + height">
<iframe <iframe
:id="iframeId" :id="iframeId"
style="width: 100%; height: 100%" style="width: 100%; height: 100%"
:src="src" :src="src"
frameborder="no" frameborder="no"
></iframe> ></iframe>
</div> </div>
</template> </template>
<script> <script setup>
export default { const props = defineProps({
props: { src: {
src: { type: String,
type: String, default: "/"
default: "/" },
}, iframeId: {
iframeId: { type: String
type: String }
} });
},
data() { const height = ref(document.documentElement.clientHeight - 94.5 + "px");
return { </script>
loading: false,
height: document.documentElement.clientHeight - 94.5 + "px;"
};
},
mounted() {
var _this = this;
const iframeId = ("#" + this.iframeId).replace(/\//g, "\\/");
const iframe = document.querySelector(iframeId);
// iframe页面loading控制
if (iframe.attachEvent) {
this.loading = true;
iframe.attachEvent("onload", function () {
_this.loading = false;
});
} else {
this.loading = true;
iframe.onload = function () {
_this.loading = false;
};
}
}
};
</script>

View File

@@ -1,200 +1,191 @@
<template> <template>
<div class="navbar"> <div class="navbar">
<hamburger id="hamburger-container" :is-active="sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" /> <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container" @toggleClick="toggleSideBar" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!settingsStore.topNav" />
<breadcrumb id="breadcrumb-container" class="breadcrumb-container" v-if="!topNav"/> <top-nav id="topmenu-container" class="topmenu-container" v-if="settingsStore.topNav" />
<top-nav id="topmenu-container" class="topmenu-container" v-if="topNav"/>
<div class="right-menu">
<div class="right-menu"> <template v-if="appStore.device !== 'mobile'">
<template v-if="device!=='mobile'"> <header-search id="header-search" class="right-menu-item" />
<search id="header-search" class="right-menu-item" />
<el-tooltip content="源码地址" effect="dark" placement="bottom">
<el-tooltip content="源码地址" effect="dark" placement="bottom"> <ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" />
<ruo-yi-git id="ruoyi-git" class="right-menu-item hover-effect" /> </el-tooltip>
</el-tooltip>
<el-tooltip content="文档地址" effect="dark" placement="bottom">
<el-tooltip content="文档地址" effect="dark" placement="bottom"> <ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" />
<ruo-yi-doc id="ruoyi-doc" class="right-menu-item hover-effect" /> </el-tooltip>
</el-tooltip>
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<screenfull id="screenfull" class="right-menu-item hover-effect" />
<el-tooltip content="布局大小" effect="dark" placement="bottom">
<el-tooltip content="布局大小" effect="dark" placement="bottom"> <size-select id="size-select" class="right-menu-item hover-effect" />
<size-select id="size-select" class="right-menu-item hover-effect" /> </el-tooltip>
</el-tooltip> </template>
<div class="avatar-container">
</template> <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
<div class="avatar-wrapper">
<el-dropdown class="avatar-container right-menu-item hover-effect" trigger="click"> <img :src="userStore.avatar" class="user-avatar" />
<div class="avatar-wrapper"> <el-icon><caret-bottom /></el-icon>
<img :src="avatar" class="user-avatar"> </div>
<i class="el-icon-caret-bottom" /> <template #dropdown>
</div> <el-dropdown-menu>
<el-dropdown-menu slot="dropdown"> <router-link to="/user/profile">
<router-link to="/user/profile"> <el-dropdown-item>个人中心</el-dropdown-item>
<el-dropdown-item>个人中心</el-dropdown-item> </router-link>
</router-link> <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
<el-dropdown-item @click.native="setting = true"> <span>布局设置</span>
<span>布局设置</span> </el-dropdown-item>
</el-dropdown-item> <el-dropdown-item divided command="logout">
<el-dropdown-item divided @click.native="logout"> <span>退出登录</span>
<span>退出登录</span> </el-dropdown-item>
</el-dropdown-item> </el-dropdown-menu>
</el-dropdown-menu> </template>
</el-dropdown> </el-dropdown>
</div> </div>
</div> </div>
</template> </div>
</template>
<script>
import { mapGetters } from 'vuex' <script setup>
import Breadcrumb from '@/components/Breadcrumb' import { ElMessageBox } from 'element-plus'
import TopNav from '@/components/TopNav' import Breadcrumb from '@/components/Breadcrumb'
import Hamburger from '@/components/Hamburger' import TopNav from '@/components/TopNav'
import Screenfull from '@/components/Screenfull' import Hamburger from '@/components/Hamburger'
import SizeSelect from '@/components/SizeSelect' import Screenfull from '@/components/Screenfull'
import Search from '@/components/HeaderSearch' import SizeSelect from '@/components/SizeSelect'
import RuoYiGit from '@/components/RuoYi/Git' import HeaderSearch from '@/components/HeaderSearch'
import RuoYiDoc from '@/components/RuoYi/Doc' import RuoYiGit from '@/components/RuoYi/Git'
import RuoYiDoc from '@/components/RuoYi/Doc'
export default { import useAppStore from '@/store/modules/app'
components: { import useUserStore from '@/store/modules/user'
Breadcrumb, import useSettingsStore from '@/store/modules/settings'
TopNav,
Hamburger, const appStore = useAppStore()
Screenfull, const userStore = useUserStore()
SizeSelect, const settingsStore = useSettingsStore()
Search,
RuoYiGit, function toggleSideBar() {
RuoYiDoc appStore.toggleSideBar()
}, }
computed: {
...mapGetters([ function handleCommand(command) {
'sidebar', switch (command) {
'avatar', case "setLayout":
'device' setLayout();
]), break;
setting: { case "logout":
get() { logout();
return this.$store.state.settings.showSettings break;
}, default:
set(val) { break;
this.$store.dispatch('settings/changeSetting', { }
key: 'showSettings', }
value: val
}) function logout() {
} ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
}, confirmButtonText: '确定',
topNav: { cancelButtonText: '取消',
get() { type: 'warning'
return this.$store.state.settings.topNav }).then(() => {
} userStore.logOut().then(() => {
} location.href = '/index';
}, })
methods: { }).catch(() => { });
toggleSideBar() { }
this.$store.dispatch('app/toggleSideBar')
}, const emits = defineEmits(['setLayout'])
async logout() { function setLayout() {
this.$confirm('确定注销并退出系统吗?', '提示', { emits('setLayout');
confirmButtonText: '确定', }
cancelButtonText: '取消', </script>
type: 'warning'
}).then(() => { <style lang='scss' scoped>
this.$store.dispatch('LogOut').then(() => { .navbar {
location.href = '/index'; height: 50px;
}) overflow: hidden;
}).catch(() => {}); position: relative;
} background: #fff;
} box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
}
</script> .hamburger-container {
line-height: 46px;
<style lang="scss" scoped> height: 100%;
.navbar { float: left;
height: 50px; cursor: pointer;
overflow: hidden; transition: background 0.3s;
position: relative; -webkit-tap-highlight-color: transparent;
background: #fff;
box-shadow: 0 1px 4px rgba(0,21,41,.08); &:hover {
background: rgba(0, 0, 0, 0.025);
.hamburger-container { }
line-height: 46px; }
height: 100%;
float: left; .breadcrumb-container {
cursor: pointer; float: left;
transition: background .3s; }
-webkit-tap-highlight-color:transparent;
.topmenu-container {
&:hover { position: absolute;
background: rgba(0, 0, 0, .025) left: 50px;
} }
}
.errLog-container {
.breadcrumb-container { display: inline-block;
float: left; vertical-align: top;
} }
.topmenu-container { .right-menu {
position: absolute; float: right;
left: 50px; height: 100%;
} line-height: 50px;
display: flex;
.errLog-container {
display: inline-block; &:focus {
vertical-align: top; outline: none;
} }
.right-menu { .right-menu-item {
float: right; display: inline-block;
height: 100%; padding: 0 8px;
line-height: 50px; height: 100%;
font-size: 18px;
&:focus { color: #5a5e66;
outline: none; vertical-align: text-bottom;
}
&.hover-effect {
.right-menu-item { cursor: pointer;
display: inline-block; transition: background 0.3s;
padding: 0 8px;
height: 100%; &:hover {
font-size: 18px; background: rgba(0, 0, 0, 0.025);
color: #5a5e66; }
vertical-align: text-bottom; }
}
&.hover-effect {
cursor: pointer; .avatar-container {
transition: background .3s; margin-right: 40px;
&:hover { .avatar-wrapper {
background: rgba(0, 0, 0, .025) margin-top: 5px;
} position: relative;
}
} .user-avatar {
cursor: pointer;
.avatar-container { width: 40px;
margin-right: 30px; height: 40px;
border-radius: 10px;
.avatar-wrapper { }
margin-top: 5px;
position: relative; i {
cursor: pointer;
.user-avatar { position: absolute;
cursor: pointer; right: -20px;
width: 40px; top: 25px;
height: 40px; font-size: 12px;
border-radius: 10px; }
} }
}
.el-icon-caret-bottom { }
cursor: pointer; }
position: absolute; </style>
right: -20px;
top: 25px;
font-size: 12px;
}
}
}
}
}
</style>

Some files were not shown because too many files have changed in this diff Show More