feat: scheduling对接接口,完成增删改和拖拽交换任务功能,优化其他页面样式

This commit is contained in:
Xu Zhimeng 2023-05-18 18:42:52 +08:00
parent cf8017581b
commit 14b539bbef
7 changed files with 473 additions and 149 deletions

View File

@ -1,7 +1,13 @@
<template>
<div class="popover-search">
<a-popover v-model="visible" trigger="click" placement="bottom" :overlayStyle="{ width: width + 'px' }">
<a-input ref="inputRef" v-model="keyword" @click.stop="">
<a-popover
overlayClassName="search-popover"
v-model="visible"
trigger="click"
placement="bottom"
:overlayStyle="{ width: width + 'px' }"
>
<a-input ref="inputRef" :placeholder="placeholder" v-model="innerKeyWord" @click.stop="">
<template slot="suffix">
<img class="popover-search-btn" src="@/assets/images/global/search-blue.png" @click.stop="onSearch" />
</template>
@ -11,15 +17,27 @@
<a-spin :spinning="isLoading"></a-spin>
</template>
<template v-else>
<div
class="popover-search-item"
v-for="option in innerOptions"
:key="option.value"
@click="option.checked = !option.checked"
>
<a-checkbox v-model="option.checked"></a-checkbox>
<span class="popover-search-item-inner">{{ option.label }}</span>
</div>
<template v-if="innerOptions.length">
<div style="max-height: 200px; overflow: auto">
<div
class="popover-search-item"
v-for="option in innerOptions"
:key="option.value"
@click="onSelect(option)"
>
<a-checkbox v-model="option.checked"></a-checkbox>
<span class="popover-search-item-inner">{{ option.label }}</span>
</div>
</div>
<div class="btn-group" @click="onAdd">
<span>Add</span>
</div>
</template>
<template v-else>
<div style="padding: 20px 0">
<a-empty></a-empty>
</div>
</template>
</template>
</template>
</a-popover>
@ -39,11 +57,16 @@ export default {
},
value: {
type: Array
},
keyword: {
type: String
},
placeholder: {
type: String
}
},
data() {
return {
keyword: '',
visible: false,
width: 0,
isLoading: true,
@ -60,6 +83,15 @@ export default {
await this.remoteMethod()
this.isLoading = false
}
},
onSelect(option) {
option.checked = !option.checked
},
onAdd() {
const selectedOptions = this.innerOptions.filter(option => option.checked).map(option => option.value)
this.$emit('add', selectedOptions)
this.visible = false
this.innerKeyWord = ''
}
},
watch: {
@ -71,6 +103,16 @@ export default {
this.innerOptions = options
}
}
},
computed: {
innerKeyWord: {
set(val) {
this.$emit('update:keyword', val)
},
get() {
return this.keyword
}
}
}
}
</script>
@ -92,4 +134,28 @@ export default {
}
}
}
.ant-spin {
height: 150px;
line-height: 150px;
width: 100%;
text-align: center;
}
.btn-group {
height: 30px;
line-height: 30px;
text-align: center;
cursor: pointer;
border-top: 1px solid #0da397;
}
</style>
<style lang="less">
.search-popover {
.ant-popover {
&-inner {
&-content {
padding: 0;
}
}
}
}
</style>

View File

