Преглед изворни кода

feat(risk): 巡检任务编辑 - 扫码时间根据巡检时段自动同步

之前扫码开启时间/扫码间隔是独立输入字段,跟 timeSlotList 完全脱钩,
导致多时段任务时扫码窗口只覆盖部分时段(例:开启 16:09 + 间隔 30min
只能覆盖 16:10-17:00,18:10-19:00 就扫不上)。

新增 watch(timeSlotList) → syncScanTime:
  - 首个 slot 的开始 → scanStartTime (今天日期 + HH:mm:00)
  - 末个 slot 的结束 - 首个 slot 的开始 → scanIntervalSec (秒)
  - 仅 cycle='每日' 生效(其他周期用 dayOfWeek/dayOfMonth)
  - 无 slot 时不动,保留默认/手动值

在巡检时间标签下方加 hint 提示自动同步结果,绿色加粗关键值,
让用户一眼看到是 watch 算出来的。

测试: 扫码测试任务_2026 (slot=09:00-18:00) 编辑时
  - scanStartTime 自动同步为 2026-07-23 09:00:00
  - scanIntervalSec 自动同步为 32400 (9 小时)
yangjingjing пре 10 часа
родитељ
комит
915eafaefa
1 измењених фајлова са 84 додато и 2 уклоњено
  1. 84 2
      src/views/risk/inspectionTask/index.vue

+ 84 - 2
src/views/risk/inspectionTask/index.vue

@@ -282,6 +282,10 @@
               {{ t }}
             </el-tag>
             <el-button size="small" type="primary" plain @click="openAddTimeSlot">添加时间段</el-button>
+            <div v-if="timeSlotList.length > 0" class="time-slot-hint">
+              <el-icon><InfoFilled /></el-icon>
+              <span>扫码窗口已自动同步:<b>{{ form.scanStartTime || '-' }}</b> 起 <b>{{ formatDuration(form.scanIntervalSec) }}</b> 内有效(覆盖全部巡检时段)</span>
+            </div>
           </div>
           <div v-else-if="form.cycle === '每周'">
             <el-checkbox-group v-model="weekDayList">
@@ -546,7 +550,7 @@
 </template>
 
 <script setup name="InspectionTask" lang="ts">
-import { ref, reactive, toRefs, onMounted, getCurrentInstance } from 'vue';
+import { ref, reactive, toRefs, onMounted, getCurrentInstance, watch } from 'vue';
 import {
   listInspectionTask,
   getInspectionTask,
@@ -572,7 +576,7 @@ import type { UserVO } from '@/api/system/user/types';
 import { listDept } from '@/api/system/dept';
 import { FormInstance } from 'element-plus';
 import type { ComponentInternalInstance } from 'vue';
-import { Calendar, Check, Warning, Clock, Location } from '@element-plus/icons-vue';
+import { Calendar, Check, Warning, Clock, Location, InfoFilled } from '@element-plus/icons-vue';
 import MapPickerDialog from '@/components/MapPickerDialog/index.vue';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -851,6 +855,71 @@ const removeTimeSlot = (idx: number) => {
   timeSlotList.value.splice(idx, 1);
 };
 
+// ============ 扫码时间自动同步(从 timeSlotList 推导 scanStartTime + scanIntervalSec) ============
+/**
+ * 解析 "HH:mm-HH:mm" 字符串 → { start: 'HH:mm', end: 'HH:mm' } 或 null
+ * 兼容 "16:10-17:00" / "8:5-9:30"(自动补 0)
+ */
+const parseSlot = (s: string): { start: string; end: string } | null => {
+  if (!s || typeof s !== 'string') return null;
+  const m = s.match(/^\s*(\d{1,2}):(\d{1,2})\s*-\s*(\d{1,2}):(\d{1,2})\s*$/);
+  if (!m) return null;
+  const sh = +m[1], sm = +m[2], eh = +m[3], em = +m[4];
+  if ([sh, sm, eh, em].some((n) => Number.isNaN(n))) return null;
+  if (sh > 23 || sm > 59 || eh > 23 || em > 59) return null;
+  if (timeToSec(`${eh}:${em}`) <= timeToSec(`${sh}:${sm}`)) return null; // 结束必须 > 开始
+  const pad = (n: number) => String(n).padStart(2, '0');
+  return { start: `${pad(sh)}:${pad(sm)}`, end: `${pad(eh)}:${pad(em)}` };
+};
+
+/** "HH:mm" → 当天 0 点起的秒数 */
+const timeToSec = (t: string): number => {
+  const [h, m] = t.split(':').map(Number);
+  return h * 3600 + m * 60;
+};
+
+/** 今天 YYYY-MM-DD(本地时区) */
+const todayStr = (): string => {
+  const d = new Date();
+  const pad = (n: number) => String(n).padStart(2, '0');
+  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
+};
+
+/** 秒数 → "X 小时 Y 分" / "Y 分 Z 秒" 人类可读 */
+const formatDuration = (sec: number | undefined | null): string => {
+  if (!sec || sec <= 0) return '0 秒';
+  const h = Math.floor(sec / 3600);
+  const m = Math.floor((sec % 3600) / 60);
+  const s = sec % 60;
+  if (h > 0) return `${h} 小时 ${m} 分`;
+  if (m > 0) return s > 0 ? `${m} 分 ${s} 秒` : `${m} 分`;
+  return `${s} 秒`;
+};
+
+/**
+ * 根据 timeSlotList 自动同步 scanStartTime + scanIntervalSec
+ * - 仅 cycle=每日 时生效(其他周期用 dayOfWeek/dayOfMonth,timeSlot 留空)
+ * - 首个 slot 的开始 = 扫码开启时间
+ * - 末个 slot 的结束 - 首个 slot 的开始 = 扫码窗口长度(秒)
+ *   覆盖整个巡检窗口期,任意 slot 期间扫码都有效
+ * - 没有 slot 时不动,保留默认/手动值
+ */
+const syncScanTime = () => {
+  if (form.value.cycle !== '每日') return;
+  const slots = timeSlotList.value
+    .map(parseSlot)
+    .filter((s): s is { start: string; end: string } => !!s);
+  if (slots.length === 0) return;
+  const first = slots[0];
+  const last = slots[slots.length - 1];
+  form.value.scanStartTime = `${todayStr()} ${first.start}:00`;
+  form.value.scanIntervalSec = Math.max(60, timeToSec(last.end) - timeToSec(first.start));
+};
+
+watch(timeSlotList, () => {
+  syncScanTime();
+}, { deep: true });
+
 const addInspectionItem = () => {
   form.value.itemList.push({
     itemCategory: '',
@@ -1248,4 +1317,17 @@ const loadStores = async () => {
   display: flex;
   align-items: center;
 }
+.time-slot-hint {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #909399;
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  line-height: 1.4;
+  b {
+    color: #67c23a;
+    font-weight: 600;
+  }
+}
 </style>