src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue
@@ -37,11 +37,17 @@ <el-option v-for="item in shifts_list" :key="item.value" :label="item.label" :value="item.value" /> :value="item.value" :disabled=" operationType === 'add' && (String(item.label || '').includes('假') || String(item.label || '').includes('休')) " /> </el-select> </el-form-item> <el-form-item label="打卡范围(m)" prop="radius"> prop="radius" v-show="!hideClockFields"> <el-input-number v-model="form.radius" :min="10" :max="1000" @@ -51,7 +57,8 @@ </el-form-item> <!-- 高德地图选择 --> <el-form-item label="打卡位置" prop="longitude"> prop="longitude" v-show="!hideClockFields"> <div class="map-container"> <div class="map-header" style="margin-bottom: 10px"> @@ -82,14 +89,16 @@ </div> </el-form-item> <el-form-item label="地点名称" prop="locationName"> prop="locationName" v-show="!hideClockFields"> <el-input v-model="form.locationName" :disabled="operationType === 'view'" placeholder="请输入地点名称" /> </el-form-item> <!-- 上下班时间 --> <el-form-item label="上班时间" prop="startAt"> prop="startAt" v-show="!hideClockFields"> <el-time-picker v-model="form.startAt" format="HH:mm" value-format="HH:mm" @@ -97,7 +106,8 @@ :disabled="operationType === 'view'" /> </el-form-item> <el-form-item label="下班时间" prop="endAt"> prop="endAt" v-show="!hideClockFields"> <el-time-picker v-model="form.endAt" format="HH:mm" value-format="HH:mm" @@ -110,7 +120,9 @@ </el-form> <template #footer> <span class="dialog-footer"> <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">确定</el-button> <el-button type="primary" @click="submitForm" v-if="operationType !== 'view'">确定</el-button> <el-button @click="dialogVisible = false">取消</el-button> </span> </template> @@ -157,6 +169,21 @@ // 获取班次字典值 const { shifts_list } = useDict("shifts_list"); const selectedShiftLabel = computed(() => { const item = shifts_list.value?.find( s => String(s.value) === String(form.shift) ); return item?.label || ""; }); const hideClockFields = computed(() => { if (props.operationType === "view") return false; return ( selectedShiftLabel.value.includes("假") || selectedShiftLabel.value.includes("休") ); }); // 表单数据 const formRef = ref(); const form = reactive({ @@ -172,22 +199,30 @@ }); // 表单验证规则 const rules = { const rules = computed(() => { const base = { sysDeptId: [{ required: true, message: "请选择部门", trigger: "change" }], shift: [{ required: true, message: "请选择班次", trigger: "change" }], }; if (hideClockFields.value) { return base; } return { ...base, locationName: [ { required: true, message: "请输入地点名称", trigger: "blur" }, ], longitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }], latitude: [{ required: true, message: "请选择打卡位置", trigger: "blur" }], shift: [{ required: true, message: "请选择班次", trigger: "change" }], radius: [{ required: true, message: "请输入打卡范围", trigger: "blur" }], startAt: [{ required: true, message: "请选择上班时间", trigger: "change" }], endAt: [ { required: true, message: "请选择下班时间", trigger: "change" }, { validator: (rule, value, callback) => { // 如果是夜班,不限制时间顺序 if (form.shift && ['夜班', '夜'].includes(form.shift)) { if (form.shift && ["夜班", "夜"].includes(form.shift)) { callback(); return; } @@ -210,6 +245,7 @@ }, ], }; }); // 部门选项 const deptOptions = ref([]); @@ -393,8 +429,8 @@ watch( () => form.radius, newValue => { if (circle) { circle.setRadius(newValue); if (circle && newValue != null && newValue !== "") { circle.setRadius(Number(newValue)); } } ); @@ -405,6 +441,29 @@ () => { if (formRef.value && form.endAt) { formRef.value.validateField("endAt"); } } ); watch( () => hideClockFields.value, val => { if (!val) return; form.radius = null; form.longitude = ""; form.latitude = ""; form.locationName = ""; form.startAt = ""; form.endAt = ""; if (formRef.value) { formRef.value.clearValidate([ "radius", "longitude", "latitude", "locationName", "startAt", "endAt", ]); } } ); @@ -442,7 +501,9 @@ // 初始化地图 setTimeout(() => { if (!hideClockFields.value) { initMap(); } }, 100); } } src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue
@@ -74,23 +74,39 @@ </template> </el-table-column> <el-table-column prop="locationName" label="地点名称" /> label="地点名称"> <template #default="scope"> {{ scope.row.locationName || "-" }} </template> </el-table-column> <el-table-column prop="longitude" label="经度" /> label="经度"> <template #default="scope"> {{ scope.row.longitude ?? "-" }} </template> </el-table-column> <el-table-column prop="latitude" label="纬度" /> label="纬度"> <template #default="scope"> {{ scope.row.latitude ?? "-" }} </template> </el-table-column> <el-table-column prop="radius" label="打卡范围(m)" /> label="打卡范围(m)"> <template #default="scope"> {{ scope.row.radius ?? "-" }} </template> </el-table-column> <el-table-column prop="startAt" label="上班时间"> <template #default="scope"> {{ scope.row.startAt }} {{ scope.row.startAt || "-" }} </template> </el-table-column> <el-table-column prop="endAt" label="下班时间"> <template #default="scope"> {{ scope.row.endAt }} {{ scope.row.endAt || "-" }} </template> </el-table-column> <el-table-column label="操作" @@ -232,7 +248,8 @@ tableLoading.value = true; getAttendanceRules({ ...page, ...searchForm }) .then(res => { tableData.value = res.data.records; const records = Array.isArray(res?.data?.records) ? res.data.records : []; tableData.value = records.filter(r => String(r?.id) !== "999"); page.total = res.data.total; }) .finally(() => { src/views/personnelManagement/classsSheduling/index.vue
@@ -149,12 +149,12 @@ 'shift-box-early': m.shift === '早班', 'shift-box-mid': m.shift === '中班', 'shift-box-night': m.shift === '夜班', 'shift-box-rest': m.shift === '休息', 'shift-box-rest': !m.shift || m.shift === '休息', 'shift-box-leave': m.shift === '请假', 'shift-box-other': m.shift === '夜11', 'shift-box-business': m.shift === '夜12', }"> <span class="shift-text">{{ getShiftNameByValue(m.shift) || '—' }}</span> <span class="shift-text">{{ getShiftNameByValue(m.shift) || '休息' }}</span> </div> <template #dropdown> <el-dropdown-menu> @@ -199,7 +199,7 @@ </div> --> <div class="user-total"> <span class="total-label">合计出勤:</span> <span class="total-value">{{ item.work_time }}天</span> <span class="total-value">{{ item.year.totalYearAttendance }}天</span> </div> </div> </div> @@ -271,6 +271,23 @@ <div class="search_thing"> <div class="search_label" style="width: 90px"> <span style="color: red; margin-right: 4px">*</span>部门: </div> <div class="search_input"> <el-tree-select v-model="schedulingQuery.deptId" :data="deptOptions" :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择部门" check-strictly clearable @change="onSchedulingDeptChange" style="width: 100%" /> </div> </div> <div class="search_thing"> <div class="search_label" style="width: 90px"> <span style="color: red; margin-right: 4px">*</span>人员名称: </div> <div class="search_input"> @@ -279,7 +296,8 @@ style="width: 100%" multiple clearable collapse-tags> collapse-tags :disabled="!schedulingQuery.deptId"> <el-option v-for="item in personList" :key="item.id" :label="item.staffName" @@ -296,8 +314,9 @@ <div class="search_input"> <el-select v-model="schedulingQuery.shift" placeholder="请选择" style="width: 100%"> <el-option v-for="item in classType" style="width: 100%" :disabled="!schedulingQuery.deptId"> <el-option v-for="item in schedulingClassType" :key="item.id" :label="getShiftNameByValue(item.shift)" :value="item.id"> @@ -318,7 +337,14 @@ </template> <script setup> import { ref, reactive, onMounted, getCurrentInstance } from "vue"; import { ref, reactive, computed, watch, onMounted, getCurrentInstance, } from "vue"; import { useRouter } from "vue-router"; import { page, @@ -376,6 +402,7 @@ // 人员列表 const personList = ref([]); const personListAll = ref([]); // 加载状态 const loading = ref(false); @@ -383,8 +410,18 @@ // 排班查询条件 const schedulingQuery = reactive({ week: "", deptId: "", userId: null, shift: "", }); const schedulingClassType = computed(() => { if (!schedulingQuery.deptId) return classType.value || []; return (classType.value || []).filter(item => { if (["998", "999"].includes(String(item?.id))) return true; const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id; return String(itemDeptId) === String(schedulingQuery.deptId); }); }); // 列表数据 @@ -593,6 +630,10 @@ proxy.$modal.msgError("请选择周次"); return; } if (!schedulingQuery.deptId) { proxy.$modal.msgError("请选择部门"); return; } let time = schedulingQuery.week.getTime(); // 格式化日期为 YYYY-MM-DD 格式 @@ -628,8 +669,10 @@ proxy.$modal.msgSuccess("操作成功"); schedulingVisible.value = false; schedulingQuery.week = ""; schedulingQuery.deptId = ""; schedulingQuery.userId = null; schedulingQuery.shift = ""; personList.value = []; refresh(); }) .catch(err => { @@ -683,8 +726,7 @@ } proxy.$download.saveAs(blob, fileName + ".xlsx"); }) .catch(err => { }) .catch(err => {}) .finally(() => { downLoading.value = false; }); @@ -709,7 +751,18 @@ // 查询规则列表 const fetchData = () => { getAttendanceRules({ current: -1, size: -1 }).then(res => { classType.value = res.data.records; const records = Array.isArray(res?.data?.records) ? res.data.records : []; const appendList = [ { id: 998, shift: "休息" }, { id: 999, shift: "请假" }, ]; const exists = new Set(records.map(r => String(r?.id))); appendList.forEach(item => { if (!exists.has(String(item.id))) { records.push(item); } }); classType.value = records; }); }; // 获取用户 @@ -724,8 +777,61 @@ staffState: 1, }).then(res => { let arr = res.data.records; personList.value = arr; personListAll.value = arr; updatePersonListByDept(); }); }; const findDeptNodeById = (deptId, deptList = deptOptions.value) => { const list = Array.isArray(deptList) ? deptList : []; for (const node of list) { if (String(node.id) === String(deptId)) return node; if (node.children && node.children.length) { const found = findDeptNodeById(deptId, node.children); if (found) return found; } } return null; }; const collectDeptIds = node => { const ids = []; if (!node) return ids; ids.push(String(node.id)); if (node.children && node.children.length) { node.children.forEach(child => { ids.push(...collectDeptIds(child)); }); } return ids; }; const updatePersonListByDept = () => { if (!schedulingQuery.deptId) { personList.value = []; return; } const node = findDeptNodeById(schedulingQuery.deptId); const deptIds = new Set(collectDeptIds(node)); personList.value = (personListAll.value || []).filter(item => { const itemDeptId = item.deptId ?? item.sysDeptId ?? item.dept_id; return deptIds.has(String(itemDeptId)); }); }; const setDefaultShiftByDept = () => { if (!schedulingQuery.deptId) { schedulingQuery.shift = ""; return; } const first = schedulingClassType.value?.[0]; schedulingQuery.shift = first ? first.id : ""; }; const onSchedulingDeptChange = () => { schedulingQuery.userId = null; updatePersonListByDept(); setDefaultShiftByDept(); }; // 根据字典获取日期 @@ -761,6 +867,27 @@ } monthList.value.reverse(); }); watch( () => classType.value, () => { if (schedulingVisible.value && schedulingQuery.deptId) { setDefaultShiftByDept(); } } ); watch( () => schedulingVisible.value, val => { if (!val) return; schedulingQuery.week = ""; schedulingQuery.deptId = ""; schedulingQuery.userId = null; schedulingQuery.shift = ""; personList.value = []; } ); </script> <style scoped>