| | |
| | | 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' # |
| | | |
| | |
| | | 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' |
| | | |
| | |
| | | }); |
| | | }, |
| | | |
| | | // æ¥è¯¢å¯é¢ç¨çåæææ¹æ¬¡ï¼ä¸ 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>>({ |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | 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; |
| | |
| | | { |
| | | "path": "pages/timely/index", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¶ææ¥å·¥" |
| | | "navigationBarTitleText": "æ¶æä»»å¡" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/timely/detail", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¶æä»»å¡æç»" |
| | | } |
| | | }, |
| | | { |
| | | "path": "pages/timely/inspection", |
| | | "style": { |
| | | "navigationBarTitleText": "æ¶æå·¡æ£è®°å½" |
| | | } |
| | | }, |
| | | { |
| | |
| | | url: "/pages/outbound/index", |
| | | show: false, |
| | | }, |
| | | { |
| | | icon: "/static/icons/log.png", |
| | | title: "æ¶æ", |
| | | url: "/pages/timely/index", |
| | | show: true, |
| | | }, |
| | | ]); |
| | | |
| | | // å 载访é®ç»è®¡æ°æ® |
| | |
| | | <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" /> |
| | |
| | | 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" |
| | |
| | | /> |
| | | <wd-input |
| | | v-model="model.weight" |
| | | label="éé" |
| | | label="é¢ç¨éé" |
| | | label-width="100px" |
| | | prop="weight" |
| | | clearable |
| | | placeholder="请è¾å
¥éé" |
| | | placeholder="请è¾å
¥é¢ç¨éé(kg)" |
| | | /> |
| | | <wd-picker |
| | | v-model="model.supplier" |
| | |
| | | /> |
| | | </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">æ£å¨å è½½æ¹æ¬¡...</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({ |
| | |
| | | const toast = useToast(); |
| | | const { form: model } = useFormData({ |
| | | diskMaterial: undefined, // è¯çº¿ç±»å |
| | | batchNo: undefined, // æ¹æ¬¡å·ï¼å±ç¤ºåé¢è¯é¢ç¨åæ®µï¼ |
| | | batchCode: undefined, // å
¼å®¹åºåé¢ç¨å段 |
| | | model: undefined, // è§æ ¼åå· |
| | | monofilamentNumber: undefined, // çå· |
| | | amount: undefined, // æ°é |
| | |
| | | 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 }>>([]); |
| | |
| | | |
| | | // æ¶éè¡¨åæ°æ®ï¼ä¸è° 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, |
| | | }; |
| | | }; |
| | |
| | | // éç½®è¡¨åæ°æ® |
| | | const resetFormData = () => { |
| | | model.diskMaterial = undefined; |
| | | model.batchNo = undefined; |
| | | model.batchCode = undefined; |
| | | model.model = undefined; |
| | | model.monofilamentNumber = undefined; |
| | | model.amount = undefined; |
| | |
| | | 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 || ""; |
| | |
| | | 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 || ""; |
| | |
| | | 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> |
| | |
| | | <view class="list_box"> |
| | | <z-paging |
| | | ref="pagingRef" |
| | | v-model="steelData" |
| | | :fixed="false" |
| | | :auto-show-back-to-top="true" |
| | | :loading-more-enabled="false" |
| | |
| | | <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> |
| | |
| | | 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(); |
| | | } |
| | | }); |
| | | |
| | | // è·åé¢è¯å±çº§æ°æ®ï¼åç
§PC端 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(); |
| | | } |
| | |
| | | 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) { |
| | |
| | | } |
| | | } |
| | | |
| | | // æ·»å å°å½åå± |
| | | if (!currentLayer.strandedWireDish) { |
| | | currentLayer.strandedWireDish = []; |
| | | } |
| | | currentLayer.strandedWireDish.push({ |
| | | steelData.value.push({ |
| | | ...formData, |
| | | wireId: paramsId.value, |
| | | saleTwistId: null, |
| | | }); |
| | | |
| | | getList(); |
| | | showAddForm.value = false; |
| | | toast.success("æ·»å æå"); |
| | | }; |
| | |
| | | } |
| | | |
| | | 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"]; |
| | |
| | | return; |
| | | } |
| | | |
| | | // åå±é夿£æ¥ |
| | | if (parseData.monofilamentNumber) { |
| | | const exists = currentLayer.strandedWireDish?.some( |
| | | const exists = steelData.value.some( |
| | | (item: any) => item.monofilamentNumber === parseData.monofilamentNumber |
| | | ); |
| | | if (exists) { |
| | |
| | | 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("äºç»´ç å¼å¸¸ï¼è¯·æ´æ¢äºç»´ç ï¼"); |
| | |
| | | |
| | | // ä¿åï¼æ¹éæäº¤ï¼ |
| | | 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; |
| | | } |
| | |
| | | |
| | | // æ§è¡æ¹éä¿å |
| | | 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; |
| | | } |
| | | |
| | |
| | | }; |
| | | |
| | | // å é¤å¡ç |
| | | const handleDeleteCard = async (layer: any, cardData: any) => { |
| | | const handleDeleteCard = async (cardData: any) => { |
| | | uni.showModal({ |
| | | title: "æç¤º", |
| | | content: "ç¡®å®è¦å é¤è¯¥é¢è¯åï¼", |
| | |
| | | } |
| | | } |
| | | |
| | | // åç«¯ç§»é¤ |
| | | 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 || "å é¤å¤±è´¥"); |
| | |
| | | onLoad(async (options: any) => { |
| | | uni.$on("scanSteelCore", getScanCode); |
| | | paramsId.value = options.id; |
| | | fetchSteelCoreData(options.id); |
| | | fetchSteelCoreData(Number(options.id)); |
| | | }); |
| | | |
| | | onShow(() => { |
| | |
| | | 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) { |
| | |
| | | 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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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("æéç©æç¼ºå°æç»ID"); |
| | | 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> |
| | |
| | | <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> |
| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <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> |