feat: 新增考勤模板管理功能
- 后端新增 WmsAttendanceTemplate CRUD(Controller/Service/Mapper/Domain) - 前端新增 attendanceTemplate API 对接 - 前端新增 AttendanceTemplateManager 组件(拖拽排序 + 模板编辑) - 优化考勤 drag.vue 页面交互
This commit is contained in:
44
klp-ui/src/api/wms/attendanceTemplate.js
Normal file
44
klp-ui/src/api/wms/attendanceTemplate.js
Normal file
@@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询排班模板列表
|
||||
export function listAttendanceTemplate(query) {
|
||||
return request({
|
||||
url: '/wms/attendanceTemplate/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询排班模板详细
|
||||
export function getAttendanceTemplate(templateId) {
|
||||
return request({
|
||||
url: '/wms/attendanceTemplate/' + templateId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增排班模板
|
||||
export function addAttendanceTemplate(data) {
|
||||
return request({
|
||||
url: '/wms/attendanceTemplate',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改排班模板
|
||||
export function updateAttendanceTemplate(data) {
|
||||
return request({
|
||||
url: '/wms/attendanceTemplate',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除排班模板
|
||||
export function delAttendanceTemplate(templateId) {
|
||||
return request({
|
||||
url: '/wms/attendanceTemplate/' + templateId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
@@ -1,11 +1,28 @@
|
||||
<template>
|
||||
<div class="template-manager">
|
||||
<div class="left-panel">
|
||||
<div class="custom-tabs">
|
||||
<div
|
||||
:class="['custom-tab', { active: activeTab === 'shared' }]"
|
||||
@click="switchTab('shared')"
|
||||
>共享模板</div>
|
||||
<div
|
||||
:class="['custom-tab', { active: activeTab === 'personal' }]"
|
||||
@click="switchTab('personal')"
|
||||
>自用模板</div>
|
||||
</div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">模板列表</span>
|
||||
<el-button icon="el-icon-plus" size="mini" type="primary" @click="addTemplate">新增</el-button>
|
||||
<div class="panel-header-actions">
|
||||
<el-button icon="el-icon-plus" size="mini" type="primary" @click="addTemplate">新增</el-button>
|
||||
<el-button icon="el-icon-download" size="mini" type="success" @click="exportCsv" :disabled="!currentTemplate.id">导出</el-button>
|
||||
<el-button icon="el-icon-upload2" size="mini" type="info" @click="importCsv">导入</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="template-list">
|
||||
<div class="shared-toolbar" v-show="activeTab === 'shared'">
|
||||
<el-input v-model="sharedQueryParams.templateName" placeholder="搜索模板名称" size="small" clearable prefix-icon="el-icon-search" @input="handleSearchTemplate" style="width: 200px;" />
|
||||
</div>
|
||||
<div class="template-list" v-loading="templateLoading">
|
||||
<div
|
||||
v-for="template in templateList"
|
||||
:key="template.id"
|
||||
@@ -25,7 +42,18 @@
|
||||
<el-empty description="暂无模板" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="shared-pagination" v-show="activeTab === 'shared'">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:current-page="sharedQueryParams.pageNum"
|
||||
:page-size="sharedQueryParams.pageSize"
|
||||
:total="sharedTotal"
|
||||
@current-change="handleSharedPageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<input type="file" ref="csvInput" accept=".csv" style="display:none" @change="handleCsvImport" />
|
||||
<div class="right-panel">
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">员工配置</span>
|
||||
@@ -62,6 +90,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { listAttendanceTemplate, addAttendanceTemplate, updateAttendanceTemplate, delAttendanceTemplate } from '@/api/wms/attendanceTemplate'
|
||||
|
||||
export default {
|
||||
name: 'AttendanceTemplateManager',
|
||||
props: {
|
||||
@@ -72,7 +102,16 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
templateList: [],
|
||||
activeTab: 'shared',
|
||||
sharedTemplateList: [],
|
||||
sharedTemplateLoading: false,
|
||||
sharedQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
templateName: ''
|
||||
},
|
||||
sharedTotal: 0,
|
||||
personalTemplateList: [],
|
||||
currentTemplate: {
|
||||
id: '',
|
||||
name: '',
|
||||
@@ -83,22 +122,202 @@ export default {
|
||||
selectedEmployeeIds: [],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
templateList() {
|
||||
return this.activeTab === 'shared' ? this.sharedTemplateList : this.personalTemplateList
|
||||
},
|
||||
templateLoading() {
|
||||
return this.activeTab === 'shared' ? this.sharedTemplateLoading : false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadTemplates()
|
||||
},
|
||||
methods: {
|
||||
loadTemplates() {
|
||||
try {
|
||||
const templates = localStorage.getItem('attendanceTemplates')
|
||||
this.templateList = templates ? JSON.parse(templates) : []
|
||||
} catch (e) {
|
||||
this.templateList = []
|
||||
if (this.activeTab === 'shared') {
|
||||
this.sharedTemplateLoading = true
|
||||
const query = {
|
||||
pageNum: this.sharedQueryParams.pageNum,
|
||||
pageSize: this.sharedQueryParams.pageSize
|
||||
}
|
||||
if (this.sharedQueryParams.templateName) {
|
||||
query.templateName = this.sharedQueryParams.templateName
|
||||
}
|
||||
listAttendanceTemplate(query).then(res => {
|
||||
this.sharedTemplateList = (res.rows || []).map(row => ({
|
||||
id: row.templateId,
|
||||
name: row.templateName,
|
||||
employeeIds: this.parseTemplateContent(row.templateContent),
|
||||
employeeCount: this.parseTemplateContent(row.templateContent).length,
|
||||
createTime: row.createTime
|
||||
}))
|
||||
this.sharedTotal = res.total || 0
|
||||
}).finally(() => {
|
||||
this.sharedTemplateLoading = false
|
||||
})
|
||||
} else {
|
||||
try {
|
||||
const templates = localStorage.getItem('attendanceTemplates')
|
||||
this.personalTemplateList = templates ? JSON.parse(templates) : []
|
||||
} catch (e) {
|
||||
this.personalTemplateList = []
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
saveTemplates() {
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(this.templateList))
|
||||
this.$emit('update')
|
||||
parseTemplateContent(content) {
|
||||
if (!content) return []
|
||||
try {
|
||||
const parsed = JSON.parse(content)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch (e) {
|
||||
return content.split(',').filter(Boolean)
|
||||
}
|
||||
},
|
||||
|
||||
buildTemplateContent(employeeIds) {
|
||||
return JSON.stringify(employeeIds)
|
||||
},
|
||||
|
||||
handleSearchTemplate() {
|
||||
this.sharedQueryParams.pageNum = 1
|
||||
this.loadTemplates()
|
||||
},
|
||||
|
||||
handleSharedPageChange(pageNum) {
|
||||
this.sharedQueryParams.pageNum = pageNum
|
||||
this.loadTemplates()
|
||||
},
|
||||
|
||||
switchTab(tab) {
|
||||
if (this.activeTab === tab) return
|
||||
this.activeTab = tab
|
||||
this.loadTemplates()
|
||||
this.resetForm()
|
||||
},
|
||||
|
||||
savePersonalTemplates() {
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(this.personalTemplateList))
|
||||
},
|
||||
|
||||
exportCsv() {
|
||||
if (!this.currentTemplate.id || !this.currentTemplate.employeeIds.length) {
|
||||
this.$message.warning('当前模板没有员工可导出')
|
||||
return
|
||||
}
|
||||
const empMap = {}
|
||||
this.employeeList.forEach(emp => {
|
||||
empMap[emp.key] = emp.label
|
||||
})
|
||||
let csvContent = '\uFEFF员工ID,员工姓名\n'
|
||||
this.currentTemplate.employeeIds.forEach(id => {
|
||||
const name = empMap[id] || id
|
||||
csvContent += `${id},${name}\n`
|
||||
})
|
||||
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' })
|
||||
const link = document.createElement('a')
|
||||
const url = URL.createObjectURL(blob)
|
||||
link.setAttribute('href', url)
|
||||
link.setAttribute('download', `${this.currentTemplate.name}.csv`)
|
||||
link.style.visibility = 'hidden'
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
},
|
||||
|
||||
importCsv() {
|
||||
this.$refs.csvInput.click()
|
||||
},
|
||||
|
||||
handleCsvImport(event) {
|
||||
const file = event.target.files[0]
|
||||
if (!file) return
|
||||
|
||||
const reader = new FileReader()
|
||||
reader.onload = async (e) => {
|
||||
const content = e.target.result
|
||||
const lines = content.split('\n').filter(line => line.trim())
|
||||
if (lines.length === 0) {
|
||||
this.$message.warning('CSV文件内容为空')
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
const header = lines[0].trim()
|
||||
if (header !== '员工ID,员工姓名' && header !== '员工ID\t员工姓名') {
|
||||
this.$message.warning('CSV文件格式不正确,表头应为"员工ID,员工姓名"')
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const empKeySet = new Set()
|
||||
const empLabelMap = {}
|
||||
this.employeeList.forEach(emp => {
|
||||
empKeySet.add(String(emp.key))
|
||||
empLabelMap[emp.label] = emp.key
|
||||
})
|
||||
|
||||
const matchedIds = []
|
||||
const notFoundNames = []
|
||||
const lines_data = lines.slice(1)
|
||||
for (const line of lines_data) {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed) continue
|
||||
const parts = trimmed.includes('\t') ? trimmed.split('\t') : trimmed.split(',')
|
||||
const key = parts[0] ? parts[0].trim() : ''
|
||||
const label = parts.length > 1 ? parts.slice(1).join(',').trim() : key
|
||||
|
||||
if (key && empKeySet.has(String(key))) {
|
||||
matchedIds.push(key)
|
||||
} else if (label && empLabelMap[label] !== undefined) {
|
||||
matchedIds.push(empLabelMap[label])
|
||||
} else {
|
||||
notFoundNames.push(label || key)
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedIds.length === 0) {
|
||||
this.$message.warning('CSV文件中没有匹配到任何员工')
|
||||
event.target.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const templateName = file.name.replace(/\.csv$/i, '')
|
||||
|
||||
if (this.activeTab === 'shared') {
|
||||
await addAttendanceTemplate({
|
||||
templateName: templateName,
|
||||
templateContent: this.buildTemplateContent(matchedIds)
|
||||
})
|
||||
this.loadTemplates()
|
||||
const newTemplate = this.sharedTemplateList.find(t => t.name === templateName)
|
||||
if (newTemplate) {
|
||||
this.currentTemplate = JSON.parse(JSON.stringify(newTemplate))
|
||||
this.selectedEmployeeIds = [...newTemplate.employeeIds]
|
||||
}
|
||||
} else {
|
||||
const templateData = {
|
||||
id: Date.now().toString(),
|
||||
name: templateName,
|
||||
employeeIds: matchedIds,
|
||||
employeeCount: matchedIds.length,
|
||||
createTime: new Date().toLocaleString('zh-CN')
|
||||
}
|
||||
this.personalTemplateList.push(templateData)
|
||||
this.savePersonalTemplates()
|
||||
this.currentTemplate = JSON.parse(JSON.stringify(templateData))
|
||||
this.selectedEmployeeIds = [...matchedIds]
|
||||
}
|
||||
this.$emit('update')
|
||||
|
||||
if (notFoundNames.length > 0) {
|
||||
this.$message.warning(`成功导入${matchedIds.length}名员工,${notFoundNames.length}名未匹配:${notFoundNames.join('、')}`)
|
||||
} else {
|
||||
this.$message.success(`成功导入${matchedIds.length}名员工`)
|
||||
}
|
||||
}
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
event.target.value = ''
|
||||
},
|
||||
|
||||
addTemplate() {
|
||||
@@ -118,20 +337,29 @@ export default {
|
||||
},
|
||||
|
||||
deleteTemplate(template) {
|
||||
this.$confirm('确定删除?', '提示', {
|
||||
const confirmMsg = this.activeTab === 'shared'
|
||||
? '删除后该模板所有人都无法使用,确定删除?'
|
||||
: '确定删除?'
|
||||
this.$confirm(confirmMsg, '提示', {
|
||||
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()
|
||||
}).then(async () => {
|
||||
if (this.activeTab === 'shared') {
|
||||
await delAttendanceTemplate(template.id)
|
||||
} else {
|
||||
const index = this.personalTemplateList.findIndex(t => t.id === template.id)
|
||||
if (index > -1) {
|
||||
this.personalTemplateList.splice(index, 1)
|
||||
this.savePersonalTemplates()
|
||||
}
|
||||
}
|
||||
this.$message.success('删除成功')
|
||||
if (this.currentTemplate.id === template.id) {
|
||||
this.resetForm()
|
||||
}
|
||||
this.loadTemplates()
|
||||
this.$emit('update')
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消')
|
||||
})
|
||||
@@ -142,32 +370,52 @@ export default {
|
||||
this.selectedEmployeeIds = [...template.employeeIds]
|
||||
},
|
||||
|
||||
saveTemplate() {
|
||||
async 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
|
||||
if (this.activeTab === 'shared') {
|
||||
const apiData = {
|
||||
templateName: this.currentTemplate.name.trim(),
|
||||
templateContent: this.buildTemplateContent(this.selectedEmployeeIds)
|
||||
}
|
||||
this.$message.success('修改成功')
|
||||
} else {
|
||||
this.templateList.push(templateData)
|
||||
this.$message.success('新增成功')
|
||||
}
|
||||
|
||||
this.saveTemplates()
|
||||
if (this.currentTemplate.id) {
|
||||
apiData.templateId = this.currentTemplate.id
|
||||
await updateAttendanceTemplate(apiData)
|
||||
this.$message.success('修改成功')
|
||||
} else {
|
||||
await addAttendanceTemplate(apiData)
|
||||
this.$message.success('新增成功')
|
||||
}
|
||||
|
||||
this.loadTemplates()
|
||||
this.$emit('update')
|
||||
} else {
|
||||
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.personalTemplateList.findIndex(t => t.id === this.currentTemplate.id)
|
||||
if (index > -1) {
|
||||
this.personalTemplateList[index] = templateData
|
||||
}
|
||||
this.$message.success('修改成功')
|
||||
} else {
|
||||
this.personalTemplateList.push(templateData)
|
||||
this.$message.success('新增成功')
|
||||
}
|
||||
|
||||
this.savePersonalTemplates()
|
||||
this.$emit('update')
|
||||
}
|
||||
},
|
||||
|
||||
resetForm() {
|
||||
@@ -220,6 +468,21 @@ export default {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.panel-header-actions {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.shared-toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.shared-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 0 4px;
|
||||
}
|
||||
|
||||
.template-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
@@ -315,4 +578,31 @@ export default {
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.custom-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.custom-tab {
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.custom-tab:hover {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.custom-tab.active {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
@@ -226,8 +226,15 @@
|
||||
</el-dialog>
|
||||
|
||||
<!-- 班次模板选择弹窗 -->
|
||||
<el-dialog title="选择人员模板" :visible.sync="showTemplateDialog" width="500px">
|
||||
<el-table :data="templateList" border style="width: 100%;">
|
||||
<el-dialog title="选择人员模板" :visible.sync="showTemplateDialog" width="540px">
|
||||
<div class="template-dialog-tabs">
|
||||
<div :class="['template-dialog-tab', { active: templateDialogTab === 'personal' }]" @click="switchTemplateDialogTab('personal')">自用模板</div>
|
||||
<div :class="['template-dialog-tab', { active: templateDialogTab === 'shared' }]" @click="switchTemplateDialogTab('shared')">共享模板</div>
|
||||
</div>
|
||||
<div class="template-dialog-toolbar" v-show="templateDialogTab === 'shared'">
|
||||
<el-input v-model="sharedDialogSearch" placeholder="搜索模板名称" size="small" clearable prefix-icon="el-icon-search" @input="handleDialogSearch" style="width: 200px;" />
|
||||
</div>
|
||||
<el-table :data="filteredTemplateList" border style="width: 100%;" v-loading="sharedDialogLoading">
|
||||
<el-table-column prop="name" label="模板名称" />
|
||||
<el-table-column prop="employeeCount" label="员工数量" align="center" />
|
||||
<el-table-column prop="createTime" label="创建时间" />
|
||||
@@ -238,6 +245,16 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="template-dialog-pagination" v-show="templateDialogTab === 'shared' && sharedDialogTotal > sharedDialogPageSize">
|
||||
<el-pagination
|
||||
background
|
||||
layout="prev, pager, next"
|
||||
:current-page="sharedDialogPageNum"
|
||||
:page-size="sharedDialogPageSize"
|
||||
:total="sharedDialogTotal"
|
||||
@current-change="handleDialogPageChange"
|
||||
/>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="showTemplateDialog = false">关闭</el-button>
|
||||
</div>
|
||||
@@ -358,6 +375,7 @@ import { listAttendanceSchedule, generateenerateSchedule, updateAttendanceSchedu
|
||||
import { listShift } from '@/api/wms/attendanceShift'
|
||||
import { listAttendanceShiftRule } from '@/api/wms/attendanceShiftRule'
|
||||
import { listEmployeeInfo } from '@/api/wms/employeeInfo'
|
||||
import { listAttendanceTemplate, delAttendanceTemplate } from '@/api/wms/attendanceTemplate'
|
||||
|
||||
export default {
|
||||
name: 'AttendanceSchedule',
|
||||
@@ -387,6 +405,15 @@ export default {
|
||||
editDialogVisible: false,
|
||||
showTemplateDialog: false,
|
||||
showTemplateManager: false,
|
||||
templateDialogTab: 'personal',
|
||||
sharedDialogList: [],
|
||||
sharedDialogLoading: false,
|
||||
sharedDialogPageNum: 1,
|
||||
sharedDialogPageSize: 20,
|
||||
sharedDialogTotal: 0,
|
||||
sharedDialogSearch: '',
|
||||
sharedTemplateList: [],
|
||||
personalTemplateList: [],
|
||||
templateList: [],
|
||||
currentShiftIndex: -1,
|
||||
employeeList: [],
|
||||
@@ -543,6 +570,12 @@ export default {
|
||||
if (!this.batchCellEditShiftId) return ''
|
||||
const shift = this.shiftList.find(s => s.shiftId === this.batchCellEditShiftId)
|
||||
return shift ? shift.shiftType : ''
|
||||
},
|
||||
filteredTemplateList() {
|
||||
if (this.templateDialogTab === 'shared') {
|
||||
return this.sharedDialogList
|
||||
}
|
||||
return this.personalTemplateList
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -1323,22 +1356,51 @@ export default {
|
||||
this.getScheduleList()
|
||||
},
|
||||
|
||||
// 加载模板列表
|
||||
// 加载模板列表(共享 + 自用)
|
||||
loadTemplates() {
|
||||
try {
|
||||
const templates = localStorage.getItem('attendanceTemplates')
|
||||
this.templateList = templates ? JSON.parse(templates) : []
|
||||
this.personalTemplateList = (templates ? JSON.parse(templates) : []).map(t => ({ ...t, source: 'personal' }))
|
||||
} catch (e) {
|
||||
this.templateList = []
|
||||
this.personalTemplateList = []
|
||||
}
|
||||
|
||||
listAttendanceTemplate().then(res => {
|
||||
this.sharedTemplateList = (res.rows || []).map(row => ({
|
||||
id: row.templateId,
|
||||
name: row.templateName,
|
||||
employeeIds: this.parseTemplateContent(row.templateContent),
|
||||
employeeCount: this.parseTemplateContent(row.templateContent).length,
|
||||
createTime: row.createTime,
|
||||
source: 'shared'
|
||||
}))
|
||||
this.templateList = [...this.sharedTemplateList, ...this.personalTemplateList]
|
||||
}).catch(() => {
|
||||
this.templateList = [...this.personalTemplateList]
|
||||
})
|
||||
},
|
||||
|
||||
parseTemplateContent(content) {
|
||||
if (!content) return []
|
||||
try {
|
||||
const parsed = JSON.parse(content)
|
||||
return Array.isArray(parsed) ? parsed : []
|
||||
} catch (e) {
|
||||
return content.split(',').filter(Boolean)
|
||||
}
|
||||
},
|
||||
|
||||
// 保存模板到localStorage
|
||||
saveTemplates() {
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(this.templateList))
|
||||
// 保存自用模板到localStorage
|
||||
savePersonalTemplates() {
|
||||
const plainList = this.personalTemplateList.map(t => {
|
||||
const { source, ...rest } = t
|
||||
return rest
|
||||
})
|
||||
localStorage.setItem('attendanceTemplates', JSON.stringify(plainList))
|
||||
this.templateList = [...this.sharedTemplateList, ...this.personalTemplateList]
|
||||
},
|
||||
|
||||
// 保存单个班次为模板
|
||||
// 保存单个班次为模板(保存到自用模板)
|
||||
saveSingleTemplate(index) {
|
||||
const shiftItem = this.form.shiftList[index]
|
||||
if (!shiftItem.employeeIds || !shiftItem.employeeIds.trim()) {
|
||||
@@ -1357,17 +1419,18 @@ export default {
|
||||
}
|
||||
|
||||
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')
|
||||
createTime: new Date().toLocaleString('zh-CN'),
|
||||
source: 'personal'
|
||||
}
|
||||
|
||||
this.templateList.push(template)
|
||||
this.saveTemplates()
|
||||
this.personalTemplateList.push(template)
|
||||
this.savePersonalTemplates()
|
||||
this.$message.success('模板保存成功')
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消保存')
|
||||
@@ -1378,6 +1441,57 @@ export default {
|
||||
openTemplateDialog(index) {
|
||||
this.currentShiftIndex = index
|
||||
this.showTemplateDialog = true
|
||||
if (this.templateDialogTab === 'shared') {
|
||||
this.sharedDialogPageNum = 1
|
||||
this.sharedDialogSearch = ''
|
||||
this.loadSharedDialogTemplates()
|
||||
}
|
||||
},
|
||||
|
||||
// 切换模板弹窗的 tab
|
||||
switchTemplateDialogTab(tab) {
|
||||
if (this.templateDialogTab === tab) return
|
||||
this.templateDialogTab = tab
|
||||
if (tab === 'shared') {
|
||||
this.sharedDialogPageNum = 1
|
||||
this.sharedDialogSearch = ''
|
||||
this.loadSharedDialogTemplates()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载共享模板弹窗列表(带分页)
|
||||
loadSharedDialogTemplates() {
|
||||
this.sharedDialogLoading = true
|
||||
const query = {
|
||||
pageNum: this.sharedDialogPageNum,
|
||||
pageSize: this.sharedDialogPageSize
|
||||
}
|
||||
if (this.sharedDialogSearch) {
|
||||
query.templateName = this.sharedDialogSearch
|
||||
}
|
||||
listAttendanceTemplate(query).then(res => {
|
||||
this.sharedDialogList = (res.rows || []).map(row => ({
|
||||
id: row.templateId,
|
||||
name: row.templateName,
|
||||
employeeIds: this.parseTemplateContent(row.templateContent),
|
||||
employeeCount: this.parseTemplateContent(row.templateContent).length,
|
||||
createTime: row.createTime,
|
||||
source: 'shared'
|
||||
}))
|
||||
this.sharedDialogTotal = res.total || 0
|
||||
}).finally(() => {
|
||||
this.sharedDialogLoading = false
|
||||
})
|
||||
},
|
||||
|
||||
handleDialogSearch() {
|
||||
this.sharedDialogPageNum = 1
|
||||
this.loadSharedDialogTemplates()
|
||||
},
|
||||
|
||||
handleDialogPageChange(pageNum) {
|
||||
this.sharedDialogPageNum = pageNum
|
||||
this.loadSharedDialogTemplates()
|
||||
},
|
||||
|
||||
// 应用单个模板到当前班次
|
||||
@@ -1392,16 +1506,26 @@ export default {
|
||||
|
||||
// 删除单个模板
|
||||
deleteSingleTemplate(template) {
|
||||
this.$confirm('确定要删除这个模板吗?', '提示', {
|
||||
const confirmMsg = template.source === 'shared'
|
||||
? '删除后该模板所有人都无法使用,确定删除?'
|
||||
: '确定要删除这个模板吗?'
|
||||
this.$confirm(confirmMsg, '提示', {
|
||||
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()
|
||||
}).then(async () => {
|
||||
if (template.source === 'shared') {
|
||||
await delAttendanceTemplate(template.id)
|
||||
this.$message.success('删除成功')
|
||||
this.loadTemplates()
|
||||
this.loadSharedDialogTemplates()
|
||||
} else {
|
||||
const index = this.personalTemplateList.findIndex(t => t.id === template.id)
|
||||
if (index > -1) {
|
||||
this.personalTemplateList.splice(index, 1)
|
||||
this.savePersonalTemplates()
|
||||
this.$message.success('删除成功')
|
||||
}
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
@@ -1782,4 +1906,41 @@ export default {
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.template-dialog-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.template-dialog-tab {
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.template-dialog-tab:hover {
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.template-dialog-tab.active {
|
||||
color: #1890ff;
|
||||
border-bottom-color: #1890ff;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.template-dialog-toolbar {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.template-dialog-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 10px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user