清空代码
|
@ -1,6 +0,0 @@
|
|||
VITE_BASE_URL = "./"
|
||||
VITE_BASE_NAME = "pms-front-test"
|
||||
VITE_APP_PROXYURL = 'http://192.168.124.202:8080'
|
||||
VITE_APP_REQUESTURL = '/pms-front-test'
|
||||
VITE_APP_ROUTERURL = '/pms-front-test/'
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
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/'
|
|
@ -1,6 +0,0 @@
|
|||
VITE_BASE_URL = "./"
|
||||
VITE_BASE_NAME = "dev"
|
||||
VITE_APP_PROXYURL = 'http://192.168.124.202:8080'
|
||||
VITE_APP_REQUESTURL = '/prod-api'
|
||||
VITE_APP_ROUTERURL = '/dev/'
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
pms-front
|
||||
dev
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# 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 +0,0 @@
|
|||
**/*none.js
|
11
.prettierrc
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "auto",
|
||||
"singleQuote": true,
|
||||
"jsxBracketSameLine": false,
|
||||
"htmlWhitespaceSensitivity": "ignore",
|
||||
"semi": false
|
||||
}
|
25
index.html
|
@ -1,25 +0,0 @@
|
|||
<!--
|
||||
* @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
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,12 +0,0 @@
|
|||
<?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>
|
Before Width: | Height: | Size: 1.0 KiB |
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* @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
Before Width: | Height: | Size: 43 KiB |
|
@ -1 +0,0 @@
|
|||
<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>
|
Before Width: | Height: | Size: 1.5 KiB |
25
src/App.vue
|
@ -1,25 +0,0 @@
|
|||
<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>
|
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 556 B |
Before Width: | Height: | Size: 315 KiB |
Before Width: | Height: | Size: 5.6 KiB |
|
@ -1,355 +0,0 @@
|
|||
@use 'sass:math';
|
||||
|
||||
|
||||
* {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//外边距
|
||||
.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;
|
||||
}
|
||||
|
||||
|
||||
/* 竖向弹性盒子 */
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// dialog弹出给body加了该类名,导致页面右侧偏移
|
||||
.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;
|
||||
}
|
||||
// 全局修改table表体内cell文字大小
|
||||
.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;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
: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;
|
||||
}
|
||||
|
||||
|
||||
: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;
|
||||
}
|
||||
|
||||
:deep(.el-dialog--center .el-dialog__body) {
|
||||
padding: 32px !important;
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
|
||||
/* 适用于所有页面的通用样式 */
|
||||
:root {
|
||||
--primary-color: #1890ff;
|
||||
--secondary-color: #f0f2f5;
|
||||
--text-color: #333333;
|
||||
--border-color: #e8e8e8;
|
||||
}
|
||||
|
||||
.page-container {
|
||||
background-color: var(--secondary-color);
|
||||
padding: 24px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
background-color: #ffffff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
:deep(.el-table) {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.el-table__header) {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #fafafa;
|
||||
color: var(--text-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.el-table__body td) {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* 按钮样式优化 */
|
||||
.el-button {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.el-button--primary {
|
||||
background-color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* 表单样式优化 */
|
||||
.el-form-item__label {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.el-input__wrapper,
|
||||
.el-select .el-input__wrapper {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 对话框样式优化 */
|
||||
:deep(.el-dialog) {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__header) {
|
||||
background-color: #fafafa;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__title) {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
:deep(.el-dialog__body) {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
padding: 16px 24px;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
/* 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;
|
||||
}
|
|
@ -1,80 +0,0 @@
|
|||
<!--
|
||||
* @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>
|
|
@ -1,161 +0,0 @@
|
|||
<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>
|
|
@ -1,120 +0,0 @@
|
|||
<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>
|
|
@ -1,430 +0,0 @@
|
|||
<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>
|
|
@ -1,116 +0,0 @@
|
|||
<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>
|
|
@ -1,114 +0,0 @@
|
|||
<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>
|
|
@ -1,559 +0,0 @@
|
|||
<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;
|
||||
// 获取规则数组[0秒、1分、2时、3日、4月、5星期、6年]
|
||||
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') {
|
||||
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
|
||||
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) {
|
||||
// 当星期6时只需判断不是1号就可进行操作
|
||||
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') {
|
||||
// 如果指定了每月最后一个星期几
|
||||
// 校验并调整如果是2月30号这种日期传进来时需调整至正常月底
|
||||
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)
|
||||
}
|
||||
}
|
||||
// 判断时间值是否小于10置换成“05”这种格式
|
||||
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--表示是否从0开始(则从1开始)
|
||||
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>
|
|
@ -1,117 +0,0 @@
|
|||
<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>
|
|
@ -1,202 +0,0 @@
|
|||
<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>
|
|
@ -1,131 +0,0 @@
|
|||
<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>
|
|
@ -1,206 +0,0 @@
|
|||
<template>
|
||||
<div class="custom-table" ref="tableContainer">
|
||||
<el-table
|
||||
ref="elTableRef"
|
||||
:data="tableData"
|
||||
:max-height="computedTableHeight"
|
||||
v-bind="$attrs"
|
||||
@selection-change="handleSelectionChange"
|
||||
:border="border"
|
||||
:highlight-current-row="highligt"
|
||||
>
|
||||
<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">
|
||||
<template v-if="column.type === 'multiButton'">
|
||||
<div class="button-group">
|
||||
<el-button
|
||||
v-for="(userName, userIndex) in scope.row[column.prop]"
|
||||
:key="userIndex"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="handleButtonClick(userName, column.prop)"
|
||||
>
|
||||
{{ userName }}
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.type === 'status'">
|
||||
<span v-if="column.callback" v-html="column.callback(scope.row[column.prop], scope.row)"></span>
|
||||
<span v-else>
|
||||
{{ scope.row[column.prop] }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ scope.row[column.prop] }}
|
||||
</template>
|
||||
</slot>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</el-table>
|
||||
<div class="pagination-container" ref="paginationContainer" v-if="showPagination">
|
||||
<el-pagination
|
||||
: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, 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],
|
||||
},
|
||||
maxHeight: {
|
||||
type: String,
|
||||
default: '100%',
|
||||
},
|
||||
offsetHeight: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
multiSelect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
highligt: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['selection-change', 'size-change', 'current-change', 'button-click'])
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const tableContainer = ref(null)
|
||||
const paginationContainer = ref(null)
|
||||
const computedTableHeight = ref(null)
|
||||
const elTableRef = ref(null)
|
||||
|
||||
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 parentElement = tableContainer.value.parentElement
|
||||
const parentHeight = parentElement.clientHeight
|
||||
const tableTop = tableContainer.value.getBoundingClientRect().top - parentElement.getBoundingClientRect().top
|
||||
const paginationHeight = props.showPagination ? paginationContainer.value.offsetHeight + 30 : 0 // 加上 margin-top
|
||||
computedTableHeight.value = parentHeight - tableTop - paginationHeight
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露 el-table 的方法
|
||||
defineExpose({
|
||||
clearSelection: () => elTableRef.value?.clearSelection(),
|
||||
toggleRowSelection: (row, selected) => elTableRef.value?.toggleRowSelection(row, selected),
|
||||
setCurrentRow: row => elTableRef.value?.setCurrentRow(row),
|
||||
// 可以根据需要暴露更多的方法
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
updateTableHeight()
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.custom-table {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
.pagination-container {
|
||||
margin-top: 10px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
:deep(.el-scrollbar__wrap--hidden-default) {
|
||||
min-height: 100px;
|
||||
}
|
||||
:deep(.el-table) {
|
||||
--el-table-text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table th) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table .cell) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 如果操作列需要特殊处理,可以添加以下样式 */
|
||||
:deep(.operation-column .cell) {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
:deep(.el-table__inner-wrapper::before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.el-table-column--selection) {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -1,231 +0,0 @@
|
|||
<template>
|
||||
<el-dialog title="选择人员" :modelValue="dialogVisible" width="50%" @close="handleClose">
|
||||
<div class="select-user-container">
|
||||
<div class="org-tree">
|
||||
<el-tree :data="treeData" :props="defaultProps" @node-click="handleNodeClick" default-expand-all />
|
||||
</div>
|
||||
<div class="user-list">
|
||||
<CustomTable
|
||||
ref="customTableRef"
|
||||
:columns="columns"
|
||||
:tableData="userData"
|
||||
:total="total"
|
||||
:show-selection="true"
|
||||
:show-index="true"
|
||||
:table-height="tableHeight"
|
||||
:multiSelect="multiSelect"
|
||||
@selection-change="handleSelectionChange"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<div class="operation-buttons">
|
||||
<el-button text type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工作日志</el-button>
|
||||
<el-button text type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</CustomTable>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="handleClose">取消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确认</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
import { systemApi } from '@/utils/api'
|
||||
|
||||
const props = defineProps({
|
||||
multiSelect: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
dialogVisible: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
currentSelectedUser: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:dialogVisible', 'close', 'confirm'])
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
const selectedUsers = ref([])
|
||||
const currentDepartment = ref('')
|
||||
const tableHeight = ref(350) // 设置一个合适的高度
|
||||
|
||||
// 组织树假数据
|
||||
const treeData = ref([])
|
||||
|
||||
const defaultProps = {
|
||||
children: 'children',
|
||||
label: 'label',
|
||||
}
|
||||
|
||||
// 表格列定义
|
||||
const columns = [
|
||||
{ prop: 'nickName', label: '姓名' },
|
||||
{ prop: 'dept', label: '部门', type: 'status', callback: (data, row) => data?.deptName },
|
||||
{ prop: 'roles', label: '角色', type: 'status', callback: (data, row) => data.map(ele => ele.roleName).join(',') },
|
||||
]
|
||||
|
||||
|
||||
const userData = ref([])
|
||||
|
||||
const handleNodeClick = data => {
|
||||
currentDepartment.value = data.id
|
||||
currentPage.value = 1
|
||||
fetchUserList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = val => {
|
||||
currentPage.value = val
|
||||
fetchUserList()
|
||||
}
|
||||
|
||||
const handleSizeChange = val => {
|
||||
pageSize.value = val
|
||||
fetchUserList()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
emit('update:dialogVisible', false)
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedUsers.value)
|
||||
handleClose()
|
||||
}
|
||||
const customTableRef = ref(null)
|
||||
const isInternalChange = ref(false)
|
||||
|
||||
const handleSelectionChange = val => {
|
||||
if (isInternalChange.value) return
|
||||
|
||||
if (!props.multiSelect) {
|
||||
isInternalChange.value = true
|
||||
nextTick(() => {
|
||||
if (val.length > 0) {
|
||||
const lastSelected = val[val.length - 1]
|
||||
selectedUsers.value = [lastSelected]
|
||||
customTableRef.value?.clearSelection()
|
||||
customTableRef.value?.toggleRowSelection(lastSelected, true)
|
||||
} else {
|
||||
selectedUsers.value = []
|
||||
}
|
||||
isInternalChange.value = false
|
||||
})
|
||||
} else {
|
||||
selectedUsers.value = val
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 currentSelectedUser 变化
|
||||
watch(
|
||||
() => props.currentSelectedUser,
|
||||
newVal => {
|
||||
isInternalChange.value = true
|
||||
nextTick(() => {
|
||||
selectedUsers.value = newVal
|
||||
if (customTableRef.value) {
|
||||
customTableRef.value.clearSelection()
|
||||
newVal.forEach(user => {
|
||||
const row = userData.value.find(item => item.userId === user.userId)
|
||||
if (row) {
|
||||
customTableRef.value.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
isInternalChange.value = false
|
||||
})
|
||||
},
|
||||
{ immediate: true, deep: true },
|
||||
)
|
||||
|
||||
// 在组件挂载时设置初始选中状态
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (customTableRef.value && props.currentSelectedUser.length > 0) {
|
||||
customTableRef.value.clearSelection()
|
||||
props.currentSelectedUser.forEach(user => {
|
||||
const row = userData.value.find(item => item.userId === user.userId)
|
||||
if (row) {
|
||||
customTableRef.value.toggleRowSelection(row, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const fetchUserList = async () => {
|
||||
// 在实际应用中,这里应该调用后端 API
|
||||
const response = await systemApi.getUserList({
|
||||
pageNum: currentPage.value,
|
||||
pageSize: pageSize.value,
|
||||
deptId: currentDepartment.value,
|
||||
})
|
||||
userData.value = response.rows
|
||||
total.value = response.total
|
||||
}
|
||||
const fetchTreeData = async () => {
|
||||
const response = await systemApi.getDeptTree()
|
||||
treeData.value = response.data
|
||||
}
|
||||
// 可以在组件挂载时调用这个方法获取初始数据
|
||||
onMounted(() => {
|
||||
fetchTreeData()
|
||||
fetchUserList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.select-user-container {
|
||||
display: flex;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.org-tree {
|
||||
width: 200px;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
padding-right: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.user-list {
|
||||
flex: 1;
|
||||
padding-left: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
:deep(.el-dialog__footer) {
|
||||
padding: 20px 20px 20px 0;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dialog-footer .el-button {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.dialog-footer .el-button:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* 不带插槽按钮点击回弹
|
||||
|
||||
*/
|
||||
export default {
|
||||
mounted(el) {
|
||||
el.addEventListener('click', (e) => {
|
||||
if (e.target.nodeName === 'SPAN') {
|
||||
e.target.parentNode.blur()
|
||||
}
|
||||
e.target.blur()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* @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,
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* @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)
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* @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
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* @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
|
|
@ -1,306 +0,0 @@
|
|||
<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
|
||||
// screen是window的属性方法,window.screen可省略window,指的是窗口
|
||||
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>
|
|
@ -1,138 +0,0 @@
|
|||
<!--
|
||||
* @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>
|
|
@ -1,51 +0,0 @@
|
|||
<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>
|
|
@ -1,96 +0,0 @@
|
|||
<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.matched得到list的值
|
||||
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); /*将盒子相对于X轴选择30度*/
|
||||
// }
|
||||
.el-tag {
|
||||
margin-right: 5px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
|
@ -1,87 +0,0 @@
|
|||
<!--
|
||||
* @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>
|
|
@ -1,150 +0,0 @@
|
|||
<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>
|
|
@ -1,116 +0,0 @@
|
|||
<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
|
||||
}
|
||||
// 删除tag返回新路由历史数组并存入vuex
|
||||
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宽度+20左右已经大于sroll宽度,表示tag已经到了尽头了
|
||||
// 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>
|
|
@ -1,43 +0,0 @@
|
|||
<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>
|
|
@ -1,19 +0,0 @@
|
|||
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)
|
||||
})
|
||||
}
|
52
src/main.js
|
@ -1,52 +0,0 @@
|
|||
|
||||
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'
|
||||
import '@/assets/styles/pagecss.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')
|
|
@ -1,9 +0,0 @@
|
|||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
setup() {},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped></style>
|
|
@ -1,125 +0,0 @@
|
|||
/*
|
||||
* @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
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* @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 {
|
||||
HomeFilled,
|
||||
Briefcase,
|
||||
User,
|
||||
Calendar,
|
||||
Document,
|
||||
Setting,
|
||||
PieChart,
|
||||
List,
|
||||
Tickets,
|
||||
Monitor
|
||||
} from '@element-plus/icons-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/list',
|
||||
meta: { title: '项目管理', imgSrc: 'slider/system' },
|
||||
component: shallowRef(layout),
|
||||
children: [
|
||||
{
|
||||
path: '/project/list',
|
||||
name: 'projectList',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目列表',
|
||||
},
|
||||
component: () => import('@/views/project/list.vue'),
|
||||
},
|
||||
{
|
||||
path: '/project/detail',
|
||||
name: 'projectDetail',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目详情',
|
||||
menuHide: true, // 添加这个属性来隐藏菜单项
|
||||
},
|
||||
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'),
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/worklog',
|
||||
name: 'worklog',
|
||||
authKey: 'Workorder',
|
||||
meta: { title: '工作日志', imgSrc: 'slider/worklog' }, // 您可能需要为工作日志添加一个图标
|
||||
component: shallowRef(layout),
|
||||
children: [
|
||||
{
|
||||
path: '/worklog/list',
|
||||
name: 'worklogList',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '工作日志',
|
||||
},
|
||||
component: () => import('@/views/workLog/list.vue'),
|
||||
},
|
||||
// 如果需要其他工作日志相关的子路由,可以在这里添加
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/projectBank',
|
||||
name: 'projectBank',
|
||||
authKey: 'Workorder',
|
||||
meta: { title: '项目看板', imgSrc: 'slider/worklog' }, // 您可能需要为工作日志添加一个图标
|
||||
component: shallowRef(layout),
|
||||
children: [
|
||||
{
|
||||
path: '/projectBank/projectProgress',
|
||||
name: 'projectProgress',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目执行表',
|
||||
},
|
||||
component: () => import('@/views/projectBank/projectProgress.vue'),
|
||||
},
|
||||
{
|
||||
path: '/projectBank/userProject',
|
||||
name: 'userProject',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '人员项目表',
|
||||
},
|
||||
component: () => import('@/views/projectBank/userProject.vue'),
|
||||
},
|
||||
{
|
||||
path: '/projectBank/projectUser',
|
||||
name: 'projectUser',
|
||||
authKey: 'Workorder',
|
||||
meta: {
|
||||
title: '项目人员表',
|
||||
},
|
||||
component: () => import('@/views/projectBank/projectUser.vue'),
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* @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: {
|
||||
}
|
||||
})
|
|
@ -1,93 +0,0 @@
|
|||
/*
|
||||
* @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)
|
||||
setToken(res.token)
|
||||
await this.updateUserInfo()
|
||||
ElMessage.success({ message: '登录成功' })
|
||||
const permissions = [...this.userInfo.permissions]
|
||||
// , '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() {
|
||||
const userInfo = await loginApi.getUserInfo()
|
||||
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')))
|
||||
// let result = filterAsyncRoutes(asyncRoutes, sessionPermissions)
|
||||
let result = asyncRoutes
|
||||
|
||||
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')
|
||||
},
|
||||
},
|
||||
})
|
|
@ -1,73 +0,0 @@
|
|||
import request from '@/utils/request'
|
||||
//登录板块api
|
||||
export const loginApi = {
|
||||
// 登录
|
||||
login: data => request('/login', 'json', data),
|
||||
// 退出登录
|
||||
loginOut: () => request('/logout', 'get'),
|
||||
//修改密码
|
||||
editPassword: data => request('/auth/user/updatePwd', 'post', data),
|
||||
// 获取验证码
|
||||
getCode: () => request('/captchaImage', 'captchaImage', {}, {
|
||||
SHOW_LOADING: false, headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}),
|
||||
getUserInfo: () => request('/getInfo', 'get'),
|
||||
}
|
||||
|
||||
// 项目板块
|
||||
export const projectApi = {
|
||||
// 查询项目列表
|
||||
listProject: query => request('/business/project/list', 'get', query),
|
||||
deleteProject: id => request(`/business/project/${id}`, 'delete'),
|
||||
addProject: data => request('/business/project/add', 'json', data),
|
||||
updateProject: data => request('/business/project/update', 'json', data),
|
||||
getProjectCode: () => request('/business/project/getCode', 'get'),
|
||||
getProjectDetail: id => request(`/business/project/info/${id}`, 'get'),
|
||||
// 项目成员
|
||||
getProjectUser: id => request(`/business/project/${id}`, 'get'),
|
||||
updateProjectUser: data => request('/business/project/team', 'json', data),
|
||||
deleteProjectUser: id => request(`/business/project/team/${id}`, 'delete'),
|
||||
}
|
||||
// 工作日志
|
||||
export const workLogApi = {
|
||||
userProject: id => request(`/business/work/hour/project/${id}`, 'get'),
|
||||
getLogData: data => request('/business/work/hour/calendar', 'json', data, { SHOW_LOADING: false }),
|
||||
getLogDataDetail: data => request('/business/work/hour/getInfo', 'json', data, { SHOW_LOADING: false }),
|
||||
addLog: data => request('/business/work/hour/add', 'json', data),
|
||||
}
|
||||
// 用户板块
|
||||
export const useUserApi = () => {
|
||||
|
||||
|
||||
}
|
||||
// 系统板块
|
||||
export const systemApi = {
|
||||
getUserList: data => request('/system/user/list', 'get', data),
|
||||
getDictData: dictCode => request('/system/dict/data/type/' + dictCode, 'get'),
|
||||
getDeptTree: () => request('/system/user/deptTree', 'get'),
|
||||
}
|
||||
// 系统定时任务
|
||||
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 }),
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* @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()
|
||||
}
|
|
@ -1,473 +0,0 @@
|
|||
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 和 array,for ... 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),
|
||||
)
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* @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,
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
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['Authorization'] = 'Bearer ' + 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'
|
||||
}else if (/captchaImage/i.test(method)) {
|
||||
method = 'get'
|
||||
opts.headers['Content-Type'] = 'application/json'
|
||||
}
|
||||
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 === 200) {
|
||||
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 => {
|
||||
opts.SHOW_LOADING && loadingInstance && loadingInstance.close()
|
||||
if (err.code === 'ERR_CANCELED') return
|
||||
ElMessage.error({
|
||||
dangerouslyUseHTMLString: true,
|
||||
message: err || '网络异常,请检查网络连接!',
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default doRequest
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* @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
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
/*
|
||||
* @FilePath : /src/utils/validate.js
|
||||
* @Description :校验
|
||||
*/
|
||||
|
||||
// 去空格空字符校验
|
||||
export const commonCheck = (rule, value, callback) => {
|
||||
if (!value || !value.trim()) {
|
||||
callback(new Error('内容不能为空!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* @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)
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
<!--
|
||||
* @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>
|
|
@ -1,287 +0,0 @@
|
|||
<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">账 号</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">密 码</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">验 证 码</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 { loginApi } from '@/utils/api'
|
||||
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()
|
||||
const uuid = ref()
|
||||
const updateCode = async () => {
|
||||
const res = await loginApi.getCode()
|
||||
codeSrc.value = 'data:image/png;base64,' + res.img
|
||||
uuid.value = res.uuid
|
||||
}
|
||||
|
||||
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: login.pwd,
|
||||
username: login.account,
|
||||
code: login.validateCode,
|
||||
uuid: uuid.value,
|
||||
}
|
||||
try {
|
||||
await userStore.login(params)
|
||||
routerHistory.value = []
|
||||
} catch (error) {
|
||||
updateCode()
|
||||
}
|
||||
}
|
||||
const usershadow = ref(false)
|
||||
const shadow = ref(false)
|
||||
const codeShadow = ref(false)
|
||||
updateCode()
|
||||
</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: #fff;
|
||||
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>
|
|
@ -1,676 +0,0 @@
|
|||
<template>
|
||||
<div class="project-management">
|
||||
<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="custom-form">
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="项目名称" prop="projectName">
|
||||
<el-input
|
||||
v-model="formData.projectName"
|
||||
placeholder="请输入项目名称"
|
||||
:disabled="isEditing"
|
||||
class="full-width longInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编码" prop="projectCode">
|
||||
<el-input v-model="formData.projectCode" class="full-width" :disabled="isEditing" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目负责人" prop="projectLeader">
|
||||
<el-input
|
||||
v-model="formData.projectLeaderName"
|
||||
placeholder="选择项目负责人"
|
||||
readonly
|
||||
:disabled="isEditing"
|
||||
@click="openProjectManagerSelect"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目状态" prop="projectState">
|
||||
<el-select
|
||||
v-model="formData.projectState"
|
||||
placeholder="请选择项目状态"
|
||||
:disabled="isEditing"
|
||||
class="full-width"
|
||||
>
|
||||
<el-option label="未启动" value="0" />
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="预算天数" prop="budgetDate">
|
||||
<el-input v-model.number="formData.budgetDate" :min="0" :disabled="isEditing" class="full-width" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="24">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开始日期" prop="startDate">
|
||||
<el-date-picker
|
||||
v-model="formData.startDate"
|
||||
type="date"
|
||||
placeholder="选择开始日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="结束日期" prop="endDate">
|
||||
<el-date-picker
|
||||
v-model="formData.endDate"
|
||||
type="date"
|
||||
placeholder="选择结束日期"
|
||||
format="YYYY-MM-DD"
|
||||
value-format="YYYY-MM-DD"
|
||||
class="full-width"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<div class="form-actions jcc">
|
||||
<el-button type="primary" @click="saveProject">保存</el-button>
|
||||
<el-button @click="cancelEdit">取消</el-button>
|
||||
</div>
|
||||
<div class="table-actions">
|
||||
<el-button type="primary" :icon="Plus" @click="addUser">新增成员</el-button>
|
||||
</div>
|
||||
|
||||
<CustomTable
|
||||
:columns="columns"
|
||||
:tableData="tableData"
|
||||
:total="total"
|
||||
:show-selection="false"
|
||||
:show-index="true"
|
||||
:show-pagination="false"
|
||||
:table-height="tableHeight"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #name="{ row }">
|
||||
<el-input
|
||||
v-if="row.isNew"
|
||||
v-model="row.userName"
|
||||
placeholder="选择人员"
|
||||
readonly
|
||||
@click="openSelectUser(row)"
|
||||
/>
|
||||
<span v-else>{{ row.userName }}</span>
|
||||
</template>
|
||||
<template #post="{ row }">
|
||||
<el-select
|
||||
v-if="row.isEditing || row.isNew"
|
||||
v-model="row.postId"
|
||||
placeholder="请选择职位"
|
||||
@change="handlePostChange(row)"
|
||||
>
|
||||
<el-option v-for="post in postOptions" :key="post" :label="post.dictLabel" :value="post.dictValue" />
|
||||
</el-select>
|
||||
<span v-else>{{ postOptions.find(post => post.dictValue === row.postId)?.dictLabel }}</span>
|
||||
</template>
|
||||
<template #operation="{ row }">
|
||||
<div class="operation-buttons">
|
||||
<template v-if="row.isNew">
|
||||
<el-button text type="primary" @click="confirmAddUser(row)">确认</el-button>
|
||||
<el-button text type="danger" @click="cancelAddUser(row)">取消</el-button>
|
||||
</template>
|
||||
<template v-else-if="row.isEditing">
|
||||
<el-button text type="primary" @click="saveUserEdit(row)">保存</el-button>
|
||||
<el-button text type="info" @click="cancelUserEdit(row)">取消</el-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-button text type="primary" @click="editUser(row)">编辑</el-button>
|
||||
<el-button text type="primary" @click="showTimesheet(row)">工作日志</el-button>
|
||||
<el-button text type="danger" @click="confirmDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</CustomTable>
|
||||
|
||||
<!-- 修改 SelectUser 组件 -->
|
||||
<SelectUser
|
||||
v-if="showSelectUser"
|
||||
v-model:dialogVisible="showSelectUser"
|
||||
:multi-select="false"
|
||||
:selected-users="currentSelectedUser"
|
||||
@confirm="handleUserConfirm"
|
||||
@close="closeSelectUser"
|
||||
/>
|
||||
|
||||
<SelectUser
|
||||
v-if="showProjectManagerSelect"
|
||||
v-model:dialogVisible="showProjectManagerSelect"
|
||||
:multi-select="false"
|
||||
:selected-users="projectManagerSelectedUser"
|
||||
@confirm="handleProjectManagerConfirm"
|
||||
@close="closeProjectManagerSelect"
|
||||
/>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog v-model="deleteDialogVisible" title="确认删除" width="30%">
|
||||
<div class="delete-confirm">
|
||||
<el-icon class="warning-icon"><WarningFilled /></el-icon>
|
||||
<p>确定要删除该成员吗?此操作不可逆。</p>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="deleteDialogVisible = false">取消</el-button>
|
||||
<el-button type="danger" @click="handleDelete">确认删除</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, computed, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
import { Plus,WarningFilled } from '@element-plus/icons-vue'
|
||||
import SelectUser from '@/components/SelectUser.vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { projectApi, systemApi } from '@/utils/api'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const formRef = ref(null)
|
||||
|
||||
const isEditing = computed(() => !!formData.projectId)
|
||||
const showSelectUser = ref(false)
|
||||
const showProjectManagerSelect = ref(false)
|
||||
const formData = reactive({
|
||||
projectId: null,
|
||||
projectName: '',
|
||||
projectCode: '',
|
||||
projectLeaderName: '',
|
||||
projectLeader: '',
|
||||
projectState: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
budgetDate: 0,
|
||||
state: 0,
|
||||
})
|
||||
|
||||
const postOptions = ref([]) // 根据实际情况调整
|
||||
|
||||
const columns = [
|
||||
{ prop: 'name', label: '姓名' },
|
||||
{ prop: 'post', label: '项目职位' },
|
||||
{ prop: 'workdays', label: '工作天数' },
|
||||
{ prop: 'operation', label: '操作', width: '250px' },
|
||||
]
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const currentEditingRow = ref(null)
|
||||
const currentSelectedUser = ref([])
|
||||
const projectManagerSelectedUser = ref([])
|
||||
|
||||
// 自动更新项目状态
|
||||
watch([() => formData.startDate, () => formData.endDate], updateProjectState, { immediate: true })
|
||||
|
||||
function updateProjectState() {
|
||||
const now = new Date().getTime()
|
||||
const start = formData.startDate ? new Date(formData.startDate).getTime() : null
|
||||
const end = formData.endDate ? new Date(formData.endDate).getTime() : null
|
||||
|
||||
if (start && end) {
|
||||
if (now < start) {
|
||||
formData.projectState = '0' // 未开始
|
||||
} else if (now >= start && now <= end) {
|
||||
formData.projectState = '1' // 进行中
|
||||
} else {
|
||||
formData.projectState = '2' // 已结束
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateDates = (rule, value, callback) => {
|
||||
if (formData.startDate && formData.endDate) {
|
||||
const start = new Date(formData.startDate)
|
||||
const end = new Date(formData.endDate)
|
||||
if (start > end) {
|
||||
callback(new Error('开始日期不能晚于结束日期'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
const rules = {
|
||||
projectName: [
|
||||
{ required: true, message: '请输入项目名称', trigger: 'blur' },
|
||||
{ max: 200, message: '项目名称不能超过200个字符', trigger: 'blur' },
|
||||
],
|
||||
projectCode: [
|
||||
{ required: true, message: '请输入项目编码', trigger: 'blur' },
|
||||
{ max: 50, message: '项目编码不能超过50个字符', trigger: 'blur' },
|
||||
],
|
||||
projectLeader: [{ required: true, message: '请选择项目负责人', trigger: 'change' }],
|
||||
projectState: [{ required: true, message: '请选择项目状态', trigger: 'change' }],
|
||||
startDate: [
|
||||
{ required: true, message: '请选择开始日期', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' },
|
||||
],
|
||||
endDate: [
|
||||
{ required: true, message: '请选择结束日期', trigger: 'change' },
|
||||
{ validator: validateDates, trigger: 'change' },
|
||||
],
|
||||
budgetDate: [
|
||||
{ required: true, message: '请输入预算天数', trigger: 'blur' },
|
||||
{ type: 'number', min: 1, message: '预算天数必须大于0', trigger: 'blur' },
|
||||
],
|
||||
}
|
||||
|
||||
const saveProject = async () => {
|
||||
if (!formRef.value) return
|
||||
|
||||
try {
|
||||
await formRef.value.validate()
|
||||
// 表单验证通过,继续保存操作
|
||||
const projectDataToSave = {
|
||||
...formData,
|
||||
startDate: formData.startDate ? new Date(formData.startDate).getTime() : null,
|
||||
endDate: formData.endDate ? new Date(formData.endDate).getTime() : null,
|
||||
}
|
||||
if (!formData.projectId) {
|
||||
const res = await projectApi.addProject(projectDataToSave)
|
||||
formData.projectId = res.data.projectId
|
||||
} else {
|
||||
await projectApi.updateProject(projectDataToSave)
|
||||
}
|
||||
ElMessage.success('项目保存成功')
|
||||
// 如果是新建项目,保存后不要立即返回,而是停留在当前页面
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
// 表单验证失败
|
||||
console.error('表单验证失败', error)
|
||||
ElMessage.error('请检查表单填写是否正确')
|
||||
} else {
|
||||
// API 调用失败
|
||||
console.error('保存项目失败', error)
|
||||
ElMessage.error('保存项目失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
router.back()
|
||||
}
|
||||
|
||||
const addUser = () => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再添加用户')
|
||||
return
|
||||
}
|
||||
|
||||
const newUser = {
|
||||
id: Date.now(),
|
||||
name: '',
|
||||
post: '',
|
||||
postId: '',
|
||||
workdays: 0,
|
||||
userId: null,
|
||||
isNew: true,
|
||||
}
|
||||
tableData.value.unshift(newUser)
|
||||
total.value = tableData.value.length
|
||||
}
|
||||
|
||||
const handlePostChange = row => {
|
||||
// 可以在这里添加一些额外的逻辑,如果需要的话
|
||||
}
|
||||
|
||||
const confirmAddUser = async row => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再添加用户')
|
||||
return
|
||||
}
|
||||
|
||||
if (!row.userId || !row.postId) {
|
||||
ElMessage.warning('请选择用户并指定职位')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
let params = {
|
||||
projectId: formData.projectId,
|
||||
userId: row.userId,
|
||||
postId: row.postId,
|
||||
}
|
||||
if (row.teamId) params.teamId = row.teamId
|
||||
await projectApi.updateProjectUser(params)
|
||||
row.isNew = false
|
||||
ElMessage.success('成员添加成功')
|
||||
getProjectUser()
|
||||
} catch (error) {
|
||||
console.error('添加成员失败', error)
|
||||
ElMessage.error('添加成员失败')
|
||||
}
|
||||
}
|
||||
|
||||
const cancelAddUser = row => {
|
||||
const index = tableData.value.findIndex(item => item.id === row.id)
|
||||
if (index !== -1) {
|
||||
tableData.value.splice(index, 1)
|
||||
total.value = tableData.value.length
|
||||
}
|
||||
}
|
||||
|
||||
const openSelectUser = row => {
|
||||
currentEditingRow.value = row
|
||||
currentSelectedUser.value = row.userId ? [{ id: row.userId, name: row.name }] : []
|
||||
showSelectUser.value = true
|
||||
}
|
||||
|
||||
const handleUserConfirm = selectedUsers => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息后再选择用户')
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedUsers.length > 0) {
|
||||
const selectedUser = selectedUsers[0]
|
||||
currentEditingRow.value.userName = selectedUser.nickName
|
||||
currentEditingRow.value.userId = selectedUser.userId
|
||||
} else {
|
||||
currentEditingRow.value.name = ''
|
||||
currentEditingRow.value.userId = null
|
||||
}
|
||||
showSelectUser.value = false
|
||||
}
|
||||
|
||||
const closeSelectUser = () => {
|
||||
showSelectUser.value = false
|
||||
currentEditingRow.value = null
|
||||
currentSelectedUser.value = []
|
||||
}
|
||||
|
||||
const openProjectManagerSelect = () => {
|
||||
projectManagerSelectedUser.value = formData.projectLeader
|
||||
? [{ id: formData.projectLeaderId, name: formData.projectLeader }]
|
||||
: []
|
||||
showProjectManagerSelect.value = true
|
||||
}
|
||||
|
||||
const handleProjectManagerConfirm = users => {
|
||||
if (users.length > 0) {
|
||||
const selectedUser = users[0]
|
||||
formData.projectLeaderName = selectedUser.nickName
|
||||
formData.projectLeader = selectedUser.userId
|
||||
} else {
|
||||
formData.projectLeaderName = ''
|
||||
formData.projectLeader = null
|
||||
}
|
||||
showProjectManagerSelect.value = false
|
||||
}
|
||||
|
||||
const closeProjectManagerSelect = () => {
|
||||
showProjectManagerSelect.value = false
|
||||
}
|
||||
|
||||
const handleSizeChange = val => {
|
||||
console.log('每页显示条数:', val)
|
||||
}
|
||||
|
||||
const handleCurrentChange = val => {
|
||||
console.log('当前页:', val)
|
||||
}
|
||||
|
||||
const showTimesheet = row => {
|
||||
router.push({
|
||||
path: '/worklog/list',
|
||||
query: { userId: row.userId, projectId: formData.projectId },
|
||||
})
|
||||
}
|
||||
|
||||
const deleteDialogVisible = ref(false)
|
||||
|
||||
const confirmDelete = row => {
|
||||
ElMessageBox.confirm('确定要删除该成员吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
.then(() => {
|
||||
deleteUser(row)
|
||||
})
|
||||
.catch(() => {
|
||||
// 取消删除操作
|
||||
})
|
||||
}
|
||||
|
||||
const deleteUser = async row => {
|
||||
try {
|
||||
await projectApi.deleteProjectUser(row.teamId)
|
||||
ElMessage.success('成员删除成功')
|
||||
getProjectUser()
|
||||
} catch (error) {
|
||||
console.error('删除成员失败', error)
|
||||
ElMessage.error('删除成员失败')
|
||||
}
|
||||
}
|
||||
|
||||
const tableHeight = ref(400)
|
||||
// 获取项目编码
|
||||
const getProjectCode = async () => {
|
||||
const res = await projectApi.getProjectCode()
|
||||
formData.projectCode = res.data
|
||||
}
|
||||
|
||||
const fetchProjectData = async id => {
|
||||
try {
|
||||
const response = await projectApi.getProjectDetail(id)
|
||||
const projectData = response.data
|
||||
// 将时间戳转换为日期字符串
|
||||
formData.startDate = projectData.startDate ? new Date(projectData.startDate).toISOString().split('T')[0] : ''
|
||||
formData.endDate = projectData.endDate ? new Date(projectData.endDate).toISOString().split('T')[0] : ''
|
||||
// 复制其他字段
|
||||
Object.assign(formData, {
|
||||
...projectData,
|
||||
startDate: formData.startDate,
|
||||
endDate: formData.endDate,
|
||||
})
|
||||
updateProjectState() // 更新项目状态
|
||||
} catch (error) {
|
||||
console.error('获取项目数据失败', error)
|
||||
ElMessage.error('获取项目数据失败')
|
||||
}
|
||||
}
|
||||
// 获取数据字典
|
||||
const getDictData = async () => {
|
||||
const res = await systemApi.getDictData('business_positions')
|
||||
postOptions.value = res.data
|
||||
}
|
||||
// 获取项目人员
|
||||
const getProjectUser = async () => {
|
||||
await getDictData()
|
||||
const res = await projectApi.getProjectUser(formData.projectId)
|
||||
tableData.value = res.data
|
||||
}
|
||||
|
||||
const editUser = row => {
|
||||
row.isEditing = true
|
||||
row.originalPost = row.postId // 保存原始职位,以便取消时恢复
|
||||
}
|
||||
|
||||
const saveUserEdit = async row => {
|
||||
if (!formData.projectId) {
|
||||
ElMessage.warning('请先保存项目信息')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await projectApi.updateProjectUser({
|
||||
projectId: formData.projectId,
|
||||
userId: row.userId,
|
||||
postId: row.postId,
|
||||
teamId: row.teamId,
|
||||
})
|
||||
row.isEditing = false
|
||||
delete row.originalPost
|
||||
ElMessage.success('用户信息更新成功')
|
||||
} catch (error) {
|
||||
console.error('更新用户信息失败', error)
|
||||
ElMessage.error('更新用户信息失败')
|
||||
}
|
||||
}
|
||||
|
||||
const cancelUserEdit = row => {
|
||||
row.postId = row.originalPost
|
||||
row.isEditing = false
|
||||
delete row.originalPost
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const projectId = route.params.id || route.query.id
|
||||
formData.projectId = projectId
|
||||
if (projectId) {
|
||||
fetchProjectData(projectId)
|
||||
getProjectUser()
|
||||
} else {
|
||||
updateProjectState() // 对于新项目,也要初始化状态
|
||||
getProjectCode()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-management {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
height: 88vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
.custom-form {
|
||||
width: 80%;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-container {
|
||||
width: 100%;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.12);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.custom-form :deep(.el-form-item) {
|
||||
margin-bottom: 25px; /* 增加表单行间距 */
|
||||
}
|
||||
|
||||
.custom-form :deep(.el-form-item__content) {
|
||||
margin-left: auto !important;
|
||||
width: 80%;
|
||||
}
|
||||
.custom-form :deep(.el-input) {
|
||||
height: 42px; /* 调高输入框高度 */
|
||||
}
|
||||
:deep(.el-form-item__label) {
|
||||
height: 42px; /* 调高输入框高度 */
|
||||
line-height: 42px;
|
||||
}
|
||||
.custom-form :deep(.el-input__wrapper),
|
||||
.custom-form :deep(.el-date-editor.el-input),
|
||||
.custom-form :deep(.el-input-number) {
|
||||
height: 42px; /* 调高输入框高度 */
|
||||
}
|
||||
.custom-form :deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
.custom-form :deep(.el-input__wrapper),
|
||||
.custom-form :deep(.el-select .el-input__wrapper),
|
||||
.custom-form :deep(.el-date-editor.el-input .el-input__wrapper),
|
||||
.custom-form :deep(.el-input-number .el-input__wrapper) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.table-actions {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
/* 添加以下样式以使日期选择器宽度一致 */
|
||||
:deep(.el-date-editor.el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input .el-input__wrapper) {
|
||||
width: 100%;
|
||||
}
|
||||
:deep(.longInput) {
|
||||
width: 100% !important;
|
||||
}
|
||||
.el-button.is-text {
|
||||
min-width: 32px !important;
|
||||
}
|
||||
|
||||
:deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-select .el-input__wrapper) {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.delete-confirm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
|
@ -1,357 +0,0 @@
|
|||
<template>
|
||||
<div class="project-list">
|
||||
<div class="search-bar">
|
||||
<el-form :inline="true" :model="searchForm" class="demo-form-inline">
|
||||
<el-form-item label="项目名称" class="form-item">
|
||||
<el-input v-model="searchForm.projectName" placeholder="项目名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="负责人" class="form-item">
|
||||
<el-input
|
||||
v-model="searchForm.projectLeaderName"
|
||||
placeholder="负责人"
|
||||
readonly
|
||||
@click="openUserSelectDialog"
|
||||
>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目状态" class="form-item">
|
||||
<el-select v-model="searchForm.projectState" placeholder="项目状态">
|
||||
<el-option label="未启动" value="0" />
|
||||
<el-option label="进行中" value="1" />
|
||||
<el-option label="已完成" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item class="search-buttons">
|
||||
<el-button type="primary" @click="onSearch">查询</el-button>
|
||||
<el-button @click="onReset">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div class="table-actions mb10">
|
||||
<el-button type="primary" @click="addProject">+ 新建项目</el-button>
|
||||
</div>
|
||||
|
||||
<CustomTable
|
||||
:columns="columns"
|
||||
:tableData="tableData"
|
||||
:total="total"
|
||||
:show-selection="false"
|
||||
:show-index="true"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #operation="{ row }">
|
||||
<div class="operation-buttons">
|
||||
<el-button text type="primary" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button text type="danger" @click="handleDelete(row)">删除</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</CustomTable>
|
||||
|
||||
<el-dialog
|
||||
v-model="deleteDialogVisible"
|
||||
title="删除项目"
|
||||
width="30%"
|
||||
:before-close="handleCloseDeleteDialog"
|
||||
class="delete-dialog"
|
||||
>
|
||||
<div class="delete-content">
|
||||
<el-icon class="warning-icon"><WarningFilled /></el-icon>
|
||||
<span>删除项目时将删除相关工作日志,此操作不可逆,请慎重考虑</span>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="deleteDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="confirmDelete">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 用户选择弹框 -->
|
||||
<SelectUser
|
||||
v-model:dialogVisible="userSelectDialogVisible"
|
||||
:multiSelect="false"
|
||||
:currentSelectedUser="currentSelectedUser"
|
||||
@close="handleUserSelectClose"
|
||||
@confirm="handleUserConfirm"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
import SelectUser from '@/components/selectUser.vue'
|
||||
import { WarningFilled } from '@element-plus/icons-vue'
|
||||
import { projectApi } from '@/utils/api'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const searchForm = reactive({
|
||||
projectName: '',
|
||||
projectLeaderName: '',
|
||||
projectLeader: '',
|
||||
projectState: '',
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{ prop: 'projectName', label: '项目名称' },
|
||||
{ prop: 'projectCode', label: '项目编号' },
|
||||
{ prop: 'projectLeaderName', label: '负责人' },
|
||||
{ prop: 'budgetDate', label: '预计工时(天)' },
|
||||
{
|
||||
prop: 'startDate',
|
||||
label: '开始时间',
|
||||
type: 'status',
|
||||
callback: (data, row) => {
|
||||
return data.split(' ')[0]
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'endDate',
|
||||
label: '结束时间',
|
||||
type: 'status',
|
||||
callback: (data, row) => {
|
||||
return data.split(' ')[0]
|
||||
},
|
||||
},
|
||||
{
|
||||
prop: 'projectState',
|
||||
label: '项目状态',
|
||||
type: 'status',
|
||||
callback: (value, row) => {
|
||||
let status = '未知'
|
||||
let color = ''
|
||||
|
||||
switch (value) {
|
||||
case '0':
|
||||
status = '未启动'
|
||||
color = '#909399' // 灰色
|
||||
break
|
||||
case '1':
|
||||
status = '进行中'
|
||||
color = '#409EFF' // 蓝色
|
||||
break
|
||||
case '2':
|
||||
status = '已完成'
|
||||
color = '#67C23A' // 绿色
|
||||
break
|
||||
}
|
||||
|
||||
return `<span style="color: ${color}">${status}</span>`
|
||||
},
|
||||
},
|
||||
{ prop: 'teamNum', label: '参与项目人数' },
|
||||
{ prop: 'createByName', label: '项目创建人' },
|
||||
{
|
||||
prop: 'operation',
|
||||
label: '操作',
|
||||
width: '150',
|
||||
fixed: 'right',
|
||||
className: 'operation-column',
|
||||
},
|
||||
]
|
||||
|
||||
const tableData = ref([])
|
||||
const total = ref(0)
|
||||
const deleteDialogVisible = ref(false)
|
||||
const currentDeleteItem = ref(null)
|
||||
|
||||
const pageNum = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 用户选择相关
|
||||
const userSelectDialogVisible = ref(false)
|
||||
const currentSelectedUser = ref([])
|
||||
|
||||
const openUserSelectDialog = () => {
|
||||
userSelectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleUserSelectClose = () => {
|
||||
userSelectDialogVisible.value = false
|
||||
}
|
||||
|
||||
const handleUserConfirm = (selectedUsers) => {
|
||||
if (selectedUsers.length > 0) {
|
||||
const selectedUser = selectedUsers[0]
|
||||
searchForm.projectLeaderName = selectedUser.nickName
|
||||
searchForm.projectLeader = selectedUser.userId
|
||||
currentSelectedUser.value = [selectedUser]
|
||||
} else {
|
||||
searchForm.projectLeaderName = ''
|
||||
searchForm.projectLeader = ''
|
||||
currentSelectedUser.value = []
|
||||
}
|
||||
userSelectDialogVisible.value = false
|
||||
}
|
||||
|
||||
const onSearch = () => {
|
||||
// 实现搜索逻辑
|
||||
console.log('Search with:', searchForm)
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const onReset = () => {
|
||||
Object.keys(searchForm).forEach(key => {
|
||||
searchForm[key] = ''
|
||||
})
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const addProject = () => {
|
||||
router.push('/project/detail')
|
||||
}
|
||||
|
||||
const handleEdit = row => {
|
||||
router.push({
|
||||
path: '/project/detail',
|
||||
query: { id: row.projectId },
|
||||
state: { projectData: row },
|
||||
})
|
||||
}
|
||||
|
||||
const handleDelete = row => {
|
||||
currentDeleteItem.value = row
|
||||
deleteDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleCloseDeleteDialog = () => {
|
||||
deleteDialogVisible.value = false
|
||||
currentDeleteItem.value = null
|
||||
}
|
||||
|
||||
const confirmDelete = async () => {
|
||||
// 实现删除逻辑
|
||||
await projectApi.deleteProject(currentDeleteItem.value.projectId)
|
||||
deleteDialogVisible.value = false
|
||||
currentDeleteItem.value = null
|
||||
ElMessage.success('删除成功')
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const handleSizeChange = val => {
|
||||
pageSize.value = val
|
||||
pageNum.value = 1 // 重置为第一页
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const handleCurrentChange = val => {
|
||||
pageNum.value = val
|
||||
fetchProjectList()
|
||||
}
|
||||
|
||||
const fetchProjectList = async () => {
|
||||
// 这里使用假数据,实际应该调用API
|
||||
const res = await projectApi.listProject({
|
||||
...searchForm,
|
||||
pageNum: pageNum.value,
|
||||
pageSize: pageSize.value,
|
||||
})
|
||||
tableData.value = res.rows
|
||||
total.value = res.total
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
fetchProjectList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-list {
|
||||
padding: 20px;
|
||||
background-color: white;
|
||||
height: 88vh;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.demo-form-inline {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.demo-form-inline .el-form-item {
|
||||
margin-right: 50px; /* 将间距设置为 30px */
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.demo-form-inline .el-form-item:last-child {
|
||||
margin-right: 0; /* 移除最后一个元素的右边距 */
|
||||
}
|
||||
|
||||
.form-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.form-item :deep(.el-form-item__content) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-item :deep(.el-input),
|
||||
.form-item :deep(.el-select) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-buttons {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
: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);
|
||||
}
|
||||
|
||||
.el-button.is-text {
|
||||
min-width: 32px !important;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dialog-footer .el-button {
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.delete-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.warning-icon {
|
||||
font-size: 24px;
|
||||
color: #e6a23c;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
/* 可以删除或注释掉之前的 .el-button.is-text 样式,如果不再需要的话 */
|
||||
/* .el-button.is-text {
|
||||
min-width: 32px !important;
|
||||
} */
|
||||
|
||||
/* 添加以下样式来使对话框垂直居中 */
|
||||
:deep(.delete-dialog.el-dialog) {
|
||||
margin-top: 0 !important;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
</style>
|
|
@ -1,336 +0,0 @@
|
|||
<template>
|
||||
<div class="project-progress-container">
|
||||
<!-- 左侧固定列表格 -->
|
||||
<div class="left-section">
|
||||
<h2 class="mb20 textC">项目执行表</h2>
|
||||
<CustomTable
|
||||
:columns="fixedColumns"
|
||||
:tableData="executionData"
|
||||
:showPagination="false"
|
||||
:maxHeight="tableHeight"
|
||||
:showSummary="true"
|
||||
:summaryMethod="getFixedColumnsSummaries"
|
||||
>
|
||||
<template #name="{ row }">
|
||||
<span class="project-name" @click="goToDetail(row)">{{ row.name }}</span>
|
||||
</template>
|
||||
</CustomTable>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滚动列表格 -->
|
||||
<div class="right-section">
|
||||
<div class="date-range-container">
|
||||
<span class="date-range-label">统计时间:</span>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<CustomTable
|
||||
:columns="scrollableColumns"
|
||||
:tableData="executionData"
|
||||
:showPagination="false"
|
||||
:maxHeight="tableHeight"
|
||||
:showSummary="true"
|
||||
:summaryMethod="getScrollableColumnsSummaries"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const dateRange = ref(getDefaultDateRange())
|
||||
const tableHeight = ref('100%')
|
||||
|
||||
function getDefaultDateRange() {
|
||||
const now = new Date()
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||
return [startOfMonth, endOfMonth]
|
||||
}
|
||||
|
||||
const fixedColumns = [
|
||||
{ prop: 'name', label: '项目', slot: 'name' },
|
||||
{ prop: 'presetDays', label: '预计工时\n(天)', width: 100 },
|
||||
{ prop: 'actualDays', label: '累计工时\n(天)', width: 100 },
|
||||
]
|
||||
|
||||
const scrollableColumns = computed(() => {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
const days = []
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
days.push({
|
||||
prop: dateStr,
|
||||
label: `${dayOfWeek}\n${dateStr}`,
|
||||
minWidth: 100
|
||||
})
|
||||
}
|
||||
return days
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const executionData = computed(() => {
|
||||
const projectList = [
|
||||
{ id: 1, name: '项目1', presetDays: 20, actualDays: 21 },
|
||||
{ id: 2, name: '项目2', presetDays: 20, actualDays: 13 },
|
||||
{ id: 3, name: '项目3', presetDays: 20, actualDays: 15 },
|
||||
{ id: 4, name: '项目4', presetDays: 20, actualDays: 17 },
|
||||
{ id: 5, name: '项目5', presetDays: 20, actualDays: 18 },
|
||||
]
|
||||
|
||||
return projectList.map(project => {
|
||||
const data = {
|
||||
...project,
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
data[dateStr] = Math.floor(Math.random() * 8) // 随机生成0-8的工时据
|
||||
}
|
||||
}
|
||||
return data
|
||||
})
|
||||
})
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||
}
|
||||
|
||||
const getFixedColumnsSummaries = param => {
|
||||
const { columns, data } = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
if (index === 0) {
|
||||
sums[index] = '合计工时(天)'
|
||||
return
|
||||
}
|
||||
const values = data.map(item => Number(item[column.property]))
|
||||
if (!values.every(value => isNaN(value))) {
|
||||
sums[index] = values.reduce((prev, curr) => {
|
||||
const value = Number(curr)
|
||||
if (!isNaN(value)) {
|
||||
return prev + curr
|
||||
} else {
|
||||
return prev
|
||||
}
|
||||
}, 0)
|
||||
sums[index] = Number(sums[index].toFixed(2))
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
const getScrollableColumnsSummaries = param => {
|
||||
const { columns, data } = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
const values = data.map(item => Number(item[column.property]))
|
||||
if (!values.every(value => isNaN(value))) {
|
||||
sums[index] = values.reduce((prev, curr) => {
|
||||
const value = Number(curr)
|
||||
if (!isNaN(value)) {
|
||||
return prev + curr
|
||||
} else {
|
||||
return prev
|
||||
}
|
||||
}, 0)
|
||||
sums[index] = Number(sums[index].toFixed(2))
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
const updateTableHeight = () => {
|
||||
nextTick(() => {
|
||||
const container = document.querySelector('.project-execution-table')
|
||||
if (container) {
|
||||
const containerHeight = container.clientHeight
|
||||
const otherElementsHeight =
|
||||
container.querySelector('h2').offsetHeight + container.querySelector('.date-range').offsetHeight
|
||||
tableHeight.value = `${containerHeight - otherElementsHeight - 40}px` // 40px for margins and paddings
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToDetail = (row) => {
|
||||
router.push({
|
||||
name: 'ProjectDetail',
|
||||
params: { id: row.id },
|
||||
query: {
|
||||
startDate: dateRange.value[0].toISOString().split('T')[0],
|
||||
endDate: dateRange.value[1].toISOString().split('T')[0]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
updateTableHeight()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-progress-container {
|
||||
display: flex;
|
||||
height: 88vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.left-section,
|
||||
.right-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
width: 400px; /* 根据固定列的总宽度调整 */
|
||||
padding-top: 34px;
|
||||
padding-bottom: 20px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||
z-index: 1; /* 确保左侧在右侧之上 */
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||
}
|
||||
|
||||
.placeholder-header {
|
||||
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||
}
|
||||
|
||||
.date-range {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
/* border: 1px solid #dcdfe6; */
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #4a4a4a;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__body td) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: bold;
|
||||
text-align: center; /* 确保合计行内容居中 */
|
||||
}
|
||||
|
||||
:deep(.el-table__header .cell),
|
||||
:deep(.el-table__body .cell),
|
||||
:deep(.el-table__footer .cell) {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.2;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 确保两个表格的高度一致 */
|
||||
.left-section :deep(.el-table),
|
||||
.right-section :deep(.el-table) {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 调整日期选择器样式 */
|
||||
:deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整表格内容的字体大小 */
|
||||
:deep(.el-table) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 调整表头的样式 */
|
||||
:deep(.el-table__header-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.rightTab :deep(.el-table__header th) {
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 调整合计行的样式 */
|
||||
:deep(.el-table__footer-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa !important;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-range-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
width: 500px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.date-range-label {
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input__wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
.custom-table {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
cursor: pointer;
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.project-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
|
@ -1,316 +0,0 @@
|
|||
<template>
|
||||
<div class="project-progress-container">
|
||||
<!-- 左侧项目信息表单 -->
|
||||
<div class="left-section">
|
||||
<h3 class="mb20 ml10 textl">项目人员表</h3>
|
||||
<el-form :model="projectInfo" label-width="120px" class="project-info-form">
|
||||
<el-form-item label="选择项目">
|
||||
<el-select v-model="selectedProject" placeholder="请选择项目" @change="handleProjectChange">
|
||||
<el-option
|
||||
v-for="project in projectList"
|
||||
:key="project.id"
|
||||
:label="project.name"
|
||||
:value="project.id"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目名称">
|
||||
<span>{{ projectInfo.name }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目编码">
|
||||
<span>{{ projectInfo.code }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="预计工时">
|
||||
<span>{{ projectInfo.presetDays }} 天</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目开始时间">
|
||||
<span>{{ projectInfo.startDate }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="项目结束时间">
|
||||
<span>{{ projectInfo.endDate }}</span>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滚动列表格 -->
|
||||
<div class="right-section">
|
||||
<div class="date-range-container">
|
||||
<span class="date-range-label">统计时间:</span>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<CustomTable
|
||||
:columns="scrollableColumns"
|
||||
:tableData="executionData"
|
||||
:showPagination="false"
|
||||
:maxHeight="tableHeight"
|
||||
@button-click="handleButtonClick"
|
||||
>
|
||||
</CustomTable>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const dateRange = ref(getDefaultDateRange())
|
||||
const tableHeight = ref('100%')
|
||||
const selectedProject = ref(null)
|
||||
const projectInfo = ref({
|
||||
name: '',
|
||||
code: '',
|
||||
presetDays: '',
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
})
|
||||
|
||||
const projectList = [
|
||||
{ id: 1, name: '项目1', code: 'XM001', presetDays: 120, startDate: '2024-09-01', endDate: '2024-09-30' },
|
||||
{ id: 2, name: '项目2', code: 'XM002', presetDays: 90, startDate: '2024-10-01', endDate: '2024-12-31' },
|
||||
// 添加更多项目...
|
||||
]
|
||||
|
||||
function getDefaultDateRange() {
|
||||
const now = new Date()
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||
return [startOfMonth, endOfMonth]
|
||||
}
|
||||
|
||||
const scrollableColumns = computed(() => {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
const days = []
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
days.push({
|
||||
prop: dateStr,
|
||||
label: `${dayOfWeek}\n${dateStr}`,
|
||||
minWidth: 100,
|
||||
type: 'multiButton'
|
||||
})
|
||||
}
|
||||
return days
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const executionData = computed(() => {
|
||||
// 这里我们模拟一些数据,实际使用时应该从后端获取
|
||||
const data = {
|
||||
'张三': ['2023/5/1', '2023/5/2', '2023/5/3'],
|
||||
'李四': ['2023/5/1', '2023/5/3', '2023/5/4'],
|
||||
'王五': ['2023/5/2', '2023/5/3', '2023/5/5'],
|
||||
'赵六': ['2023/5/1', '2023/5/4', '2023/5/5'],
|
||||
'钱七': ['2023/5/2', '2023/5/4', '2023/5/5']
|
||||
}
|
||||
|
||||
const result = {}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
result[dateStr] = Object.keys(data).filter(name =>
|
||||
data[name].includes(`${d.getFullYear()}/${dateStr}`)
|
||||
)
|
||||
}
|
||||
}
|
||||
return [result] // 将结果包装在数组中,因为 el-table 期望一个数组
|
||||
})
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||
}
|
||||
|
||||
const handleProjectChange = projectId => {
|
||||
const selectedProjectInfo = projectList.find(project => project.id === projectId)
|
||||
if (selectedProjectInfo) {
|
||||
projectInfo.value = { ...selectedProjectInfo }
|
||||
}
|
||||
}
|
||||
|
||||
const handleButtonClick = ({ userName, date }) => {
|
||||
console.log(`点击了 ${userName} 在 ${date} 的工作日志`)
|
||||
// 这里可以添加跳转到工作日志页面的逻辑
|
||||
router.push({
|
||||
name: 'WorkLog',
|
||||
params: { userId: userName },
|
||||
query: {
|
||||
date: date,
|
||||
projectId: selectedProject.value
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-progress-container {
|
||||
display: flex;
|
||||
height: 88vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.left-section,
|
||||
.right-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
width: 400px; /* 根据固定列的总宽度调整 */
|
||||
padding-top: 34px;
|
||||
padding-bottom: 20px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||
z-index: 1; /* 确保左侧在右侧之上 */
|
||||
margin-right: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||
}
|
||||
|
||||
.placeholder-header {
|
||||
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||
}
|
||||
|
||||
.date-range {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
/* border: 1px solid #dcdfe6; */
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #4a4a4a;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__body td) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: bold;
|
||||
text-align: center; /* 确保合计行内容居中 */
|
||||
}
|
||||
|
||||
:deep(.el-table__header .cell),
|
||||
:deep(.el-table__body .cell),
|
||||
:deep(.el-table__footer .cell) {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.2;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 确保两个表格的高度一致 */
|
||||
.left-section :deep(.el-table),
|
||||
.right-section :deep(.el-table) {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 调整日期选择器样式 */
|
||||
:deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整表内容的字体大小 */
|
||||
:deep(.el-table) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 调整表头的样式 */
|
||||
:deep(.el-table__header-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.rightTab :deep(.el-table__header th) {
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 调整合计行的样式 */
|
||||
:deep(.el-table__footer-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa !important;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-range-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
width: 500px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.date-range-label {
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input__wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
.custom-table {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.project-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.user-select-container {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 10px;
|
||||
width: 220px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap: 10px;
|
||||
span {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,380 +0,0 @@
|
|||
<template>
|
||||
<div class="project-progress-container">
|
||||
<!-- 左侧固定列表格 -->
|
||||
<div class="left-section">
|
||||
<h3 class="mb20 ml10 textl">人员项目表</h3>
|
||||
<div class="user-select-container">
|
||||
<span>选择人员</span>
|
||||
<el-input v-model="selectedUserName" placeholder="请选择用户" readonly @click="openUserSelectDialog"></el-input>
|
||||
</div>
|
||||
<CustomTable
|
||||
:columns="fixedColumns"
|
||||
:tableData="executionData"
|
||||
:showPagination="false"
|
||||
:maxHeight="tableHeight"
|
||||
:showSummary="true"
|
||||
:summaryMethod="getFixedColumnsSummaries"
|
||||
:border="true"
|
||||
>
|
||||
<template #name="{ row }">
|
||||
<span class="project-name" @click="goToDetail(row)">{{ row.name }}</span>
|
||||
</template>
|
||||
</CustomTable>
|
||||
</div>
|
||||
|
||||
<!-- 右侧滚动列表格 -->
|
||||
<div class="right-section">
|
||||
<div class="date-range-container">
|
||||
<span class="date-range-label">统计时间:</span>
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
range-separator="至"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
@change="handleDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<CustomTable
|
||||
:columns="scrollableColumns"
|
||||
:tableData="executionData"
|
||||
:showPagination="false"
|
||||
:maxHeight="tableHeight"
|
||||
:showSummary="true"
|
||||
:summaryMethod="getScrollableColumnsSummaries"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 用户选择对话框 -->
|
||||
<SelectUser
|
||||
v-model:dialogVisible="userSelectDialogVisible"
|
||||
:multiSelect="false"
|
||||
:currentSelectedUser="selectedUser ? [selectedUser] : []"
|
||||
@confirm="handleUserSelect"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
import SelectUser from '@/components/selectUser.vue'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const dateRange = ref(getDefaultDateRange())
|
||||
const tableHeight = ref('100%')
|
||||
const userSelectDialogVisible = ref(false)
|
||||
const selectedUser = ref(null)
|
||||
const selectedUserName = computed(() => (selectedUser.value ? selectedUser.value.name : ''))
|
||||
|
||||
function getDefaultDateRange() {
|
||||
const now = new Date()
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1)
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0)
|
||||
return [startOfMonth, endOfMonth]
|
||||
}
|
||||
|
||||
const fixedColumns = [
|
||||
{ prop: 'name', label: '项目', slot: 'name' },
|
||||
{ prop: 'presetDays', label: '统计工时\n(天)' },
|
||||
]
|
||||
|
||||
const scrollableColumns = computed(() => {
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
const days = []
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dayOfWeek = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][d.getDay()]
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
days.push({
|
||||
prop: dateStr,
|
||||
label: `${dayOfWeek}\n${dateStr}`,
|
||||
minWidth: 100,
|
||||
})
|
||||
}
|
||||
return days
|
||||
}
|
||||
return []
|
||||
})
|
||||
|
||||
const executionData = computed(() => {
|
||||
const projectList = [
|
||||
{ id: 1, name: '项目1', presetDays: 20 },
|
||||
{ id: 2, name: '项目2', presetDays: 20 },
|
||||
{ id: 3, name: '项目3', presetDays: 20 },
|
||||
{ id: 4, name: '项目4', presetDays: 20 },
|
||||
{ id: 5, name: '项目5', presetDays: 20 },
|
||||
]
|
||||
|
||||
return projectList.map(project => {
|
||||
const data = {
|
||||
...project,
|
||||
}
|
||||
if (dateRange.value && dateRange.value.length === 2) {
|
||||
const start = new Date(dateRange.value[0])
|
||||
const end = new Date(dateRange.value[1])
|
||||
for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
|
||||
const dateStr = `${d.getMonth() + 1}/${d.getDate()}`
|
||||
data[dateStr] = Math.floor(Math.random() * 8) // 随机生成0-8的工时据
|
||||
}
|
||||
}
|
||||
return data
|
||||
})
|
||||
})
|
||||
|
||||
const handleDateRangeChange = () => {
|
||||
// 日期范围变化时,可以在这里添加额外的逻辑
|
||||
}
|
||||
|
||||
const getFixedColumnsSummaries = param => {
|
||||
const { columns, data } = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
if (index === 0) {
|
||||
sums[index] = '合计工时(天)'
|
||||
return
|
||||
}
|
||||
const values = data.map(item => Number(item[column.property]))
|
||||
if (!values.every(value => isNaN(value))) {
|
||||
sums[index] = values.reduce((prev, curr) => {
|
||||
const value = Number(curr)
|
||||
if (!isNaN(value)) {
|
||||
return prev + curr
|
||||
} else {
|
||||
return prev
|
||||
}
|
||||
}, 0)
|
||||
sums[index] = Number(sums[index].toFixed(2))
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
const getScrollableColumnsSummaries = param => {
|
||||
const { columns, data } = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
const values = data.map(item => Number(item[column.property]))
|
||||
if (!values.every(value => isNaN(value))) {
|
||||
sums[index] = values.reduce((prev, curr) => {
|
||||
const value = Number(curr)
|
||||
if (!isNaN(value)) {
|
||||
return prev + curr
|
||||
} else {
|
||||
return prev
|
||||
}
|
||||
}, 0)
|
||||
sums[index] = Number(sums[index].toFixed(2))
|
||||
} else {
|
||||
sums[index] = ''
|
||||
}
|
||||
})
|
||||
return sums
|
||||
}
|
||||
|
||||
const updateTableHeight = () => {
|
||||
nextTick(() => {
|
||||
const container = document.querySelector('.project-execution-table')
|
||||
if (container) {
|
||||
const containerHeight = container.clientHeight
|
||||
const otherElementsHeight =
|
||||
container.querySelector('h2').offsetHeight + container.querySelector('.date-range').offsetHeight
|
||||
tableHeight.value = `${containerHeight - otherElementsHeight - 40}px` // 40px for margins and paddings
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goToDetail = row => {
|
||||
router.push({
|
||||
name: 'ProjectDetail',
|
||||
params: { id: row.id },
|
||||
query: {
|
||||
startDate: dateRange.value[0].toISOString().split('T')[0],
|
||||
endDate: dateRange.value[1].toISOString().split('T')[0],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const openUserSelectDialog = () => {
|
||||
userSelectDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleUserSelect = users => {
|
||||
if (users.length > 0) {
|
||||
selectedUser.value = users[0]
|
||||
} else {
|
||||
selectedUser.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('resize', updateTableHeight)
|
||||
updateTableHeight()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', updateTableHeight)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.project-progress-container {
|
||||
display: flex;
|
||||
height: 88vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.left-section,
|
||||
.right-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.left-section {
|
||||
width: 400px; /* 根据固定列的总宽度调整 */
|
||||
padding-top: 34px;
|
||||
padding-bottom: 20px;
|
||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.5); /* 添加右侧阴影 */
|
||||
z-index: 1; /* 确保左侧在右侧之上 */
|
||||
margin-right: 5px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.right-section {
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
padding: 20px;
|
||||
padding-left: 0; /* 移除左侧内边距,与左侧部分紧密相连 */
|
||||
}
|
||||
|
||||
.placeholder-header {
|
||||
height: 102px; /* 与左侧表格的标题和日期选择器高度一致 */
|
||||
}
|
||||
|
||||
.date-range {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-table) {
|
||||
/* border: 1px solid #dcdfe6; */
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #4a4a4a;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__body td) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa;
|
||||
font-weight: bold;
|
||||
text-align: center; /* 确保合计行内容居中 */
|
||||
}
|
||||
|
||||
:deep(.el-table__header .cell),
|
||||
:deep(.el-table__body .cell),
|
||||
:deep(.el-table__footer .cell) {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.2;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
:deep(.el-table__body-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* 确保两个表格的高度一致 */
|
||||
.left-section :deep(.el-table),
|
||||
.right-section :deep(.el-table) {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
/* 调整日期选择器样式 */
|
||||
:deep(.el-date-editor) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 调整表<E695B4><E8A1A8><EFBFBD>内容的字体大小 */
|
||||
:deep(.el-table) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 调整表头的样式 */
|
||||
:deep(.el-table__header-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
.rightTab :deep(.el-table__header th) {
|
||||
background-color: #fff;
|
||||
}
|
||||
:deep(.el-table__header th) {
|
||||
background-color: #f5f7fa;
|
||||
color: #606266;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* 调整合计行的样式 */
|
||||
:deep(.el-table__footer-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa !important;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.date-range-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
width: 500px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.date-range-label {
|
||||
white-space: nowrap;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-date-editor.el-input__wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
.custom-table {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.project-name {
|
||||
cursor: pointer;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.project-name:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.user-select-container {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 10px;
|
||||
width: 220px;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
gap:10px;
|
||||
span{
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,340 +0,0 @@
|
|||
<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>
|
|
@ -1,615 +0,0 @@
|
|||
<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/CustomTable.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>
|
|
@ -1,295 +0,0 @@
|
|||
<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>
|
|
@ -1,199 +0,0 @@
|
|||
<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>
|
|
@ -1,605 +0,0 @@
|
|||
<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>
|
|
@ -1,136 +0,0 @@
|
|||
<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>
|
|
@ -1,79 +0,0 @@
|
|||
<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>
|
|
@ -1,79 +0,0 @@
|
|||
<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>
|
|
@ -1,274 +0,0 @@
|
|||
<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/CustomTable.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>
|
|
@ -1,623 +0,0 @@
|
|||
<template>
|
||||
<div class="work-log-container">
|
||||
<!-- 左侧项目列表 -->
|
||||
<div class="project-list" :class="{ collapsed: isCollapsed }">
|
||||
<div class="list-header">
|
||||
<span v-if="!isCollapsed">项目列表</span>
|
||||
<div class="collapse-button-wrapper">
|
||||
<el-button type="text" @click="toggleCollapse" class="collapse-button">
|
||||
<el-icon :size="20">
|
||||
<ArrowLeft v-if="!isCollapsed" />
|
||||
<ArrowRight v-else />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<CustomTable
|
||||
v-show="!isCollapsed"
|
||||
:columns="[
|
||||
{ prop: 'name', label: '项目', align: 'center' },
|
||||
{ prop: 'workDay', label: '工时(天)', align: 'center' },
|
||||
]"
|
||||
:tableData="projectList"
|
||||
@row-click="handleProjectClick"
|
||||
:show-summary="true"
|
||||
:summary-method="getSummaries"
|
||||
sum-text="合计工时(h)"
|
||||
:showPagination="false"
|
||||
:highligt="true"
|
||||
ref="projectRef"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 右侧项目信息和日历 -->
|
||||
<div class="project-info-calendar">
|
||||
<h2 class="mb20 textC">工作日志</h2>
|
||||
<!-- 项目信息表单 -->
|
||||
<el-form :model="projectInfo" label-width="100px" disabled class="project-info-form log-form">
|
||||
<el-form-item label="项目名称">
|
||||
<el-input v-model="projectInfo.projectName"></el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目编号">
|
||||
<el-input v-model="projectInfo.projectCode"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="工时填报人">
|
||||
<el-input v-model="projectInfo.nickName"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目开始时间">
|
||||
<el-date-picker v-model="projectInfo.startDate" type="date"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="项目结束时间">
|
||||
<el-date-picker v-model="projectInfo.endDate" type="date"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<!-- 日<EFBFBD><EFBFBD><EFBFBD>选择器 -->
|
||||
|
||||
<!-- 日历视图 -->
|
||||
<div class="calendar-view" v-if="projectInfo">
|
||||
<el-calendar v-model="selectedDate" @input="updateDateColors">
|
||||
<template #header="{ date }">
|
||||
<div class="calendar-header">
|
||||
<el-date-picker v-model="selectedDate" type="month" format="YYYY年MM月" :clearable="false" />
|
||||
</div>
|
||||
</template>
|
||||
<template #dateCell="{ data }">
|
||||
<div
|
||||
:key="data.day"
|
||||
:id="data.day"
|
||||
@click="openLogDialog(data)"
|
||||
:class="{
|
||||
'date-cell': true,
|
||||
'in-range': isInProjectRange(data),
|
||||
'out-range': !isInProjectRange(data),
|
||||
disabled: isFutureDate(data.date) && isInProjectRange(data),
|
||||
}"
|
||||
>
|
||||
{{ data.day.split('-')[2] }}
|
||||
</div>
|
||||
</template>
|
||||
</el-calendar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 工作日志对话框 -->
|
||||
<el-dialog v-model="logDialogVisible" title="工作日志" width="30%">
|
||||
<el-form :model="logForm" label-width="120px" class="log-form">
|
||||
<el-form-item label="工作时长(小时)">
|
||||
<el-input v-model.number="logForm.workTime"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="工作内容">
|
||||
<el-input v-model="logForm.workContent" type="textarea" :rows="4"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer" v-if="logForm.status == -1">
|
||||
<el-button @click="logDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="saveWorkLog">确定</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, nextTick } from 'vue'
|
||||
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { workLogApi, projectApi } from '@/utils/api'
|
||||
import CustomTable from '@/components/CustomTable.vue'
|
||||
|
||||
const isCollapsed = ref(false)
|
||||
const selectedDate = ref(new Date())
|
||||
|
||||
// 更新项目列表,包含工时数据
|
||||
const projectList = ref([])
|
||||
|
||||
// 当前选中的项目
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value
|
||||
}
|
||||
|
||||
const logData = ref([])
|
||||
const handleProjectClick = async row => {
|
||||
const res = await projectApi.getProjectDetail(row.projectId)
|
||||
projectInfo.value.projectName = res.data.projectName
|
||||
projectInfo.value.projectId = res.data.projectId
|
||||
projectInfo.value.projectCode = res.data.projectCode
|
||||
projectInfo.value.startDate = res.data.startDate
|
||||
projectInfo.value.endDate = res.data.endDate.split(' ')[0] + ' 23:59:59'
|
||||
|
||||
// 更新日历的选中日期和当前月份
|
||||
selectedDate.value = new Date(projectInfo.value.startDate) // 设置为项目开始时间
|
||||
// 获取日历日志数据
|
||||
|
||||
const res2 = await workLogApi.getLogData({
|
||||
projectId: projectInfo.value.projectId,
|
||||
startDate: projectInfo.value.startDate,
|
||||
endDate: projectInfo.value.endDate,
|
||||
userId: projectInfo.value.userId,
|
||||
})
|
||||
logData.value = res2.data
|
||||
initDateColor()
|
||||
}
|
||||
// 渲染日期颜色
|
||||
const initDateColor = () => {
|
||||
logData.value.map(item => {
|
||||
var ele = document.getElementById(item.date.split(' ')[0])
|
||||
if (ele) {
|
||||
if (item.state == -1) {
|
||||
ele.style = 'background:#ecf5ff'
|
||||
} else if (item.state == 0) {
|
||||
ele.style = 'background:#409eff;color:#fff'
|
||||
} else if (item.state == 1) {
|
||||
ele.style = 'background:#f5dc58;color:#fff'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
const openLogDialog = async data => {
|
||||
if (!projectInfo.value) {
|
||||
ElMessage.warning('请先选择一个项目')
|
||||
return
|
||||
}
|
||||
handleProjectClick(projectInfo.value)
|
||||
// 使用 data.day 而不是 data.date
|
||||
const clickedDate = new Date(data.day)
|
||||
const currentDate = new Date()
|
||||
|
||||
// 将两个日期都设置为当天的开始时间(UTC)
|
||||
clickedDate.setUTCHours(0, 0, 0, 0)
|
||||
currentDate.setUTCHours(0, 0, 0, 0)
|
||||
|
||||
if (
|
||||
clickedDate.getTime() > currentDate.getTime() &&
|
||||
clickedDate.getTime() < new Date(projectInfo.value.endDate).getTime()
|
||||
) {
|
||||
ElMessage.warning('不可编辑未来日期')
|
||||
return
|
||||
}
|
||||
|
||||
const date = new Date(data.day)
|
||||
const start = new Date(projectInfo.value.startDate)
|
||||
const end = new Date(projectInfo.value.endDate)
|
||||
// 使用 getTime() 进行比较,这样可以准确比较日期
|
||||
let flag = date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
|
||||
|
||||
if (!flag) {
|
||||
ElMessage.warning('该日期不在项目范围内')
|
||||
return
|
||||
}
|
||||
// 这里可以添加打开日志对话框的逻辑
|
||||
logForm.value.loggerDate = data.day + ' 00:00:00'
|
||||
let logTime = logData.value.find(ele => ele.date == logForm.value.loggerDate) || {}
|
||||
logForm.value.status = logTime.state
|
||||
if (logForm.value.status != -1) {
|
||||
const res = await workLogApi.getLogDataDetail({
|
||||
userId: projectInfo.value.userId,
|
||||
projectId: projectInfo.value.projectId,
|
||||
loggerDate: logForm.value.loggerDate,
|
||||
})
|
||||
logForm.value.workTime = res.data.workTime
|
||||
logForm.value.workContent = res.data.workContent
|
||||
} else {
|
||||
logForm.value.workTime = ''
|
||||
logForm.value.workContent = ''
|
||||
}
|
||||
logDialogVisible.value = true
|
||||
}
|
||||
|
||||
const isInProjectRange = data => {
|
||||
if (!projectInfo.value) return false
|
||||
const date = new Date(data.day)
|
||||
const start = new Date(projectInfo.value.startDate)
|
||||
const end = new Date(projectInfo.value.endDate)
|
||||
// 使用 getTime() 进行比较,这样可以准确比较日期
|
||||
return date.getTime() >= start.getTime() && date.getTime() <= end.getTime()
|
||||
}
|
||||
|
||||
const isFutureDate = date => {
|
||||
const clickedDate = new Date(date)
|
||||
const currentDate = new Date()
|
||||
return clickedDate.getTime() > currentDate.getTime()
|
||||
}
|
||||
|
||||
const getSummaries = param => {
|
||||
const { columns, data } = param
|
||||
const sums = []
|
||||
columns.forEach((column, index) => {
|
||||
if (index === 0) {
|
||||
sums[index] = '合计工时(h)'
|
||||
return
|
||||
}
|
||||
const values = data.map(item => Number(item[column.property]))
|
||||
if (!values.every(value => isNaN(value))) {
|
||||
sums[index] = values.reduce((prev, curr) => {
|
||||
const value = Number(curr)
|
||||
if (!isNaN(value)) {
|
||||
return prev + curr * 24
|
||||
} else {
|
||||
return prev
|
||||
}
|
||||
}, 0)
|
||||
} else {
|
||||
sums[index] = 'N/A'
|
||||
}
|
||||
})
|
||||
if (sums[1] != 'N/A') sums[1] = Number(sums[1]).toFixed(2)
|
||||
return sums
|
||||
}
|
||||
|
||||
const logForm = ref({
|
||||
loggerDate: '',
|
||||
workContent: '',
|
||||
workTime: '',
|
||||
status: -1,
|
||||
})
|
||||
// 项目信息
|
||||
const projectInfo = ref({})
|
||||
|
||||
// 工作日志对话框
|
||||
const logDialogVisible = ref(false)
|
||||
const workLog = ref({
|
||||
hours: 0,
|
||||
content: '',
|
||||
})
|
||||
|
||||
// 保存工作日志
|
||||
const saveWorkLog = async () => {
|
||||
// 这里添加保存工作日志的逻辑
|
||||
// 判断是否为补填
|
||||
let state = new Date(logForm.value.loggerDate).getTime() == $moment().startOf('day').valueOf() ? 0 : 1
|
||||
await workLogApi.addLog({
|
||||
...logForm.value,
|
||||
projectId: projectInfo.value.projectId,
|
||||
state,
|
||||
userId: projectInfo.value.userId,
|
||||
})
|
||||
logDialogVisible.value = false
|
||||
ElMessage.success('添加成功')
|
||||
handleProjectClick(projectInfo.value)
|
||||
}
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// 获取用户项目
|
||||
const projectRef = ref(null)
|
||||
const fetchUserProjects = async () => {
|
||||
try {
|
||||
const response = await workLogApi.userProject(projectInfo.value.userId)
|
||||
projectList.value = response.data // 假设返回的数<EFBFBD><EFBFBD><EFBFBD>结构中有 projects 数组
|
||||
if (projectList.value.length) {
|
||||
handleProjectClick(projectList.value[0])
|
||||
projectRef.value.setCurrentRow(projectList.value[0])
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
const updateDateColors = () => {
|
||||
initDateColor() // 重新渲染日期颜色
|
||||
}
|
||||
onMounted(() => {
|
||||
if (route.params.userId) {
|
||||
projectInfo.value.userId = route.params.userId
|
||||
projectInfo.value.nickName = route.params.nickName
|
||||
} else {
|
||||
let userInfo =
|
||||
sessionStorage.getItem('userInfo') !== 'undefined' && sessionStorage.getItem('userInfo')
|
||||
? JSON.parse($utils.decryptByDES.CBC(sessionStorage.getItem('userInfo'))).user
|
||||
: {}
|
||||
projectInfo.value.userId = userInfo.userId
|
||||
projectInfo.value.nickName = userInfo.nickName
|
||||
}
|
||||
//获取项目列表
|
||||
fetchUserProjects()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.work-log-container {
|
||||
display: flex;
|
||||
height: 88vh;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.project-list {
|
||||
width: 300px;
|
||||
border-right: 1px solid #dcdfe6;
|
||||
transition: all 0.3s;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.project-list.collapsed {
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
.list-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
position: relative;
|
||||
height: 40px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.collapse-button-wrapper {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 40px; /* 固定宽度 */
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.project-list.collapsed .collapse-button-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.collapse-button {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.collapse-button:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.collapse-button :deep(.el-icon) {
|
||||
font-size: 20px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.project-info-calendar {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.calendar-picker {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar-day) {
|
||||
padding: 4px; /* 恢复默认内边距 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar-day:hover) {
|
||||
cursor: pointer;
|
||||
background-color: #f2f6fc;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.date-cell) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.in-range) {
|
||||
background-color: #ecf5ff;
|
||||
}
|
||||
.calendar-view :deep(.out-range) {
|
||||
background-color: rgba(0, 0, 0, 0.05) !important;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.disabled) {
|
||||
background-color: #fff !important;
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.project-list :deep(.el-table .cell) {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-form-item) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-input__inner) {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-textarea__inner) {
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar-header) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-form-item) {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-input__wrapper) {
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-input__inner) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-textarea__inner) {
|
||||
min-height: 150px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-input__wrapper) {
|
||||
height: 50px;
|
||||
width: 100% !important;
|
||||
}
|
||||
:deep(.el-date-editor--date) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-input__inner) {
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.log-form :deep(.el-textarea__inner) {
|
||||
min-height: 150px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
padding: 12px 15px;
|
||||
}
|
||||
|
||||
/* 增加 el-form-item__label 的高度和行高 */
|
||||
:deep(.el-form-item__label) {
|
||||
height: 42px; /* 默认通常是 32px,所以增加 10px <20><>变为 42px */
|
||||
line-height: 42px; /* 行高与高度相同,确保文字垂直居中 */
|
||||
}
|
||||
|
||||
/* 保持 el-form-item__content 的一致性 */
|
||||
:deep(.el-form-item__content) {
|
||||
min-height: 42px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 调整输入框的高度以匹配增加的空间 */
|
||||
:deep(.el-input__wrapper) {
|
||||
height: 42px;
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
/* 对于 textarea,可能需要单独调整 */
|
||||
:deep(.el-textarea__inner) {
|
||||
min-height: 42px;
|
||||
}
|
||||
|
||||
:deep(.el-calendar__header) {
|
||||
justify-content: center !important;
|
||||
}
|
||||
:deep(.el-calendar__body) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.calendar-view {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
font-size: 14px; /* 缩小字体大小 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar) {
|
||||
--el-calendar-cell-width: 40px; /* 缩小日历单元格宽度 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar__header) {
|
||||
padding: 10px 0; /* 减小头部内边距 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar__body) {
|
||||
padding: 10px 0; /* 减小主体内边距 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar__week) {
|
||||
background-color: #4a4a4a; /* 深灰色背景 */
|
||||
color: white; /* 白色文字 */
|
||||
padding: 5px 0; /* 增加一些内边距 */
|
||||
}
|
||||
|
||||
.calendar-view :deep(.el-calendar-day) {
|
||||
height: 65px; /* 增加日期单元格高度(原高度 + 5px) */
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.project-list :deep(.el-table) {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.project-list :deep(.el-table__body-wrapper) {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.project-list :deep(.el-table .cell) {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
:deep(.el-dialog__body) {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.date-cell {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.date-cell.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
:deep(.el-table__footer-wrapper) {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
:deep(.el-table__footer td) {
|
||||
background-color: #f5f7fa !important;
|
||||
font-weight: bold;
|
||||
color: #606266;
|
||||
}
|
||||
:deep(.el-table__row) {
|
||||
cursor: pointer;
|
||||
}
|
||||
:deep(tr.el-table__row:hover .el-table__cell) {
|
||||
background: #409eff !important;
|
||||
color: #fff;
|
||||
}
|
||||
:deep(tr.el-table__row.current-row .el-table__cell) {
|
||||
background-color: #409eff !important;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
106
vite.config.js
|
@ -1,106 +0,0 @@
|
|||
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'
|
||||
|
||||
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: {
|
||||
|
||||
[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
|
||||
},
|
||||
})
|
||||
}
|
||||
|