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