From 166ff56efefe6682a9d840fe0f1fa6e456833556 Mon Sep 17 00:00:00 2001
From: spring <2396852758@qq.com>
Date: 星期五, 04 九月 2026 18:34:23 +0800
Subject: [PATCH] fix: 时效模块原型
---
.env.development | 8
src/pages.json | 14
src/api/product/twist.ts | 14
src/pages/timely/detail.vue | 727 +++++++++++++
src/pages/production/twist/components/SteelCoreCard.vue | 1
.env.production | 4
src/api/timeSensitiveTask.ts | 709 ++++++++++++
src/pages/timely/inspection.vue | 248 ++++
src/pages/index/index.vue | 6
src/pages/timely/index.vue | 1008 ++++++++++++++++-
src/pages/production/twist/receive/steelCore/index.vue | 268 +---
src/pages/production/twist/receive/steelCore/form.vue | 240 ++++
12 files changed, 2,978 insertions(+), 269 deletions(-)
diff --git a/.env.development b/.env.development
index 83c7fe3..4ca4dc4 100644
--- a/.env.development
+++ b/.env.development
@@ -7,13 +7,13 @@
VITE_APP_PORT = 4096
# API 鍩虹璺緞锛屽紑鍙戠幆澧冧笅鐨勮姹傚墠缂�
-# VITE_APP_BASE_API = 'http://114.132.189.42:7002/mes'
+VITE_APP_BASE_API = 'http://114.132.189.42:7002/mes'
# VITE_APP_BASE_API = 'http://114.132.189.42:7002/mes'
# VITE_APP_BASE_API = 'http://192.168.10.119:7002/mes' # 閭硅
-VITE_APP_BASE_API = 'http://192.168.100.131:7002/mes' #
+# VITE_APP_BASE_API = 'http://192.168.100.131:7002/mes' #
# API 鏈嶅姟鍣ㄧ殑 URL
-# VITE_APP_API_URL = 'http://114.132.189.42:7002/mes'
+VITE_APP_API_URL = 'http://114.132.189.42:7002/mes'
# VITE_APP_API_URL = 'http://192.168.10.119:7002/mes'
-VITE_APP_API_URL = 'http://192.168.100.131:7002/mes' #
+# VITE_APP_API_URL = 'http://192.168.100.131:7002/mes' #
diff --git a/.env.production b/.env.production
index 11cf31d..78c2d8b 100644
--- a/.env.production
+++ b/.env.production
@@ -4,7 +4,7 @@
VITE_APP_BASE_API = '/prod-api'
# VITE_APP_BASE_API = 'http://114.132.189.42:7002/mes'
# API 鏈嶅姟鍣ㄧ殑 URL
-# VITE_APP_API_URL = 'http://114.132.189.42:7002/mes'
+VITE_APP_API_URL = 'http://114.132.189.42:7002/mes'
# 灞变笢
-VITE_APP_API_URL = 'http://192.168.100.131:7002/mes'
+# VITE_APP_API_URL = 'http://192.168.100.131:7002/mes'
diff --git a/src/api/product/twist.ts b/src/api/product/twist.ts
index c64fbc4..5677da7 100644
--- a/src/api/product/twist.ts
+++ b/src/api/product/twist.ts
@@ -81,6 +81,20 @@
});
},
+ // 鏌ヨ鍙鐢ㄧ殑鍘熸潗鏂欐壒娆★紙涓� PC 绔挗鑺鐢ㄤ娇鐢ㄥ悓涓�鎺ュ彛锛�
+ getMaterialBatchStockPage(params: {
+ current?: number;
+ size?: number;
+ batchCode?: string;
+ materialType?: string;
+ }) {
+ return request<BaseResult<any>>({
+ url: "/material/batch/list",
+ method: "POST",
+ data: params,
+ });
+ },
+
// 鏍规嵁鍗曚笣缂栧彿鏌ヨ
selectByMonofilamentNumber(params: { monofilamentNumber: string }) {
return request<BaseResult<any>>({
diff --git a/src/api/timeSensitiveTask.ts b/src/api/timeSensitiveTask.ts
new file mode 100644
index 0000000..b24d32e
--- /dev/null
+++ b/src/api/timeSensitiveTask.ts
@@ -0,0 +1,709 @@
+import request from "@/utils/request";
+import type { BaseResult, PageResult } from "@/models/base";
+
+export type TimelinessMaterialType = "鍗曚笣" | "缁炵嚎";
+
+export interface TimeSensitiveTaskQuery {
+ current: number;
+ size?: number;
+ initiatorName?: string;
+ machineName?: string;
+ materialType?: TimelinessMaterialType | string;
+ taskStatus?: number | string;
+ startTime?: string;
+ endTime?: string;
+}
+
+export interface TimeSensitiveTaskForm {
+ materialType: TimelinessMaterialType;
+ initiatorName: string;
+ initiateTime: string;
+ setTemperature: number | string;
+ agingDuration: number | string;
+ machineName: string;
+ inspectorName: string;
+}
+
+export interface TimeSensitiveTaskStartForm extends TimeSensitiveTaskForm {
+ id: number | string;
+ furnaceInTime: string;
+}
+
+export interface TimeSensitiveCandidateQuery {
+ current: number;
+ size?: number;
+ taskId: number | string;
+ materialType: TimelinessMaterialType;
+ model?: string;
+ materialCode?: string;
+}
+
+export interface TimelinessInspectionDetailForm {
+ id?: number | string;
+ routingInspectionUser?: string;
+ zoneOne?: number | string;
+ zoneTwo?: number | string;
+ zoneThree?: number | string;
+ zoneFour?: number | string;
+ zoneFive?: number | string;
+ zoneSix?: number | string;
+ timelinessInspectionId?: number | string;
+}
+
+/** 鑱旇皟瀹屾垚鍚庢敼涓� false 鍗冲彲鎭㈠涓嬫柟鐪熷疄鎺ュ彛銆� */
+export const USE_TIME_SENSITIVE_MOCK = true;
+
+const mockTasks: any[] = [
+ {
+ id: 9001,
+ taskCode: "SX-20260904-001",
+ materialType: "鍗曚笣",
+ initiatorName: "寮犲伐",
+ initiateTime: "2026-09-04 09:10:00",
+ setTemperature: 180,
+ agingDuration: "2+4+1",
+ machineName: "鏃舵晥鐐�-01",
+ itemCount: 2,
+ furnaceInTime: "",
+ furnaceOutTime: "",
+ inspectorName: "鏉庡笀鍌�",
+ taskStatus: 0,
+ },
+ {
+ id: 9002,
+ taskCode: "JX-20260904-001",
+ materialType: "缁炵嚎",
+ initiatorName: "鐜嬪伐",
+ initiateTime: "2026-09-04 08:30:00",
+ setTemperature: 175,
+ agingDuration: "2+3+1",
+ machineName: "鏃舵晥鐐�-02",
+ itemCount: 2,
+ furnaceInTime: "2026-09-04 10:00:00",
+ furnaceOutTime: "",
+ inspectorName: "璧靛笀鍌�",
+ taskStatus: 1,
+ },
+ {
+ id: 9003,
+ taskCode: "SX-20260903-001",
+ materialType: "鍗曚笣",
+ initiatorName: "鍒樺伐",
+ initiateTime: "2026-09-03 08:00:00",
+ setTemperature: 180,
+ agingDuration: "2+4+1",
+ machineName: "鏃舵晥鐐�-01",
+ itemCount: 1,
+ furnaceInTime: "2026-09-03 08:30:00",
+ furnaceOutTime: "2026-09-03 15:30:00",
+ inspectorName: "瀛欏笀鍌�",
+ taskStatus: 2,
+ },
+];
+
+const mockMaterialPool: Record<TimelinessMaterialType, any[]> = {
+ 鍗曚笣: [
+ {
+ id: 9101,
+ materialType: "鍗曚笣",
+ model: "桅5.6",
+ poleModel: "1A60 H12 桅9.5 A2",
+ rodNo: "720526083102-014",
+ batchCode: "750826090101-005",
+ monofilamentNumber: "750826090101-005",
+ weight: 2875,
+ supplier: "灞变笢鍏冩椇鐢靛伐绉戞妧鏈夐檺鍏徃",
+ },
+ {
+ id: 9102,
+ materialType: "鍗曚笣",
+ model: "L-3.45",
+ poleModel: "1A60-H14 桅9.5 A6",
+ rodNo: "710726081602-003",
+ batchCode: "750426083002-037",
+ monofilamentNumber: "750426083002-037",
+ weight: 2650,
+ supplier: "灞变笢鍏冩椇鐢靛伐绉戞妧鏈夐檺鍏徃",
+ },
+ {
+ id: 9301,
+ outputId: 9301,
+ materialType: "鍗曚笣",
+ model: "8030-95L",
+ poleModel: "8030",
+ rodNo: "720626082404-001",
+ batchCode: "750626083101-001",
+ monofilamentNumber: "750626083101-001",
+ weight: 2400,
+ supplier: "灞变笢鍏冩椇鐢靛伐绉戞妧鏈夐檺鍏徃",
+ },
+ {
+ id: 9302,
+ outputId: 9302,
+ materialType: "鍗曚笣",
+ model: "桅3.20",
+ poleModel: "1A60 H14 桅9.5 A6",
+ rodNo: "710726090401-002",
+ batchCode: "750726090401-002",
+ monofilamentNumber: "750726090401-002",
+ weight: 2520,
+ supplier: "灞变笢鍏冩椇鐢靛伐绉戞妧鏈夐檺鍏徃",
+ },
+ {
+ id: 9501,
+ materialType: "鍗曚笣",
+ model: "桅4.50",
+ poleModel: "1A60 H12 桅9.5 A2",
+ rodNo: "720526083102-018",
+ batchCode: "750826090201-001",
+ monofilamentNumber: "750826090201-001",
+ weight: 2780,
+ supplier: "灞变笢鍏冩椇鐢靛伐绉戞妧鏈夐檺鍏徃",
+ },
+ ],
+ 缁炵嚎: [
+ {
+ id: 9201,
+ outputId: 9201,
+ materialType: "缁炵嚎",
+ model: "JLHA1/G1A-16/3-6/1",
+ batchCode: "JX260904001-01",
+ weight: 1180,
+ },
+ {
+ id: 9202,
+ outputId: 9202,
+ materialType: "缁炵嚎",
+ model: "JLHA1/G1A-25/4-6/1",
+ batchCode: "JX260904001-02",
+ weight: 1260,
+ },
+ {
+ id: 9401,
+ outputId: 9401,
+ materialType: "缁炵嚎",
+ model: "JLHA1/G1A-35/6-6/1",
+ batchCode: "JX260904002-01",
+ weight: 1380,
+ },
+ {
+ id: 9402,
+ outputId: 9402,
+ materialType: "缁炵嚎",
+ model: "JLHA1/G1A-50/8-6/1",
+ batchCode: "JX260904002-02",
+ weight: 1460,
+ },
+ ],
+};
+
+const findMockMaterial = (id: number | string) =>
+ [...mockMaterialPool.鍗曚笣, ...mockMaterialPool.缁炵嚎].find(
+ (item) => String(item.id) === String(id)
+ );
+
+const cloneMockMaterial = (id: number | string) => {
+ const item = findMockMaterial(id);
+ return item ? { ...item } : undefined;
+};
+
+const mockItems: Record<string, any[]> = {
+ "9001": [cloneMockMaterial(9101), cloneMockMaterial(9102)].filter(Boolean),
+ "9002": [cloneMockMaterial(9201), cloneMockMaterial(9202)].filter(Boolean),
+ "9003": [cloneMockMaterial(9501)].filter(Boolean),
+};
+
+const mockInspections: Record<string, any[]> = {
+ "9002": [
+ {
+ id: 9601,
+ routingInspectionUser: "璧靛笀鍌�",
+ zoneOne: 175,
+ zoneTwo: 175.2,
+ zoneThree: 174.8,
+ zoneFour: 175.1,
+ zoneFive: 175,
+ zoneSix: 174.9,
+ createTime: "2026-09-04 11:00:00",
+ timelinessInspectionId: 9002,
+ },
+ ],
+ "9003": [
+ {
+ id: 9602,
+ routingInspectionUser: "瀛欏笀鍌�",
+ zoneOne: 180,
+ zoneTwo: 180,
+ zoneThree: 179.8,
+ zoneFour: 180.1,
+ zoneFive: 180,
+ zoneSix: 179.9,
+ createTime: "2026-09-03 10:30:00",
+ timelinessInspectionId: 9003,
+ },
+ ],
+};
+
+const mockOperations: Record<string, any[]> = {
+ "9001": [
+ {
+ id: 9701,
+ operationType: 1,
+ operationTypeDesc: "鏂板",
+ beforeStatus: "",
+ afterStatus: "寰呭紑濮�",
+ operationDetail: "寮犲伐鍒涘缓鏃舵晥浠诲姟",
+ operatorName: "寮犲伐",
+ operateTime: "2026-09-04 09:10:00",
+ },
+ ],
+ "9002": [
+ {
+ id: 9702,
+ operationType: 2,
+ operationTypeDesc: "寮�濮嬫椂鏁�",
+ beforeStatus: "寰呭紑濮�",
+ afterStatus: "鏃舵晥涓�",
+ operationDetail: "璧靛笀鍌呯‘璁や换鍔′俊鎭苟寮�濮嬫椂鏁�",
+ operatorName: "璧靛笀鍌�",
+ operateTime: "2026-09-04 10:00:00",
+ },
+ ],
+ "9003": [
+ {
+ id: 9703,
+ operationType: 3,
+ operationTypeDesc: "缁撴潫鏃舵晥",
+ beforeStatus: "鏃舵晥涓�",
+ afterStatus: "鏃舵晥缁撴潫",
+ operationDetail: "瀛欏笀鍌呯粨鏉熸椂鏁堜换鍔�",
+ operatorName: "瀛欏笀鍌�",
+ operateTime: "2026-09-03 15:30:00",
+ },
+ ],
+};
+
+const mockUsers = [
+ { userId: 1, nickName: "寮犲伐" },
+ { userId: 2, nickName: "鐜嬪伐" },
+ { userId: 3, nickName: "鍒樺伐" },
+ { userId: 4, nickName: "鏉庡笀鍌�" },
+ { userId: 5, nickName: "璧靛笀鍌�" },
+ { userId: 6, nickName: "瀛欏笀鍌�" },
+];
+
+const mockMachines = [
+ { deviceId: 1, deviceModel: "鏃舵晥鐐�-01" },
+ { deviceId: 2, deviceModel: "鏃舵晥鐐�-02" },
+ { deviceId: 3, deviceModel: "鏃舵晥鐐�-03" },
+];
+
+const pad = (value: number) => String(value).padStart(2, "0");
+const mockDateTime = () => {
+ const date = new Date();
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(
+ date.getHours()
+ )}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
+};
+
+const mockResult = <T>(data: T, msg = "success") =>
+ new Promise<BaseResult<T>>((resolve) => {
+ setTimeout(() => resolve({ code: 200, msg, data }), 120);
+ });
+
+const mockMaterialCode = (item: any) =>
+ String(item?.flowCode || item?.batchCode || item?.monofilamentNumber || "").trim();
+
+const updateMockItemCount = (taskId: number | string) => {
+ const task = mockTasks.find((item) => String(item.id) === String(taskId));
+ if (task) task.itemCount = (mockItems[String(taskId)] || []).length;
+};
+
+const addMockOperation = (
+ taskId: number | string,
+ operationType: number,
+ operationTypeDesc: string,
+ beforeStatus: string,
+ afterStatus: string,
+ operationDetail: string,
+ operatorName: string
+) => {
+ const key = String(taskId);
+ mockOperations[key] ||= [];
+ mockOperations[key].unshift({
+ id: Date.now(),
+ operationType,
+ operationTypeDesc,
+ beforeStatus,
+ afterStatus,
+ operationDetail,
+ operatorName,
+ operateTime: mockDateTime(),
+ });
+};
+
+const TimeSensitiveTaskApi = {
+ queryPage(params: TimeSensitiveTaskQuery) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ let rows = [...mockTasks];
+ if (params.initiatorName) {
+ rows = rows.filter((item) => item.initiatorName.includes(params.initiatorName!));
+ }
+ if (params.machineName) {
+ rows = rows.filter((item) => item.machineName.includes(params.machineName!));
+ }
+ if (params.materialType) {
+ rows = rows.filter((item) => item.materialType === params.materialType);
+ }
+ if (params.taskStatus !== undefined && params.taskStatus !== "") {
+ rows = rows.filter((item) => Number(item.taskStatus) === Number(params.taskStatus));
+ }
+ if (params.startTime) {
+ rows = rows.filter((item) => item.initiateTime >= params.startTime!);
+ }
+ if (params.endTime) {
+ rows = rows.filter((item) => item.initiateTime <= params.endTime!);
+ }
+ const current = Number(params.current || 1);
+ const size = Number(params.size || 10);
+ return mockResult<PageResult>({
+ records: rows.slice((current - 1) * size, current * size),
+ total: rows.length,
+ current,
+ size,
+ pages: current * size < rows.length,
+ });
+ }
+ return request<BaseResult<PageResult>>({
+ url: "/timeSensitiveTask/queryPage",
+ method: "GET",
+ data: params,
+ });
+ },
+
+ getDetail(id: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ return mockResult(mockTasks.find((item) => String(item.id) === String(id)) || null);
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/getDetailById",
+ method: "GET",
+ data: { id },
+ });
+ },
+
+ getItems(taskId: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ return mockResult<any[]>((mockItems[String(taskId)] || []).map((item) => ({ ...item })));
+ }
+ return request<BaseResult<any[]>>({
+ url: "/timeSensitiveTask/getItems",
+ method: "GET",
+ data: { taskId },
+ });
+ },
+
+ create(data: TimeSensitiveTaskForm) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const id = Date.now();
+ const taskCode = `${data.materialType === "鍗曚笣" ? "SX" : "JX"}-${String(id).slice(-10)}`;
+ mockTasks.unshift({
+ id,
+ taskCode,
+ ...data,
+ itemCount: 0,
+ furnaceInTime: "",
+ furnaceOutTime: "",
+ taskStatus: 0,
+ });
+ mockItems[String(id)] = [];
+ addMockOperation(
+ id,
+ 1,
+ "鏂板",
+ "",
+ "寰呭紑濮�",
+ `${data.initiatorName}鍒涘缓鏃舵晥浠诲姟`,
+ data.initiatorName
+ );
+ return mockResult({ id, taskCode }, "鏃舵晥浠诲姟鏂板鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/add",
+ method: "POST",
+ data,
+ });
+ },
+
+ update(data: TimeSensitiveTaskForm & { id: number | string }) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const task = mockTasks.find((item) => String(item.id) === String(data.id));
+ if (task) Object.assign(task, data);
+ addMockOperation(
+ data.id,
+ 2,
+ "淇敼",
+ "寰呭紑濮�",
+ "寰呭紑濮�",
+ "淇敼鏃舵晥浠诲姟鍩虹淇℃伅",
+ data.initiatorName
+ );
+ return mockResult(task, "鏃舵晥浠诲姟淇敼鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/update",
+ method: "POST",
+ data,
+ });
+ },
+
+ delete(id: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const index = mockTasks.findIndex((item) => String(item.id) === String(id));
+ if (index >= 0) mockTasks.splice(index, 1);
+ delete mockItems[String(id)];
+ delete mockOperations[String(id)];
+ delete mockInspections[String(id)];
+ return mockResult(true, "鏃舵晥浠诲姟鍒犻櫎鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/delete",
+ method: "DELETE",
+ data: { id },
+ });
+ },
+
+ start(data: TimeSensitiveTaskStartForm) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const task = mockTasks.find((item) => String(item.id) === String(data.id));
+ if (task) Object.assign(task, data, { taskStatus: 1 });
+ addMockOperation(
+ data.id,
+ 2,
+ "寮�濮嬫椂鏁�",
+ "寰呭紑濮�",
+ "鏃舵晥涓�",
+ `${data.inspectorName}纭浠诲姟淇℃伅骞跺紑濮嬫椂鏁坄,
+ data.inspectorName
+ );
+ return mockResult(task, "鏃舵晥宸插紑濮�");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/start",
+ method: "POST",
+ data,
+ });
+ },
+
+ checkEnd(id: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ return mockResult({
+ canEnd: false,
+ reachedDuration: false,
+ elapsedMinutes: 120,
+ requiredMinutes: 360,
+ message: "Mock锛氬皻鏈揪鍒拌瀹氭椂鏁堟椂闀裤��",
+ });
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/checkEnd",
+ method: "GET",
+ data: { id },
+ });
+ },
+
+ end(data: { id: number | string; forceEnd: boolean }) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const task = mockTasks.find((item) => String(item.id) === String(data.id));
+ if (task) Object.assign(task, { taskStatus: 2, furnaceOutTime: mockDateTime() });
+ addMockOperation(
+ data.id,
+ 3,
+ "缁撴潫鏃舵晥",
+ "鏃舵晥涓�",
+ "鏃舵晥缁撴潫",
+ data.forceEnd ? "鏈揪鍒版椂闀匡紝纭寮哄埗缁撴潫鏃舵晥" : "缁撴潫鏃舵晥浠诲姟",
+ task?.inspectorName || "鎿嶄綔浜�"
+ );
+ return mockResult(task, "鏃舵晥宸茬粨鏉�");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/end",
+ method: "POST",
+ data,
+ });
+ },
+
+ queryCandidates(params: TimeSensitiveCandidateQuery) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const existingIds = new Set(
+ (mockItems[String(params.taskId)] || []).map((item) => String(item.id))
+ );
+ let rows = mockMaterialPool[params.materialType]
+ .filter((item) => !existingIds.has(String(item.id)))
+ .map((item) => ({ ...item }));
+ if (params.model) {
+ const model = params.model.trim().toUpperCase();
+ rows = rows.filter((item) =>
+ String(item.model || "")
+ .toUpperCase()
+ .includes(model)
+ );
+ }
+ if (params.materialCode) {
+ const code = params.materialCode.trim().toUpperCase();
+ rows = rows.filter(
+ (item) =>
+ mockMaterialCode(item).toUpperCase().includes(code) ||
+ String(item.id).toUpperCase() === code ||
+ String(item.outputId || "").toUpperCase() === code
+ );
+ }
+ const current = Number(params.current || 1);
+ const size = Number(params.size || 10);
+ return mockResult<PageResult>({
+ records: rows.slice((current - 1) * size, current * size),
+ total: rows.length,
+ current,
+ size,
+ pages: current * size < rows.length,
+ });
+ }
+ return request<BaseResult<PageResult>>({
+ url: "/timeSensitiveTask/queryCandidates",
+ method: "GET",
+ data: params,
+ });
+ },
+
+ addItems(data: { taskId: number | string; itemIds: Array<number | string> }) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const key = String(data.taskId);
+ mockItems[key] ||= [];
+ const existingIds = new Set(mockItems[key].map((item) => String(item.id)));
+ data.itemIds.forEach((id) => {
+ const material = cloneMockMaterial(id);
+ if (material && !existingIds.has(String(id))) mockItems[key].push(material);
+ });
+ updateMockItemCount(data.taskId);
+ const task = mockTasks.find((item) => String(item.id) === key);
+ addMockOperation(
+ data.taskId,
+ 1,
+ "鏂板鏄庣粏",
+ "寰呭紑濮�",
+ "寰呭紑濮�",
+ `鏂板 ${data.itemIds.length} 鏉℃椂鏁堟槑缁哷,
+ task?.initiatorName || "鎿嶄綔浜�"
+ );
+ return mockResult(true, "鏃舵晥鏄庣粏鏂板鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/addItems",
+ method: "POST",
+ data,
+ });
+ },
+
+ deleteItems(data: { taskId: number | string; itemIds: Array<number | string> }) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const key = String(data.taskId);
+ const idSet = new Set(data.itemIds.map(String));
+ mockItems[key] = (mockItems[key] || []).filter((item) => !idSet.has(String(item.id)));
+ updateMockItemCount(data.taskId);
+ const task = mockTasks.find((item) => String(item.id) === key);
+ addMockOperation(
+ data.taskId,
+ 4,
+ "鍒犻櫎鏄庣粏",
+ "寰呭紑濮�",
+ "寰呭紑濮�",
+ `鍒犻櫎 ${data.itemIds.length} 鏉℃椂鏁堟槑缁哷,
+ task?.initiatorName || "鎿嶄綔浜�"
+ );
+ return mockResult(true, "鏃舵晥鏄庣粏鍒犻櫎鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timeSensitiveTask/deleteItems",
+ method: "POST",
+ data,
+ });
+ },
+
+ getMachines() {
+ if (USE_TIME_SENSITIVE_MOCK) return mockResult(mockMachines.map((item) => ({ ...item })));
+ return request<BaseResult<any[]>>({
+ url: "/device/querydeviceSx",
+ method: "GET",
+ });
+ },
+
+ getUsers() {
+ if (USE_TIME_SENSITIVE_MOCK) return mockResult(mockUsers.map((item) => ({ ...item })));
+ return request<BaseResult<any[]>>({
+ url: "/team/getUser",
+ method: "GET",
+ });
+ },
+
+ getOperationRecords(recordId: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ return mockResult<any[]>(
+ (mockOperations[String(recordId)] || []).map((item) => ({ ...item }))
+ );
+ }
+ return request<BaseResult<any[]>>({
+ url: "/operationRecord/list",
+ method: "POST",
+ data: { tableName: "time_sensitive_task", recordId },
+ });
+ },
+
+ getInspectionDetails(id: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ return mockResult<any[]>((mockInspections[String(id)] || []).map((item) => ({ ...item })));
+ }
+ return request<BaseResult<any>>({
+ url: "/timelinessInspection/getDetailById",
+ method: "GET",
+ data: { id },
+ });
+ },
+
+ saveInspectionDetails(data: TimelinessInspectionDetailForm[]) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ const inspectionId = data[0]?.timelinessInspectionId;
+ if (inspectionId !== undefined) {
+ mockInspections[String(inspectionId)] = data.map((item, index) => ({
+ ...item,
+ id: item.id || Date.now() + index,
+ createTime: (item as any).createTime || mockDateTime(),
+ }));
+ }
+ return mockResult(true, "宸℃璁板綍淇濆瓨鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timelinessInspectionDetail/add",
+ method: "POST",
+ data,
+ });
+ },
+
+ deleteInspectionDetail(id: number | string) {
+ if (USE_TIME_SENSITIVE_MOCK) {
+ Object.keys(mockInspections).forEach((key) => {
+ mockInspections[key] = mockInspections[key].filter(
+ (item) => String(item.id) !== String(id)
+ );
+ });
+ return mockResult(true, "宸℃璁板綍鍒犻櫎鎴愬姛");
+ }
+ return request<BaseResult<any>>({
+ url: "/timelinessInspectionDetail/delete",
+ method: "DELETE",
+ data: { id },
+ });
+ },
+};
+
+export default TimeSensitiveTaskApi;
diff --git a/src/pages.json b/src/pages.json
index 3f648e0..09dedb4 100644
--- a/src/pages.json
+++ b/src/pages.json
@@ -268,7 +268,19 @@
{
"path": "pages/timely/index",
"style": {
- "navigationBarTitleText": "鏃舵晥鎶ュ伐"
+ "navigationBarTitleText": "鏃舵晥浠诲姟"
+ }
+ },
+ {
+ "path": "pages/timely/detail",
+ "style": {
+ "navigationBarTitleText": "鏃舵晥浠诲姟鏄庣粏"
+ }
+ },
+ {
+ "path": "pages/timely/inspection",
+ "style": {
+ "navigationBarTitleText": "鏃舵晥宸℃璁板綍"
}
},
{
diff --git a/src/pages/index/index.vue b/src/pages/index/index.vue
index 6dd4b40..ae6915c 100644
--- a/src/pages/index/index.vue
+++ b/src/pages/index/index.vue
@@ -230,6 +230,12 @@
url: "/pages/outbound/index",
show: false,
},
+ {
+ icon: "/static/icons/log.png",
+ title: "鏃舵晥",
+ url: "/pages/timely/index",
+ show: true,
+ },
]);
// 鍔犺浇璁块棶缁熻鏁版嵁
diff --git a/src/pages/production/twist/components/SteelCoreCard.vue b/src/pages/production/twist/components/SteelCoreCard.vue
index 9e03def..baa1389 100644
--- a/src/pages/production/twist/components/SteelCoreCard.vue
+++ b/src/pages/production/twist/components/SteelCoreCard.vue
@@ -10,6 +10,7 @@
<wd-card>
<wd-cell-group :border="true">
<wd-cell title="鑺嚎绫诲瀷" :value="data.diskMaterial || '-'" />
+ <wd-cell title="鎵规鍙�" :value="data.batchNo || data.batchCode || '-'" />
<wd-cell title="瑙勬牸鍨嬪彿" :value="data.model || '-'" />
<wd-cell title="鐩樺彿" :value="data.monofilamentNumber || '-'" />
<wd-cell title="鏁伴噺" :value="formatAmount" />
diff --git a/src/pages/production/twist/receive/steelCore/form.vue b/src/pages/production/twist/receive/steelCore/form.vue
index 7d31a3c..b853066 100644
--- a/src/pages/production/twist/receive/steelCore/form.vue
+++ b/src/pages/production/twist/receive/steelCore/form.vue
@@ -11,13 +11,23 @@
clearable
@confirm="handleDiskMaterialChange"
/>
+ <wd-cell
+ title="鎵规鍙�"
+ title-width="100px"
+ :value="model.batchNo || '璇烽�夋嫨鎵规鍙�'"
+ :value-class="model.batchNo ? '' : 'batch-placeholder'"
+ is-link
+ required
+ clickable
+ @click="openBatchSelector"
+ />
<wd-input
v-model="model.model"
label="瑙勬牸鍨嬪彿"
label-width="100px"
prop="model"
- clearable
- placeholder="璇疯緭鍏ヨ鏍煎瀷鍙�"
+ readonly
+ placeholder="閫夋嫨鎵规鍚庤嚜鍔ㄥ甫鍑�"
/>
<wd-input
v-model="model.monofilamentNumber"
@@ -37,11 +47,11 @@
/>
<wd-input
v-model="model.weight"
- label="閲嶉噺"
+ label="棰嗙敤閲嶉噺"
label-width="100px"
prop="weight"
clearable
- placeholder="璇疯緭鍏ラ噸閲�"
+ placeholder="璇疯緭鍏ラ鐢ㄩ噸閲�(kg)"
/>
<wd-picker
v-model="model.supplier"
@@ -55,12 +65,57 @@
/>
</wd-cell-group>
</wd-form>
+
+ <wd-popup
+ v-model="showBatchSelector"
+ position="bottom"
+ :z-index="30"
+ :close-on-click-modal="false"
+ custom-style="height: 72vh; border-radius: 24rpx 24rpx 0 0;"
+ >
+ <view class="batch-selector">
+ <view class="batch-header">
+ <text class="batch-title">閫夋嫨鎵规鍙�</text>
+ <wd-icon name="close" size="20px" @click="showBatchSelector = false" />
+ </view>
+ <wd-search
+ v-model="batchKeyword"
+ placeholder="璇疯緭鍏ユ壒娆″彿鎼滅储"
+ hide-cancel
+ @search="searchBatchRemote"
+ @clear="searchBatchRemote('')"
+ />
+ <scroll-view class="batch-list" scroll-y>
+ <view v-if="batchSearching" class="batch-state">姝e湪鍔犺浇鎵规...</view>
+ <template v-else-if="filteredBatchOptions.length">
+ <view
+ v-for="batch in filteredBatchOptions"
+ :key="batch.batchCode"
+ class="batch-item"
+ :class="{ active: batch.batchCode === model.batchNo }"
+ @click="selectBatch(batch)"
+ >
+ <view class="batch-item-header">
+ <text class="batch-code">{{ batch.batchCode }}</text>
+ <text class="batch-weight">褰撳墠 {{ stockWeightKg(batch) ?? "-" }} kg</text>
+ </view>
+ <view class="batch-meta">
+ <text>{{ batch.materialName || "-" }}</text>
+ <text>{{ getBatchModel(batch) }}</text>
+ </view>
+ </view>
+ </template>
+ <view v-else class="batch-state">鏆傛棤鍙鐢ㄧ殑鍚堟牸鎵规</view>
+ </scroll-view>
+ </view>
+ </wd-popup>
</template>
<script lang="ts" setup>
import { onMounted, watch } from "vue";
import useFormData from "@/hooks/useFormData";
import ManageApi from "@/api/product/manage";
+import TwistApi from "@/api/product/twist";
import { useToast } from "wot-design-uni";
const props = defineProps({
@@ -82,6 +137,8 @@
const toast = useToast();
const { form: model } = useFormData({
diskMaterial: undefined, // 鑺嚎绫诲瀷
+ batchNo: undefined, // 鎵规鍙凤紙灞曠ず鍙婇挗鑺鐢ㄥ瓧娈碉級
+ batchCode: undefined, // 鍏煎搴撳瓨棰嗙敤瀛楁
model: undefined, // 瑙勬牸鍨嬪彿
monofilamentNumber: undefined, // 鐩樺彿
amount: undefined, // 鏁伴噺
@@ -89,6 +146,75 @@
supplier: undefined, // 鍘傚
type: "閽㈣姱",
});
+
+interface MaterialBatch {
+ batchCode: string;
+ materialName?: string;
+ resolvedSpecModel?: string;
+ mesSpec?: string;
+ ncSpec?: string;
+ currentWeight?: number;
+ mainUnit?: string;
+ qualityStatus?: string;
+ ncReturnFlag?: string;
+}
+
+const showBatchSelector = ref(false);
+const batchKeyword = ref("");
+const batchOptions = ref<MaterialBatch[]>([]);
+const batchSearching = ref(false);
+
+const filteredBatchOptions = computed(() =>
+ batchOptions.value.filter((batch) => {
+ const qualified = batch.qualityStatus === "鍚堟牸" || batch.qualityStatus === "OK";
+ return qualified && batch.ncReturnFlag !== "Y";
+ })
+);
+
+const getBatchModel = (batch: MaterialBatch) =>
+ batch.resolvedSpecModel || batch.mesSpec || batch.ncSpec || "-";
+
+const stockWeightKg = (batch: MaterialBatch) => {
+ if (batch.currentWeight === undefined || batch.currentWeight === null) return undefined;
+ const weight = Number(batch.currentWeight);
+ if (!Number.isFinite(weight)) return undefined;
+ return batch.mainUnit === "鍚�" ? weight * 1000 : weight;
+};
+
+const searchBatchRemote = async (keyword: string | { value?: string } = batchKeyword.value) => {
+ const searchValue =
+ typeof keyword === "string" ? keyword : String(keyword?.value || batchKeyword.value || "");
+ batchKeyword.value = searchValue;
+ batchSearching.value = true;
+ try {
+ const { code, data } = await TwistApi.getMaterialBatchStockPage({
+ batchCode: searchValue.trim() || undefined,
+ materialType: "鍘熸潗鏂�",
+ current: 1,
+ size: 20,
+ });
+ batchOptions.value = code === 200 && Array.isArray(data?.records) ? data.records : [];
+ } catch (error: any) {
+ batchOptions.value = [];
+ toast.error(error?.message || "鎵规鏌ヨ澶辫触");
+ } finally {
+ batchSearching.value = false;
+ }
+};
+
+const openBatchSelector = () => {
+ showBatchSelector.value = true;
+ batchKeyword.value = model.batchNo || "";
+ searchBatchRemote(batchKeyword.value);
+};
+
+const selectBatch = (batch: MaterialBatch) => {
+ const batchModel = getBatchModel(batch);
+ model.batchNo = batch.batchCode;
+ model.batchCode = batch.batchCode;
+ model.model = batchModel === "-" ? "" : batchModel;
+ showBatchSelector.value = false;
+};
// 鑺嚎绫诲瀷瀛楀吀鏁版嵁
const diskMaterialOptions = ref<Array<{ label: string; value: string }>>([]);
@@ -148,13 +274,37 @@
// 鏀堕泦琛ㄥ崟鏁版嵁锛堜笉璋� API锛岃繑鍥炵粰鐖剁粍浠跺鐞嗭級
const collectData = () => {
+ if (!model.batchNo) {
+ toast.error("璇烽�夋嫨鎵规鍙�");
+ return null;
+ }
+ if (!model.diskMaterial) {
+ toast.error("璇烽�夋嫨鑺嚎绫诲瀷");
+ return null;
+ }
+ if (!model.monofilamentNumber) {
+ toast.error("璇疯緭鍏ョ洏鍙�");
+ return null;
+ }
+ if (!Number.isFinite(Number(model.weight)) || Number(model.weight) <= 0) {
+ toast.error("璇疯緭鍏ュぇ浜�0鐨勯鐢ㄩ噸閲�");
+ return null;
+ }
+ if (!model.supplier) {
+ toast.error("璇烽�夋嫨鍘傚");
+ return null;
+ }
+
return {
diskMaterial: model.diskMaterial,
+ batchNo: model.batchNo,
+ batchCode: model.batchNo,
model: model.model,
monofilamentNumber: model.monofilamentNumber,
amount: model.amount,
weight: model.weight,
supplier: model.supplier,
+ unit: "kg",
type: model.type,
};
};
@@ -172,6 +322,8 @@
// 閲嶇疆琛ㄥ崟鏁版嵁
const resetFormData = () => {
model.diskMaterial = undefined;
+ model.batchNo = undefined;
+ model.batchCode = undefined;
model.model = undefined;
model.monofilamentNumber = undefined;
model.amount = undefined;
@@ -179,12 +331,16 @@
model.supplier = undefined;
model.type = "閽㈣姱";
diskMaterialValue.value = "";
+ batchKeyword.value = "";
+ batchOptions.value = [];
};
// 濉厖琛ㄥ崟鏁版嵁锛堢敤浜庢壂鐮佸悗鍥炴樉锛�
const fillFormData = (data: any) => {
if (data) {
model.diskMaterial = data.diskMaterial || "";
+ model.batchNo = data.batchNo || data.batchCode || "";
+ model.batchCode = data.batchCode || data.batchNo || "";
model.model = data.model || "";
model.monofilamentNumber = data.monofilamentNumber || "";
model.amount = data.amount || data.oneLength || "";
@@ -199,6 +355,8 @@
const setFormData = (item: any) => {
if (item) {
model.diskMaterial = item.diskMaterial || "";
+ model.batchNo = item.batchNo || item.batchCode || "";
+ model.batchCode = item.batchCode || item.batchNo || "";
model.model = item.model || "";
model.monofilamentNumber = item.monofilamentNumber || "";
model.amount = item.amount || "";
@@ -241,4 +399,78 @@
bottom: 0;
width: 100%;
}
+
+:deep(.batch-placeholder) {
+ color: #bfbfbf;
+}
+
+.batch-selector {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+}
+
+.batch-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 16px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+.batch-title {
+ font-size: 17px;
+ font-weight: 600;
+ color: #333333;
+}
+
+.batch-list {
+ flex: 1;
+ height: 0;
+ padding: 0 12px 16px;
+ box-sizing: border-box;
+}
+
+.batch-item {
+ padding: 14px 12px;
+ margin-bottom: 8px;
+ background: #ffffff;
+ border: 1px solid #e5e5e5;
+ border-radius: 8px;
+}
+
+.batch-item.active {
+ background: #f0fbfa;
+ border-color: #0d867f;
+}
+
+.batch-item-header,
+.batch-meta {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+}
+
+.batch-code {
+ font-weight: 600;
+ color: #222222;
+}
+
+.batch-weight {
+ font-size: 12px;
+ color: #0d867f;
+}
+
+.batch-meta {
+ margin-top: 8px;
+ font-size: 12px;
+ color: #888888;
+}
+
+.batch-state {
+ padding: 48px 16px;
+ color: #999999;
+ text-align: center;
+}
</style>
diff --git a/src/pages/production/twist/receive/steelCore/index.vue b/src/pages/production/twist/receive/steelCore/index.vue
index 77e607a..d4f8c7e 100644
--- a/src/pages/production/twist/receive/steelCore/index.vue
+++ b/src/pages/production/twist/receive/steelCore/index.vue
@@ -2,6 +2,7 @@
<view class="list_box">
<z-paging
ref="pagingRef"
+ v-model="steelData"
:fixed="false"
:auto-show-back-to-top="true"
:loading-more-enabled="false"
@@ -11,38 +12,34 @@
<CardTitle title="鑺嚎棰嗙敤" :hideAction="false">
<template #action>
<wd-button type="icon" icon="scan" color="#0D867F" @click="openScan"></wd-button>
- <wd-button type="icon" icon="add-circle" color="#0D867F" @click="openAddForm"></wd-button>
+ <wd-button
+ type="icon"
+ icon="add-circle"
+ color="#0D867F"
+ @click="openAddForm"
+ ></wd-button>
</template>
</CardTitle>
</template>
- <!-- 灞傜骇 tabs -->
- <wd-tabs v-model="tab" slidable="always" class="tabs-container">
- <block v-for="item in nodeList" :key="item.twistedLayer">
- <wd-tab :title="item.twistedLayer" :name="item.twistedLayer">
- <scroll-view class="content" scroll-y>
- <SteelCoreCard
- v-for="(m, i) in item.strandedWireDish"
- :key="i"
- :data="m"
- @delete="handleDeleteCard(item, m)"
- />
- </scroll-view>
- </wd-tab>
- </block>
- </wd-tabs>
-
- <template #bottom>
- <view class="flex justify-center items-center">
- <wd-button block @click="save">
- <text class="text-[#fff]">淇濆瓨</text>
- </wd-button>
- </view>
- </template>
+ <view class="content">
+ <SteelCoreCard
+ v-for="item in steelData"
+ :key="item.id || item.monofilamentNumber"
+ :data="item"
+ @delete="handleDeleteCard(item)"
+ />
+ </view>
</z-paging>
+ <view class="save-footer">
+ <wd-button block @click="save">
+ <text class="text-[#fff]">淇濆瓨</text>
+ </wd-button>
+ </view>
+
<!-- 鏂板閽㈣姱寮圭獥 -->
- <wd-popup v-model="showAddForm" position="bottom" custom-class="yl-popup">
+ <wd-popup v-model="showAddForm" position="bottom" :z-index="30" custom-class="yl-popup">
<view class="action px-3">
<wd-button type="text" @click="cancelAdd">鍙栨秷</wd-button>
<wd-button type="text" @click="submitAdd">纭畾</wd-button>
@@ -70,59 +67,37 @@
const addFormRef = ref();
const scanRef = ref();
const toast = useToast();
-const tab = ref("");
-const nodeList = ref<any[]>([]);
+const steelData = ref<any[]>([]);
const showAddForm = ref(false);
const isPageVisible = ref(false);
-// 鐩戝惉鏍囩鍒囨崲
-watch(tab, () => {
- if (tab.value) {
- getList();
- }
-});
-
-// 鑾峰彇閽㈣姱灞傜骇鏁版嵁锛堝弬鐓C绔� getSteelCoreDishTypeByWire锛�
+// 閽㈣姱涓� PC 绔繚鎸佷竴鑷达紝鐩存帴浣跨敤鈥滃叾浠栤�濇暟鎹紝涓嶅啀鎸夌粸绾垮眰绾ч鐢ㄣ��
const fetchSteelCoreData = async (wireId: number) => {
const { code, data, msg } = await TwistApi.getSteelCoreDishTypeByWire(wireId);
if (code === 200 && data) {
- nodeList.value = data.nodeList || [];
-
- // 杩藉姞"鍏朵粬"灞�
- if (data.otherStrandedWireDish && data.otherStrandedWireDish.length > 0) {
- nodeList.value.push({
- strandedWireDish: data.otherStrandedWireDish,
- twistedLayer: "鍏朵粬",
- twistId: null,
- });
- }
-
- // 璁剧疆榛樿绗竴灞�
- if (nodeList.value.length > 0 && !tab.value) {
- tab.value = nodeList.value[0].twistedLayer;
- getList();
- }
+ const list = Array.isArray(data.otherStrandedWireDish)
+ ? data.otherStrandedWireDish.map((item: any) => ({
+ ...item,
+ batchNo: item.batchNo || item.batchCode || "",
+ batchCode: item.batchCode || item.batchNo || "",
+ type: item.type || "閽㈣姱",
+ saleTwistId: null,
+ }))
+ : [];
+ pagingRef.value?.complete(list);
} else {
- toast.error(msg || "鑾峰彇閽㈣姱灞傜骇鏁版嵁澶辫触");
+ pagingRef.value?.complete([]);
+ toast.error(msg || "鑾峰彇閽㈣姱棰嗙敤鏁版嵁澶辫触");
}
};
-// 浠庡綋鍓嶉�変腑灞傝幏鍙栧垪琛ㄦ暟鎹�
const getList = async () => {
- const currentLayer = nodeList.value.find((node) => node.twistedLayer === tab.value);
- if (currentLayer && currentLayer.strandedWireDish) {
- pagingRef.value.complete(currentLayer.strandedWireDish);
- } else {
- pagingRef.value.complete([]);
- }
+ if (!paramsId.value) return;
+ await fetchSteelCoreData(Number(paramsId.value));
};
// 鎵撳紑鏂板琛ㄥ崟
const openAddForm = () => {
- if (!tab.value) {
- toast.error("璇峰厛閫夋嫨涓�涓眰");
- return;
- }
if (addFormRef.value) {
addFormRef.value.resetFormData();
}
@@ -134,16 +109,8 @@
const formData = await addFormRef.value.submit();
if (!formData) return;
- // 鏌ユ壘褰撳墠灞�
- const currentLayer = nodeList.value.find((node) => node.twistedLayer === tab.value);
- if (!currentLayer) {
- toast.error("鏈壘鍒板綋鍓嶉�変腑鐨勫眰");
- return;
- }
-
- // 鍚屽眰閲嶅妫�鏌�
if (formData.monofilamentNumber) {
- const exists = currentLayer.strandedWireDish?.some(
+ const exists = steelData.value.some(
(item: any) => item.monofilamentNumber === formData.monofilamentNumber
);
if (exists) {
@@ -152,16 +119,12 @@
}
}
- // 娣诲姞鍒板綋鍓嶅眰
- if (!currentLayer.strandedWireDish) {
- currentLayer.strandedWireDish = [];
- }
- currentLayer.strandedWireDish.push({
+ steelData.value.push({
...formData,
wireId: paramsId.value,
+ saleTwistId: null,
});
- getList();
showAddForm.value = false;
toast.success("娣诲姞鎴愬姛");
};
@@ -177,18 +140,13 @@
}
try {
- if (!tab.value) {
- toast.error("璇峰厛閫夋嫨涓�涓眰");
- return;
- }
-
- const currentLayer = nodeList.value.find((node) => node.twistedLayer === tab.value);
- if (!currentLayer) {
- toast.error("鏈壘鍒板綋鍓嶉�変腑鐨勫眰");
- return;
- }
-
const parseData = JSON.parse(code.code);
+
+ const batchNo = parseData.batchNo || parseData.batchCode || "";
+ if (!batchNo) {
+ toast.error("浜岀淮鐮侀敊璇紝缂哄皯鎵规鍙凤紒");
+ return;
+ }
// 妫�鏌ュ繀闇�瀛楁
const requiredFields = ["model", "supplier", "diskMaterial"];
@@ -198,9 +156,8 @@
return;
}
- // 鍚屽眰閲嶅妫�鏌�
if (parseData.monofilamentNumber) {
- const exists = currentLayer.strandedWireDish?.some(
+ const exists = steelData.value.some(
(item: any) => item.monofilamentNumber === parseData.monofilamentNumber
);
if (exists) {
@@ -213,19 +170,18 @@
const newItem = {
wireId: paramsId.value,
diskMaterial: parseData.diskMaterial || "",
+ batchNo,
+ batchCode: batchNo,
model: parseData.model || "",
monofilamentNumber: parseData.monofilamentNumber || "",
amount: parseData.oneLength || parseData.amount || "",
weight: parseData.weight || "",
supplier: parseData.supplier || "",
type: "閽㈣姱",
+ saleTwistId: null,
};
- if (!currentLayer.strandedWireDish) {
- currentLayer.strandedWireDish = [];
- }
- currentLayer.strandedWireDish.push(newItem);
- getList();
+ steelData.value.push(newItem);
toast.success("鎵爜鎴愬姛");
} catch (error) {
toast.error("浜岀淮鐮佸紓甯革紝璇锋洿鎹簩缁寸爜锛�");
@@ -239,25 +195,11 @@
// 淇濆瓨锛堟壒閲忔彁浜わ級
const save = () => {
- // 妫�鏌ユ槸鍚︽湁鏂版暟鎹渶瑕佷繚瀛�
- let hasNewData = false;
- let newCount = 0;
+ const newCount = steelData.value.filter(
+ (item: { id?: number }) => item.id === undefined || item.id === null
+ ).length;
- nodeList.value.forEach((node) => {
- if (node.strandedWireDish && Array.isArray(node.strandedWireDish)) {
- const hasNewInLayer = node.strandedWireDish.some(
- (item: { id?: number }) => item.id === undefined || item.id === null
- );
- if (hasNewInLayer) {
- hasNewData = true;
- newCount += node.strandedWireDish.filter(
- (item: { id?: number }) => item.id === undefined || item.id === null
- ).length;
- }
- }
- });
-
- if (!hasNewData) {
+ if (!newCount) {
toast.error("娌℃湁鏂扮殑閽㈣姱鏁版嵁闇�瑕佷繚瀛�");
return;
}
@@ -276,23 +218,21 @@
// 鎵ц鎵归噺淇濆瓨
const handleConfirmSave = async () => {
- const newData: any[] = [];
-
- nodeList.value.forEach((node) => {
- // 闄勫姞涓婁笅绾� twistId
- if (node.strandedWireDish && Array.isArray(node.strandedWireDish)) {
- node.strandedWireDish.forEach((item: any) => {
- item.saleTwistId = node.twistId;
- });
- const layerNewData = node.strandedWireDish.filter(
- (item: { id?: number }) => item.id === undefined || item.id === null
- );
- newData.push(...layerNewData);
- }
- });
+ const newData = steelData.value
+ .filter((item: { id?: number }) => item.id === undefined || item.id === null)
+ .map((item) => ({
+ ...item,
+ saleTwistId: null,
+ }));
if (newData.length === 0) {
toast.error("娌℃湁鏂扮殑閽㈣姱鏁版嵁闇�瑕佷繚瀛�");
+ return;
+ }
+
+ const invalidBatchItem = newData.find((item) => !(item.batchNo || item.batchCode));
+ if (invalidBatchItem) {
+ toast.error(`${invalidBatchItem.monofilamentNumber || "閽㈣姱"}锛氳閫夋嫨鎵规鍙穈);
return;
}
@@ -307,7 +247,7 @@
};
// 鍒犻櫎鍗$墖
-const handleDeleteCard = async (layer: any, cardData: any) => {
+const handleDeleteCard = async (cardData: any) => {
uni.showModal({
title: "鎻愮ず",
content: "纭畾瑕佸垹闄よ閽㈣姱鍚楋紵",
@@ -323,16 +263,12 @@
}
}
- // 鍓嶇绉婚櫎
- if (layer.strandedWireDish && Array.isArray(layer.strandedWireDish)) {
- const index = layer.strandedWireDish.findIndex(
- (item: any) => item.monofilamentNumber === cardData.monofilamentNumber
- );
- if (index !== -1) {
- layer.strandedWireDish.splice(index, 1);
- toast.success("鍒犻櫎鎴愬姛");
- getList();
- }
+ const index = steelData.value.findIndex(
+ (item: any) => item.monofilamentNumber === cardData.monofilamentNumber
+ );
+ if (index !== -1) {
+ steelData.value.splice(index, 1);
+ toast.success("鍒犻櫎鎴愬姛");
}
} catch (error: any) {
toast.error(error.msg || "鍒犻櫎澶辫触");
@@ -345,7 +281,7 @@
onLoad(async (options: any) => {
uni.$on("scanSteelCore", getScanCode);
paramsId.value = options.id;
- fetchSteelCoreData(options.id);
+ fetchSteelCoreData(Number(options.id));
});
onShow(() => {
@@ -379,15 +315,22 @@
color: #0d867f;
}
-.tabs-container {
- height: calc(100vh - 200px);
- display: flex;
- flex-direction: column;
+.content {
+ width: 100%;
+ padding-bottom: 76px;
+ box-sizing: border-box;
}
-.content {
- height: calc(100vh - 200px);
- width: 100%;
+.save-footer {
+ position: fixed;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 20;
+ padding: 8px 12px calc(8px + env(safe-area-inset-bottom));
+ background: #ffffff;
+ border-top: 1px solid #eeeeee;
+ box-shadow: 0 -2px 10px rgb(0 0 0 / 6%);
}
:deep(.zp-paging-container) {
@@ -404,38 +347,7 @@
flex-direction: column;
}
-:deep(.wd-tabs) {
- background: transparent !important;
- height: 100%;
- display: flex;
- flex-direction: column;
- overflow: hidden;
-}
-
-:deep(.wd-tabs__nav) {
- margin-bottom: 10px;
- flex-shrink: 0;
- position: sticky;
- top: 0;
- z-index: 10;
- background: #f3f9f8;
-}
-
-:deep(.wd-tabs__content) {
- flex: 1;
- overflow: visible;
-}
-
-:deep(.wd-tab__pane) {
- height: 100%;
-}
-
:deep(.zp-paging-container-top) {
flex-shrink: 0;
-}
-
-:deep(.zp-paging-container-bottom) {
- flex-shrink: 0;
- padding: 8px 12px;
}
</style>
diff --git a/src/pages/timely/detail.vue b/src/pages/timely/detail.vue
new file mode 100644
index 0000000..da96719
--- /dev/null
+++ b/src/pages/timely/detail.vue
@@ -0,0 +1,727 @@
+<template>
+ <view class="detail-page">
+ <view class="detail-header">
+ <CardTitle title="鏃舵晥浠诲姟鏄庣粏" :hideAction="false">
+ <template #action>
+ <wd-button
+ v-if="isWaiting && USE_TIME_SENSITIVE_MOCK"
+ size="small"
+ plain
+ @click="simulateScan"
+ >
+ 妯℃嫙鎵爜
+ </wd-button>
+ <wd-button
+ v-if="isWaiting"
+ type="icon"
+ icon="scan"
+ color="#0D867F"
+ :loading="scanSubmitting"
+ @click="openScan"
+ />
+ <wd-button
+ v-if="isWaiting"
+ type="icon"
+ icon="add-circle"
+ color="#0D867F"
+ @click="openCandidatePopup"
+ />
+ </template>
+ </CardTitle>
+ </view>
+
+ <scroll-view class="detail-scroll" scroll-y>
+ <wd-card v-if="task.id" custom-class="summary-card">
+ <template #title>
+ <view class="summary-title">
+ <text>{{ task.taskCode || `浠诲姟-${task.id}` }}</text>
+ <wd-tag :type="statusType" size="small">{{ statusLabel }}</wd-tag>
+ </view>
+ </template>
+ <view class="summary-grid">
+ <view>
+ <text class="label">绫诲瀷</text>
+ {{ materialType }}
+ </view>
+ <view>
+ <text class="label">鏄庣粏</text>
+ {{ items.length }} 浠�
+ </view>
+ <view>
+ <text class="label">娓╁害</text>
+ {{ temperatureText }}
+ </view>
+ <view>
+ <text class="label">鏃堕暱</text>
+ {{ durationText }}
+ </view>
+ <view>
+ <text class="label">鏈哄彴</text>
+ {{ task.machineName || task.machine || "-" }}
+ </view>
+ <view>
+ <text class="label">宸℃鍛�</text>
+ {{ inspectorName }}
+ </view>
+ </view>
+ </wd-card>
+
+ <view class="section-title">
+ <text>鏃舵晥鏄庣粏锛坽{ items.length }}锛�</text>
+ <view v-if="isWaiting" class="section-actions">
+ <text class="section-link" @click="toggleSelectAll">
+ {{ allItemsSelected ? "鍙栨秷鍏ㄩ��" : "鍏ㄩ��" }}
+ </text>
+ <text class="section-link danger" @click="deleteSelectedItems">
+ 鍒犻櫎鎵�閫�({{ selectedItems.length }})
+ </text>
+ <text class="section-link" @click="openCandidatePopup">鎵嬪姩娣诲姞</text>
+ </view>
+ </view>
+
+ <view v-if="loading" class="state-box">
+ <wd-loading />
+ <text>鍔犺浇涓�...</text>
+ </view>
+ <view v-else-if="!items.length" class="state-box">
+ <wd-status-tip image="content" tip="鏆傛棤鏃舵晥鏄庣粏" />
+ </view>
+ <view v-else class="item-list">
+ <wd-card v-for="(item, index) in items" :key="getItemKey(item, index)">
+ <template #title>
+ <view class="item-title">
+ <view class="item-title-main">
+ <wd-checkbox v-if="isWaiting" v-model="item._selected" @click.stop />
+ <text>{{ getMaterialCode(item) || `鏄庣粏${index + 1}` }}</text>
+ <wd-tag
+ size="small"
+ :type="
+ normalizeMaterialType(item.materialType || materialType) === '鍗曚笣'
+ ? 'primary'
+ : 'success'
+ "
+ >
+ {{ normalizeMaterialType(item.materialType || materialType) }}
+ </wd-tag>
+ </view>
+ <wd-button
+ v-if="isWaiting"
+ type="text"
+ size="small"
+ custom-class="delete-button"
+ @click="deleteItem(item)"
+ >
+ 鍒犻櫎
+ </wd-button>
+ </view>
+ </template>
+ <wd-cell-group>
+ <wd-cell title="瑙勬牸鍨嬪彿" :value="item.model || '-'" />
+ <wd-cell
+ v-if="normalizeMaterialType(item.materialType || materialType) === '鍗曚笣'"
+ title="閾濇潌鍨嬪彿"
+ :value="item.poleModel || item.rodModel || '-'"
+ />
+ <wd-cell
+ v-if="normalizeMaterialType(item.materialType || materialType) === '鍗曚笣'"
+ title="閾濇潌鍙�"
+ :value="item.rodNo || item.poleNumber || '-'"
+ />
+ <wd-cell title="娴佽浆鐮�/鎵规鍙�" :value="getMaterialCode(item) || '-'" />
+ <wd-cell title="閲嶉噺(kg)" :value="String(item.weight ?? '-')" />
+ <wd-cell v-if="item.supplier" title="鍘傚" :value="item.supplier" />
+ </wd-cell-group>
+ </wd-card>
+ </view>
+ </scroll-view>
+
+ <wd-popup
+ v-model="candidatePopupVisible"
+ position="bottom"
+ :z-index="30"
+ :close-on-click-modal="false"
+ custom-style="height: 86vh; border-radius: 24rpx 24rpx 0 0;"
+ >
+ <view class="popup-layout">
+ <view class="popup-header">
+ <view>
+ <text class="popup-title">鏂板鏃舵晥鏄庣粏</text>
+ <text class="popup-subtitle">{{ materialType }}</text>
+ </view>
+ <wd-icon name="close" size="20px" @click="candidatePopupVisible = false" />
+ </view>
+
+ <scroll-view class="candidate-list" scroll-y>
+ <view v-if="candidateLoading && !candidates.length" class="state-box">
+ <wd-loading />
+ <text>鏌ヨ涓�...</text>
+ </view>
+ <view v-else-if="!candidates.length" class="state-box">
+ <wd-status-tip image="search" tip="鏆傛棤鍙姞鍏ョ殑寰呮椂鏁堢墿鏂�" />
+ </view>
+ <view
+ v-for="(candidate, index) in candidates"
+ :key="getItemKey(candidate, index)"
+ class="candidate-item"
+ @click="candidate._selected = !candidate._selected"
+ >
+ <wd-checkbox v-model="candidate._selected" @click.stop />
+ <view class="candidate-info">
+ <text class="candidate-code">{{ getMaterialCode(candidate) || "-" }}</text>
+ <text>{{ candidate.model || "-" }} 路 {{ candidate.weight ?? "-" }} kg</text>
+ <text v-if="materialType === '鍗曚笣'" class="candidate-meta">
+ {{ candidate.poleModel || candidate.rodModel || "-" }} 路
+ {{ candidate.rodNo || candidate.poleNumber || "-" }}
+ </text>
+ </view>
+ </view>
+ <wd-button
+ v-if="candidates.length < candidateTotal"
+ block
+ plain
+ :loading="candidateLoading"
+ @click="loadMoreCandidates"
+ >
+ 鍔犺浇鏇村
+ </wd-button>
+ </scroll-view>
+
+ <view class="popup-footer">
+ <text>宸查�� {{ selectedCandidates.length }} 鏉�</text>
+ <view class="footer-buttons">
+ <wd-button type="info" plain @click="candidatePopupVisible = false">鍙栨秷</wd-button>
+ <wd-button :loading="candidateSubmitting" @click="addSelectedCandidates">
+ 纭畾娣诲姞
+ </wd-button>
+ </view>
+ </view>
+ </view>
+ </wd-popup>
+
+ <Scan ref="scanRef" emitName="scanTimeSensitiveItem" />
+ <wd-toast />
+ </view>
+</template>
+
+<script setup lang="ts">
+import { onHide, onLoad, onShow, onUnload } 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 Scan from "@/components/scan/index.vue";
+import TimeSensitiveTaskApi, {
+ USE_TIME_SENSITIVE_MOCK,
+ type TimelinessMaterialType,
+} from "@/api/timeSensitiveTask";
+import TwistApi from "@/api/product/twist";
+import OutboundApi from "@/api/product/outbound";
+
+const toast = useToast();
+const scanRef = ref();
+const taskId = ref<number | string>("");
+const task = ref<any>({});
+const items = ref<any[]>([]);
+const loading = ref(false);
+const scanSubmitting = ref(false);
+const pageVisible = ref(false);
+const candidatePopupVisible = ref(false);
+const candidateLoading = ref(false);
+const candidateSubmitting = ref(false);
+const candidates = ref<any[]>([]);
+const candidateTotal = ref(0);
+const candidatePage = ref(1);
+const candidatePageSize = 20;
+const materialType = computed<TimelinessMaterialType>(() =>
+ normalizeMaterialType(task.value.materialType)
+);
+const status = computed(() => normalizeStatus(task.value));
+const isWaiting = computed(() => status.value === 0);
+const statusLabel = computed(() => ["寰呭紑濮�", "鏃舵晥涓�", "鏃舵晥缁撴潫"][status.value] || "鏈煡");
+const statusType = computed<TagType>(() => {
+ if (status.value === 0) return "warning";
+ if (status.value === 1) return "primary";
+ if (status.value === 2) return "success";
+ return "default";
+});
+const inspectorName = computed(
+ () => task.value.inspectorName || task.value.routingInspectionUser || "-"
+);
+const temperatureText = computed(() => {
+ const value = task.value.setTemperature ?? task.value.temperature;
+ return value === undefined || value === "" ? "-" : `${value}鈩僠;
+});
+const durationText = computed(() => {
+ const value = task.value.agingDuration ?? task.value.duration;
+ return value === undefined || value === "" ? "-" : `${value} 灏忔椂`;
+});
+const selectedItems = computed(() => items.value.filter((item) => item._selected));
+const allItemsSelected = computed(
+ () => items.value.length > 0 && selectedItems.value.length === items.value.length
+);
+const selectedCandidates = computed(() => candidates.value.filter((item) => item._selected));
+
+function normalizeStatus(value: any) {
+ const key = String(value?.taskStatus ?? value?.timelinessStatus ?? value?.status ?? value ?? "")
+ .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 getMaterialCode(item: any) {
+ return String(
+ item?.flowCode ||
+ item?.batchCode ||
+ item?.monofilamentNumber ||
+ item?.monofilamentnumber ||
+ item?.materialCode ||
+ item?.systemNo ||
+ item?.systemno ||
+ ""
+ ).trim();
+}
+
+function getCandidateId(item: any): number | string | undefined {
+ return item?.id ?? item?.awaitTimelinessId ?? item?.qualitativeProductionId ?? item?.outputId;
+}
+
+function getItemKey(item: any, index: number) {
+ return getCandidateId(item) || getMaterialCode(item) || index;
+}
+
+async function loadTask() {
+ if (!taskId.value) return;
+ loading.value = true;
+ try {
+ const [detailResult, itemsResult] = await Promise.all([
+ TimeSensitiveTaskApi.getDetail(taskId.value),
+ TimeSensitiveTaskApi.getItems(taskId.value),
+ ]);
+ task.value = detailResult.data || { id: taskId.value };
+ const itemData = itemsResult.data as any;
+ const list = Array.isArray(itemData)
+ ? itemData
+ : itemData?.records || itemData?.list || itemData?.items || [];
+ items.value = list.map((item: any) => ({ ...item, _selected: false }));
+ } catch (error: any) {
+ toast.error(error?.message || "鑾峰彇鏃舵晥浠诲姟鏄庣粏澶辫触");
+ } finally {
+ loading.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 deleteItems(rows: any[]) {
+ if (!isWaiting.value) return toast.error("鍙湁寰呭紑濮嬬殑浠诲姟鍙互鍒犻櫎鏄庣粏");
+ if (!rows.length) return toast.error("璇疯嚦灏戦�夋嫨涓�鏉℃椂鏁堟槑缁�");
+ const itemIds = rows
+ .map((item) => getCandidateId(item))
+ .filter((id): id is number | string => id !== undefined && id !== null && id !== "");
+ if (!itemIds.length) return toast.error("鏄庣粏ID涓嶅瓨鍦紝鏃犳硶鍒犻櫎");
+ const confirmed = await showConfirm(
+ "鍒犻櫎鏄庣粏",
+ `纭畾鍒犻櫎閫変腑鐨� ${itemIds.length} 鏉℃椂鏁堟槑缁嗗悧锛熷垹闄ゅ悗鍙噸鏂版坊鍔犮�俙,
+ "鍒犻櫎"
+ );
+ if (!confirmed) return;
+ try {
+ await TimeSensitiveTaskApi.deleteItems({ taskId: taskId.value, itemIds });
+ toast.success("鏃舵晥鏄庣粏鍒犻櫎鎴愬姛");
+ await loadTask();
+ } catch (error: any) {
+ toast.error(error?.message || "鍒犻櫎鏃舵晥鏄庣粏澶辫触");
+ }
+}
+
+function deleteItem(item: any) {
+ deleteItems([item]);
+}
+
+function deleteSelectedItems() {
+ deleteItems(selectedItems.value);
+}
+
+function toggleSelectAll() {
+ const nextValue = !allItemsSelected.value;
+ items.value.forEach((item) => {
+ item._selected = nextValue;
+ });
+}
+
+function openCandidatePopup() {
+ if (!isWaiting.value) return toast.error("鍙湁寰呭紑濮嬬殑浠诲姟鍙互鏂板鏄庣粏");
+ candidatePopupVisible.value = true;
+ loadCandidates(true);
+}
+
+async function loadCandidates(reset = false) {
+ if (reset) {
+ candidatePage.value = 1;
+ candidates.value = [];
+ }
+ candidateLoading.value = true;
+ try {
+ const { data } = await TimeSensitiveTaskApi.queryCandidates({
+ current: candidatePage.value,
+ size: candidatePageSize,
+ taskId: taskId.value,
+ materialType: materialType.value,
+ });
+ const rows = (data?.records || []).map((item: any) => ({ ...item, _selected: false }));
+ candidates.value = reset ? rows : [...candidates.value, ...rows];
+ candidateTotal.value = Number(data?.total || candidates.value.length);
+ } catch (error: any) {
+ toast.error(error?.message || "鏌ヨ鍙�夋椂鏁堟槑缁嗗け璐�");
+ } finally {
+ candidateLoading.value = false;
+ }
+}
+
+function loadMoreCandidates() {
+ if (candidateLoading.value || candidates.value.length >= candidateTotal.value) return;
+ candidatePage.value += 1;
+ loadCandidates(false);
+}
+
+async function addCandidates(rows: any[], successMessage = "鏃舵晥鏄庣粏鏂板鎴愬姛") {
+ const itemIds = rows
+ .map((item) => getCandidateId(item))
+ .filter((id): id is number | string => id !== undefined && id !== null && id !== "");
+ if (!itemIds.length) return toast.error("鎵�閫夌墿鏂欑己灏戞槑缁咺D");
+ await TimeSensitiveTaskApi.addItems({ taskId: taskId.value, itemIds });
+ toast.success(successMessage);
+ await loadTask();
+}
+
+async function addSelectedCandidates() {
+ if (!selectedCandidates.value.length) return toast.error("璇疯嚦灏戦�夋嫨涓�鏉″緟鏃舵晥鏁版嵁");
+ candidateSubmitting.value = true;
+ try {
+ await addCandidates(selectedCandidates.value);
+ candidatePopupVisible.value = false;
+ } catch (error: any) {
+ toast.error(error?.message || "鏂板鏃舵晥鏄庣粏澶辫触");
+ } finally {
+ candidateSubmitting.value = false;
+ }
+}
+
+function readScanPayload(event: any) {
+ const rawValue = event?.code ?? event;
+ const raw = typeof rawValue === "string" ? rawValue.trim() : JSON.stringify(rawValue || "");
+ let parsed: any = {};
+ if (raw.startsWith("{") || raw.startsWith("[")) {
+ try {
+ parsed = JSON.parse(raw);
+ if (Array.isArray(parsed)) parsed = parsed[0] || {};
+ } catch {
+ parsed = {};
+ }
+ }
+ const directCode = getMaterialCode(parsed);
+ const outputId = parsed.id ?? parsed.outPutId ?? parsed.outputId;
+ if (directCode) return { raw, parsed, outputId, materialCode: directCode };
+
+ const parts = raw.split(/[锛�,\s]/).filter(Boolean);
+ const singleWireCode = parts.find((part) => part.startsWith("75"));
+ return {
+ raw,
+ parsed,
+ outputId,
+ materialCode: singleWireCode || (!Object.keys(parsed).length ? raw : ""),
+ };
+}
+
+async function resolveScannedMaterialCode(scan: ReturnType<typeof readScanPayload>) {
+ if (scan.materialCode) return scan.materialCode;
+ if (scan.outputId === undefined || scan.outputId === null || scan.outputId === "") return "";
+ if (USE_TIME_SENSITIVE_MOCK) return String(scan.outputId);
+ try {
+ const result =
+ materialType.value === "鍗曚笣"
+ ? await TwistApi.getTagByIdLs({ outPutId: scan.outputId })
+ : await OutboundApi.getTagByIdJx({ outPutId: scan.outputId });
+ const data = Array.isArray(result.data) ? result.data[0] : result.data;
+ return getMaterialCode(data) || String(scan.outputId);
+ } catch {
+ return String(scan.outputId);
+ }
+}
+
+async function handleScan(event: any) {
+ if (!pageVisible.value || scanSubmitting.value) return;
+ if (!isWaiting.value) return toast.error("鍙湁寰呭紑濮嬬殑浠诲姟鍙互鎵爜鏂板鏄庣粏");
+ scanSubmitting.value = true;
+ try {
+ const scan = readScanPayload(event);
+ const materialCode = await resolveScannedMaterialCode(scan);
+ if (!materialCode) return toast.error("浜岀淮鐮佹牸寮忛敊璇紝鏈瘑鍒埌娴佽浆鐮佹垨鎵规鍙�");
+
+ const { data } = await TimeSensitiveTaskApi.queryCandidates({
+ current: 1,
+ size: 20,
+ taskId: taskId.value,
+ materialType: materialType.value,
+ materialCode,
+ });
+ const rows = data?.records || [];
+ const normalizedCode = materialCode.trim().toUpperCase();
+ const candidate =
+ rows.find((item: any) => getMaterialCode(item).toUpperCase() === normalizedCode) ||
+ rows.find((item: any) => String(getCandidateId(item)) === String(scan.outputId)) ||
+ (rows.length === 1 ? rows[0] : undefined);
+ if (!candidate) {
+ return toast.error(`鏈壘鍒板彲娣诲姞鐨�${materialType.value}锛屽彲鑳藉凡娣诲姞鎴栦笉鍦ㄥ緟鏃舵晥鑼冨洿`);
+ }
+ await addCandidates([candidate], "鎵爜娣诲姞鎴愬姛");
+ } catch (error: any) {
+ toast.error(error?.message || "鎵爜娣诲姞鏃舵晥鏄庣粏澶辫触");
+ } finally {
+ scanSubmitting.value = false;
+ }
+}
+
+function openScan() {
+ if (!isWaiting.value) return toast.error("鍙湁寰呭紑濮嬬殑浠诲姟鍙互鎵爜鏂板鏄庣粏");
+ scanRef.value?.triggerScan();
+}
+
+function simulateScan() {
+ const mockIds = materialType.value === "鍗曚笣" ? [9301, 9302, 9501] : [9401, 9402, 9201, 9202];
+ const existingIds = new Set(items.value.map((item) => String(getCandidateId(item))));
+ const nextId = mockIds.find((id) => !existingIds.has(String(id)));
+ if (!nextId) return toast.error(`娌℃湁鏇村鍙ā鎷熸壂鐮佺殑${materialType.value}`);
+ handleScan({ code: JSON.stringify({ id: nextId }) });
+}
+
+onLoad((options: any) => {
+ taskId.value = options?.id || "";
+ if (!taskId.value) {
+ toast.error("鏃舵晥浠诲姟ID涓嶈兘涓虹┖");
+ return;
+ }
+ uni.$off("scanTimeSensitiveItem", handleScan);
+ uni.$on("scanTimeSensitiveItem", handleScan);
+ loadTask();
+});
+
+onShow(() => {
+ pageVisible.value = true;
+});
+
+onHide(() => {
+ pageVisible.value = false;
+});
+
+onUnload(() => {
+ pageVisible.value = false;
+ uni.$off("scanTimeSensitiveItem", handleScan);
+});
+</script>
+
+<style lang="scss" scoped>
+.detail-page {
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 50px);
+ background: #f3f9f8;
+}
+
+.detail-header {
+ flex-shrink: 0;
+ background: #ffffff;
+}
+
+.detail-scroll {
+ flex: 1;
+ height: 0;
+}
+
+:deep(.summary-card) {
+ margin: 10px;
+}
+
+.summary-title,
+.item-title,
+.item-title-main,
+.popup-header,
+.popup-footer,
+.footer-buttons {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.summary-title,
+.item-title-main {
+ min-width: 0;
+ font-weight: 600;
+}
+
+.summary-grid {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 12px 16px;
+ font-size: 13px;
+}
+
+.summary-grid > view {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+}
+
+.label,
+.popup-subtitle,
+.candidate-meta {
+ font-size: 12px;
+ color: #999999;
+}
+
+.scan-tip {
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+ padding: 10px 12px;
+ margin: 0 10px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: #287b75;
+ background: #eaf8f6;
+ border-radius: 8px;
+}
+
+.section-title {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 4px 12px 10px;
+ font-weight: 600;
+}
+
+.section-actions {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ justify-content: flex-end;
+}
+
+.section-link {
+ font-size: 13px;
+ font-weight: 400;
+ color: #0d867f;
+}
+
+.section-link.danger {
+ color: #fa4350;
+}
+
+.item-list {
+ padding: 0 10px 24px;
+}
+
+.item-list :deep(.wd-card) {
+ margin: 0 0 10px;
+}
+
+:deep(.delete-button .wd-button__content) {
+ color: #fa4350;
+}
+
+.state-box {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ min-height: 130px;
+ color: #999999;
+}
+
+.popup-layout {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #f7f8fa;
+}
+
+.popup-header {
+ flex-shrink: 0;
+ padding: 14px 16px;
+ background: #ffffff;
+ border-bottom: 1px solid #eeeeee;
+}
+
+.popup-header > view {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.popup-title {
+ font-size: 17px;
+ font-weight: 600;
+}
+
+.candidate-list {
+ flex: 1;
+ height: 0;
+ padding: 10px 12px;
+ box-sizing: border-box;
+}
+
+.candidate-item {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ padding: 12px;
+ margin-bottom: 8px;
+ background: #ffffff;
+ border: 1px solid #eeeeee;
+ border-radius: 8px;
+}
+
+.candidate-info {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ gap: 5px;
+ min-width: 0;
+ font-size: 13px;
+ color: #666666;
+}
+
+.candidate-code {
+ overflow: hidden;
+ font-weight: 600;
+ color: #222222;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.popup-footer {
+ flex-shrink: 0;
+ padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
+ font-size: 13px;
+ background: #ffffff;
+ border-top: 1px solid #eeeeee;
+}
+</style>
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>
diff --git a/src/pages/timely/inspection.vue b/src/pages/timely/inspection.vue
new file mode 100644
index 0000000..8b1ccc2
--- /dev/null
+++ b/src/pages/timely/inspection.vue
@@ -0,0 +1,248 @@
+<template>
+ <view class="inspection-page">
+ <CardTitle title="宸℃鑼冨洿鍙婃俯搴﹁鎯�" :hideAction="false">
+ <template v-if="!readonly" #action>
+ <view class="header-actions">
+ <wd-button size="small" plain icon="add" @click="addRecord">鏂板</wd-button>
+ <wd-button size="small" plain type="warning" @click="editRecords">缂栬緫</wd-button>
+ <wd-button size="small" :loading="saving" @click="saveRecords">淇濆瓨</wd-button>
+ </view>
+ </template>
+ </CardTitle>
+
+ <view v-if="loading" class="state-box">
+ <wd-loading />
+ <text>鍔犺浇涓�...</text>
+ </view>
+ <wd-status-tip v-else-if="!records.length" image="content" tip="鏆傛棤宸℃娓╁害璁板綍" />
+ <view v-else class="record-list">
+ <wd-card v-for="(record, index) in records" :key="record.id || index">
+ <template #title>
+ <view class="record-title">
+ <text>宸℃璁板綍 {{ index + 1 }}</text>
+ <wd-button
+ v-if="!readonly"
+ type="text"
+ size="small"
+ custom-class="delete-button"
+ @click="deleteRecord(record, index)"
+ >
+ 鍒犻櫎
+ </wd-button>
+ </view>
+ </template>
+
+ <wd-cell-group v-if="readonly || (record.id && !record._editing)">
+ <wd-cell title="宸℃鍛�" :value="record.routingInspectionUser || '-'" />
+ <wd-cell title="涓�鍖�" :value="temperatureValue(record.zoneOne)" />
+ <wd-cell title="浜屽尯" :value="temperatureValue(record.zoneTwo)" />
+ <wd-cell title="涓夊尯" :value="temperatureValue(record.zoneThree)" />
+ <wd-cell title="鍥涘尯" :value="temperatureValue(record.zoneFour)" />
+ <wd-cell title="浜斿尯" :value="temperatureValue(record.zoneFive)" />
+ <wd-cell title="鍏尯" :value="temperatureValue(record.zoneSix)" />
+ <wd-cell title="宸℃鏃堕棿" :value="record.createTime || record.inspectionTime || '-'" />
+ </wd-cell-group>
+
+ <wd-cell-group v-else border>
+ <wd-input v-model="record.routingInspectionUser" label="宸℃鍛�" readonly />
+ <wd-input v-model="record.zoneOne" label="涓�鍖�(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ <wd-input v-model="record.zoneTwo" label="浜屽尯(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ <wd-input v-model="record.zoneThree" label="涓夊尯(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ <wd-input v-model="record.zoneFour" label="鍥涘尯(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ <wd-input v-model="record.zoneFive" label="浜斿尯(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ <wd-input v-model="record.zoneSix" label="鍏尯(鈩�)" type="digit" placeholder="璇疯緭鍏�" />
+ </wd-cell-group>
+ </wd-card>
+ </view>
+
+ <wd-toast />
+ </view>
+</template>
+
+<script setup lang="ts">
+import { onLoad } from "@dcloudio/uni-app";
+import { useToast } from "wot-design-uni";
+import CardTitle from "@/components/card-title/index.vue";
+import TimeSensitiveTaskApi, {
+ USE_TIME_SENSITIVE_MOCK,
+ type TimelinessInspectionDetailForm,
+} from "@/api/timeSensitiveTask";
+import { useUserStore } from "@/store/modules/user";
+import { getPrepareId } from "@/utils/cache";
+
+type InspectionRecord = TimelinessInspectionDetailForm & {
+ createTime?: string;
+ inspectionTime?: string;
+ _editing?: boolean;
+};
+
+const toast = useToast();
+const userStore = useUserStore();
+const inspectionId = ref<number | string>("");
+const readonly = ref(false);
+const loading = ref(false);
+const saving = ref(false);
+const records = ref<InspectionRecord[]>([]);
+
+function currentPerson() {
+ const user = userStore.userInfo as any;
+ return user?.nickName || user?.nickname || user?.userName || user?.username || "";
+}
+
+function temperatureValue(value: unknown) {
+ return value === undefined || value === null || value === "" ? "-" : `${value}鈩僠;
+}
+
+function ensurePrepared(action: string) {
+ if (USE_TIME_SENSITIVE_MOCK || getPrepareId()) return true;
+ toast.error(`璇峰畬鎴愮敓浜у噯澶囩‘璁わ紝鍐嶈繘琛�${action}鎿嶄綔`);
+ return false;
+}
+
+async function loadRecords() {
+ loading.value = true;
+ try {
+ const { data } = await TimeSensitiveTaskApi.getInspectionDetails(inspectionId.value);
+ const list = Array.isArray(data) ? data : data ? [data] : [];
+ records.value = list.map((item: any) => ({
+ ...item,
+ routingInspectionUser: item.routingInspectionUser || currentPerson(),
+ timelinessInspectionId: item.timelinessInspectionId || inspectionId.value,
+ _editing: false,
+ }));
+ } catch (error: any) {
+ records.value = [];
+ toast.error(error?.message || "鍔犺浇宸℃璇︽儏澶辫触");
+ } finally {
+ loading.value = false;
+ }
+}
+
+function addRecord() {
+ if (readonly.value || !ensurePrepared("鏂板宸℃鑼冨洿娓╁害")) return;
+ records.value.push({
+ routingInspectionUser: currentPerson(),
+ zoneOne: "",
+ zoneTwo: "",
+ zoneThree: "",
+ zoneFour: "",
+ zoneFive: "",
+ zoneSix: "",
+ timelinessInspectionId: inspectionId.value,
+ _editing: true,
+ });
+}
+
+function editRecords() {
+ if (readonly.value || !ensurePrepared("缂栬緫宸℃鑼冨洿娓╁害")) return;
+ if (!records.value.some((item) => item.id)) return toast.error("娌℃湁宸蹭繚瀛樼殑鏁版嵁鍙紪杈�");
+ records.value.forEach((item) => {
+ if (item.id) item._editing = true;
+ });
+ toast.show("宸茶繘鍏ョ紪杈戞ā寮忥紝淇敼鍚庤鐐瑰嚮淇濆瓨");
+}
+
+async function saveRecords() {
+ if (readonly.value || !ensurePrepared("淇濆瓨宸℃鑼冨洿娓╁害")) return;
+ if (!records.value.length) return toast.error("娌℃湁鏁版嵁闇�瑕佷繚瀛�");
+ saving.value = true;
+ try {
+ const payload = records.value.map((item) => ({
+ id: item.id,
+ routingInspectionUser: item.routingInspectionUser || currentPerson(),
+ zoneOne: item.zoneOne,
+ zoneTwo: item.zoneTwo,
+ zoneThree: item.zoneThree,
+ zoneFour: item.zoneFour,
+ zoneFive: item.zoneFive,
+ zoneSix: item.zoneSix,
+ timelinessInspectionId: item.timelinessInspectionId || inspectionId.value,
+ }));
+ await TimeSensitiveTaskApi.saveInspectionDetails(payload);
+ toast.success("淇濆瓨鎴愬姛");
+ await loadRecords();
+ } catch (error: any) {
+ toast.error(error?.message || "淇濆瓨澶辫触");
+ } finally {
+ saving.value = false;
+ }
+}
+
+function showDeleteConfirm() {
+ return new Promise<boolean>((resolve) => {
+ uni.showModal({
+ title: "鎻愮ず",
+ content: "纭畾鍒犻櫎璇ュ贰妫�璇︽儏鍚楋紵",
+ confirmText: "鍒犻櫎",
+ success: (result) => resolve(Boolean(result.confirm)),
+ fail: () => resolve(false),
+ });
+ });
+}
+
+async function deleteRecord(record: InspectionRecord, index: number) {
+ if (readonly.value) return;
+ const confirmed = await showDeleteConfirm();
+ if (!confirmed) return;
+ if (!record.id) {
+ records.value.splice(index, 1);
+ toast.success("鍒犻櫎鎴愬姛");
+ return;
+ }
+ try {
+ await TimeSensitiveTaskApi.deleteInspectionDetail(record.id);
+ toast.success("鍒犻櫎鎴愬姛");
+ await loadRecords();
+ } catch (error: any) {
+ toast.error(error?.message || "鍒犻櫎澶辫触");
+ }
+}
+
+onLoad((options: any) => {
+ inspectionId.value = options?.id || "";
+ readonly.value = String(options?.readonly || "0") === "1";
+ if (!inspectionId.value) return toast.error("宸℃璁板綍ID涓嶈兘涓虹┖");
+ loadRecords();
+});
+</script>
+
+<style lang="scss" scoped>
+.inspection-page {
+ min-height: calc(100vh - 50px);
+ padding-bottom: 24px;
+ background: #f3f9f8;
+}
+
+.header-actions,
+.record-title {
+ display: flex;
+ gap: 6px;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.record-list {
+ padding: 10px;
+}
+
+.record-list :deep(.wd-card) {
+ margin: 0 0 10px;
+}
+
+.record-title {
+ font-weight: 600;
+}
+
+:deep(.delete-button .wd-button__content) {
+ color: #fa4350;
+}
+
+.state-box {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ justify-content: center;
+ min-height: 180px;
+ color: #999999;
+}
+</style>
--
Gitblit v1.9.3