<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="员工类型">
|
<template #default="scope">
|
<el-tag :type="getTagType(scope.row.employeeType)">{{ getTypeLabel(scope.row.employeeType) }}</el-tag>
|
</template>
|
</el-table-column>
|
<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 === 'true' ? 'success' : 'info'">
|
{{ scope.row.flexibleStart === 'true' ? '是' : '否' }}
|
</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-tab-pane label="打卡记录" name="attendance">
|
<div class="tab-content">
|
<div style="margin-bottom: 20px;">
|
<el-date-picker
|
v-model="attendanceDate"
|
type="date"
|
placeholder="选择日期"
|
format="YYYY-MM-DD"
|
value-format="YYYY-MM-DD"
|
style="margin-right: 10px;"
|
@change="filterAttendanceData"
|
/>
|
<el-select
|
v-model="attendanceStatus"
|
placeholder="选择状态"
|
style="width: 120px; margin-right: 10px;"
|
@change="filterAttendanceData"
|
>
|
<el-option label="全部" value="" />
|
<el-option label="正常" value="normal" />
|
<el-option label="迟到" value="late" />
|
<el-option label="早退" value="early" />
|
<el-option label="缺勤" value="absent" />
|
</el-select>
|
<el-button type="primary" @click="exportAttendance">导出记录</el-button>
|
</div>
|
|
<el-table :data="filteredAttendanceData" border style="width: 100%;">
|
<el-table-column prop="employeeName" label="员工姓名" width="120" />
|
<el-table-column prop="department" label="部门" width="120" />
|
<el-table-column prop="date" label="日期" width="120" />
|
<el-table-column prop="clockInTime" label="上班打卡" width="120" />
|
<el-table-column prop="clockOutTime" label="下班打卡" width="120" />
|
<el-table-column prop="workHours" label="工作时长" width="100" align="center" />
|
<el-table-column prop="status" label="状态" width="100" align="center">
|
<template #default="scope">
|
<el-tag :type="getAttendanceTagType(scope.row.status)">{{ getAttendanceStatusLabel(scope.row.status) }}</el-tag>
|
</template>
|
</el-table-column>
|
<el-table-column prop="location" label="打卡地点" width="150" />
|
<el-table-column prop="remark" label="备注" min-width="150" />
|
</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-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="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'
|
import { listHolidaySettings, addHolidaySettings, updateHolidaySettings, delHolidaySettings, listAnnualLeaveSettingList, addAnnualLeaveSetting, updateAnnualLeaveSetting, delAnnualLeaveSetting, listOvertimeSettingList, addOvertimeSetting, updateOvertimeSetting, delOvertimeSetting, listWorkingHoursSettingList, addWorkingHoursSetting, updateWorkingHoursSetting, delWorkingHoursSetting } from '@/api/collaborativeApproval/attendanceManagement.js'
|
|
// 当前激活的标签页
|
const activeTab = ref('holiday')
|
|
// 弹窗相关
|
const dialogVisible = ref(false)
|
const dialogTitle = ref('')
|
const currentType = ref('')
|
const currentAction = ref('')
|
const currentEditId = ref('')
|
const formRef = ref()
|
const page = {
|
current: 1,
|
size: 20,
|
total: 0,
|
}
|
const holidayData = ref([])
|
const annualData = ref([])
|
const overtimeData = ref([])
|
const worktimeData = ref([])
|
|
// 打卡记录相关数据
|
const attendanceData = ref([])
|
const filteredAttendanceData = ref([])
|
const attendanceDate = ref('')
|
const attendanceStatus = ref('')
|
|
// 表单数据
|
const form = reactive({
|
name: '',
|
type: '',
|
dateRange: [],
|
startDate: '',
|
endDate: '',
|
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 getTagType = (type) => {
|
const tagMap = {
|
legal: 'success', adjustment: 'warning', special: 'info', company: 'primary',
|
weekday: 'primary', weekend: 'warning', holiday: 'danger', night: 'info',
|
regular: 'success', probation: 'info', intern: 'danger'
|
}
|
return tagMap[type] || 'info'
|
}
|
|
const getTypeLabel = (type) => {
|
const labelMap = {
|
legal: '法定节假日', adjustment: '调休日', special: '特殊假期', company: '公司假期',
|
weekday: '工作日加班', weekend: '周末加班', holiday: '节假日加班', night: '深夜加班',
|
regular: '正式员工', probation: '试用期员工', intern: '实习生'
|
}
|
return labelMap[type] || type
|
}
|
|
// 打卡记录相关工具函数
|
const getAttendanceTagType = (status) => {
|
const tagMap = {
|
normal: 'success',
|
late: 'warning',
|
early: 'warning',
|
absent: 'danger'
|
}
|
return tagMap[status] || 'info'
|
}
|
|
const getAttendanceStatusLabel = (status) => {
|
const labelMap = {
|
normal: '正常',
|
late: '迟到',
|
early: '早退',
|
absent: '缺勤'
|
}
|
return labelMap[status] || status
|
}
|
|
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' }
|
]
|
} else if (currentType.value === 'annual') {
|
return [
|
{ label: '正式员工', value: 'regular' },
|
{ label: '试用期员工', value: 'probation' },
|
{ label: '实习生', value: 'intern' }
|
]
|
}
|
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])
|
form.startDate = start.toISOString().split('T')[0]
|
form.endDate = end.toISOString().split('T')[0]
|
|
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: [],
|
startDate: '',
|
endDate: '',
|
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)],
|
startDate: row.startDate,
|
endDate: 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 = () => {
|
|
if (currentType.value === 'holiday') {
|
const params = {
|
name: form.name,
|
type: form.type,
|
startDate: form.startDate,
|
endDate: form.endDate,
|
days: form.days,
|
status: form.status
|
}
|
addHolidaySettings(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("添加成功");
|
// dialogVisible.value = false;
|
getHolidaySettingsList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
} else if (currentType.value === 'annual') {
|
// annualData.value.push(newItem)
|
const params = {
|
employeeType: form.employeeType,
|
workYears: form.workYears,
|
annualDays: form.annualDays,
|
maxCarryOver: form.maxCarryOver,
|
status: form.status
|
}
|
addAnnualLeaveSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("添加成功");
|
// dialogVisible.value = false;
|
getAnnualLeaveSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
} else if (currentType.value === 'overtime') {
|
const params = {
|
name: form.name,
|
type: form.type,
|
startTime: form.startTime || '',
|
endTime: form.endTime || '',
|
rate: form.rate,
|
status: form.status
|
}
|
addOvertimeSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("添加成功");
|
// dialogVisible.value = false;
|
getOvertimeSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
// newItem.startTime = form.startTime || ''
|
// newItem.endTime = form.endTime || ''
|
// overtimeData.value.push(newItem)
|
} else if (currentType.value === 'worktime') {
|
const params = {
|
name: form.name,
|
startTime: form.workStartTime || '',
|
endTime: form.workEndTime || '',
|
flexibleStart: form.flexibleStart,
|
flexibleMinutes: form.flexibleMinutes,
|
status: form.status
|
}
|
addWorkingHoursSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("添加成功");
|
getWorkingHoursSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
// newItem.startTime = form.workStartTime || ''
|
// newItem.endTime = form.workEndTime || ''
|
// worktimeData.value.push(newItem)
|
}
|
}
|
|
const editItem = () => {
|
let dataArray
|
let index
|
|
if (currentType.value === 'holiday') {
|
const params = {
|
id: currentEditId.value,
|
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
|
}
|
updateHolidaySettings(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("更新成功");
|
// dialogVisible.value = false;
|
getHolidaySettingsList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
} else if (currentType.value === 'annual') {
|
const params = {
|
id: currentEditId.value,
|
employeeType: form.employeeType,
|
workYears: form.workYears,
|
annualDays: form.annualDays,
|
maxCarryOver: form.maxCarryOver,
|
status: form.status
|
}
|
updateAnnualLeaveSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("更新成功");
|
getAnnualLeaveSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
} else if (currentType.value === 'overtime') {
|
const params = {
|
id: currentEditId.value,
|
name: form.name,
|
type: form.type,
|
startTime: form.startTime || '',
|
endTime: form.endTime || '',
|
rate: form.rate,
|
status: form.status
|
}
|
updateOvertimeSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("更新成功");
|
getOvertimeSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
|
// 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') {
|
const params = {
|
id: currentEditId.value,
|
name: form.name,
|
startTime: form.workStartTime || '',
|
endTime: form.workEndTime || '',
|
flexibleStart: form.flexibleStart,
|
flexibleMinutes: form.flexibleMinutes,
|
status: form.status
|
}
|
updateWorkingHoursSetting(params).then(res => {
|
if(res.code == 200){
|
ElMessage.success("更新成功");
|
getWorkingHoursSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
// 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 filterAttendanceData = () => {
|
let filtered = attendanceData.value
|
|
// 按日期过滤
|
if (attendanceDate.value) {
|
filtered = filtered.filter(item => item.date === attendanceDate.value)
|
}
|
|
// 按状态过滤
|
if (attendanceStatus.value) {
|
filtered = filtered.filter(item => item.status === attendanceStatus.value)
|
}
|
|
filteredAttendanceData.value = filtered
|
}
|
|
// 导出打卡记录
|
const exportAttendance = () => {
|
ElMessage.success('导出功能开发中...')
|
}
|
|
// 初始化打卡记录假数据
|
const initAttendanceData = () => {
|
// 尝试从localStorage读取已缓存的数据
|
const cachedData = localStorage.getItem('attendanceMockData')
|
const cacheDate = localStorage.getItem('attendanceMockDataDate')
|
|
// 如果有缓存数据且是当天生成的,直接使用
|
const today = new Date().toISOString().split('T')[0]
|
if (cachedData && cacheDate === today) {
|
try {
|
attendanceData.value = JSON.parse(cachedData)
|
filteredAttendanceData.value = attendanceData.value
|
console.log('使用缓存的考勤数据')
|
return
|
} catch (e) {
|
console.warn('缓存数据解析失败,重新生成')
|
}
|
}
|
|
// 员工列表
|
const employees = [
|
{ name: '朱倩倩', department: '技术部' },
|
{ name: '苏阳', department: '市场部' },
|
{ name: '林小梅', department: '人事部' },
|
{ name: '范海飞', department: '财务部' },
|
{ name: '严故培', department: '技术部' },
|
{ name: '汪转梅', department: '运营部' },
|
{ name: '张坤', department: '设计部' },
|
{ name: '张君', department: '销售部' },
|
{ name: '杨虎', department: '行政部' },
|
{ name: '拜天乐', department: '客服部' }
|
]
|
|
// 2025年法定节假日
|
const holidays = [
|
// 元旦
|
'2025-01-01',
|
// 春节 (1月28日-2月4日)
|
'2025-01-28', '2025-01-29', '2025-01-30', '2025-01-31',
|
'2025-02-01', '2025-02-02', '2025-02-03', '2025-02-04',
|
// 清明节 (4月4日-6日)
|
'2025-04-04', '2025-04-05', '2025-04-06',
|
// 劳动节 (5月1日-5日)
|
'2025-05-01', '2025-05-02', '2025-05-03', '2025-05-04', '2025-05-05',
|
// 端午节 (5月31日-6月2日)
|
'2025-05-31'
|
]
|
|
const holidaySet = new Set(holidays)
|
|
// 判断是否为工作日(单休,周日休息)
|
const isWorkday = (dateStr) => {
|
if (holidaySet.has(dateStr)) return false
|
const date = new Date(dateStr)
|
const dayOfWeek = date.getDay()
|
return dayOfWeek !== 0 // 周日休息
|
}
|
|
// 生成日期范围内的所有工作日
|
const startDate = new Date('2025-01-01')
|
const endDate = new Date('2025-05-25')
|
const workdays = []
|
|
for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) {
|
const dateStr = d.toISOString().split('T')[0]
|
if (isWorkday(dateStr)) {
|
workdays.push(dateStr)
|
}
|
}
|
|
// 生成打卡记录
|
const mockData = []
|
let id = 1
|
|
// 随机生成打卡时间(全部正常)
|
const generateClockTime = (isClockIn) => {
|
if (isClockIn) {
|
// 上班打卡:8:50-9:00之间(全部正常)
|
const minutes = Math.floor(Math.random() * 11) + 50 // 50-60分钟
|
const hour = 8
|
const min = minutes
|
return `${hour.toString().padStart(2, '0')}:${min.toString().padStart(2, '0')}:00`
|
} else {
|
// 下班打卡:18:00-18:30之间(全部正常)
|
const minutes = Math.floor(Math.random() * 31) // 0-30分钟
|
const hour = 18
|
return `${hour.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`
|
}
|
}
|
|
// 计算工作时长
|
const calculateWorkHours = (clockIn, clockOut) => {
|
if (!clockIn || !clockOut) return '0h'
|
const [inH, inM] = clockIn.split(':').map(Number)
|
const [outH, outM] = clockOut.split(':').map(Number)
|
const inMinutes = inH * 60 + inM
|
const outMinutes = outH * 60 + outM
|
const diff = outMinutes - inMinutes
|
const hours = Math.floor(diff / 60)
|
const mins = diff % 60
|
return `${hours}.${Math.round(mins / 6)}h`
|
}
|
|
// 为每个员工的每个工作日生成打卡记录
|
employees.forEach(employee => {
|
workdays.forEach(date => {
|
// 随机决定是否请假(1%概率)或加班(10%概率)
|
const random = Math.random()
|
let clockInTime = ''
|
let clockOutTime = ''
|
let status = 'normal'
|
let remark = ''
|
|
if (random < 0.01) {
|
// 请假(1%概率)
|
remark = Math.random() < 0.5 ? '请病假' : '请事假'
|
status = 'absent'
|
} else {
|
clockInTime = generateClockTime(true)
|
clockOutTime = generateClockTime(false)
|
status = 'normal'
|
|
// 加班备注(10%概率)
|
if (Math.random() < 0.1) {
|
remark = '加班'
|
}
|
}
|
|
mockData.push({
|
id: id++,
|
employeeName: employee.name,
|
department: employee.department,
|
date: date,
|
clockInTime: clockInTime,
|
clockOutTime: clockOutTime,
|
workHours: calculateWorkHours(clockInTime, clockOutTime),
|
status: status,
|
location: '公司总部',
|
remark: remark
|
})
|
})
|
})
|
|
// 按日期排序
|
mockData.sort((a, b) => {
|
if (a.date !== b.date) return a.date.localeCompare(b.date)
|
return a.id - b.id
|
})
|
|
// 缓存数据到localStorage
|
localStorage.setItem('attendanceMockData', JSON.stringify(mockData))
|
localStorage.setItem('attendanceMockDataDate', today)
|
|
attendanceData.value = mockData
|
filteredAttendanceData.value = mockData
|
console.log('生成新的考勤数据并缓存')
|
}
|
|
// 删除项目
|
const deleteItem = (type, row) => {
|
ElMessageBox.confirm('确定要删除这个项目吗?', '提示', {
|
confirmButtonText: '确定',
|
cancelButtonText: '取消',
|
type: 'warning'
|
}).then(() => {
|
let ids = [];
|
let dataArray
|
if (type === 'holiday') {
|
ids.push(row.id)
|
delHolidaySettings(ids).then(res => {
|
if(res.code == 200){
|
ElMessage.success("删除成功");
|
ids = []
|
getHolidaySettingsList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
}
|
else if (type === 'annual') {
|
ids.push(row.id)
|
delAnnualLeaveSetting(ids).then(res => {
|
if(res.code == 200){
|
ElMessage.success("删除成功");
|
ids = []
|
getAnnualLeaveSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
}
|
else if (type === 'overtime') {
|
ids.push(row.id)
|
delOvertimeSetting(ids).then(res => {
|
if(res.code == 200){
|
ElMessage.success("删除成功");
|
ids = []
|
getOvertimeSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
}
|
else if (type === 'worktime') {
|
ids.push(row.id)
|
delWorkingHoursSetting(ids).then(res => {
|
if(res.code == 200){
|
ElMessage.success("删除成功");
|
ids = []
|
getWorkingHoursSettingList()
|
}
|
}).catch(err => {
|
ElMessage.error(err.msg);
|
})
|
}
|
|
// const index = dataArray.findIndex(item => item.id === row.id)
|
// if (index > -1) {
|
// dataArray.splice(index, 1)
|
// ElMessage.success('删除成功')
|
// }
|
})
|
}
|
// 获取假期设置列表
|
const getHolidaySettingsList = () => {
|
// tableLoading.value = true;
|
listHolidaySettings({...page.value})
|
.then(res => {
|
// tableLoading.value = false;
|
holidayData.value = res.data.records
|
page.total = res.data.total;
|
}).catch(err => {
|
// tableLoading.value = false;
|
})
|
};
|
// 获取年假规则列表
|
const getAnnualLeaveSettingList = () => {
|
|
listAnnualLeaveSettingList({...page.value})
|
.then(res => {
|
// console.log(res.data)
|
annualData.value = res.data.records
|
page.total = res.data.total;
|
}).catch(err => {
|
})
|
};
|
// 获取加班规则列表
|
const getOvertimeSettingList = () => {
|
|
listOvertimeSettingList({...page.value})
|
.then(res => {
|
// console.log(res.data)
|
overtimeData.value = res.data.records
|
page.total = res.data.total;
|
}).catch(err => {
|
})
|
};
|
// 获取工作时间规则列表
|
const getWorkingHoursSettingList = () => {
|
|
listWorkingHoursSettingList({...page.value})
|
.then(res => {
|
// console.log(res.data)
|
worktimeData.value = res.data.records
|
page.total = res.data.total;
|
}).catch(err => {
|
})
|
};
|
onMounted(() => {
|
getHolidaySettingsList()
|
getAnnualLeaveSettingList()
|
getOvertimeSettingList()
|
getWorkingHoursSettingList()
|
initAttendanceData()
|
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>
|