搭建框架

main
‘wangjiuyun 2024-09-30 11:34:13 +08:00
parent e4cd612776
commit 63baa08e8c
78 changed files with 42132 additions and 8 deletions

6
.env.development 100644
View File

@ -0,0 +1,6 @@
VITE_BASE_URL = "./"
VITE_BASE_NAME = "external"
VITE_APP_PROXYURL = 'http://183.230.174.15:8088/external'
VITE_APP_REQUESTURL = '/external'
VITE_APP_ROUTERURL = '/external/'

5
.env.production 100644
View File

@ -0,0 +1,5 @@
VITE_BASE_URL = '/pms-front'
VITE_BASE_NAME = "pms-front"
VITE_APP_PROXYURL = 'http://183.230.174.15:8092/pms-front'
VITE_APP_REQUESTURL = '/pms-front'
VITE_APP_ROUTERURL = '/pms-front/'

5
.env.test 100644
View File

@ -0,0 +1,5 @@
VITE_BASE_URL = '/pms-front-test'
VITE_BASE_NAME = "pms-front-test"
VITE_APP_PROXYURL = 'http://183.230.174.15:8093/aiops-external'
VITE_APP_REQUESTURL = '/pms-front-test'
VITE_APP_ROUTERURL = '/pms-front-test/'

39
.gitignore vendored
View File

@ -1,11 +1,34 @@
# ---> Vue
# gitignore template for Vue.js projects
#
# Recommended template: Node.gitignore
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# TODO: where does this rule come from?
docs/_book
node_modules
pms-front
pms-front-test
dist
dist-ssr
*.local
# TODO: where does this rule come from?
test/
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
*.zip
*.gz
*.tar
*.7z
.history/
yarn.lock
package-lock.json

1
.prettierignore 100644
View File

@ -0,0 +1 @@
**/*none.js

11
.prettierrc 100644
View File

@ -0,0 +1,11 @@
{
"printWidth": 120,
"tabWidth": 2,
"trailingComma": "all",
"arrowParens": "avoid",
"endOfLine": "auto",
"singleQuote": true,
"jsxBracketSameLine": false,
"htmlWhitespaceSensitivity": "ignore",
"semi": false
}

25
index.html 100644
View File

@ -0,0 +1,25 @@
<!--
* @Author: suhang suhang_max@163.com
* @Date: 2023-05-31 22:06:51
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-06-01 01:01:28
* @FilePath: \train-assessd:\Code\basic-frontend\index.html
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/logo1.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PMS:新光线平台</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

43
package.json 100644
View File

@ -0,0 +1,43 @@
{
"name": "basic-frontend",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build --mode production",
"build:product": "vite build --mode product",
"build:test": "vite build --mode test",
"preview": "vite preview"
},
"dependencies": {
"axios": "^1.1.3",
"crypto-js": "^4.1.1",
"echarts": "^5.4.3",
"element-plus": "^2.2.21",
"gsap": "^3.11.5",
"js-cookie": "^3.0.5",
"jszip": "^3.10.0",
"leaflet": "^1.9.4",
"maxiliam-utils": "^1.0.6",
"moment": "^2.29.4",
"pinia": "^2.0.27",
"sass": "^1.51.0",
"sass-loader": "^13.0.2",
"shrinkpng": "^1.2.0-beta.1",
"sockjs-client": "^1.6.1",
"stompjs": "^2.3.3",
"three": "^0.148.0",
"vform3-builds": "^3.0.10",
"vue": "^3.2.47",
"vue-dompurify-html": "^3.1.2",
"vue-jsonp": "^2.0.0",
"vue-router": "^4.0.13"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"unplugin-auto-import": "^0.7.1",
"unplugin-vue-components": "^0.19.3",
"vite": "^4.3.9",
"vite-plugin-svg-icons": "^2.0.1"
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="46px" height="46px" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(-230.000000, -1012.000000)" fill-rule="nonzero">
<g id="编组-2" transform="translate(230.000000, 1012.000000)">
<rect id="矩形" fill="#FFFFFF" x="15.3333333" y="7.66666667" width="19.1666667" height="28.1111111"></rect>
<path d="M23,0 C10.2974005,0 0,10.2974005 0,23 C0,35.7025995 10.2974006,46 23,46 C35.7025994,46 46,35.7025994 46,23 C46,10.2974006 35.7025995,0 23,0 Z M32.6319034,24.527017 L19.5637216,33.1520171 C19.0018778,33.5223655 18.2820799,33.5543935 17.6895637,33.2354093 C17.0970475,32.9164251 16.7274655,32.2979237 16.7272727,31.625 L16.7272727,14.3766335 C16.7272727,13.7034921 17.0969801,13.0847404 17.6897186,12.76571 C18.2824572,12.4466796 19.002508,12.4789079 19.564375,12.8496165 L32.6325568,21.4746165 C33.1456581,21.8132186 33.4544042,22.386878 33.4544042,23.0016335 C33.4544042,23.616389 33.1456581,24.1900484 32.6325568,24.5286506 L32.6319034,24.527017 Z" id="形状" fill="currentColor"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="46px" height="46px" viewBox="0 0 46 46" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>编组 21@2x</title>
<g id="飞行" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="演习复盘-时间轴0316" transform="translate(-230.000000, -861.000000)">
<g id="编组-21" transform="translate(230.000000, 861.000000)">
<rect id="矩形" fill="#FFFFFF" x="11" y="10" width="24" height="26"></rect>
<path d="M23,0 C10.2924896,0 0,10.2924895 0,23 C0,35.7075105 10.2924895,46 23,46 C35.7075105,46 46,35.7075105 46,23 C46,10.2924895 35.7074574,0 23,0 Z M20.7000053,32.1999788 L16.1000159,32.1999788 L16.1000159,13.8000212 L20.7000053,13.8000212 L20.7000053,32.1999788 Z M29.8999841,32.1999788 L25.2999947,32.1999788 L25.2999947,13.8000212 L29.8999841,13.8000212 L29.8999841,32.1999788 Z" id="形状" fill="#32F8F9" fill-rule="nonzero"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

4
public/js/gifler.min.js vendored 100644

File diff suppressed because one or more lines are too long

33465
public/js/turf.min.js vendored 100644

File diff suppressed because it is too large Load Diff

75
public/js/zip.js 100644
View File

@ -0,0 +1,75 @@
/*
* @FilePath : /public/js/zip.js
* @Description :
*/
/*
* @Author: suhang suhang_max@163.com
* @Date: 2023-06-01 00:14:29
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-06-01 10:56:19
* @FilePath: \train-assessd:\Code\basic-frontend\zip.js
* @Description: 将打包后的文件夹自动压缩成zip
*/
// console.log(import.meta, 'import.meta.env.VITE_APP_GISURL')
import path from 'path'
import fs from 'fs'
import JSZip from 'jszip'
const plugin = (fileName = 'pms-front', output) => {
// if (!output) output = path.resolve(__dirname, `../..${import.meta.env.VITE_BASE_URL}`)
output = path.resolve(__dirname, `../..${output}`)
fileName += '.zip'
const makeZip = () => {
const zip = new JSZip()
const distPath = path.resolve(output)
const readDir = (zip, dirPath) => {
// 读取dist下的根文件目录
const files = fs.readdirSync(dirPath)
files.forEach(fileName => {
const fillPath = path.join(dirPath, './', fileName)
const file = fs.statSync(fillPath)
// 如果是文件夹的话需要递归遍历下面的子文件
if (file.isDirectory()) {
const dirZip = zip.folder(fileName)
readDir(dirZip, fillPath)
} else {
// 读取每个文件为buffer存到zip中
zip.file(fileName, fs.readFileSync(fillPath))
}
})
}
const removeExistedZip = () => {
const dest = path.join(distPath, './' + fileName)
if (fs.existsSync(dest)) {
fs.unlinkSync(dest)
}
}
const zipDir = () => {
readDir(zip, distPath)
zip
.generateAsync({
type: 'nodebuffer', // 压缩类型
compression: 'DEFLATE', // 压缩算法
compressionOptions: {
// 压缩级别
level: 9,
},
})
.then(content => {
const dest = path.join(distPath, '../' + fileName)
removeExistedZip()
// 把zip包写到硬盘中这个content现在是一段buffer
fs.writeFileSync(dest, content)
})
}
removeExistedZip()
zipDir(distPath)
}
return {
name: 'vite-plugin-auto-zip',
apply: 'build',
closeBundle() {
makeZip()
},
}
}
export default plugin

BIN
public/logo1.png 100644

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

1
public/vite.svg 100644
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

25
src/App.vue 100644
View File

@ -0,0 +1,25 @@
<script setup>
import { reactive } from '@vue/reactivity'
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
const locale = reactive(zhCn)
//
// document.oncontextmenu = () => {
// return false
// }
// console.log(`======${import.meta.env.MODE}======`)
</script>
<template>
<el-config-provider :locale="locale">
<router-view />
</el-config-provider>
</template>
<style>
#app {
position: relative;
min-height: 100vh;
height: 100%;
display: flex;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View File

