AnalysisSystemForRadionucli.../src/views/system/Scheduling.vue

936 lines
27 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="scheduling">
<!-- 左侧日程列表 -->
<a-card class="scheduling-list">
<!-- 标题 -->
<template slot="title">
Scheduling
<div class="line"></div>
</template>
<!-- 标题结束 -->
<!-- 内容 -->
<div class="scheduling-list-content">
<div class="scheduling-list-item" v-for="item of schedulingInfo" :key="item.id">
<h4 class="title">
<span>
{{ item.username }}
</span>
<a-popconfirm title="Do You Want To Delete This Item?" placement="bottom" @confirm="onDel(item)">
<a class="del"></a>
</a-popconfirm>
</h4>
<div class="scheduling-list-item-container" @dragover.prevent @drop="onDrop(item)">
<!-- 左侧边框 -->
<div class="left-border">
<div class="left-top-border">
<div class="left-top-corner"></div>
</div>
<div class="left-bottom-border"></div>
</div>
<!-- 左侧边框结束 -->
<div class="scheduling-list-item-content">
<template v-for="(station, index) in item.stationList">
<span
:key="station.stationId"
class="draggable"
draggable
@dragstart="onDragStart(station, item.userId)"
>
{{ station.stationName }}
</span>
{{ index == item.stationList.length - 1 ? '' : '、' }}
</template>
</div>
<!-- 右侧边框 -->
<div class="right-border">
<div class="right-top-border"></div>
<div class="right-bottom-border"></div>
</div>
<!-- 右侧边框结束 -->
</div>
</div>
<a-empty v-if="!schedulingInfo || !schedulingInfo.length" style="margin-top: 40px;"></a-empty>
</div>
<!-- 内容结束 -->
</a-card>
<div class="scheduling-calendar">
<!-- 日历 -->
<a-calendar v-model="currentDate" @select="onSelectDate">
<template slot="headerRender">
<!-- 搜索栏 -->
<div class="search-form">
<a-row>
<a-col :span="6">
<a-form-model class="search-form-form">
<a-form-model-item label="Month" style="marign-bottom: 0">
<a-month-picker v-model="currentMonth" :allow-clear="false"></a-month-picker>
</a-form-model-item>
</a-form-model>
</a-col>
<a-space class="btn-group">
<a-button @click="onAdd" type="primary">
<img src="@/assets/images/global/add.png" alt="" />
Add
</a-button>
<a-button @click="onEdit" type="primary">
<img src="@/assets/images/global/edit.png" alt="" />
Edit
</a-button>
<a-button type="primary">
<img src="@/assets/images/global/import.png" alt="" />
Import
</a-button>
<a-button type="primary">
<img src="@/assets/images/global/export.png" alt="" />
Export
</a-button>
</a-space>
</a-row>
</div>
<!-- 搜索栏结束 -->
</template>
<!-- 日历内部内容 -->
<template slot="dateCellRender" slot-scope="value">
<div
class="scheduling-calendar-content"
v-if="scheduleList[value.format(dateFormat)] && scheduleList[value.format(dateFormat)].length"
>
<div class="item" v-for="item in scheduleList[value.format(dateFormat)]" :key="item.id">
{{ `${item.username}(${item.stationList.length})` }}
</div>
</div>
</template>
<!-- 日历内部内容 -->
</a-calendar>
<!-- 日历结束 -->
</div>
<!-- 增加/编辑排班弹窗 -->
<custom-modal :title="isAdd ? 'Add' : 'Edit'" :width="845" v-model="visible" :okHandler="submit">
<a-spin :spinning="isGettingDetail">
<div class="account-assign">
<a-transfer
:selectedKeys="selectedStationKeys"
:target-keys="targetKeys"
:render="item => item.title"
:operations="['Assign', 'Remove']"
:titles="['Particulate Station', 'Roster personnel']"
:show-select-all="false"
@change="onChange"
@selectChange="handleSelectChange"
>
<template slot="children" slot-scope="{ props: { direction, selectedKeys }, on: { itemSelect } }">
<!-- 左侧穿梭框中的树 -->
<a-tree
v-if="direction === 'left'"
blockNode
checkable
checkStrictly
defaultExpandAll
:checkedKeys="[...selectedKeys, ...targetKeys]"
:treeData="treeData"
@check="
(_, props) => {
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect)
}
"
@select="
(_, props) => {
onChecked(_, props, [...selectedKeys, ...targetKeys], itemSelect)
}
"
/>
<!-- 右侧穿梭框中的树 -->
<a-tree
v-if="direction === 'right'"
blockNode
checkStrictly
defaultExpandAll
:treeData="accountTreeData"
:selectedKeys.sync="rightAccountChildSelectedKeys"
@select="
(_, props) => {
onSelectAccount(_, props, itemSelect)
}
"
/>
</template>
</a-transfer>
<!-- 穿梭框右上方搜索 -->
<div class="account-search">
<label>User Name</label>
<custom-popover-search
placeholder="Enter User Name"
:options="accountList"
:keyword.sync="keyword"
:remote-method="getAccountList"
@add="onAddToList"
></custom-popover-search>
</div>
<!-- 穿梭框右上方搜索结束 -->
</div>
</a-spin>
</custom-modal>
<!-- 增加/编辑排班弹窗结束 -->
</div>
</template>
<script>
import FormMixin from '@/mixins/FormMixin'
import moment from 'moment'
import { cloneDeep } from 'lodash'
import { getAction } from '@/api/manage'
import CustomPopoverSearch from '@/components/CustomPopoverSearch'
import { deleteAction, postAction, putAction } from '../../api/manage'
const dateFormat = 'YYYY-MM-DD'
const monthFormat = 'YYYY-MM'
export default {
mixins: [FormMixin],
components: {
VNodes: {
functional: true,
render: (_, ctx) => ctx.props.vnodes
},
CustomPopoverSearch
},
data() {
this.dateFormat = dateFormat
return {
currentMonth: moment(), // 当前月份
currentDate: moment(), // 当前日期
schedulingInfo: [], // 当前选中的日程详情,用于 scheduling 左侧展示
isGettingList: false,
scheduleList: [], // 该月的日程列表
visible: false, // 新增/编辑弹窗是否显示
isGettingDetail: false, // 编辑时正在获取详情
originalTreeData: [], // 站点的树状结构
selectedStationKeys: [], // 穿梭框左侧选中的站点列表
targetKeys: [],
isGettingStationList: false,
keyword: '', // 穿梭框右上角搜索用户的关键词
stationList: [], // 站点列表(原始数据)
accountList: [], // 搜索出来的用户列表
accountTreeData: [], // 用户列表树状结构
checkedAccount: '', // 右侧穿梭框选中的账号
rightAccountChildSelectedKeys: [], // 右侧穿梭框选中的值
isChanging: false,
dragItem: null,
fromUserId: ''
}
},
created() {
this.getList()
this.getStationList()
},
methods: {
// 获取该月日程列表
async getList() {
try {
this.isGettingList = true
const { result } = await getAction(`/sysTask/findList?yearMonth=${this.currentMonth.format('YYYY-MM')}`)
this.scheduleList = result
this.schedulingInfo = this.scheduleList[this.currentDate.format(dateFormat)]
} catch (error) {
console.error(error)
} finally {
this.isGettingList = false
}
},
async getStationList() {
try {
this.isGettingStationList = true
const { success, result, message } = await getAction('/gardsStations/findPage?pageIndex=1&pageSize=1000')
if (success) {
const records = result.records
const set = new Set(records.map(item => item.countryCode))
this.originalTreeData = Array.from(set).map((countryCode, index) => {
return {
title: countryCode,
key: index.toString(),
disabled: true,
children: records
.filter(item => item.countryCode == countryCode)
.map(item => ({ title: item.stationCode, key: item.stationId.toString(), children: [] }))
}
})
this.stationList = records
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
} finally {
this.isGettingStationList = false
}
},
async getAccountList() {
try {
const { success, result, message } = await getAction(
`/sys/user/list?pageIndex=1&pageSize=1000&username=${this.keyword}`
)
if (success) {
const records = result.records
this.accountList = records.map(item => ({
label: item.username,
value: item.id
}))
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
}
},
// 选中某一个日期
onSelectDate(date) {
this.currentDate = date
if (this.currentDate.format(monthFormat) !== this.currentMonth.format(monthFormat)) {
this.currentMonth = this.currentDate.clone()
}
if (this.isGettingList) {
this.$message.info('Is Getting Schedule,Waitting...')
return
}
this.schedulingInfo = this.scheduleList[date.format(dateFormat)]
},
// 获取详情
async getScheduleDetail() {
try {
this.isGettingDetail = true
const { success, result, message } = await getAction(
`/sysTask/findInfo?day=${this.currentDate.format(dateFormat)}`
)
if (success) {
const selectedStations = [] // 找出已经被选中的站点
const accountTree = [] // 构建右侧的树
result.forEach(item => {
const accountItem = {
title: item.userName,
key: item.userId,
children: []
}
accountTree.push(accountItem)
item.stationList.forEach(station => {
selectedStations.push(station.stationId)
accountItem.children.push({
title: station.stationName,
key: station.stationId,
isLeaf: true
})
})
})
this.targetKeys = selectedStations
this.accountTreeData = accountTree
} else {
this.$message.error(message)
}
} catch (error) {
console.error(error)
} finally {
this.isGettingDetail = false
}
},
// 左侧删除某一天的日程
async onDel({ id: taskId, userId }) {
try {
const { success } = await deleteAction('/sysTask/deleteById', {
taskId,
userId
})
if (success) {
this.$message.success('Delete Success')
this.getList()
} else {
this.$message.success('Delete Fail')
}
} catch (error) {
console.error(error)
}
},
onAdd() {
if (this.schedulingInfo.length) {
this.$message.warn('Has Schedule, Only Can Edit')
return
}
this.isAdd = true
this.visible = true
this.selectedStationKeys = []
this.targetKeys = []
this.accountTreeData = []
this.rightAccountChildSelectedKeys = []
this.checkedAccount = ''
},
onEdit() {
if (!this.schedulingInfo.length) {
this.$message.warn('Has No Schedule, Add It First')
return
}
this.isAdd = false
this.visible = true
this.rightAccountChildSelectedKeys = []
this.checkedAccount = ''
this.getScheduleDetail()
},
onImport() {
console.log('%c [ 新增 ]-88', 'font-size:13px; background:pink; color:#bf2c9f;')
},
onExport() {
console.log('%c [ 新增 ]-88', 'font-size:13px; background:pink; color:#bf2c9f;')
},
// 开始拖拽
onDragStart(item, fromUserId) {
this.dragItem = item
this.fromUserId = fromUserId
},
// 拖拽结束
async onDrop(item) {
const toUserId = item.userId
if (this.fromUserId != toUserId) {
try {
this.isChanging = true
const { success } = await putAction('/sysTask/changeScheduling', {
day: this.currentDate.format('YYYY-MM-DD'),
fromUserId: this.fromUserId,
toUserId,
stationIds: [this.dragItem.stationId]
})
if (success) {
this.$message.success('Change Success')
this.getList()
} else {
this.$message.error('Change Fail')
}
} catch (error) {
console.error()
} finally {
this.isChanging = false
}
}
},
// 提交日程
async submit() {
if (!this.accountTreeData.length) {
this.$message.warn('Person List Is Empty')
return Promise.reject()
}
if (!this.accountTreeData.some(accountTreeItem => accountTreeItem.children.length)) {
this.$message.warn('Every Person Has No Station Assigned')
return Promise.reject()
}
try {
let method = putAction
let url = '/sysTask/update'
let successMsg = 'Edit Success'
let failMsg = 'Edit Fail'
if (this.isAdd) {
method = postAction
url = '/sysTask/create'
successMsg = 'Add Success'
failMsg = 'Add Fail'
}
const params = []
this.accountTreeData.forEach(accountTreeItem => {
if (accountTreeItem.children.length) {
params.push({
schedulingDate: this.currentDate.format('YYYY-MM-DD HH:mm:ss'),
stationList: accountTreeItem.children.map(child => {
return {
stationId: child.key
}
}),
userId: accountTreeItem.key
})
}
})
const { success } = await method(url, params)
if (success) {
this.$message.success(successMsg)
this.getList()
} else {
this.$message.error(failMsg)
return Promise.reject()
}
} catch (error) {
return Promise.reject()
}
},
/**
* 以下是对穿梭框的处理
*/
isChecked(selectedKeys, eventKey) {
return selectedKeys.indexOf(eventKey) !== -1
},
onChecked(_, e, checkedKeys, itemSelect) {
const { eventKey } = e.node
itemSelect(eventKey, !this.isChecked(checkedKeys, eventKey))
},
handleSelectChange(sourceSelectedKeys, targetSelectedKeys) {
this.selectedStationKeys = [...sourceSelectedKeys, ...targetSelectedKeys]
},
// 处理station列表
handleTreeData(data, targetKeys = [], level) {
data.forEach(item => {
if (level !== 0) {
item.disabled = targetKeys.includes(item.key)
}
if (item.children) {
this.handleTreeData(item.children, targetKeys)
}
})
return data
},
// 将选中的账号加入到右侧穿梭框
onAddToList(selectedAccount) {
const addedAccounts = selectedAccount
.filter(id => !this.accountTreeData.find(account => account.key == id)) // 如果之前没有加过,才加进去
.map(id => {
const find = this.accountList.find(account => account.value == id)
return {
title: find.label,
key: id,
children: []
}
})
this.accountTreeData.push(...addedAccounts)
this.rightAccountChildSelectedKeys = []
this.checkedAccount = ''
},
// 选中了穿梭框右侧的树节点
onSelectAccount(_, e, itemSelect) {
const { eventKey, isLeaf, selected } = e.node
// selected 是前一个状态也就是selected 为false时其实是选中了
if (isLeaf) {
// 选中了子节点,也就是站点
this.checkedAccount = ''
if (selected) {
// 取消选中
itemSelect(eventKey, false)
} else {
this.accountTreeData.forEach(account => {
// 将所有右侧的穿梭状态重置
account.children.forEach(child => {
itemSelect(child.key, false)
})
})
itemSelect(eventKey, true) // 选中该栏
}
} else {
this.checkedAccount = selected ? '' : eventKey
// 将所有右侧的穿梭状态重置
this.accountTreeData.forEach(account => {
// 将所有右侧的穿梭状态重置
account.children.forEach(child => {
itemSelect(child.key, false)
})
})
}
},
// 穿梭框变化
onChange(targetKeys, direction, moveKeys) {
if (direction == 'right') {
if (!this.checkedAccount) {
this.$message.warning('Please Select A Person To Assign')
return
}
const findAccount = this.accountTreeData.find(account => account.key == this.checkedAccount)
if (findAccount) {
const children = moveKeys.map(key => {
const findStation = this.stationList.find(station => station.stationId == key)
return {
isLeaf: true,
title: findStation.stationCode,
key
}
})
findAccount.children.push(...children)
}
} else {
const moveKey = moveKeys[0]
console.log('%c [ moveKey ]-577', 'font-size:13px; background:pink; color:#bf2c9f;', moveKey)
let parentIndex = -1,
childIndex = -1
for (const pIndex in this.accountTreeData) {
// 找到要移除的Station在右侧列表中的位置
const account = this.accountTreeData[pIndex]
const cIndex = account.children.findIndex(child => child.key == moveKey)
if (-1 !== cIndex) {
parentIndex = pIndex
childIndex = cIndex
break
}
}
this.accountTreeData[parentIndex].children.splice(childIndex, 1)
console.log('%c [ childIndex ]-591', 'font-size:13px; background:pink; color:#bf2c9f;', childIndex)
}
this.targetKeys = targetKeys
}
},
computed: {
treeData() {
return this.handleTreeData(cloneDeep(this.originalTreeData), this.targetKeys, 0)
}
},
watch: {
currentMonth() {
this.currentDate = this.currentMonth.clone()
this.getList()
}
}
}
</script>
<style lang="less" scoped>
.scheduling {
height: 100%;
overflow: hidden;
display: flex;
&-list {
flex-shrink: 0;
width: 350px;
height: 100%;
background-color: #022024;
border-color: rgb(12, 106, 102, 0.9);
border-radius: 0;
::v-deep {
.ant-card {
&-head {
font-family: MicrogrammaD-MediExte;
font-size: 20px;
font-weight: bold;
padding-left: 15px;
background-color: #022024;
border-bottom-color: rgba(12, 235, 201, 0.3);
&-title {
padding-top: 22px;
padding-bottom: 0;
}
.line {
width: 70px;
height: 3px;
background-color: #0cebc9;
margin-top: 10px;
}
}
&-body {
height: calc(100% - 66px);
overflow: auto;
}
}
}
&-content {
padding: 0 12px;
}
&-item {
margin-top: 15px;
.title {
display: flex;
justify-content: space-between;
align-items: center;
font-family: Aria;
font-size: 14px;
color: #fff;
font-weight: bold;
margin-bottom: 4px;
position: relative;
padding-left: 8px;
span {
flex: 1;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.del {
margin-left: 15px;
margin-right: 10px;
width: 14px;
height: 14px;
background: url(~@/assets/images/system/del-normal.png);
background-size: cover;
&:hover {
background-image: url(~@/assets/images/system/del-active.png);
}
}
}
@borderColor: #0a544e;
@borderRadius: 4px;
@bgColor: #03353f;
&-container {
border-bottom: 1px solid @borderColor;
border-bottom-left-radius: @borderRadius;
border-bottom-right-radius: @borderRadius;
margin-top: 4px;
display: flex;
.left-border {
width: 6px;
position: relative;
.left-top-border {
position: relative;
.left-top-corner {
border: 1px solid @borderColor;
background-color: @bgColor;
position: absolute;
top: 25px;
left: 2px;
width: 9px;
height: 9px;
transform: rotate(45deg);
border-top: none;
border-right: 0;
}
}
.left-bottom-border {
height: calc(100% - 30px);
position: absolute;
left: 0;
bottom: 0;
width: 7px;
background-color: @bgColor;
border-left: 1px solid @borderColor;
border-bottom-left-radius: @borderRadius;
}
}
.right-border {
width: 14px;
position: relative;
.right-top-border {
position: absolute;
right: 4px;
top: 4px;
width: 20px;
height: 20px;
border: 1px solid transparent;
border-top-color: @borderColor;
transform: rotate(45deg);
background-color: @bgColor;
}
.right-bottom-border {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
height: calc(100% - 14px);
border-right: 1px solid @borderColor;
border-bottom-right-radius: @borderRadius;
background-color: @bgColor;
}
}
}
&-content {
min-height: 62px;
border-top: 1px solid @borderColor;
border-left: 1px solid @borderColor;
padding: 6px;
background-color: @bgColor;
line-height: 26px;
flex: 1;
color: #6ebad0;
}
}
}
// 右侧日历
&-calendar {
height: 100%;
margin-left: 20px;
padding-right: 10px;
.ant-fullcalendar-fullscreen {
height: 100%;
overflow: auto;
padding-right: 5px;
::v-deep {
.ant-fullcalendar-full {
height: calc(100% - 61px);
overflow: auto;
padding-right: 5px;
}
}
}
.search-form {
padding: 10px;
border: 1px solid #416f7f;
border-bottom: 0;
&-form {
.ant-form-item {
margin-bottom: 0;
}
}
.btn-group {
float: right;
height: 40px;
img {
margin-right: 12px;
height: 18px;
}
}
}
&-content {
padding: 10px;
height: 100%;
overflow: hidden auto;
background-color: rgba(140, 255, 229, 0.1);
&::-webkit-scrollbar {
width: 4px !important;
}
.item {
font-family: Arial;
font-size: 14px;
color: #00fdd3;
height: 20px;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
}
.account-assign {
position: relative;
width: 672px;
margin: 0 auto;
.ant-transfer {
margin-bottom: 10px;
::v-deep {
.ant-transfer-list {
width: 282px;
height: 411px;
&-header {
height: 37px;
&-selected {
span:first-child {
display: none;
}
}
&-title {
left: 16px;
}
}
&-content {
&-item {
&:hover {
background-color: transparent;
}
}
}
&:last-child {
height: 364px;
position: relative;
top: 47px;
}
}
.ant-transfer-operation {
.ant-btn {
width: 92px;
height: 26px;
padding: 0;
.anticon {
display: none;
}
span {
margin-left: 0;
}
&:first-child {
margin-bottom: 52px;
&::after {
display: inline-block;
margin-left: 13px;
content: '';
width: 18px;
height: 10px;
background: url(~@/assets/images/system/transfer-right.png) no-repeat;
background-size: contain;
}
}
&:nth-child(2) {
&::before {
display: inline-block;
margin-right: 6px;
content: '';
width: 18px;
height: 10px;
background: url(~@/assets/images/system/transfer-left.png) no-repeat;
background-size: contain;
position: static;
opacity: initial;
}
}
}
}
}
}
.account-search {
position: absolute;
top: 0;
right: 0;
width: 282px;
display: flex;
align-items: center;
label {
color: #5b9cba;
font-size: 16px;
flex-shrink: 0;
margin-right: 10px;
user-select: none;
}
}
}
.ant-select-dropdown-content::before {
top: 2px;
}
.account-add {
padding: 0;
padding-top: 10px;
text-align: center;
cursor: pointer;
background: #03353f;
a {
display: inline-block;
width: 100%;
padding: 5px;
border-top: 1px solid #0da397;
color: #0cebc9;
}
}
.draggable {
cursor: pointer;
}
</style>