From 166ff56efefe6682a9d840fe0f1fa6e456833556 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期五, 04 九月 2026 18:34:23 +0800
Subject: [PATCH] fix: 时效模块原型

---
 src/pages/timely/index.vue | 1008 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 928 insertions(+), 80 deletions(-)

diff --git a/src/pages/timely/index.vue b/src/pages/timely/index.vue
index ab55d7b..3063f91 100644
--- a/src/pages/timely/index.vue
+++ b/src/pages/timely/index.vue
@@ -1,108 +1,956 @@
 <template>
-  <view class="list_box">
-    <z-paging ref="pagingRef" v-model="cardList" :fixed="false" :auto-show-back-to-top="true">
+  <view class="timely-page">
+    <z-paging
+      ref="pagingRef"
+      v-model="taskList"
+      :fixed="false"
+      :auto-show-back-to-top="true"
+      @query="queryTaskList"
+    >
       <template #top>
-        <CardTitle title="鏃舵晥鎶ュ伐" :hideAction="false">
-          <template #action>
-            <wd-button type="icon" icon="scan" color="#0D867F" @click="openScan"></wd-button>
-          </template>
-        </CardTitle>
-      </template>
-      <MonofilCard v-for="(item, index) in cardList" :key="index" :data="item" />
-      <template #bottom>
-        <view class="flex justify-center items-center">
-          <wd-button block @click="save">
-            <text class="text-[#fff]">淇濆瓨</text>
-          </wd-button>
+        <view class="page-top">
+          <CardTitle title="鏃舵晥浠诲姟鍙拌处" :hideAction="false">
+            <template #action>
+              <wd-tag type="warning" size="small">Mock</wd-tag>
+              <wd-button size="small" icon="add" @click="openCreatePopup">鏂板浠诲姟</wd-button>
+            </template>
+          </CardTitle>
+
+          <view class="date-filter-card">
+            <view class="date-filter-heading">
+              <view class="date-filter-title">
+                <view class="date-filter-icon">
+                  <wd-icon name="calendar" size="18px" color="#078568" />
+                </view>
+                <view>
+                  <view class="date-filter-name">閫夋嫨鏃ユ湡</view>
+                  <view class="date-filter-tip">鎸変换鍔″彂璧锋棩鏈熸煡璇�</view>
+                </view>
+              </view>
+              <text v-if="hasDateFilter" class="date-filter-clear" @click="resetFilters">娓呴櫎</text>
+            </view>
+            <view class="date-filter-fields">
+              <view class="date-filter-field">
+                <wd-datetime-picker v-model="filters.startTimestamp" type="date">
+                  <view class="date-picker-trigger">
+                    <text class="date-picker-label">寮�濮嬫棩鏈�</text>
+                    <view class="date-picker-value">
+                      <text :class="{ 'is-placeholder': !filters.startTimestamp }">
+                        {{
+                          filters.startTimestamp
+                            ? formatDateOnly(Number(filters.startTimestamp))
+                            : "璇烽�夋嫨"
+                        }}
+                      </text>
+                      <wd-icon name="arrow-right" size="13px" color="#aebbb7" />
+                    </view>
+                  </view>
+                </wd-datetime-picker>
+              </view>
+              <view class="date-filter-separator">鑷�</view>
+              <view class="date-filter-field">
+                <wd-datetime-picker v-model="filters.endTimestamp" type="date">
+                  <view class="date-picker-trigger">
+                    <text class="date-picker-label">缁撴潫鏃ユ湡</text>
+                    <view class="date-picker-value">
+                      <text :class="{ 'is-placeholder': !filters.endTimestamp }">
+                        {{
+                          filters.endTimestamp
+                            ? formatDateOnly(Number(filters.endTimestamp))
+                            : "璇烽�夋嫨"
+                        }}
+                      </text>
+                      <wd-icon name="arrow-right" size="13px" color="#aebbb7" />
+                    </view>
+                  </view>
+                </wd-datetime-picker>
+              </view>
+            </view>
+            <view class="date-filter-actions">
+              <wd-button size="small" type="info" plain @click="resetFilters">閲嶇疆</wd-button>
+              <wd-button size="small" icon="search" @click="applyFilters">鏌ヨ浠诲姟</wd-button>
+            </view>
+          </view>
         </view>
       </template>