@ -0,0 +1,548 @@
@use 'sass:math';
// @font-face {
// font-family: AliPuHui;
// /**/
// src: url('../font/Alibaba-PuHuiTi/Alibaba-PuHuiTi-Regular.ttf');
// /**/
// }
// @font-face {
// font-family: AliPuHuiBold;
// /**/
// src: url('../font/Alibaba-PuHuiTi/Alibaba-PuHuiTi-Bold.ttf');
// /**/
// }
* {
box-sizing: border-box;
-webkit-tap-highlight-color: rgba(255, 0, 0, 0);
-moz-user-select: text; //-moz-none
-khtml-user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
user-select: text;
font-family: AliPuHui;
}
$lightThemColor: #3686ff;
html {
width: 100%;
height: 100%;
}
body {
margin: 0;
overflow: hidden;
width: 100%;
height: 100%;
font-size: 14px;
position: relative;
}
@mixin flex-row {
display: flex;
display: -webkit-flex;
display: -moz-flex;
/* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox;
/* TWEENER - IE 10 */
}
@mixin flex-row-vc {
display: flex;
display: -webkit-flex;
display: -moz-flex;
/* OLD - Firefox 19- (buggy but mostly works) */
display: -ms-flexbox;
/* TWEENER - IE 10 */
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
-o-flex-direction: column;
flex-direction: column;
}
//
.df {
@include flex-row;
}
.fdc {
flex-direction: column;
-webkit-flex-direction: column;
-moz-flex-direction: column;
-ms-flex-direction: column;
-o-flex-direction: column;
}
.f1 {
flex: 1;
}
.fn {
flex: none;
}
.fw {
flex-wrap: wrap;
}
.jcc {
justify-content: center;
}
.jcsb {
justify-content: space-between;
}
.jcsa {
justify-content: space-around;
}
.jcse {
justify-content: space-evenly;
}
.jcfs {
justify-content: flex-start;
}
.jcfe {
justify-content: flex-end;
}
.aic {
align-items: center;
}
.aifs {
align-items: flex-start;
}
//
.pd8-15 {
padding: 0.08rem 0.15rem;
}
.pd8-23 {
padding: 0.08rem 0.23rem;
}
.w300-h30 {
width: 70%;
height: 32px;
flex-shrink: 0;
flex-grow: 0;
}
:deep(.w300-h30) {
width: 70%;
height: 32px;
flex-shrink: 0;
flex-grow: 0;
}
.h100 {
height: 100px;
}
.h200 {
height: 200px;
}
.h300 {
height: 300px;
}
.w100-h30 {
width: 1rem;
height: 30px;
}
.w200-h30 {
width: 70% !important;
height: 30px;
flex-shrink: 0;
flex-grow: 0;
}
:deep(.w200-h30) {
width: 70% !important;
height: 30px;
flex-shrink: 0;
flex-grow: 0;
}
.w200-h20 {
width: 2rem;
height: 20px;
}
.w140-h30 {
width: 1.4rem;
height: 30px;
}
.w150-h30 {
width: 150px;
height: 30px;
}
.w90-h40 {
width: 90px;
height: 40px;
}
.w75-h30 {
width: 75px;
height: 30px;
}
//
.ml10 {
margin-left: 0.1rem;
}
.ml20 {
margin-left: 0.2rem;
}
.mr10 {
margin-right: 0.1rem;
}
.mr20 {
margin-right: 0.2rem;
}
.mb10 {
margin-bottom: 10px;
}
.mb20 {
margin-bottom: 0.2rem;
}
.mb20-px {
margin-bottom: 20px;
}
.mb30 {
margin-bottom: 0.3rem;
}
.mt10 {
margin-top: 0.1rem;
}
.mt20 {
margin-top: 0.2rem;
}
.mt40 {
margin-top: 0.4rem;
}
.mb40 {
margin-bottom: 0.4rem;
}
.mr40 {
margin-right: 0.4rem;
}
.textBtn-B {
font-weight: bold;
color: $lightThemColor;
cursor: pointer;
}
.fs22 {
font-size: 22px;
}
.textBtn-R {
font-weight: bold;
color: #f8544b !important;
cursor: pointer;
}
.no_wrap {
white-space: nowrap;
}
.no-data {
// color: #000;
width: 100%;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// .theme-color {
// color: $lightThemColor;
// }
/* 竖向弹性盒子 */
.flex-col {
@include flex-row-vc;
flex: 1;
// height: 100%;
&.expand {
justify-content: space-between;
}
&.wrap {
flex-wrap: wrap;
}
}
/* 横向弹性盒子 */
.flex-row {
@include flex-row;
width: 100%;
&.middle {
align-items: center;
}
&.bottom {
align-items: flex-end;
}
&.expand {
justify-content: space-between;
}
&.wrap {
flex-wrap: wrap;
}
}
//
.page-conatiner {
overflow-x: hidden;
.title {
font-weight: bold;
font-size: 0.18rem;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
}
.search-item {
@include flex-row;
align-items: center;
white-space: nowrap;
width: 24%;
gap: 10px;
.label {
width: 0.8rem;
min-width: 60px;
white-space: pre-wrap;
text-align: center;
line-height: 24px;
text-align: center;
}
.el-input,
.el-select {
width: 70% !important;
.el-input {
width: 100% !important;
}
}
//
}
.search-item.short {
width: 24%;
}
.table-container {
flex: 1 1 auto;
height: 0;
.el-scrollbar {
min-height: 50px;
}
}
.page {
width: 100%;
margin-top: 0.2rem;
display: flex;
justify-content: flex-end;
}
}
@mixin scroll-none {
scrollbar-width: none;
/* firefox */
-ms-overflow-style: none;
/* IE 10+ */
overflow-x: hidden;
overflow-y: scroll;
}
// dialogbody,
.el-popup-parent--hidden {
width: 100% !important;
padding-right: 0px !important;
overflow: hidden !important;
}
/* 滚动条样式 */
::-webkit-scrollbar {
/*滚动条整体样式*/
width: 0.04rem;
/*高宽分别对应横竖滚动条的尺寸*/
height: 0.04rem;
}
::-webkit-scrollbar-thumb,
::-webkit-scrollbar-thumb:horizontal {
/*滚动条里面小方块*/
border-radius: 0.1rem;
background-color: rgba(145, 151, 155, 0.5);
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track:horizontal {
/*滚动条里面轨道*/
background: rgba(0, 0, 0, 0);
border-radius: 0.1rem;
}
// tablecell
.el-table__body {
.cell {
font-size: 14px;
}
}
//
.el-table th > .cell {
display: inline-block;
white-space: nowrap;
word-break: keep-all;
text-overflow: unset;
}
//
:deep(.el-table__empty-block) {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
// .el-table__empty-text {
// color: #ffffff;
// }
}
// dialog
.center-dialog {
display: flex;
justify-content: center;
align-items: center;
// overflow: hidden;
:deep(.el-dialog) {
margin: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.el-dialog__header {
border-bottom: 1px solid #d8d8d8;
}
.el-dialog__body {
// overflow: hidden;
overflow: auto;
// height: 70vh; //90%
}
}
}
.common-dialog {
display: flex;
justify-content: center;
align-items: center;
// overflow: hidden;
:deep(.el-dialog) {
margin: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.el-dialog__header {
border-bottom: 1px solid #d8d8d8;
height: 38px;
font-weight: bold;
font-size: 14px;
line-height: 38px;
padding: 0;
padding-left: 16px;
text-align: left;
.el-dialog__title {
color: #333333;
font-size: 14px !important;
}
}
.el-dialog__body {
// overflow: hidden;
overflow: auto;
max-height: 90vh; //90%
// height: auto;
}
.el-dialog__headerbtn {
height: 36px;
}
.table-container {
min-height: 30vh;
}
}
}
:deep(.el-form-item__content) {
align-items: flex-start !important;
}
:deep(.el-input__wrapper) {
overflow: hidden !important;
}
.textL {
text-align: left;
}
.textC {
text-align: center;
}
.textR {
text-align: right;
}
.pr15 {
padding-right: 15px;
}
.image-slot-error {
height: 100%;
width: 100%;
background-color: #f7f5f5;
align-items: center;
justify-content: center;
display: flex;
}
.no-data {
// color: #000;
width: 100%;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// 线
.text_underline {
font-family: MicrosoftYaHei;
font-size: 14px;
color: #5584ff;
line-height: 22px;
text-align: center;
font-style: normal;
text-decoration-line: underline;
-moz-text-decoration-line: underline;
cursor: pointer;
}
:deep(.el-button--primary:not(.is-text)) {
background-color: #5584ff !important ;
color: #fff !important;
}
:deep(.el-button) {
min-width: 96px !important ;
}
:deep(.el-dialog__footer) {
padding: 10px 0;
background: #f5f5f5;
}
.optionTitle {
background-color: #f7f7f7;
height: 40px;
line-height: 40px;
padding-left: 10px;
}
// :deep(.el-radio-button__inner){
// width: 120px;
// }
// :deep(*:not(.el-dialog__body) .is-active .el-radio-button__inner){
// background: #5584FF;
// }
:deep(.el-dialog--center .el-dialog__body) {
padding: 32px !important;
}

View File

@ -0,0 +1,49 @@
/* http://meyerweb.com/eric/tools/css/reset/
v2.0 | 20110126
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
// font-size: 100%;
// font: inherit;
vertical-align: baseline;
box-sizing: border-box;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}

View File

@ -0,0 +1,80 @@
<!--
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-13 10:26:40
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2023-07-04 18:15:45
* @Description : 布局card
-->
<template>
<div class="my-card">
<div v-if="props.hasTitle" class="my-card-title">
<slot name="title"></slot>
</div>
<el-card class="my-card-body" shadow="never">
<template v-if="props.hasHeader" #header>
<slot name="header"></slot>
</template>
<slot name="body"></slot>
</el-card>
</div>
</template>
<script setup>
const props = defineProps({
hasTitle: {
type: Boolean,
default: true,
},
hasHeader: {
type: Boolean,
default: true,
},
})
</script>
<style lang="scss" scoped>
.my-card {
flex: 1;
@include flex-row-vc;
font-size: 14px;
width: 100%;
height: 100%;
background-color: #f0f2f5;
.my-card-title {
flex: none;
display: flex;
justify-content: space-between;
border-radius: 0.04rem;
align-items: center;
// margin-bottom: 0.1rem;
background-color: #fff;
padding: 0.2rem 0.2rem;
}
.my-card-body {
@include flex-row-vc;
flex: 1 1 auto;
width: 100%;
// height: 100%;
:deep(.el-card__header) {
padding-top: 0.1rem;
padding-bottom: 0.1rem;
flex: none;
}
:deep(.el-card__body) {
@include flex-row;
flex-wrap: wrap;
flex: 1 1 auto;
// height: 100%;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
}
}
}
}
</style>

View File

@ -0,0 +1,161 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L W]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从
<el-input-number v-model='cycle01' :min="1" :max="30" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="31" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="30" /> 号开始
<el-input-number v-model='average02' :min="1" :max="31 - average01 || 1" /> 日执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
每月
<el-input-number v-model='workday' :min="1" :max="31" /> 号最近的那个工作日
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
本月最后一天
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="7">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 31" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
workday: 1,
cycle01: 1,
cycle02: 2,
average01: 1,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-day',
props: ['check', 'cron'],
methods: {
//
radioChange() {
('day rachange');
if (this.radioValue !== 2 && this.cron.week !== '?') {
this.$emit('update', 'week', '?', 'day')
}
switch (this.radioValue) {
case 1:
this.$emit('update', 'day', '*');
break;
case 2:
this.$emit('update', 'day', '?');
break;
case 3:
this.$emit('update', 'day', this.cycleTotal);
break;
case 4:
this.$emit('update', 'day', this.averageTotal);
break;
case 5:
this.$emit('update', 'day', this.workday + 'W');
break;
case 6:
this.$emit('update', 'day', 'L');
break;
case 7:
this.$emit('update', 'day', this.checkboxString);
break;
}
('day rachange end');
},
//
cycleChange() {
if (this.radioValue == '3') {
this.$emit('update', 'day', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '4') {
this.$emit('update', 'day', this.averageTotal);
}
},
//
workdayChange() {
if (this.radioValue == '5') {
this.$emit('update', 'day', this.workdayCheck + 'W');
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '7') {
this.$emit('update', 'day', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'workdayCheck': 'workdayChange',
'checkboxString': 'checkboxChange',
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 1, 30)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 31, 31)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 1, 30)
const average02 = this.checkNum(this.average02, 1, 31 - average01 || 0)
return average01 + '/' + average02;
},
//
workdayCheck: function () {
const workday = this.checkNum(this.workday, 1, 31)
return workday;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str == '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,120 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
小时允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="22" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="23" /> 小时
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="22" /> 小时开始
<el-input-number v-model='average02' :min="1" :max="23 - average01 || 0" /> 小时执行一次
</el-radio>
</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 24" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 0,
cycle02: 1,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-hour',
props: ['check', 'cron'],
methods: {
//
radioChange() {
if (this.cron.min === '*') {
this.$emit('update', 'min', '0', 'hour');
}
if (this.cron.second === '*') {
this.$emit('update', 'second', '0', 'hour');
}
switch (this.radioValue) {
case 1:
this.$emit('update', 'hour', '*')
break;
case 2:
this.$emit('update', 'hour', this.cycleTotal);
break;
case 3:
this.$emit('update', 'hour', this.averageTotal);
break;
case 4:
this.$emit('update', 'hour', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue == '2') {
this.$emit('update', 'hour', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '3') {
this.$emit('update', 'hour', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '4') {
this.$emit('update', 'hour', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 22)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 23)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 22)
const average02 = this.checkNum(this.average02, 1, 23 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str == '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,430 @@
<template>
<div>
<el-tabs type="border-card">
<el-tab-pane label="秒" v-if="shouldHide('second')">
<CrontabSecond
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronsecond"
/>
</el-tab-pane>
<el-tab-pane label="分钟" v-if="shouldHide('min')">
<CrontabMin
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronmin"
/>
</el-tab-pane>
<el-tab-pane label="小时" v-if="shouldHide('hour')">
<CrontabHour
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronhour"
/>
</el-tab-pane>
<el-tab-pane label="日" v-if="shouldHide('day')">
<CrontabDay
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronday"
/>
</el-tab-pane>
<el-tab-pane label="月" v-if="shouldHide('month')">
<CrontabMonth
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronmonth"
/>
</el-tab-pane>
<el-tab-pane label="周" v-if="shouldHide('week')">
<CrontabWeek
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronweek"
/>
</el-tab-pane>
<el-tab-pane label="年" v-if="shouldHide('year')">
<CrontabYear
@update="updateCrontabValue"
:check="checkNumber"
:cron="crontabValueObj"
ref="cronyear"
/>
</el-tab-pane>
</el-tabs>
<div class="popup-main">
<div class="popup-result">
<p class="title">时间表达式</p>
<table>
<thead>
<th v-for="item of tabTitles" width="40" :key="item">{{item}}</th>
<th>Cron 表达式</th>
</thead>
<tbody>
<td>
<span>{{crontabValueObj.second}}</span>
</td>
<td>
<span>{{crontabValueObj.min}}</span>
</td>
<td>
<span>{{crontabValueObj.hour}}</span>
</td>
<td>
<span>{{crontabValueObj.day}}</span>
</td>
<td>
<span>{{crontabValueObj.month}}</span>
</td>
<td>
<span>{{crontabValueObj.week}}</span>
</td>
<td>
<span>{{crontabValueObj.year}}</span>
</td>
<td>
<span>{{crontabValueString}}</span>
</td>
</tbody>
</table>
</div>
<CrontabResult :ex="crontabValueString"></CrontabResult>
<div class="pop_btn">
<el-button size="small" type="primary" @click="submitFill"></el-button>
<el-button size="small" type="warning" @click="clearCron"></el-button>
<el-button size="small" @click="hidePopup"></el-button>
</div>
</div>
</div>
</template>
<script>
import CrontabSecond from "./second.vue";
import CrontabMin from "./min.vue";
import CrontabHour from "./hour.vue";
import CrontabDay from "./day.vue";
import CrontabMonth from "./month.vue";
import CrontabWeek from "./week.vue";
import CrontabYear from "./year.vue";
import CrontabResult from "./result.vue";
export default {
data() {
return {
tabTitles: ["秒", "分钟", "小时", "日", "月", "周", "年"],
tabActive: 0,
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() {
//
if (this.expression) {
let arr = this.expression.split(" ");
if (arr.length >= 6) {
//6
let obj = {
second: arr[0],
min: arr[1],
hour: arr[2],
day: arr[3],
month: arr[4],
week: arr[5],
year: arr[6] ? arr[6] : "",
};
this.crontabValueObj = {
...obj,
};
for (let i in obj) {
if (obj[i]) this.changeRadio(i, obj[i]);
}
}
} else {
//
this.clearCron();
}
},
// tab
tabCheck(index) {
this.tabActive = index;
},
//
updateCrontabValue(name, value, from) {
"updateCrontabValue", name, value, from;
this.crontabValueObj[name] = value;
if (from && from !== name) {
console.log(`来自组件 ${from} 改变了 ${name} ${value}`);
this.changeRadio(name, value);
}
},
//
changeRadio(name, value) {
let arr = ["second", "min", "hour", "month"],
refName = "cron" + name,
insValue;
if (!this.$refs[refName]) return;
if (arr.includes(name)) {
if (value === "*") {
insValue = 1;
} 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 = 2;
} 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: "*",
min: "*",
hour: "*",
day: "*",
month: "*",
week: "?",
year: "",
};
for (let j in this.crontabValueObj) {
this.changeRadio(j, this.crontabValueObj[j]);
}
},
},
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>
<style scoped>
.pop_btn {
text-align: center;
margin-top: 20px;
}
.popup-main {
position: relative;
margin: 10px auto;
background: #fff;
border-radius: 5px;
font-size: 12px;
overflow: hidden;
}
.popup-title {
overflow: hidden;
line-height: 34px;
padding-top: 6px;
background: #f2f2f2;
}
.popup-result {
box-sizing: border-box;
line-height: 24px;
margin: 25px auto;
padding: 15px 10px 10px;
border: 1px solid #ccc;
position: relative;
}
.popup-result .title {
position: absolute;
top: -28px;
left: 50%;
width: 140px;
font-size: 14px;
margin-left: -70px;
text-align: center;
line-height: 30px;
background: #fff;
}
.popup-result table {
text-align: center;
width: 100%;
margin: 0 auto;
}
.popup-result table span {
display: block;
width: 100%;
font-family: arial;
line-height: 30px;
height: 30px;
white-space: nowrap;
overflow: hidden;
border: 1px solid #e8e8e8;
}
.popup-result-scroll {
font-size: 12px;
line-height: 24px;
height: 10em;
overflow-y: auto;
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
分钟允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次
</el-radio>
</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>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-min',
props: ['check', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'min', '*', 'min');
break;
case 2:
this.$emit('update', 'min', this.cycleTotal, 'min');
break;
case 3:
this.$emit('update', 'min', this.averageTotal, 'min');
break;
case 4:
this.$emit('update', 'min', this.checkboxString, 'min');
break;
}
},
//
cycleChange() {
if (this.radioValue == '2') {
this.$emit('update', 'min', this.cycleTotal, 'min');
}
},
//
averageChange() {
if (this.radioValue == '3') {
this.$emit('update', 'min', this.averageTotal, 'min');
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '4') {
this.$emit('update', 'min', this.checkboxString, 'min');
}
},
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange',
},
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>

View File

@ -0,0 +1,114 @@
<template>
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="1" :max="11" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 2" :max="12" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="1" :max="11" /> 月开始
<el-input-number v-model='average02' :min="1" :max="12 - average01 || 0" /> 月月执行一次
</el-radio>
</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 12" :key="item" :value="item">{{item}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 1,
average02: 1,
checkboxList: [],
checkNum: this.check
}
},
name: 'crontab-month',
props: ['check', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'month', '*');
break;
case 2:
this.$emit('update', 'month', this.cycleTotal);
break;
case 3:
this.$emit('update', 'month', this.averageTotal);
break;
case 4:
this.$emit('update', 'month', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue == '2') {
this.$emit('update', 'month', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '3') {
this.$emit('update', 'month', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '4') {
this.$emit('update', 'month', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 1, 11)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 2, 12)
return cycle01 + '-' + cycle02;
},
//
averageTotal: function () {
const average01 = this.checkNum(this.average01, 1, 11)
const average02 = this.checkNum(this.average02, 1, 12 - average01 || 0)
return average01 + '/' + average02;
},
// checkbox
checkboxString: function () {
let str = this.checkboxList.join();
return str == '' ? '*' : str;
}
}
}
</script>

View File

@ -0,0 +1,559 @@
<template>
<div class="popup-result">
<p class="title">最近5次运行时间</p>
<ul class="popup-result-scroll">
<template v-if='isShow'>
<li v-for='item in resultList' :key="item">{{item}}</li>
</template>
<li v-else>...</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
dayRule: '',
dayRuleSup: '',
dateArr: [],
resultList: [],
isShow: false
}
},
name: 'crontab-result',
methods: {
//
expressionChange() {
// -
this.isShow = false;
// [0123456]
let ruleArr = this.$options.propsData.ex.split(' ');
//
let nums = 0;
//
let resultArr = [];
// []
let nTime = new Date();
let nYear = nTime.getFullYear();
let nMonth = nTime.getMonth() + 1;
let nDay = nTime.getDate();
let nHour = nTime.getHours();
let nMin = nTime.getMinutes();
let nSecond = nTime.getSeconds();
// 100
this.getSecondArr(ruleArr[0]);
this.getMinArr(ruleArr[1]);
this.getHourArr(ruleArr[2]);
this.getDayArr(ruleArr[3]);
this.getMonthArr(ruleArr[4]);
this.getWeekArr(ruleArr[5]);
this.getYearArr(ruleArr[6], nYear);
// -便使
let sDate = this.dateArr[0];
let mDate = this.dateArr[1];
let hDate = this.dateArr[2];
let DDate = this.dateArr[3];
let MDate = this.dateArr[4];
let YDate = this.dateArr[5];
//
let sIdx = this.getIndex(sDate, nSecond);
let mIdx = this.getIndex(mDate, nMin);
let hIdx = this.getIndex(hDate, nHour);
let DIdx = this.getIndex(DDate, nDay);
let MIdx = this.getIndex(MDate, nMonth);
let YIdx = this.getIndex(YDate, nYear);
// ()
const resetSecond = function () {
sIdx = 0;
nSecond = sDate[sIdx]
}
const resetMin = function () {
mIdx = 0;
nMin = mDate[mIdx]
resetSecond();
}
const resetHour = function () {
hIdx = 0;
nHour = hDate[hIdx]
resetMin();
}
const resetDay = function () {
DIdx = 0;
nDay = DDate[DIdx]
resetHour();
}
const resetMonth = function () {
MIdx = 0;
nMonth = MDate[MIdx]
resetDay();
}
//
if (nYear !== YDate[YIdx]) {
resetMonth();
}
//
if (nMonth !== MDate[MIdx]) {
resetDay();
}
//
if (nDay !== DDate[DIdx]) {
resetHour();
}
//
if (nHour !== hDate[hIdx]) {
resetMin();
}
//
if (nMin !== mDate[mIdx]) {
resetSecond();
}
//
goYear: for (let Yi = YIdx; Yi < YDate.length; Yi++) {
let YY = YDate[Yi];
//
if (nMonth > MDate[MDate.length - 1]) {
resetMonth();
continue;
}
//
goMonth: for (let Mi = MIdx; Mi < MDate.length; Mi++) {
// 便
let MM = MDate[Mi];
MM = MM < 10 ? '0' + MM : MM;
//
if (nDay > DDate[DDate.length - 1]) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue;
}
//
goDay: for (let Di = DIdx; Di < DDate.length; Di++) {
// 便
let DD = DDate[Di];
let thisDD = DD < 10 ? '0' + DD : DD;
//
if (nHour > hDate[hDate.length - 1]) {
resetHour();
if (Di == DDate.length - 1) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue;
}
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true && this.dayRule !== 'workDay' && this.dayRule !== 'lastWeek' && this.dayRule !== 'lastDay') {
resetDay();
continue goMonth;
}
//
if (this.dayRule == 'lastDay') {
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
} else if (this.dayRule == 'workDay') {
// 230
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
// X
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
//
if (thisWeek == 1) {
//
DD++;
thisDD = DD < 10 ? '0' + DD : DD;
//
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD -= 3;
}
} else if (thisWeek == 7) {
// 61
if (this.dayRuleSup !== 1) {
DD--;
} else {
DD += 2;
}
}
} else if (this.dayRule == 'weekDay') {
//
//
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
// dayRuleSup
if (this.dayRuleSup.indexOf(thisWeek) < 0) {
//
if (Di == DDate.length - 1) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue;
}
} else if (this.dayRule == 'assWeek') {
//
// 1
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + DD + ' 00:00:00'), 'week');
if (this.dayRuleSup[1] >= thisWeek) {
DD = (this.dayRuleSup[0] - 1) * 7 + this.dayRuleSup[1] - thisWeek + 1;
} else {
DD = this.dayRuleSup[0] * 7 + this.dayRuleSup[1] - thisWeek + 1;
}
} else if (this.dayRule == 'lastWeek') {
//
// 230
if (this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
while (DD > 0 && this.checkDate(YY + '-' + MM + '-' + thisDD + ' 00:00:00') !== true) {
DD--;
thisDD = DD < 10 ? '0' + DD : DD;
}
}
//
let thisWeek = this.formatDate(new Date(YY + '-' + MM + '-' + thisDD + ' 00:00:00'), 'week');
//
if (this.dayRuleSup < thisWeek) {
DD -= thisWeek - this.dayRuleSup;
} else if (this.dayRuleSup > thisWeek) {
DD -= 7 - (this.dayRuleSup - thisWeek)
}
}
// 1005
DD = DD < 10 ? '0' + DD : DD;
//
goHour: for (let hi = hIdx; hi < hDate.length; hi++) {
let hh = hDate[hi] < 10 ? '0' + hDate[hi] : hDate[hi]
//
if (nMin > mDate[mDate.length - 1]) {
resetMin();
if (hi == hDate.length - 1) {
resetHour();
if (Di == DDate.length - 1) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue;
}
// ""
goMin: for (let mi = mIdx; mi < mDate.length; mi++) {
let mm = mDate[mi] < 10 ? '0' + mDate[mi] : mDate[mi];
//
if (nSecond > sDate[sDate.length - 1]) {
resetSecond();
if (mi == mDate.length - 1) {
resetMin();
if (hi == hDate.length - 1) {
resetHour();
if (Di == DDate.length - 1) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue goHour;
}
continue;
}
// ""
goSecond: for (let si = sIdx; si <= sDate.length - 1; si++) {
let ss = sDate[si] < 10 ? '0' + sDate[si] : sDate[si];
//
if (MM !== '00' && DD !== '00') {
resultArr.push(YY + '-' + MM + '-' + DD + ' ' + hh + ':' + mm + ':' + ss)
nums++;
}
// 退
if (nums == 5) break goYear;
//
if (si == sDate.length - 1) {
resetSecond();
if (mi == mDate.length - 1) {
resetMin();
if (hi == hDate.length - 1) {
resetHour();
if (Di == DDate.length - 1) {
resetDay();
if (Mi == MDate.length - 1) {
resetMonth();
continue goYear;
}
continue goMonth;
}
continue goDay;
}
continue goHour;
}
continue goMin;
}
} //goSecond
} //goMin
}//goHour
}//goDay
}//goMonth
}
// 100
if (resultArr.length == 0) {
this.resultList = ['没有达到条件的结果!'];
} else {
this.resultList = resultArr;
if (resultArr.length !== 5) {
this.resultList.push('最近100年内只有上面' + resultArr.length + '条结果!')
}
}
// -
this.isShow = true;
},
//
getIndex(arr, value) {
if (value <= arr[0] || value > arr[arr.length - 1]) {
return 0;
} else {
for (let i = 0; i < arr.length - 1; i++) {
if (value > arr[i] && value <= arr[i + 1]) {
return i + 1;
}
}
}
},
// ""
getYearArr(rule, year) {
this.dateArr[5] = this.getOrderArr(year, year + 100);
if (rule !== undefined) {
if (rule.indexOf('-') >= 0) {
this.dateArr[5] = this.getCycleArr(rule, year + 100, false)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[5] = this.getAverageArr(rule, year + 100)
} else if (rule !== '*') {
this.dateArr[5] = this.getAssignArr(rule)
}
}
},
// ""
getMonthArr(rule) {
this.dateArr[4] = this.getOrderArr(1, 12);
if (rule.indexOf('-') >= 0) {
this.dateArr[4] = this.getCycleArr(rule, 12, false)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[4] = this.getAverageArr(rule, 12)
} else if (rule !== '*') {
this.dateArr[4] = this.getAssignArr(rule)
}
},
// ""-
getWeekArr(rule) {
//
if (this.dayRule == '' && this.dayRuleSup == '') {
if (rule.indexOf('-') >= 0) {
this.dayRule = 'weekDay';
this.dayRuleSup = this.getCycleArr(rule, 7, false)
} else if (rule.indexOf('#') >= 0) {
this.dayRule = 'assWeek';
let matchRule = rule.match(/[0-9]{1}/g);
this.dayRuleSup = [Number(matchRule[1]), Number(matchRule[0])];
this.dateArr[3] = [1];
if (this.dayRuleSup[1] == 7) {
this.dayRuleSup[1] = 0;
}
} else if (rule.indexOf('L') >= 0) {
this.dayRule = 'lastWeek';
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);
this.dateArr[3] = [31];
if (this.dayRuleSup == 7) {
this.dayRuleSup = 0;
}
} else if (rule !== '*' && rule !== '?') {
this.dayRule = 'weekDay';
this.dayRuleSup = this.getAssignArr(rule)
}
}
},
// ""-
getDayArr(rule) {
this.dateArr[3] = this.getOrderArr(1, 31);
this.dayRule = '';
this.dayRuleSup = '';
if (rule.indexOf('-') >= 0) {
this.dateArr[3] = this.getCycleArr(rule, 31, false)
this.dayRuleSup = 'null';
} else if (rule.indexOf('/') >= 0) {
this.dateArr[3] = this.getAverageArr(rule, 31)
this.dayRuleSup = 'null';
} else if (rule.indexOf('W') >= 0) {
this.dayRule = 'workDay';
this.dayRuleSup = Number(rule.match(/[0-9]{1,2}/g)[0]);
this.dateArr[3] = [this.dayRuleSup];
} else if (rule.indexOf('L') >= 0) {
this.dayRule = 'lastDay';
this.dayRuleSup = 'null';
this.dateArr[3] = [31];
} else if (rule !== '*' && rule !== '?') {
this.dateArr[3] = this.getAssignArr(rule)
this.dayRuleSup = 'null';
} else if (rule == '*') {
this.dayRuleSup = 'null';
}
},
// ""
getHourArr(rule) {
this.dateArr[2] = this.getOrderArr(0, 23);
if (rule.indexOf('-') >= 0) {
this.dateArr[2] = this.getCycleArr(rule, 24, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[2] = this.getAverageArr(rule, 23)
} else if (rule !== '*') {
this.dateArr[2] = this.getAssignArr(rule)
}
},
// ""
getMinArr(rule) {
this.dateArr[1] = this.getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) {
this.dateArr[1] = this.getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[1] = this.getAverageArr(rule, 59)
} else if (rule !== '*') {
this.dateArr[1] = this.getAssignArr(rule)
}
},
// ""
getSecondArr(rule) {
this.dateArr[0] = this.getOrderArr(0, 59);
if (rule.indexOf('-') >= 0) {
this.dateArr[0] = this.getCycleArr(rule, 60, true)
} else if (rule.indexOf('/') >= 0) {
this.dateArr[0] = this.getAverageArr(rule, 59)
} else if (rule !== '*') {
this.dateArr[0] = this.getAssignArr(rule)
}
},
// min-max
getOrderArr(min, max) {
let arr = [];
for (let i = min; i <= max; i++) {
arr.push(i);
}
return arr;
},
//
getAssignArr(rule) {
let arr = [];
let assiginArr = rule.split(',');
for (let i = 0; i < assiginArr.length; i++) {
arr[i] = Number(assiginArr[i])
}
arr.sort(this.compare)
return arr;
},
//
getAverageArr(rule, limit) {
let arr = [];
let agArr = rule.split('/');
let min = Number(agArr[0]);
let step = Number(agArr[1]);
while (min <= limit) {
arr.push(min);
min += step;
}
return arr;
},
//
getCycleArr(rule, limit, status) {
// status--01
let arr = [];
let cycleArr = rule.split('-');
let min = Number(cycleArr[0]);
let max = Number(cycleArr[1]);
if (min > max) {
max += limit;
}
for (let i = min; i <= max; i++) {
let add = 0;
if (status == false && i % limit == 0) {
add = limit;
}
arr.push(Math.round(i % limit + add))
}
arr.sort(this.compare)
return arr;
},
// Array.sort
compare(value1, value2) {
if (value2 - value1 > 0) {
return -1;
} else {
return 1;
}
},
// 2017-9-19 18:04:33
formatDate(value, type) {
//
let time = typeof value == 'number' ? new Date(value) : value;
let Y = time.getFullYear();
let M = time.getMonth() + 1;
let D = time.getDate();
let h = time.getHours();
let m = time.getMinutes();
let s = time.getSeconds();
let week = time.getDay();
// type
if (type == undefined) {
return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D) + ' ' + (h < 10 ? '0' + h : h) + ':' + (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s);
} else if (type == 'week') {
// quartz 1
return week + 1;
}
},
//
checkDate(value) {
let time = new Date(value);
let format = this.formatDate(time)
return value === format;
}
},
watch: {
'ex': 'expressionChange'
},
props: ['ex'],
mounted: function () {
//
this.expressionChange();
}
}
</script>

View File

@ -0,0 +1,117 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" />
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 秒开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 秒执行一次
</el-radio>
</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>
</template>
<script>
export default {
data() {
return {
radioValue: 1,
cycle01: 1,
cycle02: 2,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-second',
props: ['check', 'radioParent'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'second', '*', 'second');
break;
case 2:
this.$emit('update', 'second', this.cycleTotal);
break;
case 3:
this.$emit('update', 'second', this.averageTotal);
break;
case 4:
this.$emit('update', 'second', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue == '2') {
this.$emit('update', 'second', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '3') {
this.$emit('update', 'second', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '4') {
this.$emit('update', 'second', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange',
radioParent() {
this.radioValue = this.radioParent
}
},
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>

View File

@ -0,0 +1,202 @@
<template>
<el-form size='small'>
<el-form-item>
<el-radio v-model='radioValue' :label="1">
允许的通配符[, - * ? / L #]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="2">
不指定
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="3">
周期从星期
<el-select clearable v-model="cycle01">
<el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key === 1"
>{{item.value}}</el-option>
</el-select>
-
<el-select clearable v-model="cycle02">
<el-option
v-for="(item,index) of weekList"
:key="index"
:label="item.value"
:value="item.key"
:disabled="item.key < cycle01 && item.key !== 1"
>{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="4">
<el-input-number v-model='average01' :min="1" :max="4" /> 周的星期
<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-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="5">
本月最后一个星期
<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-select>
</el-radio>
</el-form-item>
<el-form-item>
<el-radio v-model='radioValue' :label="6">
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="(item,index) of weekList" :key="index" :label="item.value" :value="String(item.key)">{{item.value}}</el-option>
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
radioValue: 2,
weekday: 2,
cycle01: 2,
cycle02: 3,
average01: 1,
average02: 2,
checkboxList: [],
weekList: [
{
key: 2,
value: '星期一'
},
{
key: 3,
value: '星期二'
},
{
key: 4,
value: '星期三'
},
{
key: 5,
value: '星期四'
},
{
key: 6,
value: '星期五'
},
{
key: 7,
value: '星期六'
},
{
key: 1,
value: '星期日'
}
],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-week',
props: ['check', 'cron'],
methods: {
//
radioChange() {
if (this.radioValue !== 2 && this.cron.day !== '?') {
this.$emit('update', 'day', '?', 'week');
}
switch (this.radioValue) {
case 1:
this.$emit('update', 'week', '*');
break;
case 2:
this.$emit('update', 'week', '?');
break;
case 3:
this.$emit('update', 'week', this.cycleTotal);
break;
case 4:
this.$emit('update', 'week', this.averageTotal);
break;
case 5:
this.$emit('update', 'week', this.weekdayCheck + 'L');
break;
case 6:
this.$emit('update', 'week', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue == '3') {
this.$emit('update', 'week', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '4') {
this.$emit('update', 'week', this.averageTotal);
}
},
//
weekdayChange() {
if (this.radioValue == '5') {
this.$emit('update', 'week', this.weekday + 'L');
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '6') {
this.$emit('update', 'week', this.checkboxString);
}
},
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'weekdayCheck': 'weekdayChange',
'checkboxString': 'checkboxChange',
},
computed: {
//
cycleTotal: function () {
this.cycle01 = this.checkNum(this.cycle01, 1, 7)
this.cycle02 = this.checkNum(this.cycle02, 1, 7)
return this.cycle01 + '-' + this.cycle02;
},
//
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>

View File

@ -0,0 +1,131 @@
<template>
<el-form size="small">
<el-form-item>
<el-radio :label="1" v-model='radioValue'>
不填允许的通配符[, - * /]
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="2" v-model='radioValue'>
每年
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="3" v-model='radioValue'>
周期从
<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-radio>
</el-form-item>
<el-form-item>
<el-radio :label="4" v-model='radioValue'>
<el-input-number v-model='average01' :min='fullYear' :max="2098"/> 年开始
<el-input-number v-model='average02' :min="1" :max="2099 - average01 || fullYear" /> 年执行一次
</el-radio>
</el-form-item>
<el-form-item>
<el-radio :label="5" v-model='radioValue'>
指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple>
<el-option v-for="item in 9" :key="item" :value="item - 1 + fullYear" :label="item -1 + fullYear" />
</el-select>
</el-radio>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
return {
fullYear: 0,
radioValue: 1,
cycle01: 0,
cycle02: 0,
average01: 0,
average02: 1,
checkboxList: [],
checkNum: this.$options.propsData.check
}
},
name: 'crontab-year',
props: ['check', 'month', 'cron'],
methods: {
//
radioChange() {
switch (this.radioValue) {
case 1:
this.$emit('update', 'year', '');
break;
case 2:
this.$emit('update', 'year', '*');
break;
case 3:
this.$emit('update', 'year', this.cycleTotal);
break;
case 4:
this.$emit('update', 'year', this.averageTotal);
break;
case 5:
this.$emit('update', 'year', this.checkboxString);
break;
}
},
//
cycleChange() {
if (this.radioValue == '3') {
this.$emit('update', 'year', this.cycleTotal);
}
},
//
averageChange() {
if (this.radioValue == '4') {
this.$emit('update', 'year', this.averageTotal);
}
},
// checkbox
checkboxChange() {
if (this.radioValue == '5') {
this.$emit('update', 'year', this.checkboxString);
}
}
},
watch: {
'radioValue': 'radioChange',
'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange'
},
computed: {
//
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
}
}
</script>

View File

@ -0,0 +1,144 @@
<template>
<div class="custom-table" ref="tableContainer">
<el-table
:data="tableData"
:height="tableHeight"
v-bind="$attrs"
@selection-change="handleSelectionChange"
>
<el-table-column
v-if="showSelection"
type="selection"
width="55"
/>
<el-table-column
v-if="showIndex"
type="index"
width="50"
label="序号"
/>
<template v-for="(column, index) in columns" :key="index">
<el-table-column
v-bind="column"
:prop="column.prop"
:label="column.label"
>
<template #default="scope">
<slot :name="column.prop" :row="scope.row">
{{ scope.row[column.prop] }}
</slot>
</template>
</el-table-column>
</template>
</el-table>
<div class="pagination-container">
<el-pagination
v-if="showPagination"
:current-page="currentPage"
:page-size="pageSize"
:total="total"
:page-sizes="pageSizes"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
import useTableResize from '@/hooks/useTableResize'
const props = defineProps({
columns: {
type: Array,
required: true
},
tableData: {
type: Array,
default: () => []
},
showSelection: {
type: Boolean,
default: false
},
showIndex: {
type: Boolean,
default: false
},
showPagination: {
type: Boolean,
default: true
},
total: {
type: Number,
default: 0
},
pageSizes: {
type: Array,
default: () => [10, 20, 30, 50]
},
tableHeight: {
type: Number,
default: 400
}
})
const emit = defineEmits(['selection-change', 'size-change', 'current-change'])
const currentPage = ref(1)
const pageSize = ref(10)
const tableContainer = ref(null)
const tableHeight = computed(() => props.tableHeight)
const { w: tableWidth } = useTableResize()
const handleSelectionChange = (selection) => {
emit('selection-change', selection)
}
const handleSizeChange = (val) => {
pageSize.value = val
emit('size-change', val)
}
const handleCurrentChange = (val) => {
currentPage.value = val
emit('current-change', val)
}
const updateTableHeight = () => {
nextTick(() => {
if (tableContainer.value) {
const containerRect = tableContainer.value.getBoundingClientRect()
const windowHeight = window.innerHeight
const containerTop = containerRect.top
const paginationHeight = props.showPagination ? 32 : 0 //
tableHeight.value = windowHeight - containerTop - paginationHeight
}
})
}
onMounted(() => {
updateTableHeight()
window.addEventListener('resize', updateTableHeight)
})
onUnmounted(() => {
window.removeEventListener('resize', updateTableHeight)
})
</script>
<style scoped>
.custom-table {
width: 100%;
display: flex;
flex-direction: column;
}
.pagination-container {
margin-top: 10px;
display: flex;
justify-content: flex-end;
}
</style>

View File

@ -0,0 +1,14 @@
/*
* 不带插槽按钮点击回弹
*/
export default {
mounted(el) {
el.addEventListener('click', (e) => {
if (e.target.nodeName === 'SPAN') {
e.target.parentNode.blur()
}
e.target.blur()
})
}
}

View File

@ -0,0 +1,62 @@
/*
* @FilePath : /src/directive/buttonPermission.js
* @Description : 按钮权限
*/
const hasPermission = value => {
let sessionPermissions = JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('permissions')))
const flag = sessionPermissions.includes(value)
return flag
}
// removeDom、addDom的存在是为了保证自定义指令绑定的权限码数据变化后可以不刷新页面动态添加/移除自定义指令所在的dom节点
// 移除dom节点
const removeDom = el => {
// 把注释点绑在元素上,方便后面使用
el._placeholderNode = document.createComment('permission-btn')
// 和父节点关联上,方便后面使用
el._parentNode = el.parentNode
// 父节点做子元素替换
el.parentNode.replaceChild(el._placeholderNode, el)
}
// 把移除的dom节点添加回来
const addDom = el => {
el._parentNode?.replaceChild(el, el._placeholderNode)
}
const mounted = (el, binding) => {
// console.log(binding, 'binding')
if (!binding.value) return
if (!hasPermission(binding.value)) {
// 移除当前dom节点
// el.parentNode?.removeChild(el) // 通过父节点删除子自己
// el.remove() // 自杀
/*
权限值会变那就涉及到更新后要不要将移除的dom节点添加回来
为了能够知道添加回来的dom节点放到哪个地方可以创建注释节点来占位
在添加时只对那个位置的子元素做替换操作即可
*/
removeDom(el)
}
}
const updated = (el, binding) => {
// 比对前后变化的值,相同不需要后续操作
let valDiff = binding.value === binding.oldValue
if (valDiff) return
// 重新判断改值前后的权限变化,相同不需要后续操作(如:修改前就没权限,修改后依旧没权限,无需操作)
let oldPermisStatus = hasPermission(binding.oldValue)
let nowPermisStatus = hasPermission(binding.value)
if (oldPermisStatus == nowPermisStatus) return
if (nowPermisStatus) {
addDom(el)
} else {
removeDom(el)
}
}
export const buttonPermission = {
mounted,
updated,
}

View File

@ -0,0 +1,11 @@
/*
* @FilePath : /src/directive/index.js
* @Description :
*/
import blur from './blur'
import { buttonPermission } from './buttonPermission'
export default function (app) {
app.directive('blur', blur)
app.directive('auth', buttonPermission)
}

View File

@ -0,0 +1,50 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-13 10:40:30
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2023-11-16 16:03:22
* @Description : table serch
*/
import { reactive, ref } from 'vue'
const useTablePgSearch = (searchApi, pageSize, defaultData) => {
const tableList = ref([])
const searchForm = ref({})
if (defaultData) searchForm.value = JSON.parse(JSON.stringify(defaultData))
const page = reactive({
currentPage: 1,
pageSize: pageSize || 10,
total: 0,
})
const initTable = () => {
page.pageSize = pageSize || 10
page.currentPage = 1
search()
}
const resetSearch = () => {
page.pageSize = pageSize || 10
page.currentPage = 1
searchForm.value = (defaultData && JSON.parse(JSON.stringify(defaultData))) || {}
search()
}
const handleCurrentChange = val => {
page.currentPage = val
search()
}
const handleSizeChange = val => {
page.pageSize = val
search()
}
const search = searchApi
return {
searchForm,
page,
tableList,
search,
resetSearch,
initTable,
handleCurrentChange,
handleSizeChange,
}
}
export default useTablePgSearch

View File

@ -0,0 +1,34 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-13 10:40:41
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2023-06-13 10:40:45
* @Description : table尺寸 Resize
*/
import { onMounted, onUnmounted, ref } from 'vue'
//导出hooks函数
const useTableResize = (width = 0) => {
const w = ref('')
//计算宽度函数
const calcTableWidth = () => {
w.value = document.body.clientWidth - 280 - width
}
//挂载后
onMounted(() => {
//初始化计算一次
calcTableWidth()
//resisze计算
window.addEventListener('resize', calcTableWidth)
})
//销毁后
onUnmounted(() => {
window.removeEventListener('resize', calcTableWidth)
})
//导出外面要用的响应式数据
return {
w,
}
}
export default useTableResize

View File

@ -0,0 +1,306 @@
<template>
<el-container class="pc-home">
<!-- 头部导航 -->
<el-header class="pc-header flex-row">
<div class="df aic jcc" style="gap: 0.1rem; height: 0.6rem; white-space: nowrap">
<img referrerpolicy="no-referrer" src="@/assets/imgs/logo.png" style="height: 30px" />
<div style="color: #ffffff; font-size: 0.16rem; font-weight: bolder">PMS:新光线平台</div>
</div>
<div class="df aic" style="gap: 10px">
<el-dropdown trigger="click" style="cursor: pointer">
<span class="el-dropdown-link">
<img class="avatar-wrap" src="@/assets/imgs/touxiang.jpg" alt="" />
{{ userInfo?.userName }}
<el-icon class="ml10">
<arrow-down />
</el-icon>
</span>
<template #dropdown>
<el-dropdown-menu>
<!-- <el-dropdown-item @click="dialogVisible = true">更改密码</el-dropdown-item> -->
<el-dropdown-item @click="outLogin">退</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<RouteTab></RouteTab>
<el-container>
<el-aside :style="{ width: isCollapse ? '64px' : '200px' }"><Aside></Aside></el-aside>
<!-- 主体部分 -->
<div class="pc-main flex-row">
<div class="page-conatiner">
<div class="mainpart">
<MainContent></MainContent>
</div>
</div>
</div>
</el-container>
</el-container>
<editPassWordCom v-model:visible="dialogVisible" @confirm="editPassWord" />
</template>
<script setup>
import { ArrowDown } from '@element-plus/icons-vue'
import { ref, onMounted, watch, nextTick } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import editPassWordCom from './components/editPassWordCom.vue'
import { useMenu } from '@/layout/hook/hook.aside.js'
import Aside from './components/Aside.vue'
import MainContent from './components/MainContent.vue'
import RouteTab from './components/routeTab.vue'
import { loginApi } from '@/utils/api'
import { removeToken } from '@/utils/auth'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
const route = useRoute()
const router = useRouter()
const userStore = useUserStore()
const isCollapse = ref(false)
// hook使
useMenu(isCollapse)
const userInfo = ref(
sessionStorage.getItem('userInfo') !== 'undefined' && sessionStorage.getItem('userInfo')
? JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('userInfo')))
: {},
)
const outLogin = async () => {
await userStore.logOut()
}
const dialogVisible = ref(false)
const editPassWord = async data => {
const params = {
newPassword: data.newPassword,
password: data.originalPassword,
id: userInfo.value.id,
}
await loginApi.editPassword(params)
ElMessage.success({ message: '修改密码成功' })
dialogVisible.value = false
removeToken()
router.push({ path: '/login' })
}
let isFull = false
//
//
window.onresize = () => {
//
const clientHeight = document.documentElement.clientHeight || document.body.clientHeight
// screenwindowwindow.screenwindow
isFull = screen.height == clientHeight
}
// f11,
window.onkeydown = function (event) {
if (event.keyCode === 122) {
event.preventDefault()
event.returnValue = false
}
}
//退
const exitFullscreen = () => {
if (document.exitFullScreen) {
document.exitFullScreen()
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen()
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen()
} else if (document.msExitFullscreen) {
document.msExitFullscreen()
}
}
//
const requestFullScreen = () => {
let element = document.getElementById('app')
let requestMethod =
element.requestFullScreen || //w3c
element.webkitRequestFullScreen || // Chrome
element.mozRequestFullScreen || // FireFox
element.msRequestFullScreen // IE11
if (requestMethod) {
requestMethod.call(element)
}
}
// forceUpdate
onMounted(() => {
haddleBreadcrumbArr()
})
watch(
() => route.path,
() => {
haddleBreadcrumbArr()
},
)
let breadcrumbArr = ref([])
const haddleBreadcrumbArr = () => {
let routeArr = [...route.matched]
routeArr.shift()
let arr = []
for (let item of routeArr) {
if (item.meta.breadcrumb?.length > 0) {
arr = arr.concat(item.meta.breadcrumb)
}
arr.push({
name: item.name,
title: item.meta.title,
})
}
breadcrumbArr.value = arr
}
//
const messageCount = ref(0)
</script>
<style lang="scss" scoped>
// @function calcHeight($height) {
// @return ($height / 1080) * 100vh;
// }
.pc-home {
display: flex;
background-color: #f0f2f5;
width: 100vw;
.pc-header {
@include flex-row;
height: 0.6rem;
background: #242a2f;
// background-color: #4684e2;
color: #ffffff;
justify-content: space-between;
align-items: center;
gap: 0.2rem;
background-size: 100% 100%;
.el-dropdown-link {
font-size: 0.16rem;
color: #999;
display: flex;
justify-content: center;
align-items: center;
.avatar-wrap {
width: 0.33rem;
height: 0.33rem;
background-color: #fff;
// background-color: red;
// padding: 3px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
box-sizing: border-box;
border-radius: 50%;
img {
display: block;
width: 0.3rem;
height: 0.3rem;
border-radius: 50%;
}
margin-right: 0.1rem;
}
// margin-top: -0.3rem;
// margin-right: 0.3rem;
}
}
// :deep(.el-header) {
// --el-header-height: calcHeight(100);
// }
.pc-main {
height: 9.9rem;
flex: 1 1 auto;
background-color: #f0f2f5;
min-width: 0;
// .pc-menu {
// height: 100%;
// flex: 0 0 calcWidth(260);
// min-width: 260px;
// }
.mainpart {
// flex: 1;
width: 100%;
@include flex-row-vc;
gap: 5px;
padding: 10px;
// .main-banner{
// flex: 1;
// height: 100%;
// background: #f60;
// }
}
.page-conatiner {
// height: 100%;
flex: 1 1 auto;
display: flex;
}
}
}
.right-box {
display: flex;
margin-left: auto;
justify-content: flex-end;
gap: 0.1rem;
align-items: center;
// position: absolute;
// right: 0;
// bottom: 10px;
}
:deep(.homePageDialog) {
.el-dialog__body {
padding-top: 0;
overflow-y: hidden;
height: 30vh;
}
.el-dialog__header {
border-bottom: 1px solid #d8d8d8;
height: 38px;
font-weight: bold;
font-size: 14px;
line-height: 38px;
padding: 0;
padding-left: 16px;
text-align: left;
.el-dialog__title {
color: #333333;
font-size: 14px !important;
}
}
}
:deep(.el-tabs) {
height: 100%;
.el-tabs__content {
overflow: auto;
height: 100%;
}
}
.messageBox {
padding: 0 10px;
.msgItem {
display: flex;
gap: 5px;
flex-direction: column;
margin-bottom: 10px;
}
.time {
color: #c1c1c1;
font-size: 12px;
}
.msgContent {
line-height: 24px;
}
.doc {
background-color: rgb(221, 37, 42);
display: inline-block;
height: 20px;
margin-right: 5px;
height: 5px;
width: 5px;
border-radius: 50%;
}
}
</style>

