From dc3037e201c5e7b3d4c96d464111b7b9c1985f0d Mon Sep 17 00:00:00 2001 From: spring <2396852758@qq.com> Date: 星期四, 21 八月 2025 11:30:13 +0800 Subject: [PATCH] 排班管理 --- src/views/personnelManagement/scheduling/index.vue | 634 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 634 insertions(+), 0 deletions(-) diff --git a/src/views/personnelManagement/scheduling/index.vue b/src/views/personnelManagement/scheduling/index.vue new file mode 100644 index 0000000..8a7174d --- /dev/null +++ b/src/views/personnelManagement/scheduling/index.vue @@ -0,0 +1,634 @@ +<template> + <div class="app-container scheduling-container"> + <!-- 绛涢�夊尯鍩� --> + <div class="filter-section"> + <el-form :inline="true" :model="filterForm" class="filter-form"> + <el-form-item label="鍛樺伐濮撳悕锛�"> + <el-input + v-model="filterForm.employeeName" + placeholder="璇疯緭鍏ュ憳宸ュ鍚�" + clearable + style="width: 150px" + /> + </el-form-item> + <el-form-item label="鐝绫诲瀷锛�"> + <el-select v-model="filterForm.shiftType" placeholder="璇烽�夋嫨鐝" clearable style="width: 120px"> + <el-option label="鏃╃彮" value="鏃╃彮" /> + <el-option label="涓彮" value="涓彮" /> + <el-option label="鏅氱彮" value="鏅氱彮" /> + <el-option label="澶滅彮" value="澶滅彮" /> + </el-select> + </el-form-item> + <el-form-item label="鏃ユ湡鑼冨洿锛�"> + <el-date-picker + v-model="filterForm.dateRange" + type="daterange" + range-separator="鑷�" + start-placeholder="寮�濮嬫棩鏈�" + end-placeholder="缁撴潫鏃ユ湡" + format="YYYY-MM-DD" + value-format="YYYY-MM-DD" + style="width: 250px" + /> + </el-form-item> + <el-form-item> + <el-button type="primary" @click="handleFilter"> + <el-icon><Search /></el-icon> + 绛涢�� + </el-button> + <el-button @click="resetFilter"> + <el-icon><Refresh /></el-icon> + 閲嶇疆 + </el-button> + <el-button type="primary" @click="openScheduleDialog('add')"> + <el-icon><Plus /></el-icon> + 鏂板鎺掔彮 + </el-button> + </el-form-item> + </el-form> + </div> + + <!-- 鎺掔彮琛ㄦ牸 --> + <div class="table-section"> + <el-table + :data="filteredScheduleList" + border + stripe + style="width: 100%" + height="calc(100vh - 18.5em)" + @selection-change="handleSelectionChange" + > + <el-table-column type="selection" width="55" /> + <el-table-column prop="employeeName" label="鍛樺伐濮撳悕" width="120" /> + <el-table-column prop="employeeId" label="鍛樺伐宸ュ彿" width="100" /> + <el-table-column prop="department" label="閮ㄩ棬" width="120" /> + <el-table-column prop="shiftType" label="鐝绫诲瀷" width="100"> + <template #default="scope"> + <el-tag :type="getShiftTagType(scope.row.shiftType)"> + {{ scope.row.shiftType }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="workDate" label="宸ヤ綔鏃ユ湡" width="120" /> + <el-table-column prop="startTime" label="寮�濮嬫椂闂�" width="100" /> + <el-table-column prop="endTime" label="缁撴潫鏃堕棿" width="100" /> + <el-table-column prop="workHours" label="宸ヤ綔鏃堕暱" width="100"> + <template #default="scope"> + {{ scope.row.workHours }}灏忔椂 + </template> + </el-table-column> + <el-table-column prop="status" label="鐘舵��" width="100"> + <template #default="scope"> + <el-tag :type="getStatusTagType(scope.row.status)"> + {{ scope.row.status }} + </el-tag> + </template> + </el-table-column> + <el-table-column prop="remark" label="澶囨敞" min-width="150" /> + <el-table-column label="鎿嶄綔" width="200" fixed="right"> + <template #default="scope"> + <el-button + type="primary" + size="small" + @click="openScheduleDialog('edit', scope.row)" + > + 缂栬緫 + </el-button> + <el-button + type="danger" + size="small" + @click="handleDelete(scope.row)" + > + 鍒犻櫎 + </el-button> + </template> + </el-table-column> + </el-table> + </div> + + <!-- 鎵归噺鎿嶄綔 --> + <div class="batch-actions" v-if="selectedRows.length > 0"> + <el-button + type="danger" + @click="handleBatchDelete" + :disabled="selectedRows.length === 0" + > + 鎵归噺鍒犻櫎 ({{ selectedRows.length }}) + </el-button> + </div> + + <!-- 鎺掔彮鏂板/缂栬緫瀵硅瘽妗� --> + <el-dialog + v-model="scheduleDialog" + :title="dialogType === 'add' ? '鏂板鎺掔彮' : '缂栬緫鎺掔彮'" + width="700px" + @close="closeScheduleDialog" + > + <el-form + :model="scheduleForm" + :rules="scheduleRules" + ref="scheduleFormRef" + label-width="120px" + > + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="鍛樺伐濮撳悕锛�" prop="employeeName"> + <el-input v-model="scheduleForm.employeeName" placeholder="璇疯緭鍏ュ憳宸ュ鍚�" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鍛樺伐宸ュ彿锛�" prop="employeeId"> + <el-input v-model="scheduleForm.employeeId" placeholder="璇疯緭鍏ュ憳宸ュ伐鍙�" /> + </el-form-item> + </el-col> + </el-row> + + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="閮ㄩ棬锛�" prop="department"> + <el-select v-model="scheduleForm.department" placeholder="璇烽�夋嫨閮ㄩ棬" style="width: 100%"> + <el-option label="鎶�鏈儴" value="鎶�鏈儴" /> + <el-option label="閿�鍞儴" value="閿�鍞儴" /> + <el-option label="浜轰簨閮�" value="浜轰簨閮�" /> + <el-option label="璐㈠姟閮�" value="璐㈠姟閮�" /> + <el-option label="鐢熶骇閮�" value="鐢熶骇閮�" /> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鐝绫诲瀷锛�" prop="shiftType"> + <el-select v-model="scheduleForm.shiftType" placeholder="璇烽�夋嫨鐝" style="width: 100%"> + <el-option label="鏃╃彮" value="鏃╃彮" /> + <el-option label="涓彮" value="涓彮" /> + <el-option label="鏅氱彮" value="鏅氱彮" /> + <el-option label="澶滅彮" value="澶滅彮" /> + </el-select> + </el-form-item> + </el-col> + </el-row> + + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="宸ヤ綔鏃ユ湡锛�" prop="workDate"> + <el-date-picker + v-model="scheduleForm.workDate" + type="date" + placeholder="閫夋嫨宸ヤ綔鏃ユ湡" + style="width: 100%" + format="YYYY-MM-DD" + value-format="YYYY-MM-DD" + /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="鐘舵�侊細" prop="status"> + <el-select v-model="scheduleForm.status" placeholder="璇烽�夋嫨鐘舵��" style="width: 100%"> + <el-option label="宸插畨鎺�" value="宸插畨鎺�" /> + <el-option label="宸茬‘璁�" value="宸茬‘璁�" /> + <el-option label="宸插畬鎴�" value="宸插畬鎴�" /> + <el-option label="宸插彇娑�" value="宸插彇娑�" /> + </el-select> + </el-form-item> + </el-col> + </el-row> + + <el-row :gutter="20"> + <el-col :span="12"> + <el-form-item label="寮�濮嬫椂闂达細" prop="startTime"> + <el-time-picker + v-model="scheduleForm.startTime" + placeholder="閫夋嫨寮�濮嬫椂闂�" + style="width: 100%" + format="HH:mm" + value-format="HH:mm" + /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="缁撴潫鏃堕棿锛�" prop="endTime"> + <el-time-picker + v-model="scheduleForm.endTime" + placeholder="閫夋嫨缁撴潫鏃堕棿" + style="width: 100%" + format="HH:mm" + value-format="HH:mm" + /> + </el-form-item> + </el-col> + </el-row> + + <el-row :gutter="20"> + <el-col :span="24"> + <el-form-item label="澶囨敞锛�" prop="remark"> + <el-input + v-model="scheduleForm.remark" + type="textarea" + :rows="3" + placeholder="璇疯緭鍏ュ娉ㄤ俊鎭�" + /> + </el-form-item> + </el-col> + </el-row> + </el-form> + + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitScheduleForm">纭</el-button> + <el-button @click="closeScheduleDialog">鍙栨秷</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup> +import { ref, reactive, computed, onMounted } from 'vue' +import { ElMessage, ElMessageBox } from 'element-plus' +import { Plus, Download, Search, Refresh } from '@element-plus/icons-vue' + +// 鍝嶅簲寮忔暟鎹� +const scheduleDialog = ref(false) +const dialogType = ref('add') +const selectedRows = ref([]) +const scheduleFormRef = ref() + +// 绛涢�夎〃鍗� +const filterForm = reactive({ + employeeName: '', + shiftType: '', + dateRange: [] +}) + +// 鎺掔彮琛ㄥ崟 +const scheduleForm = reactive({ + id: '', + employeeName: '', + employeeId: '', + department: '', + shiftType: '', + workDate: '', + startTime: '', + endTime: '', + workHours: 0, + status: '宸插畨鎺�', + remark: '' +}) + +// 琛ㄥ崟楠岃瘉瑙勫垯 +const scheduleRules = reactive({ + employeeName: [{ required: true, message: '璇疯緭鍏ュ憳宸ュ鍚�', trigger: 'blur' }], + employeeId: [{ required: true, message: '璇疯緭鍏ュ憳宸ュ伐鍙�', trigger: 'blur' }], + department: [{ required: true, message: '璇烽�夋嫨閮ㄩ棬', trigger: 'change' }], + shiftType: [{ required: true, message: '璇烽�夋嫨鐝绫诲瀷', trigger: 'change' }], + workDate: [{ required: true, message: '璇烽�夋嫨宸ヤ綔鏃ユ湡', trigger: 'change' }], + startTime: [{ required: true, message: '璇烽�夋嫨寮�濮嬫椂闂�', trigger: 'change' }], + endTime: [{ required: true, message: '璇烽�夋嫨缁撴潫鏃堕棿', trigger: 'change' }], + status: [{ required: true, message: '璇烽�夋嫨鐘舵��', trigger: 'change' }] +}) + +// 妯℃嫙鎺掔彮鏁版嵁 +const scheduleList = ref([ + { + id: 1, + employeeName: '寮犳捣娲�', + employeeId: 'EMP001', + department: '鎶�鏈儴', + shiftType: '鏃╃彮', + workDate: '2024-01-15', + startTime: '08:00', + endTime: '17:00', + workHours: 9, + status: '宸插畨鎺�', + remark: '姝e父鎺掔彮' + }, + { + id: 2, + employeeName: '鏉庤秴', + employeeId: 'EMP002', + department: '閿�鍞儴', + shiftType: '涓彮', + workDate: '2024-01-15', + startTime: '14:00', + endTime: '22:00', + workHours: 8, + status: '宸茬‘璁�', + remark: '瀹㈡埛浼氳' + }, + { + id: 3, + employeeName: '鐜嬫澃', + employeeId: 'EMP003', + department: '鐢熶骇閮�', + shiftType: '鏅氱彮', + workDate: '2024-01-15', + startTime: '22:00', + endTime: '06:00', + workHours: 8, + status: '宸插畨鎺�', + remark: '澶滅彮鐢熶骇' + } +]) + +// 璁$畻灞炴�э細绛涢�夊悗鐨勬帓鐝垪琛� +const filteredScheduleList = computed(() => { + let result = scheduleList.value + + if (filterForm.employeeName) { + result = result.filter(item => + item.employeeName.includes(filterForm.employeeName) + ) + } + + if (filterForm.shiftType) { + result = result.filter(item => item.shiftType === filterForm.shiftType) + } + + if (filterForm.dateRange && filterForm.dateRange.length === 2) { + result = result.filter(item => { + const workDate = new Date(item.workDate) + const startDate = new Date(filterForm.dateRange[0]) + const endDate = new Date(filterForm.dateRange[1]) + return workDate >= startDate && workDate <= endDate + }) + } + + return result +}) + +// 鑾峰彇鐝鏍囩绫诲瀷 +const getShiftTagType = (shiftType) => { + const typeMap = { + '鏃╃彮': 'success', + '涓彮': 'warning', + '鏅氱彮': 'info', + '澶滅彮': 'danger' + } + return typeMap[shiftType] || 'info' +} + +// 鑾峰彇鐘舵�佹爣绛剧被鍨� +const getStatusTagType = (status) => { + const typeMap = { + '宸插畨鎺�': 'info', + '宸茬‘璁�': 'warning', + '宸插畬鎴�': 'success', + '宸插彇娑�': 'danger' + } + return typeMap[status] || 'info' +} + +// 绛涢�� +const handleFilter = () => { + // 绛涢�夐�昏緫宸插湪璁$畻灞炴�т腑瀹炵幇 +} + +// 閲嶇疆绛涢�� +const resetFilter = () => { + filterForm.employeeName = '' + filterForm.shiftType = '' + filterForm.dateRange = [] +} + +// 鎵撳紑鎺掔彮瀵硅瘽妗� +const openScheduleDialog = (type, data) => { + dialogType.value = type + scheduleDialog.value = true + + if (type === 'edit' && data) { + // 缂栬緫妯″紡锛屽鍒舵暟鎹� + Object.assign(scheduleForm, { ...data }) + } else { + // 鏂板妯″紡锛岄噸缃〃鍗� + Object.keys(scheduleForm).forEach(key => { + scheduleForm[key] = '' + }) + scheduleForm.status = '宸插畨鎺�' + scheduleForm.workDate = new Date().toISOString().split('T')[0] + } +} + +// 鍏抽棴鎺掔彮瀵硅瘽妗� +const closeScheduleDialog = () => { + scheduleFormRef.value?.resetFields() + scheduleDialog.value = false +} + +// 璁$畻宸ヤ綔鏃堕暱 +const calculateWorkHours = () => { + if (scheduleForm.startTime && scheduleForm.endTime) { + const start = new Date(`2000-01-01 ${scheduleForm.startTime}`) + const end = new Date(`2000-01-01 ${scheduleForm.endTime}`) + + if (end < start) { + // 璺ㄥぉ鐨勬儏鍐� + end.setDate(end.getDate() + 1) + } + + const diffMs = end - start + const diffHours = diffMs / (1000 * 60 * 60) + scheduleForm.workHours = Math.round(diffHours * 100) / 100 + } +} + +// 鎻愪氦鎺掔彮琛ㄥ崟 +const submitScheduleForm = () => { + scheduleFormRef.value.validate((valid) => { + if (valid) { + // 璁$畻宸ヤ綔鏃堕暱 + calculateWorkHours() + + if (dialogType.value === 'add') { + // 鏂板 + const newSchedule = { + ...scheduleForm, + id: Date.now() // 浣跨敤鏃堕棿鎴充綔涓轰复鏃禝D + } + scheduleList.value.push(newSchedule) + ElMessage.success('鏂板鎺掔彮鎴愬姛') + } else { + // 缂栬緫 + const index = scheduleList.value.findIndex(item => item.id === scheduleForm.id) + if (index !== -1) { + scheduleList.value[index] = { ...scheduleForm } + ElMessage.success('缂栬緫鎺掔彮鎴愬姛') + } + } + + closeScheduleDialog() + } + }) +} + +// 鍒犻櫎鎺掔彮 +const handleDelete = (row) => { + ElMessageBox.confirm( + `纭畾瑕佸垹闄� ${row.employeeName} 鐨勬帓鐝褰曞悧锛焋, + '鍒犻櫎鎻愮ず', + { + confirmButtonText: '纭', + cancelButtonText: '鍙栨秷', + type: 'warning' + } + ).then(() => { + const index = scheduleList.value.findIndex(item => item.id === row.id) + if (index !== -1) { + scheduleList.value.splice(index, 1) + ElMessage.success('鍒犻櫎鎴愬姛') + } + }).catch(() => { + ElMessage.info('宸插彇娑堝垹闄�') + }) +} + +// 鎵归噺鍒犻櫎 +const handleBatchDelete = () => { + if (selectedRows.value.length === 0) { + ElMessage.warning('璇烽�夋嫨瑕佸垹闄ょ殑璁板綍') + return + } + + ElMessageBox.confirm( + `纭畾瑕佸垹闄ら�変腑鐨� ${selectedRows.value.length} 鏉℃帓鐝褰曞悧锛焋, + '鎵归噺鍒犻櫎鎻愮ず', + { + confirmButtonText: '纭', + cancelButtonText: '鍙栨秷', + type: 'warning' + } + ).then(() => { + const selectedIds = selectedRows.value.map(row => row.id) + scheduleList.value = scheduleList.value.filter(item => !selectedIds.includes(item.id)) + selectedRows.value = [] + ElMessage.success('鎵归噺鍒犻櫎鎴愬姛') + }).catch(() => { + ElMessage.info('宸插彇娑堝垹闄�') + }) +} + +// 閫夋嫨鍙樺寲浜嬩欢 +const handleSelectionChange = (selection) => { + selectedRows.value = selection +} + +// 鐩戝惉鏃堕棿鍙樺寲锛岃嚜鍔ㄨ绠楀伐浣滄椂闀� +const watchTimeChange = () => { + if (scheduleForm.startTime && scheduleForm.endTime) { + calculateWorkHours() + } +} + +// 鐢熷懡鍛ㄦ湡 +onMounted(() => { + // 椤甸潰鍒濆鍖� +}) +</script> + +<style scoped> +.scheduling-container { + padding: 20px; + background-color: #f5f7fa; + min-height: 100vh; +} + +.page-header { + text-align: center; + margin-bottom: 30px; + padding: 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 12px; + color: white; +} + +.page-header h2 { + color: white; + margin-bottom: 10px; + font-size: 28px; + font-weight: 600; +} + +.page-header p { + color: rgba(255, 255, 255, 0.9); + font-size: 14px; + margin: 0 0 15px 0; +} + +.header-controls { + display: flex; + justify-content: center; + gap: 15px; +} + +.filter-section { + background: white; + padding: 20px; + border-radius: 8px; + margin-bottom: 20px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.filter-form { + margin: 0; +} + +.table-section { + background: white; + border-radius: 8px; + overflow: hidden; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + margin-bottom: 20px; +} + +.batch-actions { + background: white; + padding: 15px 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.dialog-footer { + text-align: right; +} + +:deep(.el-form-item__label) { + font-weight: 500; + color: #303133; +} + +:deep(.el-input__wrapper) { + box-shadow: 0 0 0 1px #dcdfe6 inset; +} + +:deep(.el-input__wrapper:hover) { + box-shadow: 0 0 0 1px #c0c4cc inset; +} + +:deep(.el-input__wrapper.is-focus) { + box-shadow: 0 0 0 1px #409eff inset; +} + +/* 鍝嶅簲寮忚璁� */ +@media (max-width: 768px) { + .scheduling-container { + padding: 10px; + } + + .page-header { + padding: 15px; + } + + .page-header h2 { + font-size: 24px; + } + + .header-controls { + flex-direction: column; + gap: 10px; + } +} + +@media (max-width: 768px) { + .filter-form .el-form-item { + margin-bottom: 10px; + } +} +</style> -- Gitblit v1.9.3