+
+      <view class="task-list">
+        <wd-card v-for="task in taskList" :key="task.id" custom-class="task-card">
+          <template #title>
+            <view class="card-title" @click="openDetail(task)">
+              <view class="title-main">
+                <text class="task-code">{{ task.taskCode || `浠诲姟-${task.id}` }}</text>
+                <wd-tag
+                  size="small"
+                  :type="
+                    normalizeMaterialType(task.materialType) === '鍗曚笣' ? 'primary' : 'success'
+                  "
+                >
+                  {{ normalizeMaterialType(task.materialType) }}
+                </wd-tag>
+              </view>
+              <wd-tag size="small" :type="getStatusType(task)">
+                {{ getStatusLabel(task) }}
+              </wd-tag>
+            </view>
+          </template>
+
+          <view @click="openDetail(task)">
+            <view class="info-grid">
+              <view class="info-item">
+                <text class="info-label">鍙戣捣浠诲姟浜�</text>
+                <text>{{ getInitiator(task) }}</text>
+              </view>
+              <view class="info-item">
+                <text class="info-label">宸℃鍛�</text>
+                <text>{{ getInspector(task) }}</text>
+              </view>
+              <view class="info-item">
+                <text class="info-label">璁惧畾娓╁害</text>
+                <text>{{ getTemperature(task) }}</text>
+              </view>
+              <view class="info-item">
+                <text class="info-label">鏃舵晥鏃堕暱</text>
+                <text>{{ getDuration(task) }}</text>
+              </view>
+              <view class="info-item">
+                <text class="info-label">璁惧畾鏈哄彴</text>
+                <text>{{ task.machineName || task.machine || "-" }}</text>
+              </view>
+              <view class="info-item">
+                <text class="info-label">鏃舵晥浠舵暟</text>
+                <text>{{ task.itemCount ?? task.count ?? task.detailCount ?? 0 }} 浠�</text>
+              </view>
+            </view>
+            <view class="time-row">
+              <text class="info-label">鍙戣捣鏃堕棿</text>
+              <text>{{ task.initiateTime || task.startTime || task.createTime || "-" }}</text>
+            </view>
+            <view v-if="task.furnaceInTime || task.inTime" class="time-row">
+              <text class="info-label">杩涚倝鏃堕棿</text>
+              <text>{{ task.furnaceInTime || task.inTime }}</text>
+            </view>
+            <view v-if="task.furnaceOutTime || task.outTime" class="time-row">
+              <text class="info-label">鍑虹倝鏃堕棿</text>
+              <text>{{ task.furnaceOutTime || task.outTime }}</text>
+            </view>
+          </view>
+
+          <template #footer>
+            <view class="task-actions">
+              <wd-button size="small" plain @click="openDetail(task)">鏄庣粏</wd-button>
+              <wd-button
+                v-if="isWaiting(task)"
+                size="small"
+                plain
+                type="warning"
+                @click="openEditPopup(task)"
+              >
+                缂栬緫
+              </wd-button>
+              <wd-button v-if="isWaiting(task)" size="small" plain @click="startTask(task)">
+                寮�濮嬫椂鏁�
+              </wd-button>
+              <wd-button
+                v-if="isWaiting(task)"
+                size="small"
+                plain
+                type="error"
+                @click="deleteTask(task)"
+              >
+                鍒犻櫎
+              </wd-button>
+              <wd-button
+                v-if="isRunning(task)"
+                size="small"
+                plain
+                type="error"
+                @click="endTask(task)"
+              >
+                缁撴潫鏃舵晥
+              </wd-button>
+              <wd-button
+                v-if="!isWaiting(task)"
+                size="small"
+                plain
+                type="success"
+                @click="openInspection(task)"
+              >
+                宸℃璁板綍
+              </wd-button>
+            </view>
+          </template>
+        </wd-card>
+      </view>
     </z-paging>
