优化
This commit is contained in:
@@ -150,6 +150,9 @@
|
|||||||
<text v-if="evidenceQueueEntry(slot.type)" class="evidence-upload-state" :class="evidenceQueueEntry(slot.type).status.toLowerCase()">
|
<text v-if="evidenceQueueEntry(slot.type)" class="evidence-upload-state" :class="evidenceQueueEntry(slot.type).status.toLowerCase()">
|
||||||
{{ evidenceUploadState(slot.type) }}
|
{{ evidenceUploadState(slot.type) }}
|
||||||
</text>
|
</text>
|
||||||
|
<text v-else-if="hasUnboundUploadedEvidence(slot.type)" class="evidence-upload-state pending">
|
||||||
|
{{ slot.type === 'SEAL' ? '照片已上传,请输入封签号并保存' : '照片已上传,等待保存' }}
|
||||||
|
</text>
|
||||||
<text v-else-if="hasPendingLocalEvidence(slot.type)" class="evidence-upload-state pending">本地暂存,待自动上传</text>
|
<text v-else-if="hasPendingLocalEvidence(slot.type)" class="evidence-upload-state pending">本地暂存,待自动上传</text>
|
||||||
<button v-if="evidenceQueueEntry(slot.type)?.status === 'FAILED'" class="evidence-retry-btn" @tap="retryEvidence(slot.type)">重试上传</button>
|
<button v-if="evidenceQueueEntry(slot.type)?.status === 'FAILED'" class="evidence-retry-btn" @tap="retryEvidence(slot.type)">重试上传</button>
|
||||||
</view>
|
</view>
|
||||||
@@ -353,7 +356,7 @@
|
|||||||
import { navigateBackOr } from '@/utils/navigation.js'
|
import { navigateBackOr } from '@/utils/navigation.js'
|
||||||
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
|
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
|
||||||
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
|
import { playErrorSound, playSuccessSound } from '@/utils/audio-feedback.js'
|
||||||
import { claimContainerPickingTask, completeContainerPickingTask, completePicking, downloadEvidencePreview, EVIDENCE_SLOTS, getContainerPickingTask, getEvidence, getEvidencePreviewUrl, getPickingInventorySnapshot, getPickingTask, isRemoteReady, normalizePickingTask, submitContainerPickingBatch, uploadEvidenceFile, upsertEvidence, voidEvidence } from '@/api/pda-loading.js'
|
import { claimContainerPickingTask, completeContainerPickingTask, completePicking, downloadEvidencePreview, EVIDENCE_SLOTS, getContainerPickingTask, getEvidence, getEvidencePreviewUrl, getPickingInventorySnapshot, getPickingTask, isRemoteReady, listLoadingOrders, normalizePickingTask, submitContainerPickingBatch, uploadEvidenceFile, upsertEvidence, voidEvidence } from '@/api/pda-loading.js'
|
||||||
import { createEvidenceUploadQueue } from '@/utils/evidence-upload-queue.js'
|
import { createEvidenceUploadQueue } from '@/utils/evidence-upload-queue.js'
|
||||||
import { isOnline } from '@/utils/network.js'
|
import { isOnline } from '@/utils/network.js'
|
||||||
import { stableOperationId } from '@/utils/idempotency.js'
|
import { stableOperationId } from '@/utils/idempotency.js'
|
||||||
@@ -528,7 +531,7 @@
|
|||||||
this.remoteTaskId = options.taskId ? String(options.taskId) : null
|
this.remoteTaskId = options.taskId ? String(options.taskId) : null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onShow() {
|
async onShow() {
|
||||||
if (!requireSession()) return
|
if (!requireSession()) return
|
||||||
this.startHardwareScanner()
|
this.startHardwareScanner()
|
||||||
if (this.cameraPicking) {
|
if (this.cameraPicking) {
|
||||||
@@ -540,7 +543,8 @@
|
|||||||
this.debugEvidence('preview.resume', { evidenceIdentity: this.evidenceIdentity })
|
this.debugEvidence('preview.resume', { evidenceIdentity: this.evidenceIdentity })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.syncRemoteTask()
|
await this.refreshAvailableBoxes()
|
||||||
|
await this.syncRemoteTask()
|
||||||
},
|
},
|
||||||
onHide() {
|
onHide() {
|
||||||
if (this.evidencePreviewOpening) {
|
if (this.evidencePreviewOpening) {
|
||||||
@@ -558,6 +562,67 @@
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
isSelectableTask(task) {
|
||||||
|
return Boolean(task?.recordStatus === 'ACTIVE'
|
||||||
|
&& !task?.mergedIntoContainerBookingId
|
||||||
|
&& task?.taskId
|
||||||
|
&& task?.loadingPlanId
|
||||||
|
&& task?.containerBookingId
|
||||||
|
&& task?.currentContainerNo
|
||||||
|
&& task?.warehouseId
|
||||||
|
&& task?.version !== null
|
||||||
|
&& task?.version !== undefined
|
||||||
|
&& ['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'].includes(task?.workflowStatus))
|
||||||
|
},
|
||||||
|
boxFromTask(task, existing = null) {
|
||||||
|
return {
|
||||||
|
boxNo: String(task.currentContainerNo || existing?.boxNo || task.cabinetNo || '同步中'),
|
||||||
|
cabinetNo: task.cabinetNo ? String(task.cabinetNo) : (existing?.cabinetNo || null),
|
||||||
|
currentContainerNo: task.currentContainerNo ? String(task.currentContainerNo) : (existing?.currentContainerNo || null),
|
||||||
|
orderNo: String(task.displayNo || task.orderNo || existing?.orderNo || ''),
|
||||||
|
taskId: String(task.taskId),
|
||||||
|
loadingPlanId: task.loadingPlanId || existing?.loadingPlanId || null,
|
||||||
|
containerBookingId: task.containerBookingId || existing?.containerBookingId || null,
|
||||||
|
inboundId: existing?.inboundId || task.inboundId || null,
|
||||||
|
inboundVersion: existing?.inboundVersion ?? task.inboundVersion ?? null,
|
||||||
|
warehouseId: task.warehouseId || existing?.warehouseId || null,
|
||||||
|
tenantId: existing?.tenantId || task.tenantId || null,
|
||||||
|
items: existing?.items || []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
remapBoxState(previousBoxes, nextBoxes, state) {
|
||||||
|
const byTaskId = new Map(previousBoxes.map((box, index) => [String(box.taskId || ''), state[index]]))
|
||||||
|
return Object.fromEntries(nextBoxes.map((box, index) => [index, byTaskId.get(String(box.taskId || ''))]).filter(([, value]) => value !== undefined))
|
||||||
|
},
|
||||||
|
async refreshAvailableBoxes() {
|
||||||
|
if (!session.token) return
|
||||||
|
try {
|
||||||
|
const result = await listLoadingOrders({ page: 1, size: 50 })
|
||||||
|
const records = (result.records || []).filter((task) => this.isSelectableTask(task))
|
||||||
|
const previousBoxes = [...this.boxes]
|
||||||
|
const selectedTaskId = String(this.remoteTaskId || this.currentBox.taskId || '')
|
||||||
|
const existingByTaskId = new Map(previousBoxes.map((box) => [String(box.taskId || ''), box]))
|
||||||
|
const recordByTaskId = new Map(records.map((task) => [String(task.taskId), task]))
|
||||||
|
const orderedTaskIds = previousBoxes.map((box) => String(box.taskId || '')).filter((taskId) => recordByTaskId.has(taskId))
|
||||||
|
records.forEach((task) => { const taskId = String(task.taskId); if (!orderedTaskIds.includes(taskId)) orderedTaskIds.push(taskId) })
|
||||||
|
if (selectedTaskId && !orderedTaskIds.includes(selectedTaskId) && existingByTaskId.has(selectedTaskId)) orderedTaskIds.unshift(selectedTaskId)
|
||||||
|
const nextBoxes = orderedTaskIds.map((taskId) => {
|
||||||
|
const existing = existingByTaskId.get(taskId) || null
|
||||||
|
const task = recordByTaskId.get(taskId)
|
||||||
|
return task ? this.boxFromTask(task, existing) : existing
|
||||||
|
}).filter(Boolean)
|
||||||
|
if (!nextBoxes.length) return
|
||||||
|
this.boxDrafts = this.remapBoxState(previousBoxes, nextBoxes, this.boxDrafts)
|
||||||
|
this.completedBoxes = this.remapBoxState(previousBoxes, nextBoxes, this.completedBoxes)
|
||||||
|
this.boxes = nextBoxes
|
||||||
|
const selectedIndex = this.boxes.findIndex((box) => String(box.taskId || '') === selectedTaskId)
|
||||||
|
this.currentBoxIndex = selectedIndex >= 0 ? selectedIndex : 0
|
||||||
|
this.debugSubmit('tasks.available', { selectedTaskId, taskIds: this.boxes.map((box) => box.taskId), count: this.boxes.length })
|
||||||
|
} catch (error) {
|
||||||
|
this.debugSubmit('tasks.available.error', { message: error.message || '', code: error.code || '', requestIdFromServer: error.requestId || '', traceId: error.traceId || '' })
|
||||||
|
this.showToast(error.network ? '任务列表同步失败,请检查网络后重试' : '任务列表同步失败,当前任务仍可继续', 'warning')
|
||||||
|
}
|
||||||
|
},
|
||||||
selectStage(stage) {
|
selectStage(stage) {
|
||||||
if (stage === 'evidence' && !this.loadingStageReady) {
|
if (stage === 'evidence' && !this.loadingStageReady) {
|
||||||
this.showToast('请先完成新增待拣明细', 'warning')
|
this.showToast('请先完成新增待拣明细', 'warning')
|
||||||
@@ -953,6 +1018,7 @@
|
|||||||
validEvidenceEntries(type) { return this.evidenceEntries(type).filter((item) => item.id && item.fileId && item.uploadStatus !== 'PENDING') },
|
validEvidenceEntries(type) { return this.evidenceEntries(type).filter((item) => item.id && item.fileId && item.uploadStatus !== 'PENDING') },
|
||||||
evidenceEntry(type) { const list = this.evidenceEntries(type); return list.length ? list[list.length - 1] : null },
|
evidenceEntry(type) { const list = this.evidenceEntries(type); return list.length ? list[list.length - 1] : null },
|
||||||
hasPendingLocalEvidence(type) { return this.evidenceEntries(type).some((item) => item.localPath && !item.fileId) },
|
hasPendingLocalEvidence(type) { return this.evidenceEntries(type).some((item) => item.localPath && !item.fileId) },
|
||||||
|
hasUnboundUploadedEvidence(type) { return this.evidenceEntries(type).some((item) => item.fileId && !item.id) },
|
||||||
evidenceQueueEntry(type) {
|
evidenceQueueEntry(type) {
|
||||||
return [...this.evidenceQueueItems].reverse().find((item) => item.evidenceType === type) || null
|
return [...this.evidenceQueueItems].reverse().find((item) => item.evidenceType === type) || null
|
||||||
},
|
},
|
||||||
@@ -962,7 +1028,10 @@
|
|||||||
if (item.status === 'PENDING') return '本地待上传'
|
if (item.status === 'PENDING') return '本地待上传'
|
||||||
if (item.status === 'UPLOADING') return `上传中 ${item.progress || 0}%`
|
if (item.status === 'UPLOADING') return `上传中 ${item.progress || 0}%`
|
||||||
if (item.status === 'FAILED') return item.error || '上传失败'
|
if (item.status === 'FAILED') return item.error || '上传失败'
|
||||||
if (item.status === 'SUCCEEDED') return this.validEvidenceEntries(type).length ? '已上传并保存' : (item.attempts > 1 ? '重试成功' : '已上传')
|
if (item.status === 'SUCCEEDED') {
|
||||||
|
if (type === 'SEAL' && this.hasUnboundUploadedEvidence(type)) return '照片已上传,请输入封签号并保存'
|
||||||
|
return this.validEvidenceEntries(type).length ? '已上传并保存' : (item.attempts > 1 ? '重试成功' : '已上传')
|
||||||
|
}
|
||||||
if (item.status === 'REPLACED') return '已替换'
|
if (item.status === 'REPLACED') return '已替换'
|
||||||
return ''
|
return ''
|
||||||
},
|
},
|
||||||
@@ -1203,6 +1272,11 @@
|
|||||||
const entries = this.evidenceFiles[type] || []
|
const entries = this.evidenceFiles[type] || []
|
||||||
const current = entries.find((item) => item.localPath === path) || {}
|
const current = entries.find((item) => item.localPath === path) || {}
|
||||||
this.evidenceFiles = { ...this.evidenceFiles, [type]: entries.map((item) => item.localPath === path ? { ...item, fileId: latest.result?.fileId, fileName: latest.result?.fileName || item.fileName, uploadStatus: 'SUCCEEDED' } : item) }
|
this.evidenceFiles = { ...this.evidenceFiles, [type]: entries.map((item) => item.localPath === path ? { ...item, fileId: latest.result?.fileId, fileName: latest.result?.fileName || item.fileName, uploadStatus: 'SUCCEEDED' } : item) }
|
||||||
|
if (type === 'SEAL') {
|
||||||
|
this.rememberEvidenceDraft(identity)
|
||||||
|
this.showToast('带锁码照已上传,请输入封签号并点击保存封签', 'success')
|
||||||
|
return
|
||||||
|
}
|
||||||
await this.saveEvidence(type, null, path)
|
await this.saveEvidence(type, null, path)
|
||||||
},
|
},
|
||||||
async flushPendingEvidence() {
|
async flushPendingEvidence() {
|
||||||
@@ -1210,7 +1284,8 @@
|
|||||||
const pending = this.evidenceSlots.flatMap((slot) => this.evidenceEntries(slot.type).filter((entry) => !entry.id && entry.localPath).map((entry) => ({ type: slot.type, path: entry.localPath, fileId: entry.fileId })))
|
const pending = this.evidenceSlots.flatMap((slot) => this.evidenceEntries(slot.type).filter((entry) => !entry.id && entry.localPath).map((entry) => ({ type: slot.type, path: entry.localPath, fileId: entry.fileId })))
|
||||||
for (const item of pending) {
|
for (const item of pending) {
|
||||||
if (!this.loadingStageReady) return
|
if (!this.loadingStageReady) return
|
||||||
if (item.fileId) await this.saveEvidence(item.type, null, item.path)
|
if (item.fileId && item.type !== 'SEAL') await this.saveEvidence(item.type, null, item.path)
|
||||||
|
else if (item.fileId) continue
|
||||||
else await this.queueEvidenceUpload(item.type, item.path)
|
else await this.queueEvidenceUpload(item.type, item.path)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1228,10 +1303,16 @@
|
|||||||
if (latest?.status !== 'SUCCEEDED') { this.remoteError = latest?.error || '文件上传失败'; return }
|
if (latest?.status !== 'SUCCEEDED') { this.remoteError = latest?.error || '文件上传失败'; return }
|
||||||
const current = this.evidenceEntry(type) || {}
|
const current = this.evidenceEntry(type) || {}
|
||||||
this.evidenceFiles = { ...this.evidenceFiles, [type]: [...(this.evidenceFiles[type] || []).slice(0, -1), { ...current, fileId: latest.result?.fileId, fileName: latest.result?.fileName || current.fileName, uploadStatus: 'SUCCEEDED' }] }
|
this.evidenceFiles = { ...this.evidenceFiles, [type]: [...(this.evidenceFiles[type] || []).slice(0, -1), { ...current, fileId: latest.result?.fileId, fileName: latest.result?.fileName || current.fileName, uploadStatus: 'SUCCEEDED' }] }
|
||||||
|
if (type === 'SEAL') {
|
||||||
|
this.rememberEvidenceDraft(identity)
|
||||||
|
this.showToast('带锁码照已上传,请输入封签号并点击保存封签', 'success')
|
||||||
|
return
|
||||||
|
}
|
||||||
await this.saveEvidence(type)
|
await this.saveEvidence(type)
|
||||||
},
|
},
|
||||||
async saveEvidence(type, operationId = null, localPath = null) {
|
async saveEvidence(type, operationId = null, localPath = null) {
|
||||||
if (!this.requireEvidenceStage()) return
|
if (!this.requireEvidenceStage()) return
|
||||||
|
if (type === 'SEAL' && !String(this.sealNo || '').trim()) { this.showToast('请输入封签号后点击保存封签', 'warning'); return }
|
||||||
if (!this.evidenceIdentity?.inboundId) { this.showToast('状态尚未同步,请刷新重试', 'warning'); return }
|
if (!this.evidenceIdentity?.inboundId) { this.showToast('状态尚未同步,请刷新重试', 'warning'); return }
|
||||||
if (!isRemoteReady(this.evidenceIdentity.inboundId)) { this.showToast('当前会话未连接服务端,证据保留本地', 'warning'); return }
|
if (!isRemoteReady(this.evidenceIdentity.inboundId)) { this.showToast('当前会话未连接服务端,证据保留本地', 'warning'); return }
|
||||||
const contextId = this.evidenceContextId
|
const contextId = this.evidenceContextId
|
||||||
|
|||||||
@@ -92,8 +92,9 @@
|
|||||||
.task-error, .task-state { padding: 20px 14px; border-radius: 10px; background: #fff; color: #667085; font-size: 13px; line-height: 20px; text-align: center; }
|
.task-error, .task-state { padding: 20px 14px; border-radius: 10px; background: #fff; color: #667085; font-size: 13px; line-height: 20px; text-align: center; }
|
||||||
.task-error { color: #b42318; background: #fff1f0; }
|
.task-error { color: #b42318; background: #fff1f0; }
|
||||||
.task-notice { padding: 10px 12px; margin-bottom: 10px; border-left: 3px solid #f79009; border-radius: 6px; background: #fffaeb; color: #7a2e0e; font-size: 12px; line-height: 18px; }
|
.task-notice { padding: 10px 12px; margin-bottom: 10px; border-left: 3px solid #f79009; border-radius: 6px; background: #fffaeb; color: #7a2e0e; font-size: 12px; line-height: 18px; }
|
||||||
.task-list { display: flex; flex-direction: column; gap: 9px; }
|
.task-list { display: flex; flex-direction: column; }
|
||||||
.task-card { box-sizing: border-box; width: 100%; margin: 0; padding: 12px; border: 1px solid #dce6f1; border-radius: 10px; color: #12233f; text-align: left; background: #fff; box-shadow: 0 2px 8px rgba(23, 55, 93, .05); }
|
.task-card { box-sizing: border-box; width: 100%; margin: 0 0 12px; padding: 12px; border: 1px solid #dce6f1; border-radius: 10px; color: #12233f; text-align: left; background: #fff; box-shadow: 0 2px 8px rgba(23, 55, 93, .05); }
|
||||||
|
.task-card:last-child { margin-bottom: 0; }
|
||||||
.task-card:active { border-color: #8ab8ef; background: #f7faff; }
|
.task-card:active { border-color: #8ab8ef; background: #f7faff; }
|
||||||
.task-card[disabled] { opacity: .62; }
|
.task-card[disabled] { opacity: .62; }
|
||||||
.task-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
|
.task-card-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
|
||||||
|
|||||||
+3
-2
@@ -1152,8 +1152,9 @@
|
|||||||
.sync-state.ready { color: $success; background: #e8f8ef; }
|
.sync-state.ready { color: $success; background: #e8f8ef; }
|
||||||
.sync-state.error { color: $danger; background: #fff0f0; }
|
.sync-state.error { color: $danger; background: #fff0f0; }
|
||||||
.sync-state.loading { color: #8a5a00; background: #fff7df; }
|
.sync-state.loading { color: #8a5a00; background: #fff7df; }
|
||||||
.evidence-grid { display: flex; flex-direction: column; gap: 8px; margin-top: 12px; }
|
.evidence-grid { display: flex; flex-direction: column; margin-top: 12px; }
|
||||||
.evidence-card { padding: 10px; border: 1px solid #d7e1ed; border-radius: 9px; background: #f8fafc; }
|
.evidence-card { margin-bottom: 12px; padding: 10px; border: 1px solid #d7e1ed; border-radius: 9px; background: #f8fafc; }
|
||||||
|
.evidence-card:last-child { margin-bottom: 0; }
|
||||||
.evidence-card.filled { border-color: #a7d6ba; background: #f4fbf6; }
|
.evidence-card.filled { border-color: #a7d6ba; background: #f4fbf6; }
|
||||||
.evidence-card-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
.evidence-card-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
|
||||||
.evidence-card-title { display: flex; min-width: 0; align-items: center; gap: 7px; }
|
.evidence-card-title { display: flex; min-width: 0; align-items: center; gap: 7px; }
|
||||||
|
|||||||
@@ -138,6 +138,17 @@ test('picking page keeps scan, draft and risky transition contracts', async () =
|
|||||||
assert.match(page, /leavePicking\(\)[\s\S]*?navigateBackOr\('\/pages\/home\/home'\)/)
|
assert.match(page, /leavePicking\(\)[\s\S]*?navigateBackOr\('\/pages\/home\/home'\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('picking cabinet selector synchronizes all ready server tasks by stable task id', async () => {
|
||||||
|
const page = await read('../pages/picking/picking.vue')
|
||||||
|
assert.match(page, /listLoadingOrders/)
|
||||||
|
assert.match(page, /async onShow\(\)[\s\S]*?await this\.refreshAvailableBoxes\(\)[\s\S]*?await this\.syncRemoteTask\(\)/)
|
||||||
|
assert.match(page, /async refreshAvailableBoxes\(\)[\s\S]*?listLoadingOrders\(\{ page: 1, size: 50 \}\)/)
|
||||||
|
assert.match(page, /isSelectableTask\(task\)[\s\S]*?recordStatus === 'ACTIVE'[\s\S]*?\['UNASSIGNED', 'IN_PROGRESS', 'AWAITING_REVIEW'\]/)
|
||||||
|
assert.match(page, /existingByTaskId[\s\S]*?recordByTaskId[\s\S]*?orderedTaskIds[\s\S]*?this\.boxes = nextBoxes/)
|
||||||
|
assert.match(page, /selectedTaskId[\s\S]*?findIndex\(\(box\) => String\(box\.taskId \|\| ''\) === selectedTaskId\)/)
|
||||||
|
assert.match(page, /selectBox\(index\)[\s\S]*?this\.remoteTaskId = this\.currentBox\.taskId \|\| null[\s\S]*?this\.resetEvidenceState\(\)[\s\S]*?this\.syncRemoteTask\(\)/)
|
||||||
|
})
|
||||||
|
|
||||||
test('navigation returns to the real source and prevents duplicate utility pages', async () => {
|
test('navigation returns to the real source and prevents duplicate utility pages', async () => {
|
||||||
const [navigation, header, modulePage, operation, picking] = await Promise.all([
|
const [navigation, header, modulePage, operation, picking] = await Promise.all([
|
||||||
read('../utils/navigation.js'), read('../components/PortalHeader.vue'), read('../pages/module/module.vue'),
|
read('../utils/navigation.js'), read('../components/PortalHeader.vue'), read('../pages/module/module.vue'),
|
||||||
@@ -241,7 +252,7 @@ test('picking session persists per task identity and clears on logout', async ()
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('shared visual contracts preserve accessible PDA targets', async () => {
|
test('shared visual contracts preserve accessible PDA targets', async () => {
|
||||||
const [app, styles, picking] = await Promise.all([read('../App.vue'), read('../styles/pda.scss'), read('../pages/picking/picking.vue')])
|
const [app, styles, picking, tasks] = await Promise.all([read('../App.vue'), read('../styles/pda.scss'), read('../pages/picking/picking.vue'), read('../pages/tasks/tasks.vue')])
|
||||||
assert.match(app, /@import '@\/styles\/pda\.scss'/)
|
assert.match(app, /@import '@\/styles\/pda\.scss'/)
|
||||||
assert.doesNotMatch(styles, /<\/?style\b/)
|
assert.doesNotMatch(styles, /<\/?style\b/)
|
||||||
for (const className of ['pick-btn', 'partial-quick-btn']) assert.match(styles, new RegExp(`\\.${className}\\s*\\{[\\s\\S]*?height:\\s*48px;`))
|
for (const className of ['pick-btn', 'partial-quick-btn']) assert.match(styles, new RegExp(`\\.${className}\\s*\\{[\\s\\S]*?height:\\s*48px;`))
|
||||||
@@ -250,6 +261,10 @@ test('shared visual contracts preserve accessible PDA targets', async () => {
|
|||||||
assert.match(styles, /\.modal-sheet\s*\{[\s\S]*?padding:\s*20px 18px 18px;/)
|
assert.match(styles, /\.modal-sheet\s*\{[\s\S]*?padding:\s*20px 18px 18px;/)
|
||||||
assert.doesNotMatch(styles, /\.modal-sheet\s*\{[\s\S]*?padding:[^;]*env\(/)
|
assert.doesNotMatch(styles, /\.modal-sheet\s*\{[\s\S]*?padding:[^;]*env\(/)
|
||||||
assert.match(styles, /\.submit-decision-modal\s*\{[^}]*padding:\s*18px 16px 16px;/)
|
assert.match(styles, /\.submit-decision-modal\s*\{[^}]*padding:\s*18px 16px 16px;/)
|
||||||
|
assert.match(styles, /\.evidence-card\s*\{[^}]*margin-bottom:\s*12px;/)
|
||||||
|
assert.match(styles, /\.evidence-card:last-child\s*\{[^}]*margin-bottom:\s*0;/)
|
||||||
|
assert.match(tasks, /\.task-card\s*\{[^}]*margin:\s*0 0 12px;/)
|
||||||
|
assert.match(tasks, /\.task-card:last-child\s*\{[^}]*margin-bottom:\s*0;/)
|
||||||
assert.match(styles, /\.work-content\s*\{[^}]*padding:\s*1px 0 104px;/)
|
assert.match(styles, /\.work-content\s*\{[^}]*padding:\s*1px 0 104px;/)
|
||||||
assert.match(styles, /\.submit-section\s*\{[\s\S]*?bottom:\s*8px;[\s\S]*?width:\s*calc\(100% - 20px\);/)
|
assert.match(styles, /\.submit-section\s*\{[\s\S]*?bottom:\s*8px;[\s\S]*?width:\s*calc\(100% - 20px\);/)
|
||||||
assert.match(styles, /\.submit-btn\s*\{[\s\S]*?flex:\s*1;[\s\S]*?height:\s*56px;/)
|
assert.match(styles, /\.submit-btn\s*\{[\s\S]*?flex:\s*1;[\s\S]*?height:\s*56px;/)
|
||||||
|
|||||||
@@ -99,10 +99,22 @@ test('final completion preserves backend gate errors and refreshes after an atom
|
|||||||
assert.match(save, /this\.isTaskConflict\(error\)[\s\S]*?refreshAfterTaskConflict/)
|
assert.match(save, /this\.isTaskConflict\(error\)[\s\S]*?refreshAfterTaskConflict/)
|
||||||
const flush = page.match(/async flushPendingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
const flush = page.match(/async flushPendingEvidence\(\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||||
assert.match(flush, /!entry\.id && entry\.localPath/)
|
assert.match(flush, /!entry\.id && entry\.localPath/)
|
||||||
assert.match(flush, /if \(item\.fileId\) await this\.saveEvidence\(item\.type, null, item\.path\)/)
|
assert.match(flush, /if \(item\.fileId && item\.type !== 'SEAL'\) await this\.saveEvidence\(item\.type, null, item\.path\)/)
|
||||||
|
assert.match(flush, /else if \(item\.fileId\) continue/)
|
||||||
assert.match(flush, /else await this\.queueEvidenceUpload\(item\.type, item\.path\)/)
|
assert.match(flush, /else await this\.queueEvidenceUpload\(item\.type, item\.path\)/)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('seal photo upload waits for an explicit seal-number save', () => {
|
||||||
|
const queue = page.match(/async queueEvidenceUpload\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||||
|
const retry = page.match(/async retryEvidence\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||||
|
const save = page.match(/async saveEvidence\([^)]*\) \{[\s\S]*?\n\t\t\t\},/)?.[0] || ''
|
||||||
|
assert.match(queue, /if \(type === 'SEAL'\)[\s\S]*?rememberEvidenceDraft\(identity\)[\s\S]*?return[\s\S]*?saveEvidence\(type, null, path\)/)
|
||||||
|
assert.match(retry, /if \(type === 'SEAL'\)[\s\S]*?rememberEvidenceDraft\(identity\)[\s\S]*?return[\s\S]*?saveEvidence\(type\)/)
|
||||||
|
assert.match(save, /type === 'SEAL' && !String\(this\.sealNo \|\| ''\)\.trim\(\)[\s\S]*?请输入封签号后点击保存封签/)
|
||||||
|
assert.match(page, /async saveSealNo\(\)[\s\S]*?await this\.saveEvidence\('SEAL'/)
|
||||||
|
assert.match(page, /hasUnboundUploadedEvidence\(slot\.type\)[\s\S]*?照片已上传,请输入封签号并保存/)
|
||||||
|
})
|
||||||
|
|
||||||
test('evidence identity is bound to task, inbound, loading plan and container booking', () => {
|
test('evidence identity is bound to task, inbound, loading plan and container booking', () => {
|
||||||
assert.match(page, /captureEvidenceIdentity\(\)[\s\S]*?taskId:[\s\S]*?inboundId:[\s\S]*?loadingPlanId:[\s\S]*?containerBookingId:/)
|
assert.match(page, /captureEvidenceIdentity\(\)[\s\S]*?taskId:[\s\S]*?inboundId:[\s\S]*?loadingPlanId:[\s\S]*?containerBookingId:/)
|
||||||
assert.match(page, /isSameEvidenceIdentity\(left, right\)[\s\S]*?taskId[\s\S]*?inboundId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/)
|
assert.match(page, /isSameEvidenceIdentity\(left, right\)[\s\S]*?taskId[\s\S]*?inboundId[\s\S]*?loadingPlanId[\s\S]*?containerBookingId/)
|
||||||
|
|||||||
Reference in New Issue
Block a user