@ -78,6 +78,7 @@ export default {
.ant-form-item-label,
.ant-form-item-control {
line-height: 32px;
height: 32px;
}
.ant-form-item-label {
flex-shrink: 0;

View File

@ -176,6 +176,24 @@ body {
}
}
}
&-date {
&:hover {
background-color: @primary-color;
color: #fff;
}
}
&-selected-day {
.ant-calendar-date {
background-color: @primary-color;
}
}
&-today {
.ant-calendar-date {
color: #fff;
}
}
}
// 日历
@ -193,8 +211,7 @@ body {
&-content {
margin-top: 10px;
height: 98px;
background-color: rgba(140, 255, 229, 0.1);
height: 98px !important;
&::-webkit-scrollbar {
width: 4px !important;
}
@ -647,6 +664,9 @@ body {
&-message {
color: #fff;
}
&-close {
color: #fff !important;
}
}
}
@ -680,9 +700,6 @@ body {
.ant-popover {
&-inner {
background-color: #03353f;
&-content {
padding: 0;
}
}
&-arrow {
border-left-color: #03353f !important;

View File

@ -43,7 +43,7 @@ const columns = [
title: 'SITE DET CODE',
align: 'center',
dataIndex: 'siteDetCode',
width: 120
width: 130
},
{
title: 'SAMPLE ID',
@ -120,7 +120,7 @@ const columns = [
{
title: 'ACQ.REAL(S)',
align: 'center',
width: 100,
width: 120,
dataIndex: 'acquisitionRealSec'
},
{

View File

@ -10,12 +10,16 @@
<!-- 标题结束 -->
<!-- 内容 -->
<div class="scheduling-list-content">
<div class="scheduling-list-item" v-for="item of [1, 2, 3, 4]" :key="item">
<div class="scheduling-list-item" v-for="item of schedulingInfo" :key="item.id">
<h4 class="title">
fanyq
<a class="del" @click="onDel(item)"></a>
<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">
<div class="scheduling-list-item-container" @dragover.prevent @drop="onDrop(item)">
<!-- 左侧边框 -->
<div class="left-border">
<div class="left-top-border">
@ -25,7 +29,17 @@
</div>
<!-- 左侧边框结束 -->
<div class="scheduling-list-item-content">
ARP01ARP02ARP03ARP04ARP05 ARP06ARP07ARP08
<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">
@ -35,12 +49,13 @@
<!-- 右侧边框结束 -->
</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="currentMonth">
<a-calendar v-model="currentDate" @select="onSelectDate">
<template slot="headerRender">
<!-- 搜索栏 -->
<div class="search-form">
@ -75,10 +90,13 @@
<!-- 搜索栏结束 -->
</template>
<!-- 日历内部内容 -->
<template slot="dateCellRender">
<div class="scheduling-calendar-content">
<div class="item" v-for="item in [1, 2, 3, 4, 5]" :key="item">
liq (5)
<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>
@ -89,79 +107,70 @@
<!-- 增加/编辑排班弹窗 -->
<custom-modal :title="isAdd ? 'Add' : 'Edit'" :width="845" v-model="visible" :okHandler="submit">
<div class="account-assign">
<a-transfer
:data-source="stationList"
:target-keys="targetKeys"
:render="item => item.title"
:operations="['Assign', 'Remove']"
:titles="['Particulate Station', 'Roster personnel']"
:show-select-all="false"
@change="onChange"
>
<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>
<!-- <a-select
:options="accountList"
mode="multiple"
show-search
:filter-option="filterOption"
style="width: 190px"
v-model="selectedAccount"
<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"
>
<div slot="dropdownRender" slot-scope="menu">
<v-nodes :vnodes="menu" />
<div class="account-add" @click="onAddToList()">
<a>Add</a>
</div>
</div>
<a-select-option v-for="(account, index) of accountList" :key="index" :value="index">
{{ account.title }}
</a-select-option>
</a-select> -->
<custom-popover-search :options="accountList" :remote-method="getAccountList"></custom-popover-search>
<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>
<!-- 穿梭框右上方搜索结束 -->
</div>
</a-spin>
</custom-modal>
<!-- 增加/编辑排班弹窗结束 -->
</div>
@ -172,6 +181,10 @@ 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: {
@ -182,31 +195,57 @@ export default {
CustomPopoverSearch
},
data() {
this.dateFormat = dateFormat
return {
currentMonth: moment(),
form: {},
visible: true,
currentMonth: moment(), //
originalTreeData: [],
currentDate: moment(), //
schedulingInfo: [], // scheduling
isGettingList: false,
scheduleList: [], //
visible: false, // /
isGettingDetail: false, //
originalTreeData: [], //
selectedStationKeys: [], // 穿
targetKeys: [],
isGettingStationList: false,
isGettingAccountList: false,
stationList: [],
accountList: [],
selectedAccount: [], // User Name
accountTreeData: [],
keyword: '', // 穿
stationList: [], //
accountList: [], //
accountTreeData: [], //
checkedAccount: '', // 穿
rightAccountChildSelectedKeys: [] // 穿
rightAccountChildSelectedKeys: [], // 穿
isChanging: false,
dragItem: null,
fromUserId: ''
}
},
created() {
this.getList()
this.getStationList()
this.getAccountList()
},
methods: {
//
async getList() {},
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 {
@ -226,10 +265,7 @@ export default {
}
})
this.stationList = records.map(item => ({
title: item.stationCode,
key: item.stationId.toString()
}))
this.stationList = records
} else {
this.$message.error(message)
}
@ -242,12 +278,13 @@ export default {
async getAccountList() {
try {
this.isGettingAccountList = true
const { success, result, message } = await getAction('/sys/user/list?pageIndex=1&pageSize=1000')
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.realname,
label: item.username,
value: item.id
}))
} else {
@ -255,24 +292,106 @@ export default {
}
} catch (error) {
console.error(error)
} finally {
this.isGettingAccountList = false
}
},
//
onDel(item) {
console.log('%c [ 删除 ]-51', 'font-size:13px; background:pink; color:#bf2c9f;', item)
//
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() {
@ -283,6 +402,86 @@ export default {
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()
}
},
/**
* 以下是对穿梭框的处理
*/
@ -296,6 +495,10 @@ export default {
itemSelect(eventKey, !this.isChecked(checkedKeys, eventKey))
},
handleSelectChange(sourceSelectedKeys, targetSelectedKeys) {
this.selectedStationKeys = [...sourceSelectedKeys, ...targetSelectedKeys]
},
// station
handleTreeData(data, targetKeys = [], level) {
data.forEach(item => {
@ -309,20 +512,20 @@ export default {
return data
},
filterOption(input, option) {
return option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
},
// 穿
onAddToList() {
this.accountTreeData = this.selectedAccount.map(id => {
const find = this.accountList.find(account => account.value == id)
return {
title: find.label,
key: id,
children: []
}
})
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 = ''
},
@ -332,6 +535,8 @@ export default {
// selected selected false
if (isLeaf) {
//
this.checkedAccount = ''
if (selected) {
//
itemSelect(eventKey, false)
@ -359,10 +564,11 @@ export default {
const findAccount = this.accountTreeData.find(account => account.key == this.checkedAccount)
if (findAccount) {
const children = moveKeys.map(key => {
const findStation = this.stationList.find(station => station.key == key)
const findStation = this.stationList.find(station => station.stationId == key)
return {
isLeaf: true,
...cloneDeep(findStation)
title: findStation.stationCode,
key
}
})
findAccount.children.push(...children)
@ -386,15 +592,16 @@ export default {
this.targetKeys = targetKeys
}
},
watch: {
currentMonth() {
this.getList()
}
},
computed: {
treeData() {
return this.handleTreeData(cloneDeep(this.originalTreeData), this.targetKeys, 0)
}
},
watch: {
currentMonth() {
this.currentDate = this.currentMonth.clone()
this.getList()
}
}
}
</script>
@ -442,16 +649,25 @@ export default {
&-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 {
position: absolute;
top: 5px;
right: 16px;
margin-left: 15px;
margin-right: 10px;
width: 14px;
height: 14px;
background: url(~@/assets/images/system/del-normal.png);
@ -527,6 +743,7 @@ export default {
}
}
&-content {
min-height: 62px;
border-top: 1px solid @borderColor;
border-left: 1px solid @borderColor;
padding: 6px;
@ -540,9 +757,21 @@ export default {
//
&-calendar {
overflow: auto;
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;
@ -564,6 +793,12 @@ export default {
&-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;
@ -682,4 +917,8 @@ export default {
color: #0cebc9;
}
}
.draggable {
cursor: pointer;
}
</style>

View File

@ -264,7 +264,7 @@ export default {
{
type: 'custom-select',
label: 'Role',
name: 'role',
name: 'roleId',
span: 4,
props: {
options: this.roleOptions

View File

@ -21,6 +21,7 @@
</a-form-item>
</a-form-model>
<login-select-tenant ref="loginSelect" @success="loginSelectOk"></login-select-tenant>
</div>
</template>