-    <Scan ref="scanRef" emitName="scanTime" />
+
+    <wd-popup
+      v-model="taskPopupVisible"
+      position="bottom"
+      :z-index="30"
+      :close-on-click-modal="false"
+      custom-style="height: 88vh; border-radius: 24rpx 24rpx 0 0;"
+    >
+      <view class="popup-layout">
+        <view class="popup-header">
+          <text class="popup-title">{{ editingTaskId ? "缂栬緫鏃舵晥浠诲姟" : "鏂板鏃舵晥浠诲姟" }}</text>
+          <wd-icon name="close" size="20px" @click="taskPopupVisible = false" />
+        </view>
+        <scroll-view class="popup-body" scroll-y>
+          <wd-form ref="taskFormRef" :model="taskForm">
+            <wd-cell-group border>
+              <wd-picker
+                v-model="taskForm.materialType"
+                :columns="materialTypeOptions"
+                label="浠诲姟绫诲瀷"
+                required
+                :z-index="40"
+              />
+              <wd-picker
+                v-model="taskForm.initiatorName"
+                :columns="userOptions"
+                label="鍙戣捣浠诲姟浜�"
+                placeholder="璇烽�夋嫨"
+                required
+                :loading="optionsLoading"
+                :z-index="40"
+              />
+              <wd-datetime-picker
+                v-model="taskForm.initiateTimestamp"
+                label="鍙戣捣鏃堕棿"
+                type="datetime"
+                required
+                use-second
+                :z-index="40"
+              />
+              <wd-picker
+                v-model="taskForm.inspectorName"
+                :columns="userOptions"
+                label="宸℃鍛�"
+                placeholder="璇烽�夋嫨"
+                required
+                :loading="optionsLoading"
+                :z-index="40"
+              />
+              <wd-input
+                v-model="taskForm.setTemperature"
+                label="璁惧畾娓╁害(鈩�)"
+                type="digit"
+                required
+                placeholder="璇疯緭鍏�"
+              />
+              <wd-cell title="鏃舵晥鏃堕暱(灏忔椂)" required />
+              <view class="duration-row">
+                <wd-input
+                  v-model="taskForm.durationRise"
+                  type="digit"
+                  placeholder="鍗囨俯"
+                  no-border
+                />
+                <text>+</text>
+                <wd-input
+                  v-model="taskForm.durationHold"
+                  type="digit"
+                  placeholder="淇濇俯"
+                  no-border
+                />
+                <text>+</text>
+                <wd-input
+                  v-model="taskForm.durationSoak"
+                  type="digit"
+                  placeholder="鐒栫倝"
+                  no-border
+                />
+              </view>
+              <wd-picker
+                v-model="taskForm.machineName"
+                :columns="machineOptions"
+                label="璁惧畾鏈哄彴"
+                placeholder="璇烽�夋嫨鏃舵晥鏈哄彴"
+                required
+                :loading="optionsLoading"
+                :z-index="40"
+              />
+            </wd-cell-group>
+          </wd-form>
+        </scroll-view>
+        <view class="popup-footer">
+          <wd-button type="info" plain @click="taskPopupVisible = false">鍙栨秷</wd-button>
+          <wd-button :loading="taskSubmitting" @click="submitTask">
+            {{ editingTaskId ? "淇濆瓨淇敼" : "纭畾鏂板" }}
+          </wd-button>
+        </view>
+      </view>
+    </wd-popup>
+
     <wd-toast />
   </view>
 </template>
 
 <script setup lang="ts">
+import { onShow } from "@dcloudio/uni-app";
+import { useToast } from "wot-design-uni";
+import type { TagType } from "wot-design-uni/components/wd-tag/types";
 import CardTitle from "@/components/card-title/index.vue";
 import zPaging from "@/components/z-paging/z-paging.vue";
-import Scan from "@/components/scan/index.vue";
-import MonofilCard from "../production/twist/components/MonofilCard.vue";
-import TwistApi from "@/api/product/twist";
-import { useToast } from "wot-design-uni";
+import TimeSensitiveTaskApi, {
+  type TimelinessMaterialType,
+  type TimeSensitiveTaskForm,
+} from "@/api/timeSensitiveTask";
+import { useUserStore } from "@/store/modules/user";
 
-const pagingRef = ref();
-const scanRef = ref();
 const toast = useToast();
-const cardList = ref<any>([]);
+const userStore = useUserStore();
+const pagingRef = ref();
+const taskFormRef = ref();
+const taskList = ref<any[]>([]);
+const taskPopupVisible = ref(false);
+const taskSubmitting = ref(false);
+const optionsLoading = ref(false);
+const editingTaskId = ref<number | string | undefined>();
+const pageReady = ref(false);
 
-const getScanCode = async (code: any) => {
-  console.log("鏃舵晥鎵爜鍥炶皟:", code);
-  console.log("json:", JSON.parse(code.code));
-  console.log("id:=============", JSON.parse(code.code).id);
-  const { data } = await TwistApi.getScarn({
-    outPutId: JSON.parse(code.code).id,
-  });
-  console.log("=======璇锋眰========", data);
-  const exists = cardList.value.some(
-    (item: any) => item.monofilamentNumber === data.monofilamentNumber
-  );
-  if (!exists) {
-    const { id, outPutId, wireId, oneLength, ...rest } = data;
-    console.log("sb", {
-      outputId: id,
-      amount: oneLength,
-      ongLength: oneLength,
-      ...rest,
-    });
-    cardList.value.push({
-      outputId: id,
-      amount: oneLength,
-      ongLength: oneLength,
-      ...rest,
-    });
-    pagingRef.value.complete(cardList.value);
-  } else {
-    toast.error("璇ュ崟涓濆凡棰嗙敤锛岃鍕块噸澶嶆壂鐮�");
-  }
-};
-const save = async () => {
-  const { code } = await TwistApi.saveAge(cardList.value);
-  if (code == 200) {
-    toast.success("淇濆瓨鎴愬姛");
-    pagingRef.value.refresh();
-  } else {
-    toast.error("淇濆瓨澶辫触");
-  }
-};
+const materialTypeOptions = [
+  { label: "鍗曚笣", value: "鍗曚笣" },
+  { label: "缁炵嚎", value: "缁炵嚎" },
+];
 
