spring
2026-09-04 166ff56efefe6682a9d840fe0f1fa6e456833556
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>