View File

@ -0,0 +1,138 @@
<!--
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-12 15:28:46
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2024-04-16 11:38:29
* @Description : 侧边栏
-->
<template>
<aside :style="{ width: isCollapse ? '64px' : '200px' }">
<el-menu
:default-active="activeIndex"
router
unique-opened
class="el-menu-vertical-demo"
:collapse="isCollapse"
background-color="#30383e"
text-color="#ccc"
active-text-color="#ccc"
>
<template v-for="item in asyncRoutes">
<el-menu-item
v-if="
item.children?.length <= 1 && item.meta?.singleShow && !item.meta?.menuHide && item.meta?.type != 'subMenu'
"
:index="item.path"
>
<img
class="mr10"
:src="
imgSrc == item.path
? getAssetsFile(item.meta.imgSrc + '-w.png')
: getAssetsFile(item.meta.imgSrc + '.png')
"
/>
<div class="item-title">{{ item.meta.title }}</div>
</el-menu-item>
<el-sub-menu v-else-if="item.meta && !item.meta?.menuHide" :index="item.path">
<template #title>
<img
class="mr10"
:src="
imgSrc == item.path
? getAssetsFile(item.meta.imgSrc + '-w.png')
: getAssetsFile(item.meta.imgSrc + '.png')
"
/>
<span class="item-title">{{ item.meta.title }}</span>
</template>
<template v-for="subItem in item.children">
<el-menu-item
class="item-title"
v-if="!subItem.meta?.menuHide && subItem.meta?.type != 'subMenu'"
:index="subItem.path"
>
{{ subItem.meta.title }}
</el-menu-item>
<SubMenu v-else-if="subItem.meta?.type == 'subMenu'" :submenuData="subItem"></SubMenu>
</template>
</el-sub-menu>
</template>
</el-menu>
</aside>
</template>
<script setup>
import { ref, watchEffect } from 'vue'
import { DocumentRemove, Menu as IconMenu, Notebook, Setting } from '@element-plus/icons-vue'
import { useMenu } from '@/layout/hook/hook.aside.js'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user.js'
import { storeToRefs } from 'pinia'
import usePub from '@/utils/pub-use.js'
import SubMenu from './subMenu.vue'
const $router = useRouter()
const $route = useRoute()
const userStore = useUserStore()
let { asyncRoutes } = storeToRefs(userStore)
const getAssetsFile = usePub.getAssetsFile
const imgSrc = ref('')
if (!asyncRoutes.value.length) {
asyncRoutes.value = JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('asyncRoutes')))
}
const activeIndex = ref()
watchEffect(() => {
if (!$route.matched.length) return
// imgSrc.value = $route.path//
activeIndex.value = $route.path
// console.log(activeIndex.value, 'watchEffect')
})
const isCollapse = ref(false)
// hook使
useMenu(isCollapse)
</script>
<style lang="scss" scoped>
aside {
width: 200px;
height: 100vh;
background: #30383e;
transition: all 0.5;
overflow-y: auto;
}
.mr10 {
margin-right: 10px;
}
.el-menu {
border: none;
background: #30383e;
.item-title {
white-space: normal;
line-height: 14px;
font-size: 14px;
}
.is-active {
background-color: darken(#5584ff, 10%);
:deep(.el-sub-menu__title) {
color: #fff !important;
background-color: darken(#1d2429, 10%) !important;
}
}
}
.el-menu--inline {
.el-menu-item {
&:hover {
background: darken(#1d2429, 10%);
}
}
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<main>
<!-- <router-view /> -->
<router-view v-slot="{ Component }">
<Transition mode="out-in">
<keep-alive>
<component :is="Component" />
</keep-alive>
</Transition>
</router-view>
</main>
</template>
<script setup></script>
<style lang="scss" scoped>
main {
flex: 1;
height: 100%;
overflow-y: auto;
border: none;
}
.v-leave-from {
opacity: 1;
transform: translateX(0);
}
.v-leave-active {
transition: all 0.5s;
}
.v-leave-to {
opacity: 0;
transform: translateX(10px);
}
.v-enter-from {
transform: translateX(-10px);
opacity: 0;
}
.v-enter-active {
transition: all 0.5s;
}
.v-enter-to {
opacity: 1;
transform: translateX(0);
}
</style>

View File

@ -0,0 +1,96 @@
<template>
<nav>
<div class="df aic jcpb" style="height: 100%">
<div class="bread-box">
<el-icon size="12px" color="#3a84fa">
<Location />
</el-icon>
当前位置:
</div>
<el-breadcrumb separator="/">
<el-breadcrumb-item v-for="item in list" :to="{ path: item.path }">{{ item.title }}</el-breadcrumb-item>
</el-breadcrumb>
<div class="right-box">
<!-- <div>
<span style="font-size: 22px; margin-right: 10px; font-weight: bold">总体风险等级</span>
<span style="font-size: 22px; font-weight: bold; color: #418cfe">I I I </span>
</div>
<div class="right-tag">
<el-tag class="mx-1" effect="dark" type="success">监控点考核</el-tag>
<el-tag class="mx-1" effect="dark" type="warning">人脸卡口考核</el-tag>
<el-tag class="mx-1" effect="dark" type="danger">车辆卡口考核</el-tag>
<el-tag class="mx-1" effect="dark" type="success">数据数量考核</el-tag>
<el-tag class="mx-1" effect="dark" type="warning">视频质量考核</el-tag>
</div> -->
</div>
</div>
</nav>
</template>
<script setup>
import { useRoute } from 'vue-router'
import { Location } from '@element-plus/icons-vue'
// //
import { ref, watch } from 'vue'
const route = useRoute()
const list = ref([])
// route
watch(
route,
() => {
// route.matchedlist
list.value = route.matched.map(item => ({
title: item.meta.title,
path: item.path,
}))
},
{
immediate: true,
},
)
</script>
<style lang="scss" scoped>
nav {
height: 36px;
line-height: 36px;
width: 100%;
position: relative;
}
.bread-box {
// position: absolute;
// left: 25px;
// bottom: -7px;
margin-right: 0.1rem;
font-size: 0.18rem;
font-weight: bold;
}
.el-breadcrumb {
// position: absolute;
// left: 100px;
// bottom: 5px;
font-size: 0.18rem;
font-weight: bold;
}
.right-box {
display: flex;
margin-left: auto;
justify-content: flex-end;
gap: 0.1rem;
// position: absolute;
// right: 0;
// bottom: 10px;
}
// .right-tag {
// position: absolute;
// right: 20px;
// transform: skewX(-20deg); /*X30*/
// }
.el-tag {
margin-right: 5px;
font-size: 14px;
}
</style>

View File

@ -0,0 +1,87 @@
<!--
* @Author: suhang suhang_max@163.com
* @Date: 2023-02-23 16:25:56
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-06-01 11:09:13
* @FilePath: \train-assess\src\layout\NavBar.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<el-menu
:default-active="activeIndex"
router
class="el-menu-demo"
active-text-color="#fff"
background-color="transparent"
text-color="#ADCFFF"
mode="horizontal"
>
<el-menu-item @click="goRedirect" v-for="item in temp" :index="item.meta?.path" class="navbar-item">
<img src="" class="item-img" />
<span class="item-title">{{ item.meta?.title }}</span>
</el-menu-item>
</el-menu>
</template>
<script setup>
import { reactive, ref, watch, getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { asyncRoutes } from '@/router/routes.js'
//
const $route = useRoute()
const $router = useRouter()
// console.log(asyncRoutes.value)
//
const imgSrc = ref($route.matched[0].path)
const activeIndex = ref($route.matched[0].path)
// const instance = getCurrentInstance();
const goRedirect = val => {
imgSrc.value = val.index
if ($route.matched[0].redirect == $route.path) {
const cast = new BroadcastChannel('backMain')
cast.postMessage('main')
}
}
</script>
<style lang="scss" scoped>
.navbar-item {
display: flex;
flex-direction: column;
align-items: center;
border-bottom: none;
padding: 0;
margin-right: cw(28);
.item-img {
width: calcHeight(40);
height: calcHeight(40);
margin-bottom: 9px;
}
.item-title {
font-size: 0.18rem;
// font-size: 18px;
line-height: 0.18rem;
}
}
.el-menu-demo {
border-bottom: none;
flex: 1;
justify-content: center;
}
.el-menu-item:hover,
.el-menu-item:focus {
outline: 0 !important;
color: #fff !important;
background: #3686ff !important;
}
.is-active {
border-bottom: none !important;
}
</style>

View File

@ -0,0 +1,150 @@
<template>
<div class="norm-form">
<el-dialog :model-value="visible" @close="close" @open="open" width="8rem" destroy-on-close center align-center>
<template #header>
<span class="ml10" style="font-size: 18px">修改密码</span>
</template>
<el-form
:model="formInfo"
ref="formInfoRef"
:rules="formInfoRules"
width="100%"
label-width="140px"
label-suffix=":"
>
<el-form-item label="原密码" prop="originalPassword">
<el-input
show-password
@input="clearK($event, 'originalPassword')"
maxlength="20"
v-model="formInfo.originalPassword"
:rows="3"
placeholder="请输入原密码"
resize="none"
/>
</el-form-item>
<el-form-item label="新密码" prop="newPassword">
<el-input
show-password
@input="clearK($event, 'newPassword')"
maxlength="20"
v-model="formInfo.newPassword"
:rows="3"
placeholder="请输入新密码"
resize="none"
/>
</el-form-item>
<el-form-item label="确认密码" prop="confirmPassword">
<el-input
show-password
@input="clearK($event, 'confirmPassword')"
maxlength="20"
v-model="formInfo.confirmPassword"
:rows="3"
placeholder="请输入确认密码"
resize="none"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button class="w90-h40" type="primary" @click="confirm(formInfoRef)" v-blur></el-button>
<el-button class="w90-h40" @click="cancel"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { commonCheck } from '@/utils/validate.js'
import { ElMessage } from 'element-plus'
const props = defineProps({
visible: Boolean,
})
const emits = defineEmits(['update:visible', 'confirm'])
const formInfo = ref({
originalPassword: '',
newPassword: '',
confirmPassword: '',
})
const formInfoRef = ref(null)
let formInfoRules = reactive({
originalPassword: [{ required: true, validator: commonCheck, message: '内容不能为空!', trigger: 'blur' }],
newPassword: [{ required: true, validator: commonCheck, trigger: 'blur' }],
confirmPassword: [{ required: true, validator: commonCheck, trigger: 'blur' }],
})
const open = async () => {
// formInfo.value = Object.assign(formInfo.value, props.detail.row)
}
const clearK = ($event, key) => {
formInfo.value[key] = $event.trim()
}
const close = () => {
formInfo.value = {}
formInfoRef.value.resetFields()
emits('update:visible', false)
}
const confirm = async EL => {
if (!EL) return
await EL.validate(valid => {
if (valid) {
if (formInfo.value.newPassword === formInfo.value.confirmPassword) {
const temp = { ...formInfo.value }
emits('confirm', temp)
// emits('update:visible', false)
} else {
ElMessage.warning('两次密码输入不一致')
}
}
})
}
const cancel = () => {
emits('update:visible', false)
}
defineExpose({
open,
close,
})
</script>
<style lang="scss" scoped>
.norm-form {
:deep(.el-dialog__header) {
display: flex;
align-items: center;
margin-right: 0;
padding-bottom: 13px;
border-bottom: 1px solid #d8d8d8;
}
.el-form-item {
margin-bottom: 20px;
.el-select {
width: 100%;
}
.el-radio {
width: 60px;
}
:deep(.el-input-number) {
width: 100%;
.el-input__inner {
text-align: left;
}
}
:deep(.el-autocomplete) {
width: 100%;
}
}
}
</style>

View File

@ -0,0 +1,116 @@
<template>
<div class="div_">
<el-scrollbar ref="scrollbar" :vertical="false" class="scrollbar_" @wheel.native.prevent="handleScroll">
<el-tag
size="large"
v-for="(item, index) in routerHistory"
:key="index"
:closable="true"
:type="nowRouter == item.path ? '' : 'info'"
@close="removeTag(item.path)"
@click="tagClick(item.path, index)"
>
{{ item.title }}
</el-tag>
</el-scrollbar>
</div>
</template>
<script setup>
import { storeToRefs } from 'pinia'
import { ref, watch, onMounted, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { useUserStore } from '@/stores/user.js'
const userStore = useUserStore() //store
const { routerHistory } = storeToRefs(userStore) //
const { nowRouter } = storeToRefs(userStore) //
const route = useRoute() //
const router = useRouter() //
const scrollbar = ref(null)
//
watch(
() => router.currentRoute.value,
(newValue, oldValue) => {
//
nowRouter.value = newValue.path
userStore.setNowRouter(newValue.path)
},
{ immediate: true },
)
onMounted(() => {
let nowRouterIndex = routerHistory.value.findIndex(item => item.path == nowRouter)
//
nextTick(() => {
countSroll(nowRouterIndex)
})
})
//
const removeTag = routerPath => {
if (routerHistory.value.length == 1) {
return
}
// tagvuex
let newArr = routerHistory.value.filter(item => item.path !== routerPath)
userStore.setRouterHistory(newArr)
//
if (route.path == routerPath) {
router.push({
path: newArr[newArr.length - 1].path,
})
}
}
//
const tagClick = (path, index) => {
countSroll(index)
router.push(path)
}
//
const countSroll = index => {
// // tag
// let tagWidth = this.$refs['tag' + index][0].$el.offsetWidth;
// // tag
// let tagMargin = this.$refs['tag' + index][0].$el.offsetLeft;
// // sroll
// let srollWidth = this.$refs.scrollbar.$el.offsetWidth;
// // tag+tag+20srolltag
// if ((tagMargin + tagWidth + 20) > srollWidth) {
// scrollbar.wrap.scrollLeft = tagMargin
// } else {
// scrollbar.wrap.scrollLeft = 0
// }
}
const handleScroll = e => {
const eventDelta = e.wheelDelta || -e.deltaY * 40
scrollbar.scrollLeft = scrollbar.scrollLeft + eventDelta / 4
}
</script>
<style lang="scss" scoped>
.div_ {
height: 48px;
padding: 0 15px;
background-color: #242a2f;
box-sizing: content-box;
padding-bottom: 10px;
.scrollbar_ {
height: 100%;
width: 100%;
white-space: nowrap;
:deep(.el-scrollbar__view) {
height: 100%;
display: flex;
align-items: center;
}
}
}
.el-tag {
margin-left: 10px;
cursor: pointer;
}
:first-child {
margin: 0;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<div>
<el-sub-menu :index="submenuData.path" class="subMenu">
<template #title>
<span class="item-title">{{ submenuData.meta?.title }}</span>
</template>
<template v-for="(item, index) in submenuData.children">
<SubMenu v-if="item.meta?.type == 'subMenu'" :submenu="item"></SubMenu>
<el-menu-item v-else class="item-title" :index="item.path">
{{ item.meta.title }}
</el-menu-item>
</template>
</el-sub-menu>
</div>
</template>
<script setup>
import SubMenu from './subMenu.vue'
import { ref, watch, watchEffect } from 'vue'
const props = defineProps({
submenuData: {
type: Object,
default: {},
},
})
</script>
<style lang="scss" scoped>
.el-menu {
.subMenu {
:deep(div.el-sub-menu__title) {
background-color:#30383e !important;
}
}
.is-active {
background-color: darken(#5584ff, 10%);
:deep(div.el-sub-menu__title) {
background-color: darken(#1d2429, 10%) !important;
}
}
}
</style>

View File

@ -0,0 +1,19 @@
import { onBeforeUnmount} from 'vue'
export function useMenu(isCollapse) {
// 获取body的宽度缩放小于800执行
const handerReSize = () => {
let curWidth = document.body.offsetWidth
isCollapse.value = curWidth < 800
}
// 一开始计算一次
handerReSize()
// 给window绑定reSize时间每次窗口发生变化 重新计算
window.addEventListener('resize', handerReSize)
// 给window卸载resize事件
onBeforeUnmount(() => {
window.removeEventListener('resize', handerReSize)
})
}

51
src/main.js 100644
View File

@ -0,0 +1,51 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import * as utils from '@/utils/common'
window.$utils = utils
// 工具包
import * as mUtils from 'maxiliam-utils'
import moment from 'moment'
window.$moment = moment
import * as $api from '@/utils/api'
window.$api = $api
// 全局状态管理
import { createPinia } from 'pinia'
// 重置css
import '@/assets/styles/reset.scss'
// 使用svg图标组件
import 'virtual:svg-icons-register'
// 防止Xss攻击的v-html
import VueDomPurifyHTML from 'vue-dompurify-html'
// 引入elementplus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/el-message.css'
// // 引入VFormRender组件
// import VFormRender from 'vform3-builds/dist/render.umd.js'
// // 引入VFormRender样式
import 'vform3-builds/dist/render.style.css'
import 'vform3-builds/dist/designer.style.css' //引入VForm3样式
import VForm3 from 'vform3-builds' //引入VForm3库
// 引入全局指令
import directive from '@/directive'
// 初始化项目尺寸 1920 * 1080
mUtils.setRemSize(1920, 1080)
const pinia = createPinia()
const app = createApp(App)
app.config.globalProperties.$utils = utils
app.config.globalProperties.$moment = moment
app.use(pinia)
app.use(VueDomPurifyHTML)
app.use(ElementPlus)
// app.use(VFormRender)
app.use(VForm3) //全局注册VForm3(同时注册了v-form-designe、v-form-render等组件)
app.use(router)
directive(app)
app.mount('#app')

View File

@ -0,0 +1,9 @@
<template>
<router-view />
</template>
<script>
export default {
setup() {},
}
</script>
<style lang="scss" scoped></style>

125
src/router/index.js 100644
View File

@ -0,0 +1,125 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-12 10:36:30
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2023-08-01 14:37:22
* @Description : 路由总文件
*/
import { createRouter, createWebHashHistory } from 'vue-router'
import { routes, asyncRoutes } from './routes'
import { useUserStore } from '@/stores/user.js'
import { storeToRefs } from 'pinia'
import { getToken } from '@/utils/auth'
const router = createRouter({
history: createWebHashHistory(),
routes: routes,
})
// 全局前置路由守卫
router.beforeEach(async (to, from, next) => {
try {
// && to.meta.isAuth
if (getToken()) {
if (to.path !== '/login') {
const userStore = useUserStore()
const { registerRouteFresh } = storeToRefs(userStore)
// console.log(registerRouteFresh.value)
if (registerRouteFresh.value) {
// debugger
const { asyncRoutes } = storeToRefs(userStore)
userStore.updateAsyncRoutes()
asyncRoutes.value.forEach(v => {
router.addRoute(v)
})
if (from.path === '/login') {
next({
path: '/home',
})
} else {
next({ ...to, replace: true })
}
userStore.updateFresh(false)
} else {
next()
setRouterHistory(to,userStore)
}
// next({ ...to, replace: true })
} else {
next()
}
} else {
if (to.path === '/login') {
next()
} else {
next({
path: '/login',
})
}
}
} catch (error) {
console.log(error)
}
// next(); //这里是即将进入的页面是白名单的页面就直接进入
})
asyncRoutes.forEach(v => {
router.addRoute(v)
})
export const hasPermission = (names, route) => {
if (route.authKey) {
return names.some(name => route.authKey === name)
} else {
return true
}
}
export const filterAsyncRoutes = (routes, names) => {
const res = []
routes.forEach(route => {
const tmp = { ...route }
if (hasPermission(names, tmp)) {
if (tmp.children) {
tmp.children = filterAsyncRoutes(tmp.children, names)
}
res.push(tmp)
}
})
return res
}
export const editRedirect = routes => {
routes.forEach(route => {
if (route.children && route.redirect && route.children[0]) {
route.redirect = route.children[0].path || ''
editRedirect(route.children)
}
})
return routes
}
// 记录路由历史
const setRouterHistory = (to) => {
const userStore = useUserStore()
let obj = {
title: to.meta.title,
path: to.path,
}
const {routerHistory} = storeToRefs(userStore)
routerHistory.value.push(obj);
//map去重避免重复记录重复路由
let mapArr = () => {
let map = new Map();
for (let item of routerHistory.value) {
if (!map.has(item.path)) {
map.set(item.path, item);
}
}
return [...map.values()];
}
let newArr = mapArr();
userStore.setRouterHistory(newArr)
}
export default router

View File

@ -0,0 +1,78 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-12 10:36:30
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-07-07 16:09:00
* @Description :路由配置文件 routes静态路由 asyncRoutes异步路由
*/
import RouterView from './RouterView.vue'
import Login from '@/views/Login/Login.vue'
import layout from '@/layout/Layout.vue'
import { shallowRef } from 'vue'
export const routes = [
{
path: '/',
redirect: '/login',
},
{
name: 'login',
path: '/login',
meta: { title: '登录' },
component: Login,
},
]
export const asyncRoutes = [
{
path: '/home',
name: 'home',
authKey: 'Workorder',
redirect: '/project',
},
{
path: '/project',
name: 'project',
authKey: 'Workorder',
redirect: '/project/detail',
meta: { title: '项目管理', imgSrc: 'slider/system' },
component: shallowRef(layout),
children: [
{
path: '/project/detail',
name: 'userWork',
authKey: 'Workorder',
meta: {
title: '项目详情',
},
component: () => import('@/views/project/detail.vue'),
},
],
},
{
path: '/system',
name: 'system',
authKey: 'Workorder',
meta: { title: '系统管理', imgSrc: 'slider/system' },
component: shallowRef(layout),
children: [
{
path: '/system/user',
name: 'systemUser',
authKey: 'Workorder',
meta: {
title: '用户管理',
},
component: () => import('@/views/system/user/index.vue'),
},
// {
// path: '/system/job',
// name: 'systemJob',
// authKey: 'Workorder',
// meta: {
// title: '定时任务',
// },
// component: () => import('@/views/system/job/index.vue'),
// },
],
},
]

View File

@ -0,0 +1,20 @@
/*
* @Author: suhang suhang_max@163.com
* @Date: 2023-03-02 09:00:45
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-06-01 09:46:05
* @FilePath: \train-assess\src\stores\common.js
* @Description: 全局变量设置
*/
import {
defineStore
} from 'pinia'
import { setToken } from '@/utils/auth'
export const useCommonStore = defineStore('common', {
state: () => ({
// e.g. 主题
theme: 'light'
}),
actions: {
}
})

105
src/stores/user.js 100644
View File

@ -0,0 +1,105 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-12 15:34:19
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2024-01-08 14:42:07
* @Description : 登录退出 存储用户信息路由权限
*/
import { defineStore } from 'pinia'
import { asyncRoutes, routes } from '@/router/routes.js'
import { loginApi } from '@/utils/api'
import { setToken, removeToken } from '@/utils/auth'
import { filterAsyncRoutes, editRedirect } from '@/router/index'
import router from '../router'
export const useUserStore = defineStore('user', {
state: () => ({
registerRouteFresh: true,
asyncRoutes: [], //处理后异步路由表
userInfo: {}, //用户信息
permissions: [], //权限路由list
routerHistory: sessionStorage.getItem('routerHistory') ? JSON.parse(sessionStorage.getItem('routerHistory')) : [], //路由记录
nowRouter: sessionStorage.getItem('nowRouter'),
}),
actions: {
async login(params) {
try {
const res = await loginApi.login(params)
const roleType = res.data.user.roles
.filter(item => item.roleType)
.map(per => per.roleType)
.join(',')
// setPerssionsTabbar(roleType)
const roleKey = res.data.user.roles
.filter(item => item.roleKey)
.map(per => per.roleKey)
.join(',')
sessionStorage.setItem('roleType', $utils.encryptByDES.CBC(roleType))
sessionStorage.setItem('roleKey', $utils.encryptByDES.CBC(roleKey))
const token = $utils.encryptByMD5(`123456`)
setToken(token)
await this.updateUserInfo(res.data)
ElMessage.success({ message: '登录成功' })
const permissions = [...res.data.resourceKeys]
// , 'WorkOrderError', 'workOrderDestroy', 'workOrderReserver'
sessionStorage.setItem('permissions', $utils.encryptByDES.CBC(JSON.stringify(permissions)))
this.updateAsyncRoutes()
this.updateFresh(true)
router.push('/home')
} catch (error) {
console.log(error)
throw new Error(error)
}
},
async logOut() {
// await loginApi.loginOut()
removeToken()
router.push('/')
},
async updateUserInfo(data) {
const userInfo = data.user
sessionStorage.setItem('userInfo', $utils.encryptByDES.CBC(JSON.stringify(userInfo)))
this.userInfo = userInfo
},
updateFresh(flag) {
this.registerRouteFresh = flag
},
updateAsyncRoutes() {
// 权限时处理异步路由
let sessionPermissions = JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('permissions')))
// console.log(asyncRoutes, sessionPermissions, 'asyncRoutes, sessionPermissions')
let result = filterAsyncRoutes(asyncRoutes, sessionPermissions)
result = editRedirect(result)
// console.log(result, 'result')
result.unshift({
path: '/home',
name: 'home',
redirect: result[0].path,
component: () => import('@/layout/Layout.vue'),
children: [result[0]],
})
this.asyncRoutes = result
sessionStorage.setItem('asyncRoutes', $utils.encryptByDES.CBC(JSON.stringify(result)))
},
setRouterHistory(routerHistory) {
this.routerHistory = routerHistory
sessionStorage.setItem('routerHistory', JSON.stringify(routerHistory))
},
// 清除路由历史
cleanRouterHistory() {
this.routerHistory = []
sessionStorage.removeItem('routerHistory')
},
// 设置当前路由
setNowRouter(nowRouter) {
this.nowRouter = nowRouter
sessionStorage.setItem('nowRouter', nowRouter)
},
// 清除当前路由
cleanNowRouter() {
this.nowRouter = undefined
sessionStorage.removeItem('nowRouter')
},
},
})

42
src/utils/api.js 100644
View File

@ -0,0 +1,42 @@
import request from '@/utils/request'
//登录板块api
export const loginApi = {
// 登录
login: data => request('/login', 'post', data),
// 退出登录
loginOut: () => request('/aiops-api/logout', 'get'),
//修改密码
editPassword: data => request('/aiops-api/auth/user/updatePwd', 'post', data),
}
export const useUserApi = () => {
}
// 系统板块
export const systemApi = {
getUserList: data => request('/aiops-api/auth/user/list', 'post', data),
}
// 系统定时任务
export const systemJobApi = {
// 查询定时任务调度列表
listJob: query => request('/monitor/job/list', 'get', query),
// 查询定时任务调度详细
getJob: jobId => request(`/monitor/job/${jobId}`, 'get'),
// 新增定时任务调度
addJob: data => request('/monitor/job', 'post', data),
// 修改定时任务调度
updateJob: data => request('/monitor/job', 'put', data),
// 删除定时任务调度
delJob: jobId => request(`/monitor/job/${jobId}`, 'delete'),
// 任务状态修改
changeJobStatus: (jobId, status) => request('/monitor/job/changeStatus', 'put', { jobId, status }),
// 定时任务立即执行一次
runJob: (jobId, jobGroup) => request('/monitor/job/run', 'put', { jobId, jobGroup }),
}

33
src/utils/auth.js 100644
View File

@ -0,0 +1,33 @@
/*
* @FilePath : /src/utils/auth.js
* @Description : 登录凭证存取
*/
import Cookies from 'js-cookie'
const sessionTokenKey = 'Admin-Token'
const cookieTokenKey = 'JSESSIONID'
export function cookieGetToken() {
return Cookies.get(cookieTokenKey)
}
export function cookieSetToken(token) {
return Cookies.set(cookieTokenKey, token, { expires: 302 })
}
export function cookieRemoveToken() {
sessionStorage.clear()
return Cookies.remove(cookieTokenKey)
}
export function getToken() {
return sessionStorage.getItem(sessionTokenKey)
}
export function setToken(token) {
return sessionStorage.setItem(sessionTokenKey, token)
}
export function removeToken() {
return sessionStorage.clear()
}

473
src/utils/common.js 100644
View File

@ -0,0 +1,473 @@
import CryptoJS from 'crypto-js'
export var APPSTR_DES_CBC_KEY = 'AIOPS_CBC_KEY'
// DES加密
export var encryptByDES = {
ECB: function (message, key, keepLetterCase = false) {
var keyHex = CryptoJS.enc.Utf8.parse(key)
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
})
if (keepLetterCase) {
return encrypted.ciphertext.toString()
}
return encrypted.ciphertext.toString().toLowerCase()
},
CBC: function (message, key, keepLetterCase = false) {
var keyHex = CryptoJS.enc.Utf8.parse(key)
var encrypted = CryptoJS.DES.encrypt(message, keyHex, {
iv: keyHex,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
})
if (keepLetterCase) {
return encrypted.ciphertext.toString()
}
return encrypted.ciphertext.toString().toLowerCase()
},
}
export var decryptByDES = {
ECB: function (ciphertext, key) {
var keyHex = CryptoJS.enc.Utf8.parse(key)
var decrypted = CryptoJS.DES.decrypt(
{
ciphertext: CryptoJS.enc.Hex.parse(ciphertext.toLowerCase()),
},
keyHex,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
},
)
return decrypted.toString(CryptoJS.enc.Utf8)
},
CBC: function (ciphertext, key) {
var keyHex = CryptoJS.enc.Utf8.parse(key)
var decrypted = CryptoJS.DES.decrypt(
{
ciphertext: CryptoJS.enc.Hex.parse(ciphertext.toLowerCase()),
},
keyHex,
{
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: keyHex,
},
)
return decrypted.toString(CryptoJS.enc.Utf8)
},
}
// CryptoJS加密
export function encryptByBase64(message) {
var str = CryptoJS.enc.Utf8.parse(message)
return CryptoJS.enc.Base64.stringify(str)
}
export function decryptByBase64(encryptedStr) {
var words = CryptoJS.enc.Base64.parse(encryptedStr)
return words.toString(CryptoJS.enc.Utf8)
}
export function encryptByMD5(message) {
var words = CryptoJS.MD5(message)
return words.toString()
}
/**
* 日期格式化
* dateFormat(new Date(), "YYYY/MM/DD hh:mm:ss S")
* 支持格式YYYY,yyyy MM DD,dd HH,hh mm ss, S(毫秒)
*/
export function dateFormat(date, fmt) {
// console.log(date, fmt)
if (!date) {
return ''
}
if (!(date instanceof Date)) {
if (typeof date === 'number') {
date = '' + date
}
date = date.trim()
var fixstr = '1970/01/01' // 用于修复只传了年或只传了年月在IOS设备上new Date()不兼容的问题
var datePre = date.substr(0, 10).replace(/[.]|-|\\|,/gi, '/')
datePre = datePre + fixstr.substr(datePre.length)
var datePost = date.substr(10)
date = datePre + datePost
// TxtToast('' + date)
date = new Date(date)
}
if (isNaN(date.getTime())) {
return ''
}
var o = {
'M+': date.getMonth() + 1, // 月份
'd+': date.getDate(), // 日
'h+': date.getHours(), // 小时
'm+': date.getMinutes(), // 分
's+': date.getSeconds(), // 秒
'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
S: date.getMilliseconds(), // 毫秒
}
if (/(y+)/i.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
for (var k in o) {
if ((['d+', 'h+'].indexOf(k) === -1 ? new RegExp('(' + k + ')') : new RegExp('(' + k + ')', 'i')).test(fmt))
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
}
return fmt
}
/**
* 提示
* frameDiv 需要添加提示的div元素
*/
export function createTooltip(frameDiv, direction = 'right') {
var tooltip = function (frameDiv) {
var div = document.createElement('DIV')
div.className = `twipsy ${direction}`
var arrow = document.createElement('DIV')
arrow.className = 'twipsy-arrow'
div.appendChild(arrow)
var title = document.createElement('DIV')
title.className = 'twipsy-inner'
div.appendChild(title)
this._div = div
this._title = title
this.message = ''
// add to frame div and display coordinates
frameDiv.appendChild(div)
var that = this
div.onmousemove = function (evt) {
that.showAt({ x: evt.clientX, y: evt.clientY }, that.message)
}
}
tooltip.prototype.setVisible = function (visible) {
this._div.style.display = visible ? 'block' : 'none'
}
tooltip.prototype.showAt = function (position, message) {
if (position && message) {
this.setVisible(true)
this._title.innerHTML = message
if (direction === 'top') {
this._div.style.left = position.x - this._div.clientWidth / 2 + 'px'
this._div.style.top = position.y - this._div.clientHeight - 15 + 'px'
}
if (direction === 'right') {
this._div.style.left = position.x + 10 + 'px'
this._div.style.top = position.y - this._div.clientHeight / 2 + 'px'
}
this.message = message
}
}
tooltip.prototype.destroy = function () {
try {
div && frameDiv.removeChild(div)
} catch (error) {
console.log(error)
}
}
return new tooltip(frameDiv)
}
/**
* element plus自动回弹
* event 点击事件的返回的event对象
*/
export function unFoucs(event) {
// event.target.blur()
// if (event.target.nodeName === 'SPAN') {
// event.target.parentNode.blur()
// }
event.target.blur()
if (event.target.nodeName === 'SPAN') {
if (event.target.children.length == 0) {
event.target.parentNode.parentNode.blur()
}
event.target.parentNode.blur()
}
if (event.target.nodeName === 'IMG') {
const evt = event.target.parentNode.parentNode
evt.blur()
}
}
/**
* element plus自定义删除弹窗
* msg: 删除提示语回调函数使用.then
*/
import { h } from 'vue'
import { ElMessageBox } from 'element-plus'
export function deletebox(msg1, title = '提示') {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(
h('div', { class: 'df fdc jcsb aic' }, [
// h('img', { src: './img/common/icon_del.png' }),
//class: 'mt20',
h(
'div',
{ style: 'word-wrap: break-word;word-break: break-all; line-height: 20px;' },
msg1 || '您是否确定删除',
),
// h('div', { class: 'mt20' }, msg2 || ''),
]),
title,
{
type: 'warning',
// icon: h('img', { src: './img/common/icon_tips.png' }),
autofocus: false,
customClass: 'message-box',
// distinguishCancelAndClose: true,
confirmButtonText: '确认',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
// buttonSize: 'large',
// type: 'info',
center: true,
},
)
.then(() => {
resolve()
})
.catch(action => {
// reject(action)
})
})
}
/**
* 深拷贝
* data: 要拷贝的对象
*/
export function deepCopy(data, hash = new WeakMap()) {
if (typeof data !== 'object' || data === null) {
throw new TypeError('传入参数不是对象')
}
// 判断传入的待拷贝对象的引用是否存在于hash中
if (hash.has(data)) {
return hash.get(data)
}
let newData = {}
const dataKeys = Object.keys(data)
dataKeys.forEach(value => {
const currentDataValue = data[value]
// 基本数据类型的值和函数直接赋值拷贝
if (typeof currentDataValue !== 'object' || currentDataValue === null) {
newData[value] = currentDataValue
} else if (Array.isArray(currentDataValue)) {
// 实现数组的深拷贝
newData[value] = [...currentDataValue]
} else if (currentDataValue instanceof Set) {
// 实现set数据的深拷贝
newData[value] = new Set([...currentDataValue])
} else if (currentDataValue instanceof Map) {
// 实现map数据的深拷贝
newData[value] = new Map([...currentDataValue])
} else {
// 将这个待拷贝对象的引用存于hash中
hash.set(data, data)
// 普通对象则递归赋值
newData[value] = deepCopy(currentDataValue, hash)
}
})
return newData
}
/**
* element plus自定义页面返回弹框
* msg: 返回提示语回调函数使用.then
*/
export function canclebox(msg1, msg2) {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(
h('div', { class: 'df fdc jcsb aic' }, [
h('div', { class: 'mt20' }, msg1 || '您是否确定返回'),
h('div', msg2 || ''),
]),
'提示',
{
icon: h('img', { src: './img/common/icon_tips.png' }),
autofocus: false,
customClass: 'message-box',
// distinguishCancelAndClose: true,
confirmButtonText: '确认',
cancelButtonText: '取消',
dangerouslyUseHTMLString: true,
buttonSize: 'large',
// type: 'info',
center: true,
},
)
.then(() => {
resolve()
})
.catch(action => {
reject(action)
})
})
}
import { ElMessage } from 'element-plus'
export function message(message, type) {
ElMessage[type]({
message,
offset: window.screen.height / 2 - 100,
})
}
//加法:
export function dcmAdd(arg1, arg2) {
var r1, r2, m
try {
r1 = arg1.toString().split('.')[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split('.')[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (accMul(arg1, m) + accMul(arg2, m)) / m
}
//减法:
export function dcmReduce(arg1, arg2) {
var r1, r2, m
try {
r1 = arg1.toString().split('.')[1].length
} catch (e) {
r1 = 0
}
try {
r2 = arg2.toString().split('.')[1].length
} catch (e) {
r2 = 0
}
m = Math.pow(10, Math.max(r1, r2))
return (accMul(arg1, m) - accMul(arg2, m)) / m
}
//乘法:
export function accMul(arg1, arg2) {
var m = 0,
s1 = arg1.toString(),
s2 = arg2.toString()
try {
m += s1.split('.')[1].length
} catch (e) { }
try {
m += s2.split('.')[1].length
} catch (e) { }
return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m)
}
//除法:
export function accDiv(arg1, arg2) {
var t1 = 0,
t2 = 0,
r1,
r2
try {
t1 = arg1.toString().split('.')[1].length
} catch (e) { }
try {
t2 = arg2.toString().split('.')[1].length
} catch (e) { }
Math.r1 = Number(arg1.toString().replace('.', ''))
Math.r2 = Number(arg2.toString().replace('.', ''))
return (Math.r1 / Math.r2) * Math.pow(10, t2 - t1)
}
/**
* @description: 判断两个对象是否相等
* @param {*} obj1 时间对象和函数对象不做比对
* @param {*} obj2
* @return {*}
*/
export function isEqual(obj1, obj2) {
if (!isObject(obj1) && !isObject(obj2)) {
// 两个都是基本类型,直接进行严格比较
if (Number.isNaN(obj1) && Number.isNaN(obj2)) {
return true
}
return obj1 === obj2
}
if (!isObject(obj1) || !isObject(obj2)) {
// 一个对象,一个基本类型,直接返回 false
return false
}
if (typeOf(obj1) !== typeOf(obj2)) {
// 类型不同,直接返回 false
return false
}
// 后面比较的都是类型相同的对象类型
if (obj1 === obj2) {
// 直接比较引用地址,相等则返回 true
return true
}
if (['Array', 'Object'].includes(typeOf(obj1))) {
// plain object 和 arrayfor ... in 比较每一项值
let l1 = Object.keys(obj1).length
let l2 = Object.keys(obj2).length
if (l1 !== l2) {
return false
}
for (let k in obj1) {
if (!isEqual(obj1[k], obj2[k])) {
return false
}
}
return true
}
if (['Map', 'Set'].includes(typeOf(obj1))) {
// 处理 map、set 类型,转换为数组再比较
const [arr1, arr2] = [[...obj1], [...obj2]]
return isEqual(arr1, arr2)
}
return false
}
function isObject(item) {
return Object(item) === item
}
function typeOf(item) {
return Object.prototype.toString.call(item).slice(8, -1)
}
export function debounce(fn, wait) {
var timeout = null
return function () {
if (timeout !== null) clearTimeout(timeout)
timeout = setTimeout(fn, wait)
}
}
// 获取索引
export function findIndex(data, value, key) {
// 传入key则表示为对象用键名查找
console.log();
return data.findIndex(ele => { return key ? value == ele[key] : value == ele })
}
// 表头顺序调整
export function sortTableHeader(indexNow, indexRes, path, headerList) {
// 两两交换位置
let data = headerList[indexRes]
headerList[indexRes] = headerList[indexNow]
headerList[indexNow] = data
// 缓存表头排序
let name =
sessionStorage.getItem('userInfo') !== 'undefined' && sessionStorage.getItem('userInfo')
? JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('userInfo'))).userName
: ''
window.localStorage.setItem(
name + path + '_sort',
JSON.stringify(headerList),
)
}

View File

@ -0,0 +1,14 @@
/*
* @Author : ldc 951052117@qq.com
* @Date : 2023-06-12 17:34:53
* @LastEditors : ldc 951052117@qq.com
* @LastEditTime : 2023-06-12 18:17:10
* @Description : 获取assets静态资源
*/
const getAssetsFile = url => {
return new URL(`../assets/imgs/${url}`, import.meta.url).href
}
export default {
getAssetsFile,
}

View File

@ -0,0 +1,189 @@
import axios from 'axios'
import { ElMessage, ElLoading } from 'element-plus'
import { getToken, removeToken } from '@/utils/auth'
import router from '../router'
var loadingInstance = null
const BASE_URL = import.meta.env.VITE_APP_REQUESTURL
const timeout = 20000
// create an axios instance
const service = axios.create({
// baseURL: process.env.VUE_APP_BASE_API,
baseURL: BASE_URL,
timeout: timeout, // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
if (getToken()) {
config.headers['token'] = getToken()
}
return config
},
error => {
return Promise.reject(error)
},
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
// if the custom code is not 20000, it is judged as an error.
return res
},
error => {
console.log('错误:' + error) // for debug
if (error.code !== 'ERR_CANCELED') {
ElMessage.error({ message: error.msg || error.message })
}
return Promise.reject(error)
},
)
const handleUrl = (url, params) => {
if (params) {
var str = ''
var i = 0
for (var key in params) {
if (i === 0) {
str += ''
} else {
str += '&'
}
str += key + '=' + params[key]
i++
}
if (/\?/.test(url)) {
url = url + '&' + str
} else {
url = url + '?' + str
}
}
return url
}
const doRequest = (url, method = 'GET', params, opts, callback) => {
return new Promise((resolve, reject) => {
const defaultOpts = {
SHOW_LOADING: true,
headers: {},
}
opts = Object.assign(defaultOpts, opts)
// 显示loading
if (opts.SHOW_LOADING) {
loadingInstance = ElLoading.service({
lock: true,
background: 'rgba(0, 0, 0, .7)',
})
}
if (/get/i.test(method)) {
url = handleUrl(url, params)
} else if (/upload/i.test(method)) {
opts.headers['Content-Type'] = 'multipart/form-data'
method = 'POST'
} else if (/export/i.test(method)) {
url = handleUrl(url, params)
window.open(BASE_URL + url)
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
return
} else if (/post/i.test(method)) {
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
} else if (/blob/i.test(method)) {
method = 'POST'
opts.headers['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8'
opts.responseType = 'blob'
} else if (/json/i.test(method)) {
method = 'POST'
opts.headers['Content-Type'] = 'application/json'
} else if (/qqmap/i.test(method)) {
method = 'POST'
opts.headers['Content-Type'] = 'application/json'
} else if (/file/i.test(method)) {
method = 'POST'
opts.headers['Content-Type'] = 'application/json'
opts.responseType = 'blob'
}
service({
url: url,
method: method,
data: params,
headers: opts.headers,
cancelToken: new axios.CancelToken(function executor(c) {
// 设置 cancel token
callback && callback(c)
}),
responseType: opts.responseType || '',
timeout: opts.timeout || timeout,
onDownloadProgress: opts.onDownloadProgress,
onUploadProgress: opts.onUploadProgress,
})
.then(res => {
if (!res) {
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
return
}
// if (opts.responseType === 'blob') {
// resolve(res)
// opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
// return
// }
if (opts.responseType === 'blob') {
const reader = new FileReader()
reader.onload = evt => {
try {
const resultObj = JSON.parse(evt.target.result)
if (resultObj?.code) {
reject(res)
ElMessage.error({
dangerouslyUseHTMLString: true,
message: resultObj.msg,
})
} else {
// console.log(res, 'kkk')
}
// resultOb是解码后的数据然后再进行提示处理
} catch (error) {
resolve(res)
}
}
reader.readAsText(res)
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
return
}
if (res.code === 0) {
resolve(res) //
// resolve(res.data)
} else if (res.code === 401) {
if (getToken()) {
ElMessage.error({ message: res.msg || `信息已过期,请重新登录!` })
removeToken()
}
router.push({ path: '/login' })
} else {
ElMessage.error({
dangerouslyUseHTMLString: true,
message: res.msg || `异常code: ${res.code || '无法识别'},请联系管理员!`,
})
reject(res)
}
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
})
.catch(err => {
console.log('ssss')
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
if (err.code === 'ERR_CANCELED') return
ElMessage.error({
dangerouslyUseHTMLString: true,
message: err || '网络异常,请检查网络连接!',
})
})
})
}
export default doRequest

30
src/utils/tree.js 100644
View File

@ -0,0 +1,30 @@
/*
* @FilePath : /src/utils/tree.js
* @Description :
*/
// 树形组件数据转换
export const listTotree = (data, A, B) => {
let idFiled = A
let parentField = B
let i,
l,
treeData = [],
tmpMap = []
for (i = 0, l = data.length; i < l; i++) {
tmpMap[data[i][idFiled]] = data[i]
}
for (i = 0, l = data.length; i < l; i++) {
if (tmpMap[data[i][parentField]] && data[i][idFiled] != data[i][parentField]) {
if (!tmpMap[data[i][parentField]]['children']) tmpMap[data[i][parentField]]['children'] = []
tmpMap[data[i][parentField]]['children'].push(data[i])
} else {
// if (!(data[i].children && data[i]?.children?.length)) data[i].isSmall = true
treeData.push(data[i])
}
}
// console.log(tmpMap, 'tmpMap')
// console.log(data, 'data')
return treeData
}

View File

@ -0,0 +1,13 @@
/*
* @FilePath : /src/utils/validate.js
* @Description :校验
*/
// 去空格空字符校验
export const commonCheck = (rule, value, callback) => {
if (!value || !value.trim()) {
callback(new Error('内容不能为空!'))
} else {
callback()
}
}

View File

@ -0,0 +1,26 @@
/*
* @FilePath : /src/utils/validateForm.js
* @Description :
*/
//多表单校验
export const validateForms = async formRefs => {
let objectList = []
let results = formRefs.map(
formRef =>
new Promise((resolve, reject) => {
formRef.validate((valid, object) => {
if (valid) {
resolve()
} else {
objectList.push(object)
reject()
}
})
}),
)
try {
return await Promise.all(results)
} catch {
return await Promise.reject(objectList)
}
}

14
src/views/404.vue 100644
View File

@ -0,0 +1,14 @@
<!--
* @Author: suhang suhang_max@163.com
* @Date: 2023-06-01 10:07:45
* @LastEditors: suhang suhang_max@163.com
* @LastEditTime: 2023-06-01 10:08:37
* @FilePath: \train-assessd:\Code\basic-frontend\src\views\404.vue
* @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
-->
<template>
<div>404</div>
</template>
<script setup></script>
<style lang="scss"></style>

View File

@ -0,0 +1,293 @@
<template>
<div class="login" ref="indexContainer">
<!-- <img src="@/assets/imgs/login-bg01.png"> -->
<!-- 左边背景部分 -->
<div class="login-box">
<div class="textBox">
<div class="textContent">PMS:新光线平台</div>
</div>
<!-- 右边部分 -->
<div class="right-box">
<div class="login-text">欢迎登录</div>
<div class="login-content">
<div class="input-section">
<div>
<span class="title">&emsp;</span>
</div>
<div class="inp-box" :class="usershadow ? 'isfocus' : ''">
<input
class="inp"
placeholder="请输入账号"
@focus="usershadow = true"
@blur="usershadow = false"
v-model="login.account"
/>
</div>
</div>
<div class="input-section">
<div>
<span class="title">&emsp;</span>
</div>
<div class="inp-box" :class="shadow ? 'isfocus' : ''">
<input
class="inp"
type="password"
placeholder="请输入密码"
v-model="login.pwd"
@focus="shadow = true"
@blur="shadow = false"
/>
</div>
</div>
<div class="input-section">
<div>
<span class="title">&emsp;&emsp;</span>
</div>
<div class="inp-box" :class="codeShadow ? 'isfocus' : ''">
<div class="df aic jcsb" style="width: 100%">
<input
class="inp"
style="flex: 2"
placeholder="请输入验证码"
v-model="login.validateCode"
@keydown.enter="confirmLogin"
@focus="codeShadow = true"
@blur="codeShadow = false"
/>
<img style="cursor: pointer; flex: 1" @click="updateCode" height="50" :src="codeSrc" />
</div>
</div>
</div>
<div class="box-button" @click="confirmLogin">
<div class="submit">登录</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ElMessage } from 'element-plus'
import { reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
const userStore = useUserStore()
const { routerHistory } = storeToRefs(userStore) //
const login = reactive({
account: '',
pwd: '',
validateCode: '',
})
const codeSrc = ref(
process.env.NODE_ENV === 'production'
? `${window.location.origin}${
import.meta.env.VITE_APP_REQUESTURL
}/captcha/captchaImage?type=math&&t=${Math.random()}`
: `${import.meta.env.VITE_APP_REQUESTURL}/captcha/captchaImage?type=math`,
) //${window.location.origin}
const updateCode = async () => {
codeSrc.value =
process.env.NODE_ENV === 'production'
? `${window.location.origin}${
import.meta.env.VITE_APP_REQUESTURL
}/captcha/captchaImage?type=math&&t=${Math.random()}`
: `${import.meta.env.VITE_APP_REQUESTURL}/captcha/captchaImage?type=math&&t=${Math.random()}` //char math
}
// getCode()
const confirmLogin = async () => {
if (!login.account) {
ElMessage.error({ message: `用户名不能为空!` })
return
}
if (!login.pwd) {
ElMessage.error({ message: `密码不能为空!` })
return
}
if (!login.validateCode) {
ElMessage.error({ message: `验证码不能为空!` })
return
}
const params = {
password: $utils.encryptByMD5(`${login.account}${login.pwd}`),
username: login.account,
validateCode: login.validateCode,
}
try {
await userStore.login(params)
routerHistory.value = []
} catch (error) {
updateCode()
}
}
const usershadow = ref(false)
const shadow = ref(false)
const codeShadow = ref(false)
</script>
<style lang="scss">
.login {
height: 100%;
width: 100%;
@include flex-row;
// background-color: #02082e;
align-items: center;
justify-content: center;
background: url('@/assets/imgs/椭圆形 + 椭圆形 + 椭圆形 蒙版.png') no-repeat;
background-size: 100% 100%;
position: relative;
z-index: 2000;
.login-box {
display: flex;
justify-content: center;
background-size: 100% 100%;
width: 11rem;
height: 7rem;
.right-box {
flex: 1;
@include flex-row-vc;
justify-content: space-between;
gap: 10px;
background: #ffffff;
border-top-right-radius: 0.4rem;
box-shadow: 0px 10px 20px 0px #2860b5;
padding: 0.2rem;
.login-text {
flex: 1;
display: flex;
align-items: center;
overflow-wrap: break-word;
color: rgba(51, 51, 51, 1);
font-size: 0.36rem;
font-family: FZZDHJW--GB1-0;
white-space: nowrap;
font-weight: bold;
}
.login-content {
flex: 3;
@include flex-row-vc;
.input-section {
@include flex-row;
align-items: center;
margin-bottom: 0.4rem;
.title {
margin-right: 0.1rem;
color: #199cf5;
font-size: 14px;
white-space: nowrap;
}
.inp-box {
flex: 1; // height: 50px;
@include flex-row;
&::after {
content: '';
display: block;
position: absolute;
right: 0;
bottom: 0;
width: 0;
height: 0;
// border: 5px solid #062a4a;
// border-top-color: transparent;
// border-left-color: transparent;
}
.inp {
flex: 1;
color: #000;
background: transparent !important;
outline: none;
border: none;
padding-left: 0.15rem;
}
}
.isfocus {
box-shadow: 0px 0px 3px 2px #3686ff;
&::after {
content: '';
display: block;
position: absolute;
right: 0;
bottom: 0;
width: 0;
height: 0;
border: 5px solid #3686ff;
border-top-color: transparent;
border-left-color: transparent;
}
}
}
.box-button {
width: 100%;
height: 0.5rem;
// margin-top: 1rem;
cursor: pointer;
.submit {
width: 80%;
height: 0.5rem;
margin: auto;
color: #fff;
text-align: center;
line-height: 0.5rem;
border-radius: 10px;
// transform: translate(-50%, -50%);
background: #3686ff;
font-size: 0.22rem;
}
}
}
}
.textBox {
position: relative;
flex: 1;
box-shadow: 0px 10px 20px 0px #2860b5;
background: #3e8bff;
border-bottom-left-radius: 40px;
background: url('@/assets/imgs/编组 3.png') no-repeat;
background-position: center center;
background-size: 60% 60%;
padding: 0.2rem;
.textContent {
width: 100%;
font-weight: bold;
overflow-wrap: break-word;
color: rgba(255, 255, 255, 1);
font-size: 0.32rem;
font-family: FZZDHJW--GB1-0;
text-align: center;
white-space: nowrap;
}
.textItem {
// width: 100%;
display: flex;
justify-content: center;
position: absolute;
bottom: 0.4rem;
left: 50%;
transform: translate(-50%, 0%);
font-size: 0.14rem;
color: #fff;
gap: 0.1rem;
}
.textItems {
display: flex;
justify-content: center;
// width: 100%;
position: absolute;
bottom: 0.1rem;
left: 50%;
transform: translate(-50%, 0%);
font-size: 0.12rem;
color: #ffffff;
font-family: PingFangSC-Regular, PingFang SC;
white-space: nowrap;
}
}
}
}
</style>

View File

@ -0,0 +1,206 @@
<template>
<div class="project-management">
<el-form :model="formData" :disabled="!isEditing" label-width="120px">
<el-row>
<el-col :span="24">
<el-form-item label="项目名称">
<el-select v-model="formData.projectName" placeholder="请选择项目名称" class="full-width">
<el-option v-for="item in projectOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="项目编号">
<el-input v-model="formData.projectCode" class="full-width" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="项目负责人">
<el-input v-model="formData.projectManager" class="full-width" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="预算人天数">
<el-input-number v-model="formData.budgetDays" :min="0" class="full-width" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="项目状态">
<el-select v-model="formData.projectStatus" placeholder="请选择项目状态" class="full-width">
<el-option label="进行中" value="ongoing" />
<el-option label="已完成" value="completed" />
<el-option label="已暂停" value="paused" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="项目开始时间">
<el-date-picker v-model="formData.startDate" type="date" placeholder="选择日期" class="full-width" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="项目结束时间">
<el-date-picker v-model="formData.endDate" type="date" placeholder="选择日期" class="full-width" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="table-actions">
<el-button type="primary" @click="toggleEdit">{{ isEditing ? '' : '' }}</el-button>
</div>
<CustomTable
:columns="columns"
:tableData="tableData"
:total="total"
:show-selection="false"
:show-index="true"
:table-height="400"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #operation="{ row }">
<div class="operation-buttons">
<el-button text @click="handleEdit(row)"></el-button>
<el-button text type="danger" @click="handleDelete(row)"></el-button>
<el-button text type="primary" @click="showTimesheet(row)"></el-button>
</div>
</template>
</CustomTable>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import CustomTable from '@/components/table.vue'
const isEditing = ref(false)
const formData = reactive({
projectName: '',
projectCode: '',
projectManager: '',
budgetDays: 0,
projectStatus: '',
startDate: '',
endDate: ''
})
const projectOptions = [
{ value: 'project1', label: '项目1' },
{ value: 'project2', label: '项目2' },
{ value: 'project3', label: '项目3' }
]
const columns = [
{ prop: 'name', label: '人员姓名' },
{ prop: 'role', label: '项目角色' },
{ prop: 'workdays', label: '工时天数' },
{ prop: 'weight', label: '人员权重' },
{
prop: 'operation',
label: '操作',
width: 'auto',
fixed: 'right',
className: 'operation-column'
}
]
const tableData = ref(Array(22).fill(null).map((_, index) => ({
name: `员工${index + 1}`,
role: ['开发', '设计', '测试', '产品'][index % 4],
workdays: Math.floor(Math.random() * 20) + 10,
weight: (Math.random() * 0.5 + 0.5).toFixed(2)
})))
const total = ref(tableData.value.length)
const toggleEdit = () => {
isEditing.value = !isEditing.value
if (!isEditing.value) {
//
console.log('保存表单数据:', formData)
}
}
const handleSizeChange = (val) => {
console.log('每页显示条数:', val)
}
const handleCurrentChange = (val) => {
console.log('当前页:', val)
}
const handleEdit = (row) => {
console.log('编辑行:', row)
}
const handleDelete = (row) => {
console.log('删除行:', row)
}
const showTimesheet = (row) => {
console.log('显示工时表:', row)
}
//
const tableHeight = ref(400) //
onMounted(() => {
updateTableHeight()
window.addEventListener('resize', updateTableHeight)
})
const updateTableHeight = () => {
const windowHeight = window.innerHeight
const topOffset = document.querySelector('.project-management').offsetTop
const formHeight = document.querySelector('.el-form').offsetHeight
const actionsHeight = document.querySelector('.table-actions').offsetHeight
const padding = 40 // 20px
tableHeight.value = windowHeight - topOffset - formHeight - actionsHeight - padding
}
</script>
<style scoped>
.project-management {
padding: 20px;
background-color: white;
min-height: 100vh;
box-sizing: border-box;
display: flex;
flex-direction: column;
}
.table-actions {
margin-bottom: 20px;
}
.full-width {
width: 100%;
}
:deep(.el-input-number) {
width: 100%;
}
:deep(.el-input-number .el-input__wrapper) {
width: 100%;
}
:deep(.el-table) {
flex: 1;
overflow: auto;
}
.operation-buttons {
display: flex;
justify-content: flex-start;
align-items: center;
}
:deep(.operation-buttons .el-button) {
padding: 4px 8px;
margin: 0 2px;
}
:deep(.operation-column) {
background-color: #fff;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.1);
}
</style>

View File

@ -0,0 +1,340 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item label="部门名称" prop="deptName">
<el-input
v-model="queryParams.deptName"
placeholder="请输入部门名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="部门状态" clearable>
<el-option
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:dept:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="info"
plain
icon="el-icon-sort"
size="mini"
@click="toggleExpandAll"
>展开/折叠</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table
v-if="refreshTable"
v-loading="loading"
:data="deptList"
row-key="deptId"
:default-expand-all="isExpandAll"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
>
<el-table-column prop="deptName" label="部门名称" width="260"></el-table-column>
<el-table-column prop="orderNum" label="排序" width="200"></el-table-column>
<el-table-column prop="status" label="状态" width="100">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="200">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:dept:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-plus"
@click="handleAdd(scope.row)"
v-hasPermi="['system:dept:add']"
>新增</el-button>
<el-button
v-if="scope.row.parentId != 0"
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:dept:remove']"
>删除</el-button>
</template>
</el-table-column>
</el-table>
<!-- 添加或修改部门对话框 -->
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
<el-row>
<el-col :span="24" v-if="form.parentId !== 0">
<el-form-item label="上级部门" prop="parentId">
<treeselect v-model="form.parentId" :options="deptOptions" :normalizer="normalizer" placeholder="选择上级部门" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="部门名称" prop="deptName">
<el-input v-model="form.deptName" placeholder="请输入部门名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="负责人" prop="leader">
<el-input v-model="form.leader" placeholder="请输入负责人" maxlength="20" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="form.phone" placeholder="请输入联系电话" maxlength="11" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="部门状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept";
import Treeselect from "@riophae/vue-treeselect";
import "@riophae/vue-treeselect/dist/vue-treeselect.css";
export default {
name: "Dept",
dicts: ['sys_normal_disable'],
components: { Treeselect },
data() {
return {
//
loading: true,
//
showSearch: true,
//
deptList: [],
//
deptOptions: [],
//
title: "",
//
open: false,
//
isExpandAll: true,
//
refreshTable: true,
//
queryParams: {
deptName: undefined,
status: undefined
},
//
form: {},
//
rules: {
parentId: [
{ required: true, message: "上级部门不能为空", trigger: "blur" }
],
deptName: [
{ required: true, message: "部门名称不能为空", trigger: "blur" }
],
orderNum: [
{ required: true, message: "显示排序不能为空", trigger: "blur" }
],
email: [
{
type: "email",
message: "请输入正确的邮箱地址",
trigger: ["blur", "change"]
}
],
phone: [
{
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
message: "请输入正确的手机号码",
trigger: "blur"
}
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询部门列表 */
getList() {
this.loading = true;
listDept(this.queryParams).then(response => {
this.deptList = this.handleTree(response.data, "deptId");
this.loading = false;
});
},
/** 转换部门数据结构 */
normalizer(node) {
if (node.children && !node.children.length) {
delete node.children;
}
return {
id: node.deptId,
label: node.deptName,
children: node.children
};
},
//
cancel() {
this.open = false;
this.reset();
},
//
reset() {
this.form = {
deptId: undefined,
parentId: undefined,
deptName: undefined,
orderNum: undefined,
leader: undefined,
phone: undefined,
email: undefined,
status: "0"
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 新增按钮操作 */
handleAdd(row) {
this.reset();
if (row != undefined) {
this.form.parentId = row.deptId;
}
this.open = true;
this.title = "添加部门";
listDept().then(response => {
this.deptOptions = this.handleTree(response.data, "deptId");
});
},
/** 展开/折叠操作 */
toggleExpandAll() {
this.refreshTable = false;
this.isExpandAll = !this.isExpandAll;
this.$nextTick(() => {
this.refreshTable = true;
});
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
getDept(row.deptId).then(response => {
this.form = response.data;
this.open = true;
this.title = "修改部门";
listDeptExcludeChild(row.deptId).then(response => {
this.deptOptions = this.handleTree(response.data, "deptId");
if (this.deptOptions.length == 0) {
const noResultsOptions = { deptId: this.form.parentId, deptName: this.form.parentName, children: [] };
this.deptOptions.push(noResultsOptions);
}
});
});
},
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.deptId != undefined) {
updateDept(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
addDept(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 删除按钮操作 */
handleDelete(row) {
this.$modal.confirm('是否确认删除名称为"' + row.deptName + '"的数据项?').then(function() {
return delDept(row.deptId);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,615 @@
<template>
<div class="wrap">
<Card>
<template #body>
<div class="facility-box">
<div class="state-right">
<div class="iscollpInp">
<el-form ref="queryForm" :model="queryParams" :inline="true" label-width="68px">
<el-row :gutter="20">
<el-col :span="6">
<el-form-item label="任务名称" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="请输入任务名称"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="任务组名" prop="jobGroup">
<el-select v-model="queryParams.jobGroup" placeholder="请选择任务组名" clearable>
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="任务状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择任务状态" clearable>
<el-option
v-for="dict in dict.type.sys_job_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<div class="search-buttons">
<el-button type="primary" @click="handleQuery"></el-button>
<el-button @click="resetQuery"></el-button>
</div>
</el-col>
</el-row>
</el-form>
</div>
<div class="table-container">
<div class="spocebtn">
<el-button type="primary" plain @click="handleAdd" v-hasPermi="['monitor:job:add']"></el-button>
<el-button
type="danger"
plain
@click="handleDelete"
:disabled="multiple"
v-hasPermi="['monitor:job:remove']"
>
删除
</el-button>
<el-button type="warning" plain @click="handleExport" v-hasPermi="['monitor:job:export']">
导出
</el-button>
<el-button type="info" plain @click="handleJobLog" v-hasPermi="['monitor:job:query']"></el-button>
</div>
<Table
:columns="tableConfig.thead"
:tableData="jobList"
:showSelection="true"
:showIndex="false"
:total="total"
:pageSizes="[10, 20, 30, 40]"
:tableHeight="400"
@selection-change="handleSelectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #jobGroup="{ row }">
<dict-tag :options="dict.type.sys_job_group" :value="row.jobGroup" />
</template>
<template #status="{ row }">
<el-switch
v-model="row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(row)"
></el-switch>
</template>
<template #operation="{ row }">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(row)"
v-hasPermi="['monitor:job:edit']"
>
修改
</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(row)"
v-hasPermi="['monitor:job:remove']"
>
删除
</el-button>
<el-dropdown
size="mini"
@command="command => handleCommand(command, row)"
v-hasPermi="['monitor:job:changeStatus', 'monitor:job:query']"
>
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item
command="handleRun"
icon="el-icon-caret-right"
v-hasPermi="['monitor:job:changeStatus']"
>
执行一次
</el-dropdown-item>
<el-dropdown-item command="handleView" icon="el-icon-view" v-hasPermi="['monitor:job:query']">
任务详细
</el-dropdown-item>
<el-dropdown-item
command="handleJobLog"
icon="el-icon-s-operation"
v-hasPermi="['monitor:job:query']"
>
调度日志
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</Table>
</div>
</div>
</div>
</template>
</Card>
<!-- 添加或修改定时任务对话框 -->
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="120px">
<el-row>
<el-col :span="12">
<el-form-item label="任务名称" prop="jobName">
<el-input v-model="form.jobName" placeholder="请输入任务名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组" prop="jobGroup">
<el-select v-model="form.jobGroup" placeholder="请选择任务分组">
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="invokeTarget">
<span slot="label">
调用方法
<el-tooltip placement="top">
<div slot="content">
Bean调用示例ryTask.ryParams('ry')
<br />
Class类调用示例com.ruoyi.quartz.task.RyTask.ryParams('ry')
<br />
参数说明支持字符串布尔类型长整型浮点型整型
</div>
<i class="el-icon-question"></i>
</el-tooltip>
</span>
<el-input v-model="form.invokeTarget" placeholder="请输入调用目标字符串" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="cron表达式" prop="cronExpression">
<el-input v-model="form.cronExpression" placeholder="请输入cron执行表达式">
<template slot="append">
<el-button type="primary" @click="handleShowCron">
生成表达式
<i class="el-icon-time el-icon--right"></i>
</el-button>
</template>
</el-input>
</el-form-item>
</el-col>
<el-col :span="24" v-if="form.jobId !== undefined">
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio v-for="dict in dict.type.sys_job_status" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略" prop="misfirePolicy">
<el-radio-group v-model="form.misfirePolicy" size="small">
<el-radio-button label="1">立即执行</el-radio-button>
<el-radio-button label="2">执行一次</el-radio-button>
<el-radio-button label="3">放弃执行</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发" prop="concurrent">
<el-radio-group v-model="form.concurrent" size="small">
<el-radio-button label="0">允许</el-radio-button>
<el-radio-button label="1">禁止</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<el-dialog title="Cron表达式生成器" :visible.sync="openCron" append-to-body destroy-on-close class="scrollbar">
<crontab @hide="openCron = false" @fill="crontabFill" :expression="expression"></crontab>
</el-dialog>
<!-- 任务日志详细 -->
<el-dialog title="任务详细" :visible.sync="openView" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="120px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="任务编号:">{{ form.jobId }}</el-form-item>
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组:">{{ jobGroupFormat(form) }}</el-form-item>
<el-form-item label="创建时间:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="cron表达式">{{ form.cronExpression }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="下次执行时间:">{{ parseTime(form.nextValidTime) }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调用目标方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务状态:">
<div v-if="form.status == 0"></div>
<div v-else-if="form.status == 1">暂停</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="是否并发:">
<div v-if="form.concurrent == 0"></div>
<div v-else-if="form.concurrent == 1">禁止</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="执行策略:">
<div v-if="form.misfirePolicy == 0"></div>
<div v-else-if="form.misfirePolicy == 1">立即执行</div>
<div v-else-if="form.misfirePolicy == 2">执行一次</div>
<div v-else-if="form.misfirePolicy == 3">放弃执行</div>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="openView = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Search } from '@element-plus/icons-vue'
import { systemJobApi } from '@/utils/api.js'
import Crontab from '@/components/Crontab'
import Table from '@/components/table.vue'
import Card from '@/components/Card.vue'
const dict = ref({
type: {
sys_job_group: [],
sys_job_status: [],
},
})
const router = useRouter()
const { listJob, getJob, delJob, addJob, updateJob, runJob, changeJobStatus } = systemJobApi
//
const loading = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const showSearch = ref(true)
const total = ref(0)
const jobList = ref([])
const title = ref('')
const open = ref(false)
const openView = ref(false)
const openCron = ref(false)
const expression = ref('')
const queryParams = reactive({
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined,
})
const form = reactive({
jobId: undefined,
jobName: undefined,
jobGroup: undefined,
invokeTarget: undefined,
cronExpression: undefined,
misfirePolicy: 1,
concurrent: 1,
status: '0',
})
const rules = {
jobName: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],
invokeTarget: [{ required: true, message: '调用目标字符串不能为空', trigger: 'blur' }],
cronExpression: [{ required: true, message: 'cron执行表达式不能为空', trigger: 'blur' }],
}
const tableConfig = reactive({
thead: [
{ prop: 'jobId', label: '任务编号', width: '100px' },
{ prop: 'jobName', label: '任务名称' },
{ prop: 'jobGroup', label: '任务组名' },
{ prop: 'invokeTarget', label: '调用目标字符串' },
{ prop: 'cronExpression', label: 'cron执行表达式' },
{ prop: 'status', label: '状态' },
{ prop: 'operation', label: '操作', width: '150px' },
],
})
const inp = reactive({
input: '',
})
const treeDatas = reactive({
treeData: [], //
})
const selectTree = ref()
const filterNode = (value, data) => {
if (!value) return true
return data[defaultProps.label].indexOf(value) !== -1
}
watch(
() => inp.input,
newValue => {
selectTree.value.filter(newValue)
},
)
const defaultProps = {
children: 'children',
label: 'name',
}
const handleNodeClick = treeData => {
//
}
const handleCheckChange = (data, checked, indeterminate) => {
//
}
const handleSizeChange = val => {
queryParams.pageSize = val
getList()
}
const handleCurrentChange = val => {
queryParams.pageNum = val
getList()
}
//
const getList = () => {
loading.value = true
listJob(queryParams).then(response => {
jobList.value = response.rows
total.value = response.total
loading.value = false
})
}
const jobGroupFormat = row => {
return dict.value.sys_job_group.find(item => item.value === row.jobGroup)?.label || ''
}
const handleQuery = () => {
queryParams.pageNum = 1
getList()
}
const resetQuery = () => {
//
handleQuery()
}
const handleSelectionChange = selection => {
ids.value = selection.map(item => item.jobId)
single.value = selection.length !== 1
multiple.value = !selection.length
}
const handleCommand = (command, row) => {
switch (command) {
case 'handleRun':
handleRun(row)
break
case 'handleView':
handleView(row)
break
case 'handleJobLog':
handleJobLog(row)
break
}
}
const handleStatusChange = row => {
let text = row.status === '0' ? '启用' : '停用'
ElMessageBox.confirm(`确认要"${text}""${row.jobName}"任务吗?`)
.then(() => {
return changeJobStatus(row.jobId, row.status)
})
.then(() => {
ElMessage.success(text + '成功')
})
.catch(() => {
row.status = row.status === '0' ? '1' : '0'
})
}
const handleRun = row => {
ElMessageBox.confirm(`确认要立即执行一次"${row.jobName}"任务吗?`)
.then(() => {
return runJob(row.jobId, row.jobGroup)
})
.then(() => {
ElMessage.success('执行成功')
})
.catch(() => {})
}
const handleView = row => {
getJob(row.jobId).then(response => {
Object.assign(form, response.data)
openView.value = true
})
}
const handleShowCron = () => {
expression.value = form.cronExpression
openCron.value = true
}
const crontabFill = value => {
form.cronExpression = value
}
const handleJobLog = row => {
const jobId = row.jobId || 0
router.push(`/monitor/job-log/index/${jobId}`)
}
const handleAdd = () => {
Object.assign(form, {
jobId: undefined,
jobName: undefined,
jobGroup: undefined,
invokeTarget: undefined,
cronExpression: undefined,
misfirePolicy: 1,
concurrent: 1,
status: '0',
})
open.value = true
title.value = '添加任务'
}
const handleUpdate = row => {
const jobId = row.jobId || ids.value
getJob(jobId).then(response => {
Object.assign(form, response.data)
open.value = true
title.value = '修改任务'
})
}
const submitForm = () => {
//
}
const handleDelete = row => {
const jobIds = row.jobId || ids.value
ElMessageBox.confirm(`是否确认删除定时任务编号为"${jobIds}"的数据项?`)
.then(() => {
return delJob(jobIds)
})
.then(() => {
getList()
ElMessage.success('删除成功')
})
.catch(() => {})
}
const handleExport = () => {
//
}
//
onMounted(() => {
// getList()
//
// getFacilityList()
})
//
const disableSelection = computed(() => ({
single: single.value,
multiple: multiple.value,
}))
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100%;
}
.facility-box {
display: flex;
height: calc(100vh - 100px);
overflow: hidden;
flex: 1;
.state-left {
width: 20%;
padding-right: 20px;
overflow-y: auto;
.treeinp {
margin-bottom: 20px;
}
}
.state-right {
flex: 1;
display: flex;
flex-direction: column;
border-left: 1px solid #ccc;
padding-left: 20px;
overflow-y: auto;
.iscollpInp {
margin-bottom: 20px;
.el-form {
width: 100%;
.el-row {
margin-bottom: 0;
}
.el-form-item {
margin-bottom: 0;
}
}
.search-buttons {
display: flex;
justify-content: flex-end;
align-items: center;
height: 100%;
.el-button {
margin-left: 10px;
}
}
}
.table-container {
flex: 1;
display: flex;
flex-direction: column;
.spocebtn {
margin-bottom: 10px;
}
}
}
}
</style>

View File

@ -0,0 +1,295 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="任务名称" prop="jobName">
<el-input
v-model="queryParams.jobName"
placeholder="请输入任务名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="任务组名" prop="jobGroup">
<el-select
v-model="queryParams.jobGroup"
placeholder="请选择任务组名"
clearable
style="width: 240px"
>
<el-option
v-for="dict in dict.type.sys_job_group"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="执行状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择执行状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in dict.type.sys_common_status"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="执行时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['monitor:job:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
@click="handleClean"
v-hasPermi="['monitor:job:remove']"
>清空</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['monitor:job:export']"
>导出</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
>关闭</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="jobLogList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="日志编号" width="80" align="center" prop="jobLogId" />
<el-table-column label="任务名称" align="center" prop="jobName" :show-overflow-tooltip="true" />
<el-table-column label="任务组名" align="center" prop="jobGroup" :show-overflow-tooltip="true">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_job_group" :value="scope.row.jobGroup"/>
</template>
</el-table-column>
<el-table-column label="调用目标字符串" align="center" prop="invokeTarget" :show-overflow-tooltip="true" />
<el-table-column label="日志信息" align="center" prop="jobMessage" :show-overflow-tooltip="true" />
<el-table-column label="执行状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_common_status" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="执行时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-view"
@click="handleView(scope.row)"
v-hasPermi="['monitor:job:query']"
>详细</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 调度日志详细 -->
<el-dialog title="调度日志详细" :visible.sync="open" width="700px" append-to-body>
<el-form ref="form" :model="form" label-width="100px" size="mini">
<el-row>
<el-col :span="12">
<el-form-item label="日志序号:">{{ form.jobLogId }}</el-form-item>
<el-form-item label="任务名称:">{{ form.jobName }}</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="任务分组:">{{ form.jobGroup }}</el-form-item>
<el-form-item label="执行时间:">{{ form.createTime }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="调用方法:">{{ form.invokeTarget }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="日志信息:">{{ form.jobMessage }}</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="执行状态:">
<div v-if="form.status == 0"></div>
<div v-else-if="form.status == 1">失败</div>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="异常信息:" v-if="form.status == 1">{{ form.exceptionInfo }}</el-form-item>
</el-col>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="open = false"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { getJob} from "@/api/monitor/job";
import { listJobLog, delJobLog, cleanJobLog } from "@/api/monitor/jobLog";
export default {
name: "JobLog",
dicts: ['sys_common_status', 'sys_job_group'],
data() {
return {
//
loading: true,
//
ids: [],
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
jobLogList: [],
//
open: false,
//
dateRange: [],
//
form: {},
//
queryParams: {
pageNum: 1,
pageSize: 10,
jobName: undefined,
jobGroup: undefined,
status: undefined
}
};
},
created() {
const jobId = this.$route.params && this.$route.params.jobId;
if (jobId !== undefined && jobId != 0) {
getJob(jobId).then(response => {
this.queryParams.jobName = response.data.jobName;
this.queryParams.jobGroup = response.data.jobGroup;
this.getList();
});
} else {
this.getList();
}
},
methods: {
/** 查询调度日志列表 */
getList() {
this.loading = true;
listJobLog(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.jobLogList = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
//
handleClose() {
const obj = { path: "/monitor/job" };
this.$tab.closeOpenPage(obj);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.jobLogId);
this.multiple = !selection.length;
},
/** 详细按钮操作 */
handleView(row) {
this.open = true;
this.form = row;
},
/** 删除按钮操作 */
handleDelete(row) {
const jobLogIds = this.ids;
this.$modal.confirm('是否确认删除调度日志编号为"' + jobLogIds + '"的数据项?').then(function() {
return delJobLog(jobLogIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 清空按钮操作 */
handleClean() {
this.$modal.confirm('是否确认清空所有调度日志数据项?').then(function() {
return cleanJobLog();
}).then(() => {
this.getList();
this.$modal.msgSuccess("清空成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('/monitor/jobLog/export', {
...this.queryParams
}, `log_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,199 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="openSelectUser"
v-hasPermi="['system:role:add']"
>添加用户</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-circle-close"
size="mini"
:disabled="multiple"
@click="cancelAuthUserAll"
v-hasPermi="['system:role:remove']"
>批量取消授权</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-close"
size="mini"
@click="handleClose"
>关闭</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-circle-close"
@click="cancelAuthUser(scope.row)"
v-hasPermi="['system:role:remove']"
>取消授权</el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<select-user ref="select" :roleId="queryParams.roleId" @ok="handleQuery" />
</div>
</template>
<script>
import { allocatedUserList, authUserCancel, authUserCancelAll } from "@/api/system/role";
import selectUser from "./selectUser";
export default {
name: "AuthUser",
dicts: ['sys_normal_disable'],
components: { selectUser },
data() {
return {
//
loading: true,
//
userIds: [],
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
userList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
}
};
},
created() {
const roleId = this.$route.params && this.$route.params.roleId;
if (roleId) {
this.queryParams.roleId = roleId;
this.getList();
}
},
methods: {
/** 查询授权用户列表 */
getList() {
this.loading = true;
allocatedUserList(this.queryParams).then(response => {
this.userList = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
//
handleClose() {
const obj = { path: "/system/role" };
this.$tab.closeOpenPage(obj);
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.userIds = selection.map(item => item.userId)
this.multiple = !selection.length
},
/** 打开授权用户表弹窗 */
openSelectUser() {
this.$refs.select.show();
},
/** 取消授权按钮操作 */
cancelAuthUser(row) {
const roleId = this.queryParams.roleId;
this.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?').then(function() {
return authUserCancel({ userId: row.userId, roleId: roleId });
}).then(() => {
this.getList();
this.$modal.msgSuccess("取消授权成功");
}).catch(() => {});
},
/** 批量取消授权按钮操作 */
cancelAuthUserAll(row) {
const roleId = this.queryParams.roleId;
const userIds = this.userIds.join(",");
this.$modal.confirm('是否取消选中用户授权数据项?').then(function() {
return authUserCancelAll({ roleId: roleId, userIds: userIds });
}).then(() => {
this.getList();
this.$modal.msgSuccess("取消授权成功");
}).catch(() => {});
}
}
};
</script>

View File

@ -0,0 +1,605 @@
<template>
<div class="app-container">
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch">
<el-form-item label="角色名称" prop="roleName">
<el-input
v-model="queryParams.roleName"
placeholder="请输入角色名称"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="权限字符" prop="roleKey">
<el-input
v-model="queryParams.roleKey"
placeholder="请输入权限字符"
clearable
style="width: 240px"
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="角色状态"
clearable
style="width: 240px"
>
<el-option
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker
v-model="dateRange"
style="width: 240px"
value-format="yyyy-MM-dd"
type="daterange"
range-separator="-"
start-placeholder="开始日期"
end-placeholder="结束日期"
></el-date-picker>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button
type="primary"
plain
icon="el-icon-plus"
size="mini"
@click="handleAdd"
v-hasPermi="['system:role:add']"
>新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="success"
plain
icon="el-icon-edit"
size="mini"
:disabled="single"
@click="handleUpdate"
v-hasPermi="['system:role:edit']"
>修改</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="danger"
plain
icon="el-icon-delete"
size="mini"
:disabled="multiple"
@click="handleDelete"
v-hasPermi="['system:role:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-download"
size="mini"
@click="handleExport"
v-hasPermi="['system:role:export']"
>导出</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
<el-table v-loading="loading" :data="roleList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="角色编号" prop="roleId" width="120" />
<el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" />
<el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="150" />
<el-table-column label="显示顺序" prop="roleSort" width="100" />
<el-table-column label="状态" align="center" width="100">
<template slot-scope="scope">
<el-switch
v-model="scope.row.status"
active-value="0"
inactive-value="1"
@change="handleStatusChange(scope.row)"
></el-switch>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template slot-scope="scope" v-if="scope.row.roleId !== 1">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
@click="handleUpdate(scope.row)"
v-hasPermi="['system:role:edit']"
>修改</el-button>
<el-button
size="mini"
type="text"
icon="el-icon-delete"
@click="handleDelete(scope.row)"
v-hasPermi="['system:role:remove']"
>删除</el-button>
<el-dropdown size="mini" @command="(command) => handleCommand(command, scope.row)" v-hasPermi="['system:role:edit']">
<el-button size="mini" type="text" icon="el-icon-d-arrow-right">更多</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item command="handleDataScope" icon="el-icon-circle-check"
v-hasPermi="['system:role:edit']">数据权限</el-dropdown-item>
<el-dropdown-item command="handleAuthUser" icon="el-icon-user"
v-hasPermi="['system:role:edit']">分配用户</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
<!-- 添加或修改角色配置对话框 -->
<el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="角色名称" prop="roleName">
<el-input v-model="form.roleName" placeholder="请输入角色名称" />
</el-form-item>
<el-form-item prop="roleKey">
<span slot="label">
<el-tooltip content="控制器中定义的权限字符,如:@PreAuthorize(`@ss.hasRole('admin')`)" placement="top">
<i class="el-icon-question"></i>
</el-tooltip>
权限字符
</span>
<el-input v-model="form.roleKey" placeholder="请输入权限字符" />
</el-form-item>
<el-form-item label="角色顺序" prop="roleSort">
<el-input-number v-model="form.roleSort" controls-position="right" :min="0" />
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio
v-for="dict in dict.type.sys_normal_disable"
:key="dict.value"
:label="dict.value"
>{{dict.label}}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="菜单权限">
<el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand($event, 'menu')">/</el-checkbox>
<el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">/</el-checkbox>
<el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')"></el-checkbox>
<el-tree
class="tree-border"
:data="menuOptions"
show-checkbox
ref="menu"
node-key="id"
:check-strictly="!form.menuCheckStrictly"
empty-text="加载中,请稍候"
:props="defaultProps"
></el-tree>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 分配角色数据权限对话框 -->
<el-dialog :title="title" :visible.sync="openDataScope" width="500px" append-to-body>
<el-form :model="form" label-width="80px">
<el-form-item label="角色名称">
<el-input v-model="form.roleName" :disabled="true" />
</el-form-item>
<el-form-item label="权限字符">
<el-input v-model="form.roleKey" :disabled="true" />
</el-form-item>
<el-form-item label="权限范围">
<el-select v-model="form.dataScope" @change="dataScopeSelectChange">
<el-option
v-for="item in dataScopeOptions"
:key="item.value"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</el-form-item>
<el-form-item label="数据权限" v-show="form.dataScope == 2">
<el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand($event, 'dept')">/</el-checkbox>
<el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">/</el-checkbox>
<el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')"></el-checkbox>
<el-tree
class="tree-border"
:data="deptOptions"
show-checkbox
default-expand-all
ref="dept"
node-key="id"
:check-strictly="!form.deptCheckStrictly"
empty-text="加载中,请稍候"
:props="defaultProps"
></el-tree>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitDataScope"> </el-button>
<el-button @click="cancelDataScope"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listRole, getRole, delRole, addRole, updateRole, dataScope, changeRoleStatus, deptTreeSelect } from "@/api/system/role";
import { treeselect as menuTreeselect, roleMenuTreeselect } from "@/api/system/menu";
export default {
name: "Role",
dicts: ['sys_normal_disable'],
data() {
return {
//
loading: true,
//
ids: [],
//
single: true,
//
multiple: true,
//
showSearch: true,
//
total: 0,
//
roleList: [],
//
title: "",
//
open: false,
//
openDataScope: false,
menuExpand: false,
menuNodeAll: false,
deptExpand: true,
deptNodeAll: false,
//
dateRange: [],
//
dataScopeOptions: [
{
value: "1",
label: "全部数据权限"
},
{
value: "2",
label: "自定数据权限"
},
{
value: "3",
label: "本部门数据权限"
},
{
value: "4",
label: "本部门及以下数据权限"
},
{
value: "5",
label: "仅本人数据权限"
}
],
//
menuOptions: [],
//
deptOptions: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
roleName: undefined,
roleKey: undefined,
status: undefined
},
//
form: {},
defaultProps: {
children: "children",
label: "label"
},
//
rules: {
roleName: [
{ required: true, message: "角色名称不能为空", trigger: "blur" }
],
roleKey: [
{ required: true, message: "权限字符不能为空", trigger: "blur" }
],
roleSort: [
{ required: true, message: "角色顺序不能为空", trigger: "blur" }
]
}
};
},
created() {
this.getList();
},
methods: {
/** 查询角色列表 */
getList() {
this.loading = true;
listRole(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
this.roleList = response.rows;
this.total = response.total;
this.loading = false;
}
);
},
/** 查询菜单树结构 */
getMenuTreeselect() {
menuTreeselect().then(response => {
this.menuOptions = response.data;
});
},
//
getMenuAllCheckedKeys() {
//
let checkedKeys = this.$refs.menu.getCheckedKeys();
//
let halfCheckedKeys = this.$refs.menu.getHalfCheckedKeys();
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
return checkedKeys;
},
//
getDeptAllCheckedKeys() {
//
let checkedKeys = this.$refs.dept.getCheckedKeys();
//
let halfCheckedKeys = this.$refs.dept.getHalfCheckedKeys();
checkedKeys.unshift.apply(checkedKeys, halfCheckedKeys);
return checkedKeys;
},
/** 根据角色ID查询菜单树结构 */
getRoleMenuTreeselect(roleId) {
return roleMenuTreeselect(roleId).then(response => {
this.menuOptions = response.menus;
return response;
});
},
/** 根据角色ID查询部门树结构 */
getDeptTree(roleId) {
return deptTreeSelect(roleId).then(response => {
this.deptOptions = response.depts;
return response;
});
},
//
handleStatusChange(row) {
let text = row.status === "0" ? "启用" : "停用";
this.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?').then(function() {
return changeRoleStatus(row.roleId, row.status);
}).then(() => {
this.$modal.msgSuccess(text + "成功");
}).catch(function() {
row.status = row.status === "0" ? "1" : "0";
});
},
//
cancel() {
this.open = false;
this.reset();
},
//
cancelDataScope() {
this.openDataScope = false;
this.reset();
},
//
reset() {
if (this.$refs.menu != undefined) {
this.$refs.menu.setCheckedKeys([]);
}
this.menuExpand = false,
this.menuNodeAll = false,
this.deptExpand = true,
this.deptNodeAll = false,
this.form = {
roleId: undefined,
roleName: undefined,
roleKey: undefined,
roleSort: 0,
status: "0",
menuIds: [],
deptIds: [],
menuCheckStrictly: true,
deptCheckStrictly: true,
remark: undefined
};
this.resetForm("form");
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.dateRange = [];
this.resetForm("queryForm");
this.handleQuery();
},
//
handleSelectionChange(selection) {
this.ids = selection.map(item => item.roleId)
this.single = selection.length!=1
this.multiple = !selection.length
},
//
handleCommand(command, row) {
switch (command) {
case "handleDataScope":
this.handleDataScope(row);
break;
case "handleAuthUser":
this.handleAuthUser(row);
break;
default:
break;
}
},
// /
handleCheckedTreeExpand(value, type) {
if (type == 'menu') {
let treeList = this.menuOptions;
for (let i = 0; i < treeList.length; i++) {
this.$refs.menu.store.nodesMap[treeList[i].id].expanded = value;
}
} else if (type == 'dept') {
let treeList = this.deptOptions;
for (let i = 0; i < treeList.length; i++) {
this.$refs.dept.store.nodesMap[treeList[i].id].expanded = value;
}
}
},
// /
handleCheckedTreeNodeAll(value, type) {
if (type == 'menu') {
this.$refs.menu.setCheckedNodes(value ? this.menuOptions: []);
} else if (type == 'dept') {
this.$refs.dept.setCheckedNodes(value ? this.deptOptions: []);
}
},
//
handleCheckedTreeConnect(value, type) {
if (type == 'menu') {
this.form.menuCheckStrictly = value ? true: false;
} else if (type == 'dept') {
this.form.deptCheckStrictly = value ? true: false;
}
},
/** 新增按钮操作 */
handleAdd() {
this.reset();
this.getMenuTreeselect();
this.open = true;
this.title = "添加角色";
},
/** 修改按钮操作 */
handleUpdate(row) {
this.reset();
const roleId = row.roleId || this.ids
const roleMenu = this.getRoleMenuTreeselect(roleId);
getRole(roleId).then(response => {
this.form = response.data;
this.open = true;
this.$nextTick(() => {
roleMenu.then(res => {
let checkedKeys = res.checkedKeys
checkedKeys.forEach((v) => {
this.$nextTick(()=>{
this.$refs.menu.setChecked(v, true ,false);
})
})
});
});
this.title = "修改角色";
});
},
/** 选择角色权限范围触发 */
dataScopeSelectChange(value) {
if(value !== '2') {
this.$refs.dept.setCheckedKeys([]);
}
},
/** 分配数据权限操作 */
handleDataScope(row) {
this.reset();
const deptTreeSelect = this.getDeptTree(row.roleId);
getRole(row.roleId).then(response => {
this.form = response.data;
this.openDataScope = true;
this.$nextTick(() => {
deptTreeSelect.then(res => {
this.$refs.dept.setCheckedKeys(res.checkedKeys);
});
});
this.title = "分配数据权限";
});
},
/** 分配用户操作 */
handleAuthUser: function(row) {
const roleId = row.roleId;
this.$router.push("/system/role-auth/user/" + roleId);
},
/** 提交按钮 */
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
if (this.form.roleId != undefined) {
this.form.menuIds = this.getMenuAllCheckedKeys();
updateRole(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.open = false;
this.getList();
});
} else {
this.form.menuIds = this.getMenuAllCheckedKeys();
addRole(this.form).then(response => {
this.$modal.msgSuccess("新增成功");
this.open = false;
this.getList();
});
}
}
});
},
/** 提交按钮(数据权限) */
submitDataScope: function() {
if (this.form.roleId != undefined) {
this.form.deptIds = this.getDeptAllCheckedKeys();
dataScope(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
this.openDataScope = false;
this.getList();
});
}
},
/** 删除按钮操作 */
handleDelete(row) {
const roleIds = row.roleId || this.ids;
this.$modal.confirm('是否确认删除角色编号为"' + roleIds + '"的数据项?').then(function() {
return delRole(roleIds);
}).then(() => {
this.getList();
this.$modal.msgSuccess("删除成功");
}).catch(() => {});
},
/** 导出按钮操作 */
handleExport() {
this.download('system/role/export', {
...this.queryParams
}, `role_${new Date().getTime()}.xlsx`)
}
}
};
</script>

View File

@ -0,0 +1,136 @@
<template>
<!-- 授权用户 -->
<el-dialog title="选择用户" :visible.sync="visible" width="800px" top="5vh" append-to-body>
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true">
<el-form-item label="用户名称" prop="userName">
<el-input
v-model="queryParams.userName"
placeholder="请输入用户名称"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item label="手机号码" prop="phonenumber">
<el-input
v-model="queryParams.phonenumber"
placeholder="请输入手机号码"
clearable
@keyup.enter.native="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery"></el-button>
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery"></el-button>
</el-form-item>
</el-form>
<el-row>
<el-table @row-click="clickRow" ref="table" :data="userList" @selection-change="handleSelectionChange" height="260px">
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" />
<el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" />
<el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" />
<el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" />
<el-table-column label="状态" align="center" prop="status">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180">
<template slot-scope="scope">
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total>0"
:total="total"
:page.sync="queryParams.pageNum"
:limit.sync="queryParams.pageSize"
@pagination="getList"
/>
</el-row>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleSelectUser"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { unallocatedUserList, authUserSelectAll } from "@/api/system/role";
export default {
dicts: ['sys_normal_disable'],
props: {
//
roleId: {
type: [Number, String]
}
},
data() {
return {
//
visible: false,
//
userIds: [],
//
total: 0,
//
userList: [],
//
queryParams: {
pageNum: 1,
pageSize: 10,
roleId: undefined,
userName: undefined,
phonenumber: undefined
}
};
},
methods: {
//
show() {
this.queryParams.roleId = this.roleId;
this.getList();
this.visible = true;
},
clickRow(row) {
this.$refs.table.toggleRowSelection(row);
},
//
handleSelectionChange(selection) {
this.userIds = selection.map(item => item.userId);
},
//
getList() {
unallocatedUserList(this.queryParams).then(res => {
this.userList = res.rows;
this.total = res.total;
});
},
/** 搜索按钮操作 */
handleQuery() {
this.queryParams.pageNum = 1;
this.getList();
},
/** 重置按钮操作 */
resetQuery() {
this.resetForm("queryForm");
this.handleQuery();
},
/** 选择授权用户操作 */
handleSelectUser() {
const roleId = this.queryParams.roleId;
const userIds = this.userIds.join(",");
if (userIds == "") {
this.$modal.msgError("请选择要分配的用户");
return;
}
authUserSelectAll({ roleId: roleId, userIds: userIds }).then(res => {
this.$modal.msgSuccess(res.msg);
this.visible = false;
this.$emit("ok");
});
}
}
};
</script>

View File

@ -0,0 +1,79 @@
<template>
<div class="common-dialog">
<el-dialog
@close="close"
center
:model-value="visible"
title="添加"
size="32px"
width="30%"
:close-on-click-modal="false"
>
<div style="border: 1px solid #ccc; margin-bottom: 10px"></div>
<div class="inpDialog">
<span style="font-size: 16px">责任人:</span>
<el-form ref="formRef" :model="data" label-width="18px" class="demo-dynamic">
<el-form-item prop="handleMsg">
<el-input v-model="data.handleMsg" type="text" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="$emit('update:visible')"></el-button>
<el-button type="primary" @click="confim"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const $emit = defineEmits(['update:visible', 'confim', 'fail'])
const props = defineProps({
visible: {
type: Boolean,
},
})
const data = reactive({
// handleMsg: '',
// handleResultStr: ''
})
const confim = () => {
$emit('update:visible')
}
const close = () => {
$emit('update:visible')
}
</script>
<style lang="scss" scoped>
:deep(.el-dialog__title) {
font-size: 26px;
}
.common-dialog {
display: flex;
justify-content: center;
align-items: Center;
overflow: hidden;
:deep(.el-dialog) {
margin: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.inpDialog {
margin-top: 10px;
height: 50px;
display: flex;
:deep(.el-input__inner) {
width: 260px;
}
}
</style>

View File

@ -0,0 +1,79 @@
<template>
<div class="common-dialog">
<el-dialog
@close="close"
center
:model-value="visible"
title="编辑"
size="32px"
width="30%"
:close-on-click-modal="false"
>
<div style="border: 1px solid #ccc; margin-bottom: 10px"></div>
<div class="inpDialog">
<span style="font-size: 16px">责任人:</span>
<el-form ref="formRef" :model="data" label-width="18px" class="demo-dynamic">
<el-form-item prop="handleMsg">
<el-input v-model="data.handleMsg" type="text" />
</el-form-item>
</el-form>
</div>
<template #footer>
<span class="dialog-footer">
<el-button @click="$emit('update:visible')"></el-button>
<el-button type="primary" @click="confim"></el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
const $emit = defineEmits(['update:visible', 'confim', 'fail'])
const props = defineProps({
visible: {
type: Boolean,
},
})
const data = reactive({
// handleMsg: '',
// handleResultStr: ''
})
const confim = () => {
$emit('update:visible')
}
const close = () => {
$emit('update:visible')
}
</script>
<style lang="scss" scoped>
:deep(.el-dialog__title) {
font-size: 26px;
}
.common-dialog {
display: flex;
justify-content: center;
align-items: Center;
overflow: hidden;
:deep(.el-dialog) {
margin: 0 !important;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.inpDialog {
margin-top: 10px;
height: 50px;
display: flex;
:deep(.el-input__inner) {
width: 260px;
}
}
</style>

View File

@ -0,0 +1,274 @@
<template>
<div class="wrap">
<Card>
<template #body>
<div class="facility-box">
<div class="state-left">
<el-input v-model="inp.input" class="treeinp" size="large" placeholder="请输入" :suffix-icon="Search" />
<el-tree
:data="treeDatas.treeData"
show-checkbox
:props="defaultProps"
ref="selectTree"
node-key="id"
:expand-on-click-node="false"
default-expand-all
:filter-node-method="filterNode"
@node-click="handleNodeClick"
@check-change="handleCheckChange"
/>
</div>
<div class="state-right">
<div class="iscollpInp">
<el-form ref="ruleFormRef" :model="data" status-icon class="demo-ruleForm" label-width="100px">
<el-form-item label="责任人" prop="devIp">
<el-input v-model="data.devIp" clearable placeholder="请输入" type="input" />
</el-form-item>
<el-form-item label="所属项目" prop="project">
<el-input v-model="data.project" clearable placeholder="请输入" type="input" />
</el-form-item>
</el-form>
<div class="fromBtn">
<el-button type="primary" @click="submitForm(ruleFormRef)"></el-button>
<el-button @click="resetForm(ruleFormRef)"></el-button>
</div>
</div>
<div class="table-container">
<div class="spocebtn">
<el-button type="primary" plain v-blur @click="openAdd"></el-button>
<el-button type="primary" plain v-blur @click="openImport"></el-button>
</div>
<Table
:columns="tableConfig.thead"
:tableData="table.tableData"
:showSelection="true"
:showIndex="false"
:total="data.total"
:pageSizes="[20, 30, 40, 50]"
:tableHeight="400"
@selection-change="selectionChange"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)"></el-button>
<el-button type="danger" size="small" @click="handleDelete(row)"></el-button>
</template>
</Table>
</div>
</div>
</div>
</template>
</Card>
<AddPerson v-if="addVisible" v-model:visible="addVisible" />
<EditPerson v-if="editVisible" v-model:visible="editVisible" />
</div>
</template>
<script setup>
import { ref, reactive, watch } from 'vue'
import AddPerson from './compontents/AddPerson.vue'
import EditPerson from './compontents/EditPerson.vue'
import { Search } from '@element-plus/icons-vue'
import { systemApi } from '@/utils/api.js'
import { listTotree } from '@/utils/tree.js'
import Table from '@/components/table.vue'
import Card from '@/components/Card.vue'
const handleCheckChange = (data, checked, indeterminate) => {
console.log(checked)
}
const addVisible = ref(false)
const openAdd = () => {
addVisible.value = true
}
const editVisible = ref(false)
const handleEdit = row => {
console.log('编辑行:', row)
editVisible.value = true
}
const selectTree = ref()
const inp = reactive({
input: '',
})
const filterNode = (value, data) => {
if (!value) return true
return data[defaultProps.label].indexOf(value) !== -1
}
watch(
() => inp.input,
newValue => {
selectTree.value.filter(newValue)
},
)
const data = reactive({
devIp: '',
project: '',
depCoding: '',
coding: '',
pageNum: 1,
pageSize: 20,
total: 0,
})
const tableConfig = reactive({
thead: [
{ prop: 'name', label: '所属区域' },
{ prop: 'project', label: '所属项目' },
{ prop: 'devIp', label: '责任人' },
{ prop: 'operation', label: '操作', width: '150px' },
],
})
const table = reactive({
tableData: [],
})
const getTable = () => {
systemApi.getUserList(data).then(res => {
table.tableData = res.rows
data.total = res.total
})
}
const treeDatas = reactive({
treeData: [],
})
const getFacilityList = () => {
systemApi.getDeptList().then(res => {
const arr = listTotree(res.data, 'coding', 'parCoding')
function next(arr) {
arr.forEach(item => {
item.value = item.coding
item.label = item.name
if (Array.isArray(item.children)) next(item.children)
})
}
next(arr)
treeDatas.treeData = arr
})
}
const handleSizeChange = val => {
data.pageSize = val
getTable()
}
const handleCurrentChange = val => {
data.pageNum = val
getTable()
}
const handleNodeClick = treeData => {
data.depCoding = treeData.coding
getTable()
}
const defaultProps = {
children: 'children',
label: 'name',
}
const ruleFormRef = ref()
const submitForm = () => {
getTable()
}
const resetForm = formEl => {
if (!formEl) return
formEl.resetFields()
data.depCoding = ''
getTable()
}
const checkScope = ref([])
const selectionChange = val => {
checkScope.value = val
}
const handleDelete = row => {
console.log('删除行:', row)
//
}
const openImport = () => {
console.log('批量删除')
//
}
//
getTable()
getFacilityList()
</script>
<style lang="scss" scoped>
.wrap {
width: 100%;
height: 100%;
}
.facility-box {
display: flex;
height: calc(100vh - 100px);
overflow: hidden;
flex: 1;
.state-left {
width: 20%;
padding-right: 20px;
overflow-y: auto;
.treeinp {
margin-bottom: 20px;
}
}
.state-right {
flex: 1;
display: flex;
flex-direction: column;
border-left: 1px solid #ccc;
padding-left: 20px;
overflow-y: auto;
.iscollpInp {
display: flex;
margin-bottom: 20px;
.demo-ruleForm {
flex: 1;
display: flex;
flex-wrap: wrap;
.el-form-item {
width: 50%;
margin-bottom: 0;
}
}
.fromBtn {
display: flex;
align-items: flex-start;
}
}
.table-container {
flex: 1;
display: flex;
flex-direction: column;
.spocebtn {
margin-bottom: 10px;
}
}
}
}
</style>

119
vite.config.js 100644
View File

@ -0,0 +1,119 @@
import { defineConfig, loadEnv } from 'vite'
import { resolve } from 'path'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import vue from '@vitejs/plugin-vue'
import myPlugin from './public/js/zip'
// console.log(env.VITE_BASE_URL, 'env.VITE_BASE_URL')
// https://vitejs.dev/config/
export default ({ mode }) => {
const env = loadEnv(mode, process.cwd())
return defineConfig({
base: env.VITE_BASE_URL, // 设置打包路径
build: {
outDir: env.VITE_BASE_NAME,
chunkSizeWarningLimit: 10240,
rollupOptions: {
output: {
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString()
}
},
},
},
},
plugins: [
vue(),
myPlugin(env.VITE_BASE_NAME, env.VITE_BASE_URL),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
createSvgIconsPlugin({
iconDirs: [resolve(process.cwd(), 'public/img/icons')],
symbolId: 'icon-[name]',
}),
],
resolve: {
extensions: ['.js', '.vue', '.json'], //可省略文件后缀
alias: {
vue: 'vue/dist/vue.esm-bundler.js',
'@static': resolve('public'),
'@': resolve(__dirname, 'src'), // 设置 `@` 指向 `src` 目录
assets: resolve(__dirname, './src/assets'),
},
},
server: {
host: '0.0.0.0',
port: 8080, // 设置服务启动端口号
open: false, // 设置服务启动时是否自动打开浏览器
cors: true, // 允许跨域
hmr: {
overlay: false, // 禁用服务器错误遮罩层
},
// 设置代理
proxy: {
'/upload': {
target: 'http://111.10.228.245:8088/external',
changeOrigin: true,
secure: false,
rewrite: path => path.replace('/upload/', '/upload/'),
},
'/aiopsfile': {
target: 'http://111.10.228.245:8088/external',
changeOrigin: true,
secure: false,
},
[env.VITE_APP_REQUESTURL]: {
target: env.VITE_APP_PROXYURL,
// target: 'http://192.168.1.24:8081', //http://111.10.228.245:8088
ws: true,
changeOrigin: true,
secure: false,
rewrite: path => path.replace(env.VITE_APP_REQUESTURL, ''),
},
'/uri': {
target: 'https://apis.map.qq.com',
changeOrigin: true,
secure: false,
},
},
},
css: {
preprocessorOptions: {
scss: {
charset: false,
javascriptEnabled: true,
additionalData: `@import "./src/assets/styles/global.scss";`,
},
},
postcss: {
plugins: [
{
postcssPlugin: 'internal:charset-removal',
AtRule: {
charset: atRule => {
if (atRule.name === 'charset') {
atRule.remove()
}
},
},
},
],
},
},
webPreferences: {
nodeIntegration: true,
contextIsolation: false, // 允许使用define
},
})
}

1
启动服务.bat 100644
View File

@ -0,0 +1 @@
start cmd /k "yarn dev"

1
项目打包.bat 100644
View File

@ -0,0 +1 @@
start cmd /k "yarn build"