-const openScan = () => {
-  scanRef.value.triggerScan();
-};
+const statusOptions = [
+  { label: "寰呭紑濮�", value: 0 },
+  { label: "鏃舵晥涓�", value: 1 },
+  { label: "鏃舵晥缁撴潫", value: 2 },
+];
 
-// 纭繚鍏堢Щ闄ゅ啀娣诲姞鐩戝惉
-const setupScanListener = () => {
-  uni.$off("scanTime", getScanCode); // 鍏堢Щ闄ゆ棫鐨�
-  uni.$on("scanTime", getScanCode); // 鍐嶆坊鍔犳柊鐨�
-};
+const filters = reactive({
+  startTimestamp: "" as "" | number,
+  endTimestamp: "" as "" | number,
+});
+const hasDateFilter = computed(() => Boolean(filters.startTimestamp || filters.endTimestamp));
 
-onMounted(() => {
-  setupScanListener();
-  console.log("椤甸潰鏄剧ず - 鎵爜鐩戝惉宸茶缃�");
+const taskForm = reactive({
+  materialType: "鍗曚笣" as TimelinessMaterialType,
+  initiatorName: "",
+  initiateTimestamp: Date.now() as number,
+  setTemperature: "" as string | number,
+  durationRise: 2 as string | number,
+  durationHold: "" as string | number,
+  durationSoak: "" as string | number,
+  machineName: "",
+  inspectorName: "",
 });
 
-onUnmounted(() => {
-  // 寮�鍚箍鎾洃鍚簨浠�
-  uni.$off("scanTime", getScanCode);
-  console.log("椤甸潰鍗歌浇 - 鎵爜鐩戝惉宸茬Щ闄�");
+const userOptions = ref<Array<{ label: string; value: string }>>([]);
+const machineOptions = ref<Array<{ label: string; value: string }>>([]);
+
+function pad(value: number) {
+  return String(value).padStart(2, "0");
+}
+
+function formatDateTime(value: number | Date) {
+  const date = value instanceof Date ? value : new Date(value);
+  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
+    date.getHours()
+  )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
+}
+
+function formatDate(value: number, endOfDay = false) {
+  const date = new Date(value);
+  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${
+    endOfDay ? "23:59:59" : "00:00:00"
+  }`;
+}
+
+function formatDateOnly(value: number) {
+  return formatDate(value).slice(0, 10);
+}
+
+function parseDateTime(value: unknown) {
+  if (!value) return Date.now();
+  const timestamp = new Date(String(value).replace(/-/g, "/")).getTime();
+  return Number.isFinite(timestamp) ? timestamp : Date.now();
+}
+
+function normalizeStatus(task: any) {
+  const key = String(task?.taskStatus ?? task?.timelinessStatus ?? task?.status ?? "")
+    .trim()
+    .toUpperCase();
+  if (["0", "WAITING", "PENDING", "寰呭紑濮�", "寰呮椂鏁�"].includes(key)) return 0;
+  if (["1", "RUNNING", "PROCESSING", "鏃舵晥涓�"].includes(key)) return 1;
+  if (["2", "FINISHED", "COMPLETED", "鏃舵晥缁撴潫"].includes(key)) return 2;
+  return Number(key);
+}
+
+function normalizeMaterialType(value: unknown): TimelinessMaterialType {
+  const text = String(value ?? "");
+  return text.includes("缁�") || text === "鎴愬搧" ? "缁炵嚎" : "鍗曚笣";
+}
+
+function isWaiting(task: any) {
+  return normalizeStatus(task) === 0;
+}
+
+function isRunning(task: any) {
+  return normalizeStatus(task) === 1;
+}
+
+function getStatusLabel(task: any) {
+  return statusOptions.find((item) => item.value === normalizeStatus(task))?.label || "鏈煡";
+}
+
+function getStatusType(task: any): TagType {
+  const status = normalizeStatus(task);
+  if (status === 0) return "warning";
+  if (status === 1) return "primary";
+  if (status === 2) return "success";
+  return "default";
+}
+
+function getInitiator(task: any) {
+  return task.initiatorName || task.userName || task.createUserName || "-";
+}
+
+function getInspector(task: any) {
+  return task.inspectorName || task.routingInspectionUser || "-";
+}
+
+function getTemperature(task: any) {
+  const value = task.setTemperature ?? task.temperature;
+  return value === undefined || value === "" ? "-" : `${value}鈩僠;
+}
+
+function getDuration(task: any) {
+  const value = task.agingDuration ?? task.duration;
+  return value === undefined || value === "" ? "-" : `${value} 灏忔椂`;
+}
+
+async function queryTaskList(pageNo: number, pageSize: number) {
+  try {
+    const { data } = await TimeSensitiveTaskApi.queryPage({
+      current: pageNo,
+      size: pageSize,
+      startTime: filters.startTimestamp ? formatDate(Number(filters.startTimestamp)) : undefined,
+      endTime: filters.endTimestamp ? formatDate(Number(filters.endTimestamp), true) : undefined,
+    });
+    pagingRef.value?.complete(data?.records || []);
+  } catch (error: any) {
+    pagingRef.value?.complete(false);
+    toast.error(error?.message || "鑾峰彇鏃舵晥浠诲姟澶辫触");
+  } finally {
+    pageReady.value = true;
+  }
+}
+
+function applyFilters() {
+  if (
+    filters.startTimestamp &&
+    filters.endTimestamp &&
+    Number(filters.startTimestamp) > Number(filters.endTimestamp)
+  ) {
+    toast.error("寮�濮嬫棩鏈熶笉鑳芥櫄浜庣粨鏉熸棩鏈�");
+    return;
+  }
+  pagingRef.value?.reload();
+}
+
+function resetFilters() {
+  Object.assign(filters, {
+    startTimestamp: "",
+    endTimestamp: "",
+  });
+  applyFilters();
+}
+
+function getCurrentPerson() {
+  const user = userStore.userInfo as any;
+  return user?.nickName || user?.nickname || user?.userName || user?.username || "";
+}
+
+function getPersonName(item: any) {
+  return String(item?.nickName || item?.nickname || item?.userName || item?.username || "");
+}
+
+function getMachineName(item: any) {
+  return String(item?.deviceModel || item?.device_model || item?.deviceName || item?.name || "");
+}
+
+async function loadOptions() {
+  if (userOptions.value.length && machineOptions.value.length) return;
+  optionsLoading.value = true;
+  try {
+    const [usersResult, machinesResult] = await Promise.all([
+      TimeSensitiveTaskApi.getUsers(),
+      TimeSensitiveTaskApi.getMachines(),
+    ]);
+    userOptions.value = (usersResult.data || [])
+      .map((item: any) => getPersonName(item))
+      .filter(Boolean)
+      .map((name: string) => ({ label: name, value: name }));
+    machineOptions.value = (machinesResult.data || [])
+      .map((item: any) => getMachineName(item))
+      .filter(Boolean)
+      .map((name: string) => ({ label: name, value: name }));
+  } catch (error: any) {
+    toast.error(error?.message || "鑾峰彇浜哄憳鎴栨椂鏁堟満鍙板け璐�");
+  } finally {
+    optionsLoading.value = false;
+  }
+}
+
+function resetTaskForm() {
+  const currentPerson = getCurrentPerson();
+  Object.assign(taskForm, {
+    materialType: "鍗曚笣",
+    initiatorName: currentPerson,
+    initiateTimestamp: Date.now(),
+    setTemperature: "",
+    durationRise: 2,
+    durationHold: "",
+    durationSoak: "",
+    machineName: "",
+    inspectorName: currentPerson,
+  });
+}
+
+function parseDuration(value: unknown) {
+  const parts = String(value ?? "")
+    .replace(/灏忔椂/g, "")
+    .split("+")
+    .map((item) => Number(item.trim()));
+  if (parts.length >= 3 && parts.every(Number.isFinite)) return parts;
+  return [2, Number.isFinite(parts[0]) ? parts[0] : "", ""];
+}
+
+async function openCreatePopup() {
+  editingTaskId.value = undefined;
+  resetTaskForm();
+  taskPopupVisible.value = true;
+  await loadOptions();
+}
+
+async function openEditPopup(task: any) {
+  if (!isWaiting(task)) return;
+  try {
+    const { data } = await TimeSensitiveTaskApi.getDetail(task.id);
+    const detail = { ...task, ...(data || {}) };
+    const duration = parseDuration(detail.agingDuration ?? detail.duration);
+    editingTaskId.value = detail.id;
+    Object.assign(taskForm, {
+      materialType: normalizeMaterialType(detail.materialType),
+      initiatorName: getInitiator(detail) === "-" ? "" : getInitiator(detail),
+      initiateTimestamp: parseDateTime(
+        detail.initiateTime || detail.startTime || detail.createTime
+      ),
+      setTemperature: detail.setTemperature ?? detail.temperature ?? "",
+      durationRise: duration[0],
+      durationHold: duration[1],
+      durationSoak: duration[2],
+      machineName: detail.machineName || detail.machine || "",
+      inspectorName: getInspector(detail) === "-" ? "" : getInspector(detail),
+    });
+    taskPopupVisible.value = true;
+    await loadOptions();
+  } catch (error: any) {
+    toast.error(error?.message || "鑾峰彇鏃舵晥浠诲姟璇︽儏澶辫触");
+  }
+}
+
+function buildTaskPayload(): TimeSensitiveTaskForm | null {
+  if (!taskForm.materialType) return (toast.error("璇烽�夋嫨浠诲姟绫诲瀷"), null);
+  if (!taskForm.initiatorName) return (toast.error("璇烽�夋嫨鍙戣捣浠诲姟浜�"), null);
+  if (!taskForm.initiateTimestamp) return (toast.error("璇烽�夋嫨鍙戣捣鏃堕棿"), null);
+  if (taskForm.setTemperature === "") return (toast.error("璇疯緭鍏ヨ瀹氭俯搴�"), null);
+  if (taskForm.durationHold === "") return (toast.error("璇疯緭鍏ヤ繚娓╂椂闀�"), null);
+  if (!taskForm.machineName) return (toast.error("璇烽�夋嫨璁惧畾鏈哄彴"), null);
+  if (!taskForm.inspectorName) return (toast.error("璇烽�夋嫨宸℃鍛�"), null);
+
+  return {
+    materialType: taskForm.materialType,
+    initiatorName: taskForm.initiatorName,
+    initiateTime: formatDateTime(Number(taskForm.initiateTimestamp)),
+    setTemperature: taskForm.setTemperature,
+    agingDuration: `${taskForm.durationRise || 0}+${taskForm.durationHold || 0}+${
+      taskForm.durationSoak || 0
+    }`,
+    machineName: taskForm.machineName,
+    inspectorName: taskForm.inspectorName,
+  };
+}
+
+async function submitTask() {
+  const payload = buildTaskPayload();
+  if (!payload || taskSubmitting.value) return;
+  taskSubmitting.value = true;
+  try {
+    const result = editingTaskId.value
+      ? await TimeSensitiveTaskApi.update({ id: editingTaskId.value, ...payload })
+      : await TimeSensitiveTaskApi.create(payload);
+    taskPopupVisible.value = false;
+    toast.success(editingTaskId.value ? "鏃舵晥浠诲姟淇敼鎴愬姛" : "鏂板鎴愬姛锛岃娣诲姞鏃舵晥鏄庣粏");
+    pagingRef.value?.reload();
+    if (!editingTaskId.value) {
+      const resultData = result.data;
+      const newId =
+        resultData && typeof resultData === "object"
+          ? resultData.id || resultData.taskId
+          : resultData;
+      if (newId) {
+        setTimeout(() => {
+          uni.navigateTo({ url: `/pages/timely/detail?id=${newId}` });
+        }, 500);
+      }
+    }
+  } catch (error: any) {
+    toast.error(error?.message || `${editingTaskId.value ? "淇敼" : "鏂板"}鏃舵晥浠诲姟澶辫触`);
+  } finally {
+    taskSubmitting.value = false;
+  }
+}
+
+function showConfirm(title: string, content: string, confirmText = "纭畾") {
+  return new Promise<boolean>((resolve) => {
+    uni.showModal({
+      title,
+      content,
+      confirmText,
+      success: (result) => resolve(Boolean(result.confirm)),
+      fail: () => resolve(false),
+    });
+  });
+}
+
+async function deleteTask(task: any) {
+  if (!isWaiting(task)) return toast.error("鍙湁寰呭紑濮嬬殑浠诲姟鍙互鍒犻櫎");
+  const confirmed = await showConfirm(
+    "鍒犻櫎浠诲姟",
+    `纭畾鍒犻櫎鏃舵晥浠诲姟鈥�${task.taskCode || task.id}鈥濆悧锛熶换鍔℃槑缁嗕篃浼氫竴骞跺垹闄ゃ�俙,
+    "鍒犻櫎"
+  );
+  if (!confirmed) return;
+  try {
+    await TimeSensitiveTaskApi.delete(task.id);
+    toast.success("鏃舵晥浠诲姟鍒犻櫎鎴愬姛");
+    pagingRef.value?.reload();
+  } catch (error: any) {
+    toast.error(error?.message || "鍒犻櫎鏃舵晥浠诲姟澶辫触");
+  }
+}
+
+async function startTask(task: any) {
+  if (!isWaiting(task)) return;
+  try {
+    const [detailResult, itemsResult] = await Promise.all([
+      TimeSensitiveTaskApi.getDetail(task.id),
+      TimeSensitiveTaskApi.getItems(task.id),
+    ]);
+    const detail = { ...task, ...(detailResult.data || {}) };
+    const items = Array.isArray(itemsResult.data)
+      ? itemsResult.data
+      : (itemsResult.data as any)?.records || [];
+    const payload = {
+      id: task.id,
+      materialType: normalizeMaterialType(detail.materialType),
+      initiatorName: getInitiator(detail) === "-" ? "" : getInitiator(detail),
+      initiateTime: detail.initiateTime || detail.startTime || detail.createTime || "",
+      setTemperature: detail.setTemperature ?? detail.temperature ?? "",
+      agingDuration: detail.agingDuration ?? detail.duration ?? "",
+      machineName: detail.machineName || detail.machine || "",
+      furnaceInTime: detail.furnaceInTime || formatDateTime(new Date()),
+      inspectorName: getInspector(detail) === "-" ? "" : getInspector(detail),
+    };
+    if (
+      Object.values(payload).some(
+        (value) => value === undefined || value === null || String(value) === ""
+      )
+    ) {
+      return toast.error("浠诲姟鍩虹淇℃伅涓嶅畬鏁达紝璇峰厛缂栬緫瀹屽杽");
+    }
+    if (!items.length) return toast.error("褰撳墠浠诲姟娌℃湁鏃舵晥鏄庣粏锛屼笉鑳藉紑濮嬫椂鏁�");
+    const confirmed = await showConfirm(
+      "寮�濮嬫椂鏁�",
+      `浠诲姟锛�${task.taskCode || task.id}\n鏄庣粏锛�${items.length}浠禱n鏈哄彴锛�${payload.machineName}\n纭寮�濮嬫椂鏁堝悧锛焋,
+      "寮�濮�"
+    );
+    if (!confirmed) return;
+    await TimeSensitiveTaskApi.start(payload);
+    toast.success("鏃舵晥宸插紑濮�");
+    pagingRef.value?.reload();
+  } catch (error: any) {
+    toast.error(error?.message || "寮�濮嬫椂鏁堝け璐�");
+  }
+}
+
+function parseCanEnd(data: any) {
+  if (typeof data === "boolean") return data;
+  return Boolean(data?.canEnd ?? data?.reachedDuration);
+}
+
+async function endTask(task: any) {
+  if (!isRunning(task)) return;
+  try {
+    const { data } = await TimeSensitiveTaskApi.checkEnd(task.id);
+    const canEnd = parseCanEnd(data);
+    const durationText =
+      data?.elapsedMinutes != null && data?.requiredMinutes != null
+        ? `褰撳墠宸叉椂鏁� ${data.elapsedMinutes} 鍒嗛挓锛岃瀹氭椂闀� ${data.requiredMinutes} 鍒嗛挓銆俙
+        : "褰撳墠灏氭湭杈惧埌璁惧畾鏃舵晥鏃堕暱銆�";
+    const confirmed = await showConfirm(
+      canEnd ? "缁撴潫鏃舵晥" : "鏃堕暱鏈揪鍒�",
+      canEnd
+        ? "宸茶揪鍒拌瀹氭椂鏁堟椂闀匡紝纭畾缁撴潫鏃舵晥鍚楋紵"
+        : `${data?.message || durationText}鏄惁浠嶈缁撴潫鏃舵晥锛焋,
+      canEnd ? "缁撴潫" : "浠嶇劧缁撴潫"
+    );
+    if (!confirmed) return;
+    await TimeSensitiveTaskApi.end({ id: task.id, forceEnd: !canEnd });
+    toast.success("鏃舵晥宸茬粨鏉�");
+    pagingRef.value?.reload();
+  } catch (error: any) {
+    toast.error(error?.message || "缁撴潫鏃舵晥澶辫触");
+  }
+}
+
+function openDetail(task: any) {
+  uni.navigateTo({ url: `/pages/timely/detail?id=${task.id}` });
+}
+
+function openInspection(task: any) {
+  const inspectionId = task.timelinessInspectionId || task.inspectionId || task.id;
+  const readonly = normalizeStatus(task) === 2 ? 1 : 0;
+  uni.navigateTo({
+    url: `/pages/timely/inspection?id=${inspectionId}&readonly=${readonly}`,
+  });
+}
+
+onShow(() => {
+  if (pageReady.value) pagingRef.value?.reload();
 });
 </script>
 
 <style lang="scss" scoped>
-.list_box {
-  height: calc(100vh - 100px);
+.timely-page {
+  height: calc(100vh - 50px);
   background: #f3f9f8;
 }
-:deep(.wd-button__content) {
-  color: #0d867f;
+
+.page-top {
+  background: #ffffff;
+}
+
+.date-filter-card {
+  padding: 12px;
+  margin: 0 10px 10px;
+  background: linear-gradient(135deg, #f3fbf8 0%, #ffffff 72%);
+  border: 1px solid rgba(7, 133, 104, 0.12);
+  border-radius: 12px;
+  box-shadow: 0 4px 14px rgba(19, 94, 78, 0.06);
+}
+
+.date-filter-heading,
+.date-filter-title,
+.date-filter-fields,
+.date-filter-actions,
+.popup-footer,
+.task-actions {
+  display: flex;
+  align-items: center;
+}
+
+.date-filter-heading {
+  justify-content: space-between;
+  margin-bottom: 10px;
+}
+
+.date-filter-title {
+  gap: 8px;
+}
+
+.date-filter-icon {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  width: 32px;
+  height: 32px;
+  background: rgba(7, 133, 104, 0.1);
+  border-radius: 9px;
+}
+
+.date-filter-name {
+  font-size: 14px;
+  font-weight: 600;
+  color: #243832;
+}
+
+.date-filter-tip {
+  margin-top: 2px;
+  font-size: 11px;
+  color: #91a09c;
+}
+
+.date-filter-clear {
+  padding: 5px 0 5px 10px;
+  font-size: 12px;
+  color: #078568;
+}
+
+.date-filter-fields {
+  gap: 7px;
+}
+
+.date-filter-field {
+  flex: 1;
+  min-width: 0;
+  background: #ffffff;
+  border: 1px solid #e5efec;
+  border-radius: 8px;
+}
+
+.date-picker-trigger {
+  padding: 9px 10px;
+}
+
+.date-picker-label {
+  display: block;
+  margin-bottom: 5px;
+  font-size: 11px;
+  color: #8c9b97;
+}
+
+.date-picker-value {
+  display: flex;
+  gap: 4px;
+  align-items: center;
+  justify-content: space-between;
+  font-size: 13px;
+  font-weight: 500;
+  color: #2b3c37;
+}
+
+.is-placeholder {
+  font-weight: 400;
+  color: #b7c1be;
+}
+
+.date-filter-separator {
+  flex: none;
+  font-size: 12px;
+  color: #8c9b97;
+}
+
+.date-filter-actions {
+  gap: 8px;
+  justify-content: flex-end;
+  margin-top: 10px;
+}
+
+.popup-footer,
+.task-actions {
+  flex-wrap: wrap;
+  gap: 8px;
+  justify-content: flex-end;
+}
+
+.task-list {
+  padding: 10px 10px 24px;
+}
+
+:deep(.task-card) {
+  margin: 0 0 10px;
+}
+
+.card-title,
+.title-main {
+  display: flex;
+  gap: 8px;
+  align-items: center;
+  justify-content: space-between;
+}
+
+.title-main {
+  min-width: 0;
+  justify-content: flex-start;
+}
+
+.task-code {
+  overflow: hidden;
+  font-weight: 600;
+  color: #222222;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.info-grid {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 10px 16px;
+}
+
+.info-item,
+.time-row {
+  display: flex;
+  flex-direction: column;
+  gap: 3px;
+  min-width: 0;
+  font-size: 13px;
+  color: #333333;
+}
+
+.time-row {
+  flex-direction: row;
+  justify-content: space-between;
+  padding-top: 10px;
+  margin-top: 10px;
+  border-top: 1px solid #f2f2f2;
+}
+
+.info-label {
+  font-size: 12px;
+  color: #999999;
+}
+
+.popup-layout {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background: #f7f8fa;
+}
+
+.popup-header {
+  display: flex;
+  flex-shrink: 0;
+  align-items: center;
+  justify-content: space-between;
+  padding: 16px;
+  background: #ffffff;
+  border-bottom: 1px solid #eeeeee;
+}
+
+.popup-title {
+  font-size: 17px;
+  font-weight: 600;
+}
+
+.popup-body {
+  flex: 1;
+  height: 0;
+}
+
+.popup-footer {
+  flex-shrink: 0;
+  padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
+  background: #ffffff;
+  border-top: 1px solid #eeeeee;
+}
+
+.duration-row {
+  display: grid;
+  grid-template-columns: 1fr auto 1fr auto 1fr;
+  gap: 4px;
+  align-items: center;
+  padding: 0 12px 10px;
+  background: #ffffff;
+}
+
+.duration-row :deep(.wd-input) {
+  padding: 0;
 }
 </style>

--
Gitblit v1.9.3