feat(attendance): 新增考勤模板管理功能,支持模板的增删改查和导入导出
1. 新增AttendanceTemplateManager组件,实现考勤模板的本地管理 2. 在排班页面添加模板管理入口和相关操作按钮 3. 支持将班次配置保存为模板、从模板快速导入人员 4. 优化班次选择器的交互体验,添加清空功能 5. 新增单个班次的人员导入导出功能
This commit is contained in:
318
klp-ui/src/components/AttendanceTemplateManager/index.vue
Normal file
318
klp-ui/src/components/AttendanceTemplateManager/index.vue
Normal file
@@ -0,0 +1,318 @@
|
||||
<template>
|
||||
<div class="template-manager">
|
||||
<div class="left-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">模板列表</span>
|
||||
<el-button icon="el-icon-plus" size="mini" type="primary" @click="addTemplate">新增</el-button>
|
||||
</div>
|
||||
<div class="template-list">
|
||||
<div
|
||||
v-for="template in templateList"
|
||||
:key="template.id"
|
||||
:class="['template-item', { active: currentTemplate.id === template.id }]"
|
||||
@click="selectTemplate(template)"
|
||||
>
|
||||
<div class="template-name">{{ template.name }}</div>
|
||||
<div class="template-meta">
|
||||
<span>{{ template.employeeCount }}人</span>
|
||||
<span class="create-time">{{ template.createTime }}</span>
|
||||
</div>
|
||||
<div class="template-actions">
|
||||
<el-button size="mini" type="text" @click.stop="deleteTemplate(template)">删除</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="templateList.length === 0" class="empty-list">
|
||||
<el-empty description="暂无模板" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">员工配置</span>
|
||||
</div>
|
||||
<div class="template-form">
|
||||
<el-form :model="currentTemplate" label-width="70px" inline>
|
||||
<el-form-item label="模板名称">
|
||||
<el-input
|
||||
v-model="currentTemplate.name"
|
||||
placeholder="请输入模板名称"
|
||||
style="width: 200px;"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="transfer-container">
|
||||
<el-transfer
|
||||
v-model="selectedEmployeeIds"
|
||||
:data="employeeList"
|
||||
:titles="['可选员工', '已选员工']"
|
||||
:button-texts="['移除', '添加']"
|
||||
filterable
|
||||
filter-placeholder="搜索员工"
|
||||
:width="['160px', '160px']"
|
||||
:height="280"
|
||||
/>
|
||||
</div>
|
||||
<div class="right-panel-footer">
|
||||
<el-button type="primary" @click="saveTemplate" :disabled="!currentTemplate.name">保存</el-button>
|
||||
<el-button @click="resetForm">重置</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'AttendanceTemplateManager',
|
||||
props: {
|
||||
employeeList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templateList: [],
|
||||
currentTemplate: {
|
||||
id: '',
|
||||
name: '',
|
||||
employeeIds: [],
|
||||
employeeCount: 0,
|
||||
createTime: ''
|
||||
},
|
||||
selectedEmployeeIds: [],
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTemplates()
|
||||
},
|
||||
methods: {
|
||||
loadTemplates() {
|
||||
try {
|
||||
const templates = localStorage.getItem('attendanceTemplates')
|
||||
this.templateList = templates ? JSON.parse(templates) : []
|
||||
} catch (e) {
|
||||
this.templateList = []
|
||||
}
|
||||
},
|
||||
|
||||
saveTemplates() {
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(this.templateList))
|
||||
this.$emit('update')
|
||||
},
|
||||
|
||||
addTemplate() {
|
||||
this.currentTemplate = {
|
||||
id: '',
|
||||
name: '',
|
||||
employeeIds: [],
|
||||
employeeCount: 0,
|
||||
createTime: ''
|
||||
}
|
||||
this.selectedEmployeeIds = []
|
||||
},
|
||||
|
||||
editTemplate(template) {
|
||||
this.currentTemplate = JSON.parse(JSON.stringify(template))
|
||||
this.selectedEmployeeIds = [...template.employeeIds]
|
||||
},
|
||||
|
||||
deleteTemplate(template) {
|
||||
this.$confirm('确定删除?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const index = this.templateList.findIndex(t => t.id === template.id)
|
||||
if (index > -1) {
|
||||
this.templateList.splice(index, 1)
|
||||
this.saveTemplates()
|
||||
this.$message.success('删除成功')
|
||||
if (this.currentTemplate.id === template.id) {
|
||||
this.resetForm()
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消')
|
||||
})
|
||||
},
|
||||
|
||||
selectTemplate(template) {
|
||||
this.currentTemplate = JSON.parse(JSON.stringify(template))
|
||||
this.selectedEmployeeIds = [...template.employeeIds]
|
||||
},
|
||||
|
||||
saveTemplate() {
|
||||
if (!this.currentTemplate.name.trim()) {
|
||||
this.$message.warning('请输入模板名称')
|
||||
return
|
||||
}
|
||||
|
||||
const templateData = {
|
||||
id: this.currentTemplate.id || Date.now().toString(),
|
||||
name: this.currentTemplate.name.trim(),
|
||||
employeeIds: [...this.selectedEmployeeIds],
|
||||
employeeCount: this.selectedEmployeeIds.length,
|
||||
createTime: this.currentTemplate.id ? this.currentTemplate.createTime : new Date().toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
if (this.currentTemplate.id) {
|
||||
const index = this.templateList.findIndex(t => t.id === this.currentTemplate.id)
|
||||
if (index > -1) {
|
||||
this.templateList[index] = templateData
|
||||
}
|
||||
this.$message.success('修改成功')
|
||||
} else {
|
||||
this.templateList.push(templateData)
|
||||
this.$message.success('新增成功')
|
||||
}
|
||||
|
||||
this.saveTemplates()
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
this.currentTemplate = {
|
||||
id: '',
|
||||
name: '',
|
||||
employeeIds: [],
|
||||
employeeCount: 0,
|
||||
createTime: ''
|
||||
}
|
||||
this.selectedEmployeeIds = []
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.template-manager {
|
||||
display: flex;
|
||||
height: 520px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid #e4e7ed;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.right-panel {
|
||||
width: 600px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.template-list::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.template-list::-webkit-scrollbar-thumb {
|
||||
background-color: #dcdfe6;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background-color: #fafafa;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.template-item:hover {
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.template-item.active {
|
||||
background-color: #e6f7ff;
|
||||
border-left: 3px solid #1890ff;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
flex: 1;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.template-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 2px;
|
||||
margin-right: 12px;
|
||||
font-size: 11px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.create-time {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.template-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.template-actions .el-button {
|
||||
padding: 0 4px;
|
||||
font-size: 11px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.template-actions .el-button:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.empty-list {
|
||||
padding: 40px 0;
|
||||
}
|
||||
|
||||
.template-form {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.transfer-container {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-panel-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
</style>
|
||||
@@ -20,7 +20,7 @@
|
||||
<div v-if="multiple" class="custom-transfer" v-loading="loading">
|
||||
<!-- 左侧:可选列表 -->
|
||||
<div class="transfer-panel">
|
||||
<div class="panel-header">可选员工</div>
|
||||
<div class="panel-header">可选员工 <span class="count-info">(已选 {{ leftSelectedKeys.length }}/总计 {{ availableList.length }})</span></div>
|
||||
<div class="panel-search">
|
||||
<input
|
||||
type="text"
|
||||
@@ -73,7 +73,7 @@
|
||||
|
||||
<!-- 右侧:已选列表 -->
|
||||
<div class="transfer-panel">
|
||||
<div class="panel-header">已选员工</div>
|
||||
<div class="panel-header">已选员工 <span class="count-info">(共 {{ selectedList.length }}/已选 {{ rightSelectedKeys.length }})</span></div>
|
||||
<div class="panel-search">
|
||||
<input
|
||||
type="text"
|
||||
@@ -601,6 +601,13 @@ export default {
|
||||
background: #f5f7fa;
|
||||
}
|
||||
|
||||
.count-info {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.panel-search {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #f2f6fc;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<el-alert type="info" title="提示:双击排班单元格可编辑排班"></el-alert>
|
||||
@@ -90,16 +91,16 @@
|
||||
@click="removeShiftItem(index)">删除</el-button>
|
||||
</div>
|
||||
<div class="shift-config-fields">
|
||||
<el-select v-model="item.shiftId" placeholder="选择班次" style="width: 180px;">
|
||||
<el-select v-model="item.shiftId" placeholder="选择班次" style="width: 180px;" clearable>
|
||||
<el-option v-for="shift in shiftList" :key="shift.shiftId"
|
||||
:label="shift.shiftName" :value="shift.shiftId" />
|
||||
</el-select>
|
||||
<span v-if="item.shiftId" class="shift-time-display">
|
||||
{{ getShiftTime(item.shiftId) }}
|
||||
</span>
|
||||
<el-select v-model="item.ruleId" placeholder="倒班规则(可选)" style="width: 180px;">
|
||||
<el-select v-model="item.ruleId" placeholder="倒班规则(可选)" style="width: 180px;" clearable>
|
||||
<el-option v-for="rule in shiftRuleList" :key="rule.ruleId"
|
||||
:label="rule.ruleName" :value="rule.ruleId" />
|
||||
:label="rule.changeDays" :value="rule.ruleId" />
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,8 +117,16 @@
|
||||
<span class="assignment-count">
|
||||
已分配 {{ item.employeeIds ? item.employeeIds.split(',').filter(id => id.trim()).length : 0 }} 人
|
||||
</span>
|
||||
<!-- <el-button icon="el-icon-copy" size="mini" @click="copyShiftItem(index)"
|
||||
title="复制班次配置">复制</el-button> -->
|
||||
<div class="import-export-buttons">
|
||||
<el-button icon="el-icon-download" size="mini" type="success" @click="exportCsv(index)"
|
||||
title="导出CSV">导出人员</el-button>
|
||||
<el-button icon="el-icon-upload" size="mini" type="info" @click="importCsv(index)"
|
||||
title="导入CSV">导入人员</el-button>
|
||||
<el-button icon="el-icon-save" size="mini" type="warning" @click="saveSingleTemplate(index)"
|
||||
title="保存为模板">存储为模板</el-button>
|
||||
<el-button icon="el-icon-folder-open" size="mini" type="primary" @click="openTemplateDialog(index)"
|
||||
title="使用模板">使用模板</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<EmployeeSelector
|
||||
v-model="item.employeeIds"
|
||||
@@ -125,6 +134,7 @@
|
||||
:disabled-names="getExcludedIds(index)"
|
||||
placeholder="选择该班次的员工"
|
||||
title="选择班次员工" />
|
||||
<input type="file" ref="fileInput" class="file-input" accept=".csv" @change="handleFileChange($event, index)" />
|
||||
</div>
|
||||
<!-- <div class="quick-actions">
|
||||
<el-button type="success" icon="el-icon-random" @click="quickAssignByDepartment">
|
||||
@@ -162,6 +172,32 @@
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 模板管理弹窗 -->
|
||||
<el-dialog title="人员列表模板管理" :visible.sync="showTemplateManager" width="1000px" :close-on-click-modal="false">
|
||||
<AttendanceTemplateManager
|
||||
:employee-list="employeeListForTransfer"
|
||||
@update="loadTemplates"
|
||||
/>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 班次模板选择弹窗 -->
|
||||
<el-dialog title="选择人员模板" :visible.sync="showTemplateDialog" width="500px">
|
||||
<el-table :data="templateList" border style="width: 100%;">
|
||||
<el-table-column prop="name" label="模板名称" />
|
||||
<el-table-column prop="employeeCount" label="员工数量" align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" />
|
||||
<el-table-column label="操作" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="primary" @click.stop="applySingleTemplate(scope.row)">应用</el-button>
|
||||
<el-button size="mini" type="danger" @click.stop="deleteSingleTemplate(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="showTemplateDialog = false">关闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 编辑班次弹窗 -->
|
||||
<el-dialog title="编辑班次" :visible.sync="editDialogVisible" width="400px">
|
||||
<el-form ref="editForm" :model="editForm" label-width="80px">
|
||||
@@ -199,13 +235,15 @@
|
||||
<script>
|
||||
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 } from '@/api/wms/attendanceSchedule'
|
||||
import { listShift } from '@/api/wms/attendanceShift'
|
||||
import { listAttendanceShiftRule } from '@/api/wms/attendanceShiftRule'
|
||||
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
|
||||
export default {
|
||||
name: 'AttendanceSchedule',
|
||||
components: { TimeRangePicker, EmployeeSelector },
|
||||
components: { TimeRangePicker, EmployeeSelector, AttendanceTemplateManager },
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
@@ -219,6 +257,11 @@ export default {
|
||||
shiftRuleList: [],
|
||||
dialogVisible: false,
|
||||
editDialogVisible: false,
|
||||
showTemplateDialog: false,
|
||||
showTemplateManager: false,
|
||||
templateList: [],
|
||||
currentShiftIndex: -1,
|
||||
employeeList: [],
|
||||
currentStep: 1, // 步骤:1-选择班次,2-分配人员,3-确认
|
||||
form: {
|
||||
dateRange: [],
|
||||
@@ -294,12 +337,20 @@ export default {
|
||||
return null
|
||||
}
|
||||
return this.shiftList.find(s => s.shiftId === this.editForm.shiftId)
|
||||
},
|
||||
employeeListForTransfer() {
|
||||
return this.employeeList.map(emp => ({
|
||||
key: emp.id,
|
||||
label: emp.name
|
||||
}))
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.initDateRange()
|
||||
this.getShiftList()
|
||||
this.getShiftRuleList()
|
||||
this.loadTemplates()
|
||||
this.getEmployeeList()
|
||||
},
|
||||
methods: {
|
||||
// 刷新排班
|
||||
@@ -742,6 +793,189 @@ export default {
|
||||
cancel() {
|
||||
this.dialogVisible = false
|
||||
this.reset()
|
||||
},
|
||||
|
||||
// 导出CSV文件
|
||||
exportCsv(index) {
|
||||
const shiftItem = this.form.shiftList[index]
|
||||
const shiftName = this.getShiftName(shiftItem.shiftId) || '班次'
|
||||
const ruleName = shiftItem.ruleId ? this.getRuleName(shiftItem.ruleId) : ''
|
||||
|
||||
let csvContent = '名字\n'
|
||||
|
||||
if (shiftItem.employeeIds) {
|
||||
const ids = shiftItem.employeeIds.split(',').filter(id => id.trim())
|
||||
ids.forEach(id => {
|
||||
csvContent += `${id}\n`
|
||||
})
|
||||
}
|
||||
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
|
||||
const link = document.createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
const fileName = `${shiftName}${ruleName ? '_' + ruleName : ''}_员工列表.csv`
|
||||
link.setAttribute('href', url)
|
||||
link.setAttribute('download', fileName)
|
||||
link.style.visibility = 'hidden'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
|
||||
// 导入CSV文件
|
||||
importCsv(index) {
|
||||
const fileInput = document.querySelector('.file-input')
|
||||
if (fileInput) {
|
||||
fileInput.click()
|
||||
}
|
||||
},
|
||||
|
||||
// 处理文件选择
|
||||
handleFileChange(event, index) {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = (e) => {
|
||||
const content = e.target.result
|
||||
const lines = content.split('\n').filter(line => line.trim())
|
||||
|
||||
if (lines.length === 0) {
|
||||
this.$message.warning('CSV文件内容为空')
|
||||
return
|
||||
}
|
||||
|
||||
const header = lines[0].trim()
|
||||
if (header !== '名字') {
|
||||
this.$message.warning('CSV文件格式不正确,第一行应为"名字"')
|
||||
return
|
||||
}
|
||||
|
||||
const names = lines.slice(1).map(line => line.trim()).filter(name => name)
|
||||
if (names.length === 0) {
|
||||
this.$message.warning('CSV文件中没有有效的员工名字')
|
||||
return
|
||||
}
|
||||
|
||||
const shiftItem = this.form.shiftList[index]
|
||||
shiftItem.employeeIds = names.join(',')
|
||||
|
||||
this.$message.success(`成功导入 ${names.length} 名员工`)
|
||||
}
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
|
||||
event.target.value = ''
|
||||
},
|
||||
|
||||
// 获取规则名称
|
||||
getRuleName(ruleId) {
|
||||
const rule = this.shiftRuleList.find(r => r.ruleId === ruleId)
|
||||
return rule ? rule.changeDays : ''
|
||||
},
|
||||
|
||||
// 获取员工列表
|
||||
getEmployeeList() {
|
||||
const params = {
|
||||
pageNum: 1,
|
||||
pageSize: 9999,
|
||||
}
|
||||
listEmployeeInfo(params).then(response => {
|
||||
// 过滤掉已离职的员工
|
||||
const filteredList = (response.rows || []).filter(employee => {
|
||||
return employee.isLeave !== 1 && employee.isLeave !== '1'
|
||||
})
|
||||
this.employeeList = filteredList.map(item => ({
|
||||
id: item.infoId,
|
||||
name: item.name
|
||||
}))
|
||||
}).catch(() => {
|
||||
this.employeeList = []
|
||||
})
|
||||
},
|
||||
|
||||
// 加载模板列表
|
||||
loadTemplates() {
|
||||
try {
|
||||
const templates = localStorage.getItem('attendanceTemplates')
|
||||
this.templateList = templates ? JSON.parse(templates) : []
|
||||
} catch (e) {
|
||||
this.templateList = []
|
||||
}
|
||||
},
|
||||
|
||||
// 保存模板到localStorage
|
||||
saveTemplates() {
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(this.templateList))
|
||||
},
|
||||
|
||||
// 保存单个班次为模板
|
||||
saveSingleTemplate(index) {
|
||||
const shiftItem = this.form.shiftList[index]
|
||||
if (!shiftItem.employeeIds || !shiftItem.employeeIds.trim()) {
|
||||
this.$message.warning('请先分配人员')
|
||||
return
|
||||
}
|
||||
|
||||
this.$prompt('请输入模板名称', '保存模板', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputValue: this.getShiftName(shiftItem.shiftId) + '_' + new Date().toLocaleDateString()
|
||||
}).then(({ value }) => {
|
||||
if (!value.trim()) {
|
||||
this.$message.warning('模板名称不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
const employeeIds = shiftItem.employeeIds.split(',').filter(id => id.trim())
|
||||
|
||||
const template = {
|
||||
id: Date.now().toString(),
|
||||
name: value.trim(),
|
||||
employeeIds: employeeIds,
|
||||
employeeCount: employeeIds.length,
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
}
|
||||
|
||||
this.templateList.push(template)
|
||||
this.saveTemplates()
|
||||
this.$message.success('模板保存成功')
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消保存')
|
||||
})
|
||||
},
|
||||
|
||||
// 打开模板选择弹窗
|
||||
openTemplateDialog(index) {
|
||||
this.currentShiftIndex = index
|
||||
this.showTemplateDialog = true
|
||||
},
|
||||
|
||||
// 应用单个模板到当前班次
|
||||
applySingleTemplate(template) {
|
||||
if (!template || !template.employeeIds || this.currentShiftIndex < 0) return
|
||||
|
||||
this.form.shiftList[this.currentShiftIndex].employeeIds = template.employeeIds.join(',')
|
||||
this.showTemplateDialog = false
|
||||
this.currentShiftIndex = -1
|
||||
this.$message.success('模板应用成功')
|
||||
},
|
||||
|
||||
// 删除单个模板
|
||||
deleteSingleTemplate(template) {
|
||||
this.$confirm('确定要删除这个模板吗?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
const index = this.templateList.findIndex(t => t.id === template.id)
|
||||
if (index > -1) {
|
||||
this.templateList.splice(index, 1)
|
||||
this.saveTemplates()
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -977,6 +1211,16 @@ export default {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.import-export-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 快速操作按钮 */
|
||||
.quick-actions {
|
||||
margin-top: 16px;
|
||||
|
||||
Reference in New Issue
Block a user