提交
This commit is contained in:
1 parent
4daf990fab
commit
1ca2668112
219 files changed
+29817
-20945
No files matched your search
@@ -77,7 +77,7 @@
|
||||
>
|
||||
<template #default="{ row }">{{ row.expectedCellCount ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="活性" min-width="80" align="center" header-align="center">
|
||||
<el-table-column label="活率(%)" min-width="80" align="center" header-align="center">
|
||||
<template #default="{ row }">{{
|
||||
row.viability != null ? row.viability : '—'
|
||||
}}</template>
|
||||
|
||||
@@ -76,10 +76,15 @@
|
||||
</template>
|
||||
<template #task-attachments>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="viewDetail?.attachmentFiles || []"
|
||||
v-if="taskAttachmentFiles.length"
|
||||
:files="taskAttachmentFiles"
|
||||
@preview="handleAttachmentPreview"
|
||||
@download="handleAttachmentDownload"
|
||||
/>
|
||||
<p v-else class="view-empty-hint">—</p>
|
||||
</template>
|
||||
<template #params-empty>
|
||||
<p class="view-empty-hint">—</p>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
</template>
|
||||
@@ -90,7 +95,7 @@
|
||||
import ProtocolAttachmentReadonly from '@/views/cell-storage/protocol/ProtocolAttachmentReadonly.vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import { fetchQcTaskDetail, type QcTaskDetail } from '@/api/qc-task'
|
||||
import { fetchQcTaskDetail, type QcTask, type QcTaskDetail } from '@/api/qc-task'
|
||||
import {
|
||||
fetchQcTaskResults,
|
||||
fetchQcTaskMaterialUsages,
|
||||
@@ -109,12 +114,21 @@
|
||||
workOrderStatusLabel,
|
||||
qcTaskConclusionLabel
|
||||
} from '@/utils/qc-labels'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import { fetchQcInspectionBatches, loadQcInspectionBatches } from '@/utils/qc-inspection-batch'
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { loadQcInspectionBatches, type QcInspectionBatch } from '@/utils/qc-inspection-batch'
|
||||
import {
|
||||
qcAttachmentFilesFromCommaUrls,
|
||||
qcPreviewAttachment,
|
||||
qcDownloadAttachment
|
||||
} from '@/views/quality-inspection/qc-work-order/composables/useQcViewAttachmentHelpers'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
taskId?: string
|
||||
/** 协议/捐赠等嵌入场景:工单摘要内任务行,详情接口失败时作回退 */
|
||||
initialTask?: Partial<QcTask> | null
|
||||
/** 嵌入场景批次列表,用于展示检测批次而无需额外权限 */
|
||||
inspectionBatches?: QcInspectionBatch[] | null
|
||||
/** 不展示样本信息、运输信息、样本接收(出库放行审核等) */
|
||||
hideSampleContext?: boolean
|
||||
}>()
|
||||
@@ -143,7 +157,6 @@
|
||||
const viewMaterialUsages = ref<ViewMaterialUsageRow[]>([])
|
||||
const viewBatchName = ref('')
|
||||
const viewBatchNo = ref('')
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
|
||||
function materialRowsFromApi(items: QcTaskMaterialUsageItem[]): ViewMaterialUsageRow[] {
|
||||
return (items || [])
|
||||
@@ -159,9 +172,50 @@
|
||||
}))
|
||||
}
|
||||
|
||||
function mergeTaskDetail(base: Partial<QcTask>, fetched?: QcTaskDetail | null): QcTaskDetail {
|
||||
const merged = { ...(base as QcTaskDetail), ...(fetched || {}) }
|
||||
if (!merged.id && base.id) merged.id = base.id
|
||||
return merged
|
||||
}
|
||||
|
||||
function resolveBatchMeta(
|
||||
detail: QcTaskDetail,
|
||||
batches?: QcInspectionBatch[] | null
|
||||
) {
|
||||
const batchId = (detail.batchId || '').trim()
|
||||
if (!batchId) {
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
return
|
||||
}
|
||||
const embedded = batches || props.inspectionBatches
|
||||
if (embedded?.length) {
|
||||
const batch = embedded.find((b) => b.id === batchId)
|
||||
viewBatchName.value = batch?.batchName || ''
|
||||
viewBatchNo.value = batch?.batchNo || ''
|
||||
return
|
||||
}
|
||||
const workOrderId = (detail.workOrderId || '').trim()
|
||||
if (!workOrderId) {
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
return
|
||||
}
|
||||
loadQcInspectionBatches(workOrderId, embedded)
|
||||
.then((list) => {
|
||||
const batch = list.find((b) => b.id === batchId)
|
||||
viewBatchName.value = batch?.batchName || ''
|
||||
viewBatchNo.value = batch?.batchNo || ''
|
||||
})
|
||||
.catch(() => {
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => [props.visible, props.taskId] as const,
|
||||
async ([vis, id]) => {
|
||||
() => [props.visible, props.taskId, props.initialTask] as const,
|
||||
async ([vis, id, preset]) => {
|
||||
if (!vis || !id) {
|
||||
viewDetail.value = null
|
||||
viewMaterialUsages.value = []
|
||||
@@ -170,28 +224,35 @@
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
try {
|
||||
const detail = await fetchQcTaskDetail(id)
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
const batchId = (detail.batchId || '').trim()
|
||||
const workOrderId = (detail.workOrderId || '').trim()
|
||||
if (batchId && workOrderId) {
|
||||
try {
|
||||
const batches = await loadQcInspectionBatches(workOrderId)
|
||||
const batch = batches.find((b) => b.id === batchId)
|
||||
viewBatchName.value = batch?.batchName || ''
|
||||
viewBatchNo.value = batch?.batchNo || ''
|
||||
} catch {
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
}
|
||||
}
|
||||
let detail: QcTaskDetail | null = null
|
||||
try {
|
||||
const results = await fetchQcTaskResults(id)
|
||||
;(detail as QcTaskDetail).results = results || []
|
||||
detail = await fetchQcTaskDetail(id)
|
||||
} catch {
|
||||
;(detail as QcTaskDetail).results = []
|
||||
detail = null
|
||||
}
|
||||
if (!detail && preset) {
|
||||
detail = mergeTaskDetail(preset)
|
||||
} else if (detail && preset) {
|
||||
detail = mergeTaskDetail(preset, detail)
|
||||
}
|
||||
if (!detail) {
|
||||
viewDetail.value = null
|
||||
viewMaterialUsages.value = []
|
||||
return
|
||||
}
|
||||
resolveBatchMeta(detail, props.inspectionBatches)
|
||||
if ((detail.taskStatus || '').trim() !== 'dzx') {
|
||||
try {
|
||||
const results = await fetchQcTaskResults(id)
|
||||
detail.results = results || []
|
||||
} catch {
|
||||
detail.results = []
|
||||
}
|
||||
} else {
|
||||
detail.results = []
|
||||
}
|
||||
viewMaterialUsages.value = []
|
||||
if (SHOW_QC_TASK_MATERIAL_USAGE && detail.taskType !== 'dsfjc') {
|
||||
@@ -206,7 +267,7 @@
|
||||
}
|
||||
viewDetail.value = detail
|
||||
} catch {
|
||||
viewDetail.value = null
|
||||
viewDetail.value = preset ? mergeTaskDetail(preset) : null
|
||||
viewMaterialUsages.value = []
|
||||
viewBatchName.value = ''
|
||||
viewBatchNo.value = ''
|
||||
@@ -217,16 +278,20 @@
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
const taskAttachmentFiles = computed(() => {
|
||||
const d = viewDetail.value
|
||||
if (!d) return [] as Array<Record<string, unknown>>
|
||||
if (Array.isArray(d.attachmentFiles) && d.attachmentFiles.length) {
|
||||
return d.attachmentFiles as Array<Record<string, unknown>>
|
||||
}
|
||||
return qcAttachmentFilesFromCommaUrls(String(d.thirdPartyAttachments || ''))
|
||||
})
|
||||
|
||||
function formatMetric(v: unknown) {
|
||||
if (v === null || v === undefined || v === '') return '—'
|
||||
return String(v)
|
||||
}
|
||||
|
||||
function formatCheckResults(items?: Array<{ itemName?: string; checked?: boolean }>) {
|
||||
if (!items?.length) return '—'
|
||||
return items.map((x) => `${x.itemName || ''}${x.checked ? '(✓)' : '(×)'}`).join('、')
|
||||
}
|
||||
|
||||
const viewReceptionAttachmentFiles = computed(() => {
|
||||
const r = viewDetail.value?.reception
|
||||
if (!r) return []
|
||||
@@ -325,7 +390,7 @@
|
||||
value:
|
||||
r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || ''))
|
||||
},
|
||||
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
|
||||
{ label: '检查项', checkItems: r.checkResults ?? [], span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
]
|
||||
}
|
||||
@@ -343,6 +408,27 @@
|
||||
},
|
||||
{ label: '备注', value: d.remark || '—' }
|
||||
]
|
||||
const itemSection: ViewSection = {
|
||||
title: '检测项目',
|
||||
fields: [
|
||||
{
|
||||
label: '检测类型',
|
||||
value: taskTypeLabel(d.taskType),
|
||||
tagType: taskTypeTagType(d.taskType)
|
||||
},
|
||||
{ label: '项目名称', value: d.itemName || '—' },
|
||||
{ label: '项目编码', value: d.itemCode || '—' },
|
||||
...(d.taskType === 'dsfjc'
|
||||
? [
|
||||
{ label: '第三方公司', value: d.companyName || '—' },
|
||||
{
|
||||
label: '检测费用(元)',
|
||||
value: d.feeAmount != null ? `¥${formatMoneyDisplay(d.feeAmount)}` : '—'
|
||||
}
|
||||
]
|
||||
: [])
|
||||
]
|
||||
}
|
||||
const resultSection: ViewSection[] = [
|
||||
{
|
||||
title: '质检结论',
|
||||
@@ -358,13 +444,13 @@
|
||||
}))
|
||||
}
|
||||
]
|
||||
: [{ title: '检测参数', fields: [] }])
|
||||
: [{ title: '检测参数', fields: [], slot: 'params-empty' }])
|
||||
]
|
||||
const tailSections: ViewSection[] = []
|
||||
if (SHOW_QC_TASK_MATERIAL_USAGE && d.taskType !== 'dsfjc') {
|
||||
tailSections.push({ title: '物料使用', fields: [], slot: 'material-usages' })
|
||||
}
|
||||
if (d.attachmentFiles?.length) {
|
||||
if (d.taskType === 'dsfjc' || taskAttachmentFiles.value.length) {
|
||||
tailSections.push({
|
||||
title: d.taskType === 'dsfjc' ? '检测材料' : '检测附件',
|
||||
fields: [],
|
||||
@@ -401,17 +487,16 @@
|
||||
: 'info'
|
||||
},
|
||||
{
|
||||
label: '检测类型',
|
||||
value: taskTypeLabel(d.taskType),
|
||||
tagType: taskTypeTagType(d.taskType)
|
||||
label: '任务状态',
|
||||
value: taskStatusLabel(d.taskStatus),
|
||||
tagType: d.taskStatus === 'ywc' ? 'success' : d.taskStatus === 'jxz' ? 'warning' : 'info'
|
||||
},
|
||||
{ label: '检测项目', value: d.itemName },
|
||||
{ label: '项目编码', value: d.itemCode || '—' },
|
||||
{ label: '第三方公司', value: d.companyName || '—' },
|
||||
{ label: '负责人', value: d.assigneeUserNames || '—' },
|
||||
{ label: '完成时间', value: d.completedTime || '—' },
|
||||
{ label: '创建时间', value: d.createTime || '—' }
|
||||
]
|
||||
},
|
||||
itemSection,
|
||||
...cellProductSection,
|
||||
...sampleSections,
|
||||
...resultSection,
|
||||
@@ -419,21 +504,8 @@
|
||||
]
|
||||
})
|
||||
|
||||
function attachmentFullUrl(url: string) {
|
||||
const u = String(url || '').trim()
|
||||
if (!u) return ''
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
|
||||
}
|
||||
|
||||
function handleAttachmentPreview(file: Record<string, unknown>) {
|
||||
const url = attachmentFullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleAttachmentDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: attachmentFullUrl })
|
||||
}
|
||||
const handleAttachmentPreview = qcPreviewAttachment
|
||||
const handleAttachmentDownload = qcDownloadAttachment
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
<QcTaskViewDialog
|
||||
v-model:visible="taskViewVisible"
|
||||
:task-id="taskViewId"
|
||||
:initial-task="taskViewPreset"
|
||||
:inspection-batches="qcInfo?.inspectionBatches"
|
||||
:hide-sample-context="hideSampleContext"
|
||||
/>
|
||||
</Teleport>
|
||||
@@ -132,6 +134,7 @@
|
||||
|
||||
const taskViewVisible = ref(false)
|
||||
const taskViewId = ref('')
|
||||
const taskViewPreset = ref<SampleQcTask | null>(null)
|
||||
|
||||
const hasTaskContent = computed(
|
||||
() =>
|
||||
@@ -156,6 +159,7 @@
|
||||
function openTaskView(task: SampleQcTask) {
|
||||
if (!task.id) return
|
||||
taskViewId.value = task.id
|
||||
taskViewPreset.value = task
|
||||
taskViewVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -38,12 +38,6 @@
|
||||
<el-table-column label="样本编码" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ row.sampleCode || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="采集量" min-width="88" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ row.collectAmount ?? '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="72" align="center" header-align="center">
|
||||
<template #default="{ row }">{{ displayUnitName(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="showPrice"
|
||||
label="原价格(元)"
|
||||
@@ -193,38 +187,6 @@
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!hideCollectDetailFields && !hideDialogCollectAmountUnit"
|
||||
label="采集量"
|
||||
>
|
||||
<el-input
|
||||
v-model="sampleDraft.collectAmount"
|
||||
type="number"
|
||||
step="any"
|
||||
:min="0"
|
||||
style="width: 100%"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="!hideCollectDetailFields && !hideDialogCollectAmountUnit"
|
||||
label="单位"
|
||||
>
|
||||
<el-select
|
||||
v-model="sampleDraft.unitId"
|
||||
placeholder="输入单位检索"
|
||||
filterable
|
||||
remote
|
||||
reserve-keyword
|
||||
clearable
|
||||
:loading="unitOptionsLoading"
|
||||
:remote-method="handleRemoteSearchUnit"
|
||||
style="width: 100%"
|
||||
@visible-change="onUnitSelectVisible"
|
||||
@change="onSampleUnitChange"
|
||||
>
|
||||
<el-option v-for="u in unitOptions" :key="u.id" :label="u.unitName" :value="u.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showPrice" label="原价格(元)">
|
||||
<el-input
|
||||
:model-value="fmtMoney(sampleDraft.originalPrice)"
|
||||
|
||||
@@ -71,6 +71,22 @@
|
||||
<ul v-else-if="field.items?.length" class="view-plain-list">
|
||||
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
|
||||
</ul>
|
||||
<div
|
||||
v-else-if="field.checkItems !== undefined"
|
||||
class="check-list check-list-view"
|
||||
>
|
||||
<template v-if="field.checkItems.length">
|
||||
<el-checkbox
|
||||
v-for="item in field.checkItems"
|
||||
:key="item.itemCode + String(item.checkItemId ?? item.itemName)"
|
||||
:model-value="item.checked"
|
||||
disabled
|
||||
>
|
||||
{{ item.itemName }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
<template v-else>{{ formatViewValue(field.value) }}</template>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -97,6 +113,22 @@
|
||||
<ul v-else-if="field.items?.length" class="view-plain-list">
|
||||
<li v-for="(item, idx) in field.items" :key="idx">{{ item }}</li>
|
||||
</ul>
|
||||
<div
|
||||
v-else-if="field.checkItems !== undefined"
|
||||
class="check-list check-list-view"
|
||||
>
|
||||
<template v-if="field.checkItems.length">
|
||||
<el-checkbox
|
||||
v-for="item in field.checkItems"
|
||||
:key="item.itemCode + String(item.checkItemId ?? item.itemName)"
|
||||
:model-value="item.checked"
|
||||
disabled
|
||||
>
|
||||
{{ item.itemName }}
|
||||
</el-checkbox>
|
||||
</template>
|
||||
<template v-else>—</template>
|
||||
</div>
|
||||
<template v-else>{{ formatViewValue(field.value) }}</template>
|
||||
</dd>
|
||||
</div>
|
||||
@@ -145,11 +177,20 @@
|
||||
|
||||
export type TagType = 'success' | 'warning' | 'danger' | 'info' | '' | 'primary'
|
||||
|
||||
export interface ViewCheckItem {
|
||||
itemCode?: string
|
||||
itemName?: string
|
||||
checked?: boolean
|
||||
checkItemId?: string | number
|
||||
}
|
||||
|
||||
export interface ViewField {
|
||||
label: string
|
||||
value?: string | number | boolean | null | undefined
|
||||
/** 无编号列表(ul),与 value / tagType 二选一 */
|
||||
items?: string[]
|
||||
/** 样本接收检查项(只读多选框) */
|
||||
checkItems?: ViewCheckItem[]
|
||||
span?: boolean
|
||||
/** 传入后以 el-tag 渲染,值为 el-tag 的 type 属性 */
|
||||
tagType?: TagType
|
||||
@@ -581,6 +622,42 @@
|
||||
color: var(--mint-text-muted, #334155);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.check-list-view {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 16px;
|
||||
pointer-events: none;
|
||||
|
||||
.el-checkbox.is-disabled {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__label {
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.el-checkbox__input.is-disabled {
|
||||
opacity: 1;
|
||||
cursor: default;
|
||||
|
||||
.el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-bg-color);
|
||||
border-color: var(--el-checkbox-input-border-color);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.is-checked .el-checkbox__inner {
|
||||
background-color: var(--el-checkbox-checked-bg-color);
|
||||
border-color: var(--el-checkbox-checked-input-border-color);
|
||||
|
||||
&::after {
|
||||
border-color: var(--el-checkbox-checked-icon-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.system-view-dialog--compact {
|
||||
|
||||
Reference in new issue
Block a user