feat(wms/attendance): 新增批量单元格编辑排班功能
1. 新增批量修改排班班次的API接口 2. 重构排班页面工具栏,新增部门和员工姓名筛选功能 3. 新增批量编辑模式,支持选择多个已有排班单元格进行批量修改 4. 新增批量编辑弹窗,可统一修改所选排班的班次 5. 优化页面样式布局,添加批量操作相关的交互样式
This commit is contained in:
@@ -69,3 +69,12 @@ export function batchUpdateSchedule(data) {
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 批量修改排班班次(按主键)
|
||||
export function batchUpdateShiftByIds(data) {
|
||||
return request({
|
||||
url: '/wms/attendanceSchedule/batchUpdateShift',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,21 +1,46 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 日期范围选择 -->
|
||||
<div class="date-range-section">
|
||||
<TimeRangePicker v-model="dateRangeParams" startKey="scheduleDateStart" endKey="scheduleDateEnd"
|
||||
:defaultStartTime="defaultStartTime" :defaultEndTime="defaultEndTime" @change="handleDateRangeChange"
|
||||
@quick-select="getScheduleList" />
|
||||
<!-- 工具栏 -->
|
||||
<div class="tool-bar">
|
||||
<div class="tool-bar-left">
|
||||
<TimeRangePicker v-model="dateRangeParams" startKey="scheduleDateStart" endKey="scheduleDateEnd"
|
||||
:defaultStartTime="defaultStartTime" :defaultEndTime="defaultEndTime" @change="handleDateRangeChange"
|
||||
@quick-select="getScheduleList" />
|
||||
<el-select v-model="queryParams.employeeDept" placeholder="部门" clearable style="width: 160px;" @change="handleFilterChange">
|
||||
<el-option v-for="dept in deptList" :key="dept" :label="dept" :value="dept" />
|
||||
</el-select>
|
||||
<el-autocomplete
|
||||
v-model="queryParams.employeeName"
|
||||
:fetch-suggestions="querySearchAsync"
|
||||
placeholder="员工姓名"
|
||||
style="width: 180px;"
|
||||
:trigger-on-focus="false"
|
||||
@select="handleFilterChange"
|
||||
clearable
|
||||
@clear="handleFilterChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="tool-bar-right">
|
||||
<el-button plain type="primary" icon="el-icon-plus" @click="handleCreate">创建排班</el-button>
|
||||
<el-button plain type="info" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
||||
<el-button plain type="success" icon="el-icon-setting" @click="showTemplateManager = true">管理模板</el-button>
|
||||
<el-button plain type="warning" icon="el-icon-edit" @click="handleBatchUpdate">批量修改</el-button>
|
||||
<el-button plain type="primary" icon="el-icon-s-grid" @click="enterBatchCellMode">批量编辑</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作栏 -->
|
||||
<div class="operation-bar">
|
||||
<el-button plain type="primary" icon="el-icon-plus" @click="handleCreate">创建排班</el-button>
|
||||
<el-button plain type="info" icon="el-icon-refresh" @click="handleRefresh">刷新</el-button>
|
||||
<el-button plain type="success" icon="el-icon-setting" @click="showTemplateManager = true">管理模板</el-button>
|
||||
<el-button plain type="warning" icon="el-icon-edit" @click="handleBatchUpdate">批量修改</el-button>
|
||||
</div>
|
||||
<el-alert v-if="!batchCellSelectMode" type="info" title="提示:双击排班单元格可编辑排班"></el-alert>
|
||||
|
||||
<el-alert type="info" title="提示:双击排班单元格可编辑排班"></el-alert>
|
||||
<!-- 批量选择模式提示栏 -->
|
||||
<div v-if="batchCellSelectMode" class="batch-select-bar">
|
||||
<span class="batch-select-info">
|
||||
<i class="el-icon-info" /> 批量编辑模式:点击单元格选择(仅可选择已有排班的单元格),已选 <strong>{{ batchSelectedCount }}</strong> 个
|
||||
</span>
|
||||
<div class="batch-select-actions">
|
||||
<el-button size="mini" type="primary" :disabled="batchSelectedCount === 0" @click="confirmBatchCellSelection">确认选择</el-button>
|
||||
<el-button size="mini" @click="cancelBatchCellSelection">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 排班表格 -->
|
||||
<div class="schedule-table-wrapper">
|
||||
@@ -31,8 +56,11 @@
|
||||
<!-- 动态日期列 -->
|
||||
<el-table-column v-for="date in dateList" :key="date" :label="formatDateLabel(date)" width="150" align="center">
|
||||
<template slot-scope="scope">
|
||||
<div class="schedule-cell" :class="{ 'has-data': scope.row[date], 'empty-cell': !scope.row[date] }"
|
||||
@dblclick="handleCellDoubleClick(scope.row, date)">
|
||||
<div
|
||||
class="schedule-cell"
|
||||
:class="cellClass(scope.row, date)"
|
||||
@dblclick="!batchCellSelectMode && handleCellDoubleClick(scope.row, date)"
|
||||
@click="batchCellSelectMode && handleCellClick(scope.row, date)">
|
||||
<template v-if="scope.row[date]">
|
||||
<div class="shift-name" :class="getShiftTypeClass(scope.row[date].shiftType)">
|
||||
{{ scope.row[date].shiftName }}
|
||||
@@ -46,8 +74,12 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="empty-hint">双击排班</span>
|
||||
<span class="empty-hint" v-if="!batchCellSelectMode">双击排班</span>
|
||||
</template>
|
||||
<span v-if="batchCellSelectMode" class="cell-checkbox">
|
||||
<i :class="isCellSelected(scope.row, date) ? 'el-icon-check' : 'el-icon-circle-check'"
|
||||
:style="{ color: isCellSelected(scope.row, date) ? '#fff' : '#c0c4cc' }" />
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -266,6 +298,43 @@
|
||||
<el-button type="primary" @click="submitEdit" :disabled="!editForm.shiftId">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 批量单元格编辑弹窗 -->
|
||||
<el-dialog title="批量编辑排班" :visible.sync="batchCellDialogVisible" width="900px">
|
||||
<div class="batch-cell-summary">
|
||||
已选择 <strong>{{ batchEditTableData.length }}</strong> 个排班单元格
|
||||
</div>
|
||||
<el-form label-width="80px" class="batch-cell-form">
|
||||
<el-form-item label="统一班次">
|
||||
<el-select v-model="batchCellEditShiftId" placeholder="选择要设置的新班次" style="width: 280px;">
|
||||
<el-option v-for="shift in shiftList" :key="shift.shiftId" :label="shift.shiftName" :value="shift.shiftId" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-table :data="batchEditTableData" border stripe max-height="400">
|
||||
<el-table-column prop="employeeName" label="员工" width="120" />
|
||||
<el-table-column prop="workDate" label="日期" width="120" />
|
||||
<el-table-column prop="oldShiftName" label="原班次" width="120">
|
||||
<template slot-scope="scope">
|
||||
<span :class="getShiftTypeClass(scope.row.oldShiftType)">
|
||||
{{ scope.row.oldShiftName }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="newShiftName" label="新班次">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="getNewShiftName()" :class="getShiftTypeClass(batchCellEditShiftShiftType)">
|
||||
{{ getNewShiftName() }}
|
||||
</span>
|
||||
<span v-else class="empty-hint">请选择班次</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="batchCellDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="!batchCellEditShiftId" :loading="buttonLoading" @click="submitBatchCellEdit">确定修改</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -273,7 +342,7 @@
|
||||
import TimeRangePicker from '@/views/wms/report/components/timeRangePicker.vue'
|
||||
import EmployeeSelector from '@/components/EmployeeSelector/index.vue'
|
||||
import AttendanceTemplateManager from '@/components/AttendanceTemplateManager/index.vue'
|
||||
import { listAttendanceSchedule, generateenerateSchedule, updateAttendanceSchedule, addAttendanceSchedule, delAttendanceSchedule, batchUpdateSchedule } from '@/api/wms/attendanceSchedule'
|
||||
import { listAttendanceSchedule, generateenerateSchedule, updateAttendanceSchedule, addAttendanceSchedule, delAttendanceSchedule, batchUpdateSchedule, batchUpdateShiftByIds } from '@/api/wms/attendanceSchedule'
|
||||
import { listShift } from '@/api/wms/attendanceShift'
|
||||
import { listAttendanceShiftRule } from '@/api/wms/attendanceShiftRule'
|
||||
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
@@ -292,6 +361,10 @@ export default {
|
||||
scheduleData: [],
|
||||
shiftList: [],
|
||||
shiftRuleList: [],
|
||||
queryParams: {
|
||||
employeeDept: '',
|
||||
employeeName: ''
|
||||
},
|
||||
dialogVisible: false,
|
||||
batchDialogVisible: false,
|
||||
batchForm: {
|
||||
@@ -319,6 +392,10 @@ export default {
|
||||
},
|
||||
currentEditRow: null,
|
||||
currentEditDate: '',
|
||||
batchCellSelectMode: false,
|
||||
batchCellSelection: {},
|
||||
batchCellDialogVisible: false,
|
||||
batchCellEditShiftId: '',
|
||||
rules: {
|
||||
dateRange: [
|
||||
{ required: true, message: '请选择时间段', trigger: 'change' }
|
||||
@@ -395,6 +472,44 @@ export default {
|
||||
key: emp.id,
|
||||
label: emp.name
|
||||
}))
|
||||
},
|
||||
deptList() {
|
||||
const depts = new Set()
|
||||
this.employeeList.forEach(emp => {
|
||||
if (emp.dept) depts.add(emp.dept)
|
||||
})
|
||||
return Array.from(depts).sort()
|
||||
},
|
||||
employeeNameList() {
|
||||
return this.employeeList.map(emp => ({
|
||||
value: emp.name,
|
||||
dept: emp.dept || ''
|
||||
}))
|
||||
},
|
||||
batchSelectedCount() {
|
||||
return Object.keys(this.batchCellSelection).length
|
||||
},
|
||||
batchEditTableData() {
|
||||
const result = []
|
||||
Object.keys(this.batchCellSelection).forEach(key => {
|
||||
const cell = this.batchCellSelection[key]
|
||||
if (cell) {
|
||||
result.push({
|
||||
key,
|
||||
employeeName: cell.employeeName,
|
||||
workDate: cell.workDate,
|
||||
scheduleId: cell.scheduleId,
|
||||
oldShiftName: cell.shiftName,
|
||||
oldShiftType: cell.shiftType
|
||||
})
|
||||
}
|
||||
})
|
||||
return result
|
||||
},
|
||||
batchCellEditShiftShiftType() {
|
||||
if (!this.batchCellEditShiftId) return ''
|
||||
const shift = this.shiftList.find(s => s.shiftId === this.batchCellEditShiftId)
|
||||
return shift ? shift.shiftType : ''
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -472,7 +587,10 @@ export default {
|
||||
// 获取排班列表
|
||||
getScheduleList() {
|
||||
this.loading = true
|
||||
listAttendanceSchedule(this.dateRangeParams).then(response => {
|
||||
const params = { ...this.dateRangeParams }
|
||||
if (this.queryParams.employeeDept) params.employeeDept = this.queryParams.employeeDept
|
||||
if (this.queryParams.employeeName) params.employeeName = this.queryParams.employeeName
|
||||
listAttendanceSchedule(params).then(response => {
|
||||
this.scheduleData = this.transformScheduleData(response.rows || [])
|
||||
this.loading = false
|
||||
}).catch(() => {
|
||||
@@ -544,6 +662,96 @@ export default {
|
||||
return shiftType === '夜班' ? 'night-shift' : 'day-shift'
|
||||
},
|
||||
|
||||
// ========== 批量单元格编辑 ==========
|
||||
|
||||
cellKey(row, date) {
|
||||
return `${row.employeeId}_${date}`
|
||||
},
|
||||
|
||||
cellClass(row, date) {
|
||||
const hasData = !!row[date]
|
||||
const selected = this.batchCellSelectMode && this.isCellSelected(row, date)
|
||||
return {
|
||||
'has-data': hasData,
|
||||
'empty-cell': !hasData,
|
||||
'batch-mode': this.batchCellSelectMode,
|
||||
'selected-cell': selected
|
||||
}
|
||||
},
|
||||
|
||||
isCellSelected(row, date) {
|
||||
return !!this.batchCellSelection[this.cellKey(row, date)]
|
||||
},
|
||||
|
||||
handleCellClick(row, date) {
|
||||
if (!this.batchCellSelectMode) return
|
||||
if (!row[date]) return
|
||||
const key = this.cellKey(row, date)
|
||||
if (this.batchCellSelection[key]) {
|
||||
this.$delete(this.batchCellSelection, key)
|
||||
} else {
|
||||
this.$set(this.batchCellSelection, key, {
|
||||
scheduleId: row[date].scheduleId,
|
||||
employeeName: row.employeeName,
|
||||
workDate: date,
|
||||
shiftName: row[date].shiftName,
|
||||
shiftType: row[date].shiftType
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
enterBatchCellMode() {
|
||||
this.batchCellSelectMode = true
|
||||
this.batchCellSelection = {}
|
||||
},
|
||||
|
||||
cancelBatchCellSelection() {
|
||||
this.batchCellSelectMode = false
|
||||
this.batchCellSelection = {}
|
||||
},
|
||||
|
||||
confirmBatchCellSelection() {
|
||||
if (this.batchSelectedCount === 0) {
|
||||
this.$message.warning('请至少选择一个排班单元格')
|
||||
return
|
||||
}
|
||||
this.batchCellSelectMode = false
|
||||
this.batchCellEditShiftId = ''
|
||||
this.batchCellDialogVisible = true
|
||||
},
|
||||
|
||||
getNewShiftName() {
|
||||
if (!this.batchCellEditShiftId) return ''
|
||||
const shift = this.shiftList.find(s => s.shiftId === this.batchCellEditShiftId)
|
||||
return shift ? shift.shiftName : ''
|
||||
},
|
||||
|
||||
submitBatchCellEdit() {
|
||||
if (!this.batchCellEditShiftId) {
|
||||
this.$message.warning('请选择新班次')
|
||||
return
|
||||
}
|
||||
const shiftId = this.batchCellEditShiftId
|
||||
const list = this.batchEditTableData.map(item => ({
|
||||
scheduleId: item.scheduleId,
|
||||
shiftId: shiftId
|
||||
}))
|
||||
|
||||
this.buttonLoading = true
|
||||
batchUpdateShiftByIds(list).then(() => {
|
||||
this.$message.success(`已批量修改 ${list.length} 条排班`)
|
||||
this.buttonLoading = false
|
||||
this.batchCellDialogVisible = false
|
||||
this.batchCellEditShiftId = ''
|
||||
this.batchCellSelection = {}
|
||||
this.getScheduleList()
|
||||
}).catch(() => {
|
||||
this.$message.error('批量修改失败,正在重新获取数据')
|
||||
this.buttonLoading = false
|
||||
this.getScheduleList()
|
||||
})
|
||||
},
|
||||
|
||||
// 双击单元格处理
|
||||
handleCellDoubleClick(row, date) {
|
||||
this.currentEditRow = row
|
||||
@@ -1014,13 +1222,29 @@ export default {
|
||||
})
|
||||
this.employeeList = filteredList.map(item => ({
|
||||
id: item.infoId,
|
||||
name: item.name
|
||||
name: item.name,
|
||||
dept: item.dept || ''
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.employeeList = []
|
||||
})
|
||||
},
|
||||
|
||||
querySearchAsync(queryString, cb) {
|
||||
if (!queryString) {
|
||||
cb([])
|
||||
return
|
||||
}
|
||||
const results = this.employeeNameList.filter(item => {
|
||||
return item.value.toLowerCase().includes(queryString.toLowerCase())
|
||||
})
|
||||
cb(results)
|
||||
},
|
||||
|
||||
handleFilterChange() {
|
||||
this.getScheduleList()
|
||||
},
|
||||
|
||||
// 加载模板列表
|
||||
loadTemplates() {
|
||||
try {
|
||||
@@ -1114,12 +1338,30 @@ export default {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.date-range-section {
|
||||
.tool-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
padding: 12px 16px;
|
||||
background: #fafafa;
|
||||
border-radius: 4px;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.operation-bar {
|
||||
margin-bottom: 20px;
|
||||
.tool-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.tool-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.schedule-table-wrapper {
|
||||
@@ -1152,6 +1394,72 @@ export default {
|
||||
color: #c0c4cc;
|
||||
}
|
||||
|
||||
/* 批量选择模式 */
|
||||
.batch-select-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
padding: 10px 16px;
|
||||
background: #ecf5ff;
|
||||
border: 1px solid #b3d8ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.batch-select-info {
|
||||
font-size: 14px;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.batch-select-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.schedule-cell.batch-mode {
|
||||
position: relative;
|
||||
border: 1px dashed #c0c4cc;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.schedule-cell.batch-mode.empty-cell {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.schedule-cell.selected-cell {
|
||||
background-color: #ecf5ff !important;
|
||||
border-color: #409eff;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.cell-checkbox {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.schedule-cell.selected-cell .cell-checkbox {
|
||||
background-color: #409eff;
|
||||
}
|
||||
|
||||
/* 批量单元格编辑弹窗 */
|
||||
.batch-cell-summary {
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.batch-cell-form {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.shift-info {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user