zhangwencui
10 小时以前 59e4906ad374bb44b481012b4b2a7d3d077b8677
排班模块逻辑修改
已修改3个文件
267 ■■■■ 文件已修改
src/views/personnelManagement/attendanceCheckin/checkinRules/components/form.vue 87 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/attendanceCheckin/checkinRules/index.vue 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/personnelManagement/classsSheduling/index.vue 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>