spring
9 天以前 beed868dc7e61ea2f9fecadb87426ae34d98a41e
考勤管理
已添加1个文件
714 ■■■■■ 文件已修改
src/views/collaborativeApproval/attendanceManagement/index.vue 714 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/collaborativeApproval/attendanceManagement/index.vue
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,714 @@
<template>
  <div class="app-container">
    <el-tabs v-model="activeTab" type="border-card">
      <!-- å‡æœŸè®¾ç½® -->
      <el-tab-pane label="假期设置" name="holiday">
        <div class="tab-content">
          <el-button type="primary" @click="openDialog('holiday', 'add')">新增假期</el-button>
          <el-table :data="holidayData" border style="width: 100%; margin-top: 20px;">
            <el-table-column prop="name" label="假期名称" />
            <el-table-column prop="type" label="假期类型">
              <template #default="scope">
                <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="startDate" label="开始日期"  />
            <el-table-column prop="endDate" label="结束日期"  />
            <el-table-column prop="days" label="天数"  align="center" />
            <el-table-column prop="status" label="状态" >
              <template #default="scope">
                <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                  {{ scope.row.status === 'active' ? '启用' : '停用' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('holiday', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('holiday', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-tab-pane>
      <!-- å¹´å‡è®¾ç½® -->
      <el-tab-pane label="年假设置" name="annual">
        <div class="tab-content">
          <el-button type="primary" @click="openDialog('annual', 'add')">新增年假规则</el-button>
          <el-table :data="annualData" border style="width: 100%; margin-top: 20px;">
            <el-table-column prop="employeeType" label="员工类型"/>
            <el-table-column prop="workYears" label="工作年限" />
            <el-table-column prop="annualDays" label="年假天数" align="center" />
            <el-table-column prop="maxCarryOver" label="最大结转天数" align="center" />
            <el-table-column prop="status" label="状态">
              <template #default="scope">
                <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                  {{ scope.row.status === 'active' ? '启用' : '停用' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('annual', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('annual', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-tab-pane>
      <!-- åŠ ç­è®¾ç½® -->
      <el-tab-pane label="加班设置" name="overtime">
        <div class="tab-content">
          <el-button type="primary" @click="openDialog('overtime', 'add')">新增加班规则</el-button>
          <el-table :data="overtimeData" border style="width: 100%; margin-top: 20px;">
            <el-table-column prop="name" label="规则名称" />
            <el-table-column prop="type" label="加班类型" >
              <template #default="scope">
                <el-tag :type="getTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="startTime" label="开始时间"  />
            <el-table-column prop="endTime" label="结束时间"  />
            <el-table-column prop="rate" label="倍率" align="center" />
            <el-table-column prop="status" label="状态" >
              <template #default="scope">
                <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                  {{ scope.row.status === 'active' ? '启用' : '停用' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('overtime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('overtime', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-tab-pane>
      <!-- ä¸Šç­æ—¶é—´è®¾ç½® -->
      <el-tab-pane label="上班时间设置" name="worktime">
        <div class="tab-content">
          <el-button type="primary" @click="openDialog('worktime', 'add')">新增时间段</el-button>
          <el-table :data="worktimeData" border style="width: 100%; margin-top: 20px;">
            <el-table-column prop="name" label="时间段名称"  />
            <el-table-column prop="startTime" label="上班时间"/>
            <el-table-column prop="endTime" label="下班时间" />
            <el-table-column prop="flexibleStart" label="弹性上班">
              <template #default="scope">
                <el-tag :type="scope.row.flexibleStart ? 'success' : 'info'">
                  {{ scope.row.flexibleStart ? '是' : '否' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column prop="flexibleMinutes" label="弹性时间(分钟)" width="120" align="center" />
            <el-table-column prop="status" label="状态" >
              <template #default="scope">
                <el-tag :type="scope.row.status === 'active' ? 'success' : 'info'">
                  {{ scope.row.status === 'active' ? '启用' : '停用' }}
                </el-tag>
              </template>
            </el-table-column>
            <el-table-column label="操作" fixed="right">
              <template #default="scope">
                <el-button type="primary" size="small" @click="openDialog('worktime', 'edit', scope.row)">编辑</el-button>
                <el-button type="danger" size="small" @click="deleteItem('worktime', scope.row)">删除</el-button>
              </template>
            </el-table-column>
          </el-table>
        </div>
      </el-tab-pane>
    </el-tabs>
    <!-- é€šç”¨å¼¹çª— -->
    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px">
      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="名称" prop="name" v-if="currentType !== 'annual'">
          <el-input v-model="form.name" placeholder="请输入名称" />
        </el-form-item>
        <el-form-item label="类型" prop="type" v-if="currentType === 'holiday' || currentType === 'overtime'">
          <el-select v-model="form.type" placeholder="请选择类型" style="width: 100%">
            <el-option
              v-for="option in getTypeOptions()"
              :key="option.value"
              :label="option.label"
              :value="option.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="员工类型" prop="employeeType" v-if="currentType === 'annual'">
          <el-select v-model="form.employeeType" placeholder="请选择员工类型" style="width: 100%">
            <el-option label="正式员工" value="regular" />
            <el-option label="试用期员工" value="probation" />
            <el-option label="实习生" value="intern" />
          </el-select>
        </el-form-item>
        <el-form-item label="工作年限" prop="workYears" v-if="currentType === 'annual'">
          <el-input v-model="form.workYears" placeholder="如:1-3年、3-5年等" />
        </el-form-item>
        <el-form-item label="年假天数" prop="annualDays" v-if="currentType === 'annual'">
          <el-input-number v-model="form.annualDays" :min="0" :max="365" style="width: 100%" />
        </el-form-item>
        <el-form-item label="最大结转天数" prop="maxCarryOver" v-if="currentType === 'annual'">
          <el-input-number v-model="form.maxCarryOver" :min="0" :max="30" style="width: 100%" />
        </el-form-item>
                          <el-form-item label="日期范围" prop="dateRange" v-if="currentType === 'holiday'">
           <el-date-picker
             v-model="form.dateRange"
             type="daterange"
             range-separator="至"
             start-placeholder="开始日期"
             end-placeholder="结束日期"
             style="width: 100%"
             @change="calculateDays"
           />
         </el-form-item>
        <el-form-item label="天数" prop="days" v-if="currentType === 'holiday'">
          <el-input-number v-model="form.days" :min="0" style="width: 100%" />
        </el-form-item>
                 <el-form-item label="开始时间" prop="startTime" v-if="currentType === 'overtime'">
           <el-time-picker
             v-model="form.startTime"
             placeholder="开始时间"
             format="HH:mm"
             value-format="HH:mm"
             style="width: 100%"
             @change="validateTimeField('startTime')"
           />
         </el-form-item>
         <el-form-item label="结束时间" prop="endTime" v-if="currentType === 'overtime'">
           <el-time-picker
             v-model="form.endTime"
             placeholder="结束时间"
             format="HH:mm"
             value-format="HH:mm"
             style="width: 100%"
             @change="validateTimeField('endTime')"
           />
         </el-form-item>
        <el-form-item label="倍率" prop="rate" v-if="currentType === 'overtime'">
          <el-input-number v-model="form.rate" :min="1" :max="3" :step="0.5" style="width: 100%" />
        </el-form-item>
                 <el-form-item label="上班时间" prop="workStartTime" v-if="currentType === 'worktime'">
           <el-time-picker
             v-model="form.workStartTime"
             placeholder="上班时间"
             format="HH:mm"
             value-format="HH:mm"
             style="width: 100%"
             @change="validateTimeField('workStartTime')"
           />
         </el-form-item>
         <el-form-item label="下班时间" prop="workEndTime" v-if="currentType === 'worktime'">
           <el-time-picker
             v-model="form.workEndTime"
             placeholder="下班时间"
             format="HH:mm"
             value-format="HH:mm"
             style="width: 100%"
             @change="validateTimeField('workEndTime')"
           />
         </el-form-item>
        <el-form-item label="弹性上班" prop="flexibleStart" v-if="currentType === 'worktime'">
          <el-switch v-model="form.flexibleStart" />
        </el-form-item>
        <el-form-item label="弹性时间(分钟)" prop="flexibleMinutes" v-if="currentType === 'worktime' && form.flexibleStart">
          <el-input-number v-model="form.flexibleMinutes" :min="0" :max="120" style="width: 100%" />
        </el-form-item>
                 <el-form-item label="状态" prop="status">
           <el-radio-group v-model="form.status">
             <el-radio value="active">启用</el-radio>
             <el-radio value="inactive">停用</el-radio>
           </el-radio-group>
         </el-form-item>
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible = false">取消</el-button>
          <el-button type="primary" @click="submitForm">确定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>
<script setup>
import { ref, reactive, onMounted, onUnmounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// å½“前激活的标签页
const activeTab = ref('holiday')
// å¼¹çª—相关
const dialogVisible = ref(false)
const dialogTitle = ref('')
const currentType = ref('')
const currentAction = ref('')
const currentEditId = ref('')
const formRef = ref()
// è¡¨å•数据
const form = reactive({
  name: '',
  type: '',
  dateRange: [],
  days: 0,
  employeeType: '',
  workYears: '',
  annualDays: 0,
  maxCarryOver: 0,
  startTime: '', // åŠ ç­å¼€å§‹æ—¶é—´
  endTime: '',   // åŠ ç­ç»“æŸæ—¶é—´
  workStartTime: '', // ä¸Šç­æ—¶é—´
  workEndTime: '',   // ä¸‹ç­æ—¶é—´
  rate: 1.5,
  flexibleStart: false,
  flexibleMinutes: 30,
  status: 'active'
})
// è¡¨å•验证规则
const rules = {
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  type: [{ required: true, message: '请选择类型', trigger: 'change' }],
  dateRange: [{ required: true, message: '请选择日期范围', trigger: 'change' }],
  days: [{ required: true, message: '请输入天数', trigger: 'blur' }],
  employeeType: [{ required: true, message: '请选择员工类型', trigger: 'change' }],
  workYears: [{ required: true, message: '请输入工作年限', trigger: 'blur' }],
  annualDays: [{ required: true, message: '请输入年假天数', trigger: 'blur' }],
  maxCarryOver: [{ required: true, message: '请输入最大结转天数', trigger: 'blur' }],
  startTime: [{
    required: true,
    message: '请选择开始时间',
    trigger: 'change',
    validator: (rule, value, callback) => {
      if (!value) {
        callback(new Error('请选择开始时间'))
      } else {
        callback()
      }
    }
  }],
  endTime: [{
    required: true,
    message: '请选择结束时间',
    trigger: 'change',
    validator: (rule, value, callback) => {
      if (!value) {
        callback(new Error('请选择结束时间'))
      } else {
        callback()
      }
    }
  }],
  workStartTime: [{
    required: true,
    message: '请选择上班时间',
    trigger: 'change',
    validator: (rule, value, callback) => {
      if (!value) {
        callback(new Error('请选择上班时间'))
      } else {
        callback()
      }
    }
  }],
  workEndTime: [{
    required: true,
    message: '请选择下班时间',
    trigger: 'change',
    validator: (rule, value, callback) => {
      if (!value) {
        callback(new Error('请选择下班时间'))
      } else {
        callback()
      }
    }
  }],
  rate: [{ required: true, message: '请输入倍率', trigger: 'blur' }]
}
// æ¨¡æ‹Ÿæ•°æ®
const holidayData = ref([
  { id: '1', name: '春节', type: 'legal', startDate: '2024-02-10', endDate: '2024-02-17', days: 8, status: 'active' },
  { id: '2', name: '清明节', type: 'legal', startDate: '2024-04-05', endDate: '2024-04-05', days: 1, status: 'active' },
  { id: '3', name: '劳动节', type: 'legal', startDate: '2024-05-01', endDate: '2024-05-05', days: 5, status: 'active' }
])
const annualData = ref([
  { id: '1', employeeType: 'regular', workYears: '1-3å¹´', annualDays: 5, maxCarryOver: 2, status: 'active' },
  { id: '2', employeeType: 'regular', workYears: '3-5å¹´', annualDays: 10, maxCarryOver: 5, status: 'active' },
  { id: '3', employeeType: 'regular', workYears: '5年以上', annualDays: 15, maxCarryOver: 10, status: 'active' }
])
const overtimeData = ref([
  { id: '1', name: '工作日加班', type: 'weekday', startTime: '18:00', endTime: '22:00', rate: 1.5, status: 'active' },
  { id: '2', name: '周末加班', type: 'weekend', startTime: '09:00', endTime: '18:00', rate: 2.0, status: 'active' },
  { id: '3', name: '深夜加班', type: 'night', startTime: '22:00', endTime: '06:00', rate: 2.5, status: 'active' }
])
const worktimeData = ref([
  { id: '1', name: '标准工作时间', startTime: '09:00', endTime: '18:00', flexibleStart: true, flexibleMinutes: 30, status: 'active' },
  { id: '2', name: '早班时间', startTime: '08:00', endTime: '17:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' },
  { id: '3', name: '晚班时间', startTime: '14:00', endTime: '23:00', flexibleStart: false, flexibleMinutes: 0, status: 'active' }
])
// å·¥å…·å‡½æ•°
const getTagType = (type) => {
  const tagMap = {
    legal: 'success', adjustment: 'warning', special: 'info', company: 'primary',
    weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info'
  }
  return tagMap[type] || 'info'
}
const getTypeLabel = (type) => {
  const labelMap = {
    legal: '法定节假日', adjustment: '调休日', special: '特殊假期', company: '公司假期',
    weekday: '工作日加班', weekend: '周末加班', holiday: '节假日加班', night: '深夜加班'
  }
  return labelMap[type] || type
}
const getTypeOptions = () => {
  if (currentType.value === 'holiday') {
    return [
      { label: '法定节假日', value: 'legal' },
      { label: '调休日', value: 'adjustment' },
      { label: '特殊假期', value: 'special' },
      { label: '公司假期', value: 'company' }
    ]
  } else if (currentType.value === 'overtime') {
    return [
      { label: '工作日加班', value: 'weekday' },
      { label: '周末加班', value: 'weekend' },
      { label: '节假日加班', value: 'holiday' },
      { label: '深夜加班', value: 'night' }
    ]
  }
  return []
}
// è®¡ç®—假期天数
const calculateDays = () => {
  try {
    if (form.dateRange && form.dateRange.length === 2 && form.dateRange[0] && form.dateRange[1]) {
      const start = new Date(form.dateRange[0])
      const end = new Date(form.dateRange[1])
      if (isNaN(start.getTime()) || isNaN(end.getTime())) {
        console.warn('无效的日期格式')
        return
      }
      const diffTime = Math.abs(end - start)
      const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1
      form.days = diffDays
    }
  } catch (error) {
    console.error('计算天数失败:', error)
  }
}
// éªŒè¯æ—¶é—´æ ¼å¼
const validateTime = (time) => {
  if (!time) return ''
  if (typeof time === 'string') return time
  if (time instanceof Date) {
    return time.toTimeString().slice(0, 5)
  }
  return ''
}
// éªŒè¯æ—¶é—´å­—段
const validateTimeField = (fieldName) => {
  try {
    const value = form[fieldName]
    if (value && typeof value === 'object' && value.hour !== undefined) {
      // å¦‚果是时间对象,转换为字符串格式
      const hours = value.hour.toString().padStart(2, '0')
      const minutes = value.minute.toString().padStart(2, '0')
      form[fieldName] = `${hours}:${minutes}`
    }
  } catch (error) {
    console.error(`验证时间字段 ${fieldName} å¤±è´¥:`, error)
    form[fieldName] = ''
  }
}
// æ‰“开弹窗
const openDialog = (type, action, row = null) => {
  try {
    currentType.value = type
    currentAction.value = action
    if (action === 'add') {
      dialogTitle.value = `新增${getTypeName(type)}`
      currentEditId.value = ''
      resetForm()
    } else if (action === 'edit' && row) {
      dialogTitle.value = `编辑${getTypeName(type)}`
      currentEditId.value = row.id
      fillForm(row)
    }
    dialogVisible.value = true
  } catch (error) {
    console.error('打开弹窗失败:', error)
    ElMessage.error('打开弹窗失败,请重试')
  }
}
const getTypeName = (type) => {
  const nameMap = {
    holiday: '假期',
    annual: '年假规则',
    overtime: '加班规则',
    worktime: '时间段'
  }
  return nameMap[type] || ''
}
const resetForm = () => {
  Object.assign(form, {
    name: '',
    type: '',
    dateRange: [],
    days: 0,
    employeeType: '',
    workYears: '',
    annualDays: 0,
    maxCarryOver: 0,
    startTime: '',
    endTime: '',
    workStartTime: '',
    workEndTime: '',
    rate: 1.5,
    flexibleStart: false,
    flexibleMinutes: 30,
    status: 'active'
  })
}
const fillForm = (row) => {
  if (currentType.value === 'holiday') {
    Object.assign(form, {
      name: row.name,
      type: row.type,
      dateRange: [new Date(row.startDate), new Date(row.endDate)],
      days: row.days,
      status: row.status
    })
  } else if (currentType.value === 'annual') {
    Object.assign(form, {
      employeeType: row.employeeType,
      workYears: row.workYears,
      annualDays: row.annualDays,
      maxCarryOver: row.maxCarryOver,
      status: row.status
    })
  } else if (currentType.value === 'overtime') {
    Object.assign(form, {
      name: row.name,
      type: row.type,
      startTime: row.startTime || '',
      endTime: row.endTime || '',
      rate: row.rate,
      status: row.status
    })
  } else if (currentType.value === 'worktime') {
    Object.assign(form, {
      name: row.name,
      workStartTime: row.startTime || '',
      workEndTime: row.endTime || '',
      flexibleStart: row.flexibleStart,
      flexibleMinutes: row.flexibleMinutes,
      status: row.status
    })
  }
}
// æäº¤è¡¨å•
const submitForm = async () => {
  try {
    if (!formRef.value) {
      ElMessage.error('表单引用不存在')
      return
    }
    await formRef.value.validate()
    if (currentAction.value === 'add') {
      addItem()
    } else if (currentAction.value === 'edit') {
      editItem()
    }
    dialogVisible.value = false
    ElMessage.success('操作成功')
  } catch (error) {
    console.error('表单验证失败:', error)
    ElMessage.error('表单验证失败,请检查输入')
  }
}
const addItem = () => {
  const newItem = { ...form, id: Date.now().toString() }
  if (currentType.value === 'holiday') {
    newItem.startDate = form.dateRange[0].toISOString().split('T')[0]
    newItem.endDate = form.dateRange[1].toISOString().split('T')[0]
    holidayData.value.push(newItem)
  } else if (currentType.value === 'annual') {
    annualData.value.push(newItem)
  } else if (currentType.value === 'overtime') {
    newItem.startTime = form.startTime || ''
    newItem.endTime = form.endTime || ''
    overtimeData.value.push(newItem)
  } else if (currentType.value === 'worktime') {
    newItem.startTime = form.workStartTime || ''
    newItem.endTime = form.workEndTime || ''
    worktimeData.value.push(newItem)
  }
}
const editItem = () => {
  let dataArray
  let index
  if (currentType.value === 'holiday') {
    dataArray = holidayData.value
    index = dataArray.findIndex(item => item.id === currentEditId.value)
    if (index > -1) {
      dataArray[index] = {
        ...dataArray[index],
        name: form.name,
        type: form.type,
        startDate: form.dateRange[0].toISOString().split('T')[0],
        endDate: form.dateRange[1].toISOString().split('T')[0],
        days: form.days,
        status: form.status
      }
    }
  } else if (currentType.value === 'annual') {
    dataArray = annualData.value
    index = dataArray.findIndex(item => item.id === currentEditId.value)
    if (index > -1) {
      dataArray[index] = {
        ...dataArray[index],
        employeeType: form.employeeType,
        workYears: form.workYears,
        annualDays: form.annualDays,
        maxCarryOver: form.maxCarryOver,
        status: form.status
      }
    }
  } else if (currentType.value === 'overtime') {
    dataArray = overtimeData.value
    index = dataArray.findIndex(item => item.id === currentEditId.value)
    if (index > -1) {
      dataArray[index] = {
        ...dataArray[index],
        name: form.name,
        type: form.type,
        startTime: form.startTime || '',
        endTime: form.endTime || '',
        rate: form.rate,
        status: form.status
      }
    }
  } else if (currentType.value === 'worktime') {
    dataArray = worktimeData.value
    index = dataArray.findIndex(item => item.id === currentEditId.value)
    if (index > -1) {
      dataArray[index] = {
        ...dataArray[index],
        name: form.name,
        startTime: form.workStartTime || '',
        endTime: form.workEndTime || '',
        flexibleStart: form.flexibleStart,
        flexibleMinutes: form.flexibleMinutes,
        status: form.status
      }
    }
  }
}
// åˆ é™¤é¡¹ç›®
const deleteItem = (type, row) => {
  ElMessageBox.confirm('确定要删除这个项目吗?', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    let dataArray
    if (type === 'holiday') dataArray = holidayData.value
    else if (type === 'annual') dataArray = annualData.value
    else if (type === 'overtime') dataArray = overtimeData.value
    else if (type === 'worktime') dataArray = worktimeData.value
    const index = dataArray.findIndex(item => item.id === row.id)
    if (index > -1) {
      dataArray.splice(index, 1)
      ElMessage.success('删除成功')
    }
  })
}
onMounted(() => {
  console.log('考勤管理页面加载完成')
})
onUnmounted(() => {
  // æ¸…理工作
  dialogVisible.value = false
  currentType.value = ''
  currentAction.value = ''
  currentEditId.value = ''
})
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.tab-content {
  padding: 20px 0;
}
.dialog-footer {
  text-align: right;
}
:deep(.el-tabs__content) {
  padding: 20px;
}
:deep(.el-form-item) {
  margin-bottom: 20px;
}
</style>