feat(qc): add outbound QC work order tab with list, filters and action buttons
This commit is contained in:
1 parent
b32196690b
commit
b20c9f7bd7
1 file changed
+434
-77
@@ -4,7 +4,9 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>质检工单</h1>
|
||||
<p class="page-desc">管理入库与临时质检工单;入库工单由样本接收后自动生成,临时工单可独立创建并配置检测项目。</p>
|
||||
<p class="page-desc"
|
||||
>管理入库、临时与出库质检工单;入库工单由样本接收后自动生成,临时工单可独立创建并配置检测项目,出库工单由出库申请触发生成。</p
|
||||
>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -12,9 +14,14 @@
|
||||
<h2>工单列表</h2>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTypeTab" class="work-order-type-tabs" @tab-change="handleTypeTabChange">
|
||||
<el-tabs
|
||||
v-model="activeTypeTab"
|
||||
class="work-order-type-tabs"
|
||||
@tab-change="handleTypeTabChange"
|
||||
>
|
||||
<el-tab-pane label="入库质检工单" name="inbound" />
|
||||
<el-tab-pane label="临时质检工单" name="temporary" />
|
||||
<el-tab-pane label="出库质检工单" name="outbound" />
|
||||
<el-tab-pane label="全部质检工单" name="all" />
|
||||
</el-tabs>
|
||||
|
||||
@@ -24,15 +31,33 @@
|
||||
<el-form-item label="关键词">
|
||||
<el-input
|
||||
v-model="searchForm.keyword"
|
||||
:placeholder="activeTypeTab === 'temporary' ? '工单号' : '工单号/协议号/样本编码'"
|
||||
:placeholder="
|
||||
activeTypeTab === 'temporary'
|
||||
? '工单号'
|
||||
: activeTypeTab === 'outbound'
|
||||
? '工单号/申请单号/申请单名称'
|
||||
: '工单号/协议号/样本编码'
|
||||
"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="工单状态">
|
||||
<el-select v-model="searchForm.workOrderStatus" placeholder="全部" clearable style="width: 100%">
|
||||
<el-select
|
||||
v-model="searchForm.workOrderStatus"
|
||||
placeholder="全部"
|
||||
clearable
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option label="待配置" value="dpz" />
|
||||
<el-option label="已配置" value="ypz" />
|
||||
<el-option label="已完成" value="ywc" />
|
||||
<el-option label="已作废" value="yzf" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="activeTypeTab === 'outbound'" label="业务类型">
|
||||
<el-select v-model="searchForm.bizType" placeholder="全部" clearable style="width: 100%">
|
||||
<el-option label="中试服务" value="pilot_service" />
|
||||
<el-option label="临床应用" value="clinical_application" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建时间">
|
||||
@@ -50,68 +75,232 @@
|
||||
<div class="filter-actions-row">
|
||||
<span class="filter-hint">默认展示近 30 天内创建的工单。</span>
|
||||
<div class="filter-actions-end">
|
||||
<el-button class="btn-ghost" @click="handleReset"><el-icon><RefreshLeft /></el-icon>重置</el-button>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"><el-icon><Search /></el-icon>搜索</el-button>
|
||||
<el-button class="btn-ghost" @click="handleReset"
|
||||
><el-icon><RefreshLeft /></el-icon>重置</el-button
|
||||
>
|
||||
<el-button class="btn-main" type="primary" @click="handleSearch"
|
||||
><el-icon><Search /></el-icon>搜索</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<div v-if="activeTypeTab !== 'all'" class="list-toolbar">
|
||||
<div v-if="activeTypeTab !== 'all' && activeTypeTab !== 'outbound'" class="list-toolbar">
|
||||
<div class="list-toolbar-row">
|
||||
<el-button class="btn-main" type="primary" @click="handleAdd">
|
||||
<el-icon><Plus /></el-icon>{{ activeTypeTab === 'temporary' ? '新增临时工单' : '新增入库质检工单' }}
|
||||
<el-icon><Plus /></el-icon
|
||||
>{{ activeTypeTab === 'temporary' ? '新增临时工单' : '新增入库质检工单' }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table class="list-table" :data="tableData" v-loading="loading" stripe @row-dblclick="handleView">
|
||||
<el-table-column type="index" label="序号" min-width="68" align="center" :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table-column prop="workOrderNo" label="工单号" min-width="150" align="center" />
|
||||
<el-table-column v-if="activeTypeTab === 'all'" prop="workOrderType" label="工单类型" min-width="100" align="center">
|
||||
<el-table
|
||||
:class="['list-table', { 'list-table--no-action-col list-table--all-tab': activeTypeTab === 'all' }]"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
:index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="workOrderNo"
|
||||
label="工单号"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="showOutboundCols"
|
||||
prop="bizTypeLabel"
|
||||
label="业务类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.workOrderType === 'temporary' ? 'warning' : 'success'" size="small">
|
||||
<el-tag
|
||||
:type="
|
||||
row.bizType === 'pilot_service'
|
||||
? 'primary'
|
||||
: row.bizType === 'clinical_application'
|
||||
? 'warning'
|
||||
: 'info'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{ row.bizTypeLabel || '—' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
v-if="showOutboundCols"
|
||||
prop="applicationNo"
|
||||
label="申请单号"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="showOutboundCols"
|
||||
prop="applicationName"
|
||||
label="申请单名称"
|
||||
min-width="150"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="showOutboundCols"
|
||||
prop="channelName"
|
||||
label="渠道名称"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="showOutboundCols"
|
||||
prop="infusionTime"
|
||||
label="输注时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
v-if="activeTypeTab === 'all'"
|
||||
prop="workOrderType"
|
||||
label="工单类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="row.workOrderType === 'temporary' ? 'warning' : 'success'"
|
||||
size="small"
|
||||
>
|
||||
{{ row.workOrderType === 'temporary' ? '临时' : '入库' }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="activeTypeTab !== 'inbound'" prop="qcBizTypeLabel" label="业务类型" min-width="100" align="center">
|
||||
<el-table-column
|
||||
v-if="activeTypeTab !== 'inbound'"
|
||||
prop="qcBizTypeLabel"
|
||||
label="业务类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
:type="qcBizTypeTagType(row.workOrderType === 'temporary' ? 'temporary' : (row.qcBizType || 'inbound'))"
|
||||
:type="
|
||||
qcBizTypeTagType(
|
||||
row.workOrderType === 'temporary' ? 'temporary' : row.qcBizType || 'inbound'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{ row.qcBizTypeLabel || qcBizTypeLabel(row.workOrderType === 'temporary' ? 'temporary' : (row.qcBizType || 'inbound')) }}
|
||||
{{
|
||||
row.qcBizTypeLabel ||
|
||||
qcBizTypeLabel(
|
||||
row.workOrderType === 'temporary' ? 'temporary' : row.qcBizType || 'inbound'
|
||||
)
|
||||
}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showTempProductCols" prop="cellProductCode" label="产品编码" min-width="130" align="center">
|
||||
<template #default="{ row }">{{ formatTempCellProductField(row, row.cellProductCode) }}</template>
|
||||
<el-table-column
|
||||
v-if="showTempProductCols"
|
||||
prop="cellProductCode"
|
||||
label="产品编码"
|
||||
min-width="130"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatTempCellProductField(row, row.cellProductCode)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showTempProductCols" prop="cellProductName" label="产品名称" min-width="140" align="center">
|
||||
<template #default="{ row }">{{ formatTempCellProductField(row, row.cellProductName) }}</template>
|
||||
<el-table-column
|
||||
v-if="showTempProductCols"
|
||||
prop="cellProductName"
|
||||
label="产品名称"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatTempCellProductField(row, row.cellProductName)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showTempProductCols" prop="cellProductTypeName" label="产品类型" min-width="110" align="center">
|
||||
<template #default="{ row }">{{ formatTempCellProductField(row, row.cellProductTypeName) }}</template>
|
||||
<el-table-column
|
||||
v-if="showTempProductCols"
|
||||
prop="cellProductTypeName"
|
||||
label="产品类型"
|
||||
min-width="110"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{
|
||||
formatTempCellProductField(row, row.cellProductTypeName)
|
||||
}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="protocolNo" label="协议号" min-width="140" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="protocolNo"
|
||||
label="协议号"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ formatTempEmptyField(row, row.protocolNo) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="protocolStatus" label="协议状态" min-width="100" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="protocolStatus"
|
||||
label="协议状态"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="shouldShowTempEmptyField(row, row.protocolStatus)">—</span>
|
||||
<el-tag
|
||||
v-else
|
||||
:type="protocolStatusTagTypeBySource(String(row.protocolStatus || ''), row.receptionSource || 'cell_storage')"
|
||||
:type="
|
||||
protocolStatusTagTypeBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.receptionSource || 'cell_storage'
|
||||
)
|
||||
"
|
||||
size="small"
|
||||
>{{ protocolStatusLabelBySource(String(row.protocolStatus || ''), row.receptionSource || 'cell_storage') }}</el-tag>
|
||||
>{{
|
||||
protocolStatusLabelBySource(
|
||||
String(row.protocolStatus || ''),
|
||||
row.receptionSource || 'cell_storage'
|
||||
)
|
||||
}}</el-tag
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="sampleCode" label="样本编码" min-width="120" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="sampleCode"
|
||||
label="样本编码"
|
||||
min-width="120"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ formatTempEmptyField(row, row.sampleCode) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="receptionSourceLabel" label="来源类型" min-width="100" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="receptionSourceLabel"
|
||||
label="来源类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<span v-if="shouldShowTempEmptyField(row, row.receptionSource)">—</span>
|
||||
<el-tag
|
||||
@@ -123,43 +312,101 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="sampleTypeName" label="样本类型" min-width="110" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="sampleTypeName"
|
||||
label="样本类型"
|
||||
min-width="110"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.sampleTypeName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="receptionTime" label="接收时间" min-width="170" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="receptionTime"
|
||||
label="接收时间"
|
||||
min-width="170"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.receptionTime || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="showReceptionCols" prop="handlerName" label="接收经手人" min-width="110" align="center">
|
||||
<el-table-column
|
||||
v-if="showReceptionCols"
|
||||
prop="handlerName"
|
||||
label="接收经手人"
|
||||
min-width="110"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ row.handlerName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="remark" label="备注" min-width="140" align="center">
|
||||
<el-table-column
|
||||
prop="remark"
|
||||
label="备注"
|
||||
min-width="140"
|
||||
align="center"
|
||||
show-overflow-tooltip
|
||||
>
|
||||
<template #default="{ row }">{{ row.remark || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="workOrderStatus" label="状态" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.workOrderStatus === 'ywc' ? 'success' : row.workOrderStatus === 'ypz' ? 'warning' : 'info'" size="small">
|
||||
<el-tag
|
||||
:type="
|
||||
row.workOrderStatus === 'ywc'
|
||||
? 'success'
|
||||
: row.workOrderStatus === 'ypz'
|
||||
? 'warning'
|
||||
: row.workOrderStatus === 'yzf'
|
||||
? 'danger'
|
||||
: 'info'
|
||||
"
|
||||
size="small"
|
||||
>
|
||||
{{ workOrderStatusLabel(row.workOrderStatus) }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="检测方式" min-width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<span>{{ formatDetectMethods(row) }}</span>
|
||||
</template>
|
||||
<el-table-column label="检测方式" min-width="160" align="center" show-overflow-tooltip>
|
||||
<template #default="{ row }">{{ formatDetectMethods(row) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" />
|
||||
<el-table-column label="操作" min-width="380" fixed="right" align="center">
|
||||
<el-table-column
|
||||
v-if="activeTypeTab !== 'all'"
|
||||
label="操作"
|
||||
:min-width="activeTypeTab === 'outbound' ? '260' : '380'"
|
||||
fixed="right"
|
||||
align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
|
||||
<el-button class="btn-op-item" @click="handleView(row)"
|
||||
><el-icon><View /></el-icon>查看</el-button
|
||||
>
|
||||
<el-button
|
||||
v-if="activeTypeTab === 'temporary' || (activeTypeTab === 'all' && row.workOrderType === 'temporary')"
|
||||
v-if="
|
||||
activeTypeTab === 'temporary' ||
|
||||
(activeTypeTab === 'all' && row.workOrderType === 'temporary')
|
||||
"
|
||||
class="btn-op-item"
|
||||
@click="handleEdit(row)"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="activeTypeTab === 'inbound' || (activeTypeTab === 'all' && row.workOrderType !== 'temporary')"
|
||||
v-if="
|
||||
activeTypeTab === 'inbound' ||
|
||||
(activeTypeTab === 'all' && row.workOrderType !== 'temporary')
|
||||
"
|
||||
class="btn-op-item"
|
||||
@click="handleEditRemark(row)"
|
||||
>
|
||||
<el-icon><EditPen /></el-icon>编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="
|
||||
activeTypeTab === 'outbound' &&
|
||||
row.workOrderStatus !== 'ywc' &&
|
||||
row.workOrderStatus !== 'yzf'
|
||||
"
|
||||
class="btn-op-item"
|
||||
@click="handleEditRemark(row)"
|
||||
>
|
||||
@@ -170,9 +417,15 @@
|
||||
class="btn-op-item primary"
|
||||
@click="handleConfigure(row)"
|
||||
>
|
||||
<el-icon><Setting /></el-icon>{{ row.workOrderStatus === 'ypz' ? '调整检测' : '配置检测' }}
|
||||
<el-icon><Setting /></el-icon
|
||||
>{{ row.workOrderStatus === 'ypz' ? '调整检测' : '配置检测' }}
|
||||
</el-button>
|
||||
<el-button v-if="row.workOrderStatus !== 'ywc'" class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<el-button
|
||||
v-if="activeTypeTab !== 'outbound' && activeTypeTab !== 'all' && row.workOrderStatus !== 'ywc'"
|
||||
class="btn-op-item"
|
||||
@click="handleDelete(row)"
|
||||
>删除</el-button
|
||||
>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -200,21 +453,34 @@
|
||||
<el-form label-position="top" class="biz-dialog-form">
|
||||
<el-form-item label="检测方式">
|
||||
<el-checkbox v-model="configureForm.hasLab">实验室自检</el-checkbox>
|
||||
<el-checkbox v-model="configureForm.hasThirdParty" style="margin-left: 16px">第三方检测</el-checkbox>
|
||||
<el-checkbox v-model="configureForm.hasThirdParty" style="margin-left: 16px"
|
||||
>第三方检测</el-checkbox
|
||||
>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="configureForm.hasLab" label="实验室自检项目">
|
||||
<el-checkbox-group v-model="configureForm.labItemIds" class="qc-config-check-group">
|
||||
<el-checkbox v-for="item in labOptions" :key="item.id" :value="item.id">{{ item.itemName }}({{ item.itemCode }})</el-checkbox>
|
||||
<el-checkbox v-for="item in labOptions" :key="item.id" :value="item.id"
|
||||
>{{ item.itemName }}({{ item.itemCode }})</el-checkbox
|
||||
>
|
||||
</el-checkbox-group>
|
||||
<p v-if="!labOptions.length" class="hint-muted">暂无启用的实验室项目</p>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="configureForm.hasThirdParty" label="第三方检测项目">
|
||||
<el-checkbox-group v-model="configureForm.thirdPartyItemIds" class="qc-config-check-group">
|
||||
<div v-for="group in thirdOptionsGrouped" :key="group.companyId" class="qc-company-group">
|
||||
<el-checkbox-group
|
||||
v-model="configureForm.thirdPartyItemIds"
|
||||
class="qc-config-check-group"
|
||||
>
|
||||
<div
|
||||
v-for="group in thirdOptionsGrouped"
|
||||
:key="group.companyId"
|
||||
class="qc-company-group"
|
||||
>
|
||||
<div class="qc-company-group-title">{{ group.companyName }}</div>
|
||||
<div class="qc-company-group-items">
|
||||
<el-checkbox v-for="item in group.items" :key="item.id" :value="item.id">
|
||||
{{ item.itemName }}({{ item.itemCode }})¥{{ formatMoneyDisplay(item.feeAmount) }}
|
||||
{{ item.itemName }}({{ item.itemCode }})¥{{
|
||||
formatMoneyDisplay(item.feeAmount)
|
||||
}}
|
||||
</el-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
@@ -223,8 +489,16 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button class="btn-ghost" @click="configureVisible = false"><el-icon><Close /></el-icon>取消</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="configureLoading" @click="submitConfigure"><el-icon><Check /></el-icon>确认</el-button>
|
||||
<el-button class="btn-ghost" @click="configureVisible = false"
|
||||
><el-icon><Close /></el-icon>取消</el-button
|
||||
>
|
||||
<el-button
|
||||
class="btn-main"
|
||||
type="primary"
|
||||
:loading="configureLoading"
|
||||
@click="submitConfigure"
|
||||
><el-icon><Check /></el-icon>确认</el-button
|
||||
>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
@@ -275,7 +549,9 @@
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form ref="addFormRef" :model="addForm" :rules="addRules" label-position="top">
|
||||
<p v-if="addMode === 'temporary' && !editingId" class="hint-muted">临时工单需选择质检对象(细胞产品),创建后请配置检测项目。</p>
|
||||
<p v-if="addMode === 'temporary' && !editingId" class="hint-muted"
|
||||
>临时工单需选择质检对象(细胞产品),创建后请配置检测项目。</p
|
||||
>
|
||||
<div v-if="addMode === 'temporary'" class="form-grid-2">
|
||||
<el-form-item v-if="editingId" label="工单号" class="form-col-span-2">
|
||||
<el-input :model-value="editingWorkOrderNo" disabled />
|
||||
@@ -300,7 +576,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="选填" maxlength="512" />
|
||||
<el-input
|
||||
v-model="addForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="选填"
|
||||
maxlength="512"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div v-else class="form-grid-2">
|
||||
@@ -308,7 +590,11 @@
|
||||
<el-input v-model="addForm.protocolNo" placeholder="请输入协议号(可选)" clearable />
|
||||
</el-form-item>
|
||||
<el-form-item label="样本编码">
|
||||
<el-input v-model="addForm.sampleCode" placeholder="请输入样本编码(可选)" clearable />
|
||||
<el-input
|
||||
v-model="addForm.sampleCode"
|
||||
placeholder="请输入样本编码(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="来源类型" prop="receptionSource">
|
||||
<el-select v-model="addForm.receptionSource" style="width: 100%">
|
||||
@@ -322,7 +608,13 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="addForm.remark" type="textarea" :rows="3" placeholder="选填" maxlength="512" />
|
||||
<el-input
|
||||
v-model="addForm.remark"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="选填"
|
||||
maxlength="512"
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
@@ -351,7 +643,17 @@
|
||||
import { formatMoneyDisplay } from '@/utils/money-format'
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Close, Check, Search, RefreshLeft, View, Setting, Document, Plus, EditPen } from '@element-plus/icons-vue'
|
||||
import {
|
||||
Close,
|
||||
Check,
|
||||
Search,
|
||||
RefreshLeft,
|
||||
View,
|
||||
Setting,
|
||||
Document,
|
||||
Plus,
|
||||
EditPen
|
||||
} from '@element-plus/icons-vue'
|
||||
import { fetchQcLabTestItemsActive, type QcLabTestItem } from '@/api/qc-lab-test-item'
|
||||
import { fetchQcThirdPartyItemsActive, type QcThirdPartyItem } from '@/api/qc-third-party-item'
|
||||
import {
|
||||
@@ -381,16 +683,20 @@
|
||||
donationStatusLabel,
|
||||
donationStatusTagType,
|
||||
qcBizTypeLabel,
|
||||
qcBizTypeTagType,
|
||||
qcBizTypeTagType
|
||||
} from '@/utils/status-tag-registry'
|
||||
import type { TagType } from '@/utils/status-tag-registry'
|
||||
|
||||
/** 根据来源类型返回协议状态标签 */
|
||||
function protocolStatusLabelBySource(status: string, source: string): string {
|
||||
return source === 'research_donation' ? donationStatusLabel(status) : protocolStatusLabel(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusLabel(status)
|
||||
: protocolStatusLabel(status)
|
||||
}
|
||||
function protocolStatusTagTypeBySource(status: string, source: string): TagType {
|
||||
return source === 'research_donation' ? donationStatusTagType(status) : protocolStatusTagType(status)
|
||||
return source === 'research_donation'
|
||||
? donationStatusTagType(status)
|
||||
: protocolStatusTagType(status)
|
||||
}
|
||||
|
||||
/** 临时工单无关联协议/样本时,空字段展示为 — */
|
||||
@@ -408,10 +714,11 @@
|
||||
|
||||
defineOptions({ name: 'QualityInspectionQcWorkOrder' })
|
||||
|
||||
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'all'
|
||||
type WorkOrderTypeTab = 'inbound' | 'temporary' | 'outbound' | 'all'
|
||||
const activeTypeTab = ref<WorkOrderTypeTab>('inbound')
|
||||
const showReceptionCols = computed(() => activeTypeTab.value !== 'temporary')
|
||||
const showTempProductCols = computed(() => activeTypeTab.value !== 'inbound')
|
||||
const showOutboundCols = computed(() => activeTypeTab.value === 'outbound')
|
||||
const addMode = ref<'inbound' | 'temporary'>('inbound')
|
||||
const editingId = ref('')
|
||||
const editingWorkOrderNo = ref('')
|
||||
@@ -444,7 +751,9 @@
|
||||
return code ? `${name}(${code})` : name
|
||||
}
|
||||
|
||||
function ensureCellProductOption(row?: Pick<QcWorkOrder, 'cellProductId' | 'cellProductCode' | 'cellProductName'>) {
|
||||
function ensureCellProductOption(
|
||||
row?: Pick<QcWorkOrder, 'cellProductId' | 'cellProductCode' | 'cellProductName'>
|
||||
) {
|
||||
if (!row?.cellProductId) return
|
||||
if (cellProductOptions.value.some((o) => o.id === row.cellProductId)) return
|
||||
cellProductOptions.value = [
|
||||
@@ -493,7 +802,9 @@
|
||||
const addRules = computed(() => {
|
||||
if (addMode.value === 'temporary') {
|
||||
return {
|
||||
cellProductId: [{ required: true, message: '请选择质检对象(细胞产品)', trigger: 'change' }]
|
||||
cellProductId: [
|
||||
{ required: true, message: '请选择质检对象(细胞产品)', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -533,7 +844,8 @@
|
||||
const remarkEditLoading = ref(false)
|
||||
const remarkEditRow = ref<QcWorkOrder | null>(null)
|
||||
const remarkEditTitle = computed(
|
||||
() => `编辑备注${remarkEditRow.value?.workOrderNo ? ` — ${remarkEditRow.value.workOrderNo}` : ''}`
|
||||
() =>
|
||||
`编辑备注${remarkEditRow.value?.workOrderNo ? ` — ${remarkEditRow.value.workOrderNo}` : ''}`
|
||||
)
|
||||
const remarkEditInitialRemark = computed(() => remarkEditRow.value?.remark || '')
|
||||
|
||||
@@ -602,7 +914,7 @@
|
||||
return [formatLocalDate(start), formatLocalDate(end)]
|
||||
}
|
||||
|
||||
const searchForm = reactive({ keyword: '', workOrderStatus: '' })
|
||||
const searchForm = reactive({ keyword: '', workOrderStatus: '', bizType: '' })
|
||||
const createTimeRange = ref<[string, string] | null>(getDefaultLast30DaysRange())
|
||||
const tableData = ref<QcWorkOrder[]>([])
|
||||
const loading = ref(false)
|
||||
@@ -615,7 +927,10 @@
|
||||
const labOptions = ref<QcLabTestItem[]>([])
|
||||
const thirdOptions = ref<QcThirdPartyItem[]>([])
|
||||
const thirdOptionsGrouped = computed(() => {
|
||||
const map = new Map<string, { companyId: string; companyName: string; items: QcThirdPartyItem[] }>()
|
||||
const map = new Map<
|
||||
string,
|
||||
{ companyId: string; companyName: string; items: QcThirdPartyItem[] }
|
||||
>()
|
||||
for (const item of thirdOptions.value) {
|
||||
const cid = item.companyId
|
||||
if (!map.has(cid)) {
|
||||
@@ -719,7 +1034,10 @@
|
||||
function formatConfiguredThirdItems(d: QcWorkOrderDetail) {
|
||||
return (
|
||||
d.thirdPartyItems
|
||||
?.map((li) => `${li.companyName || ''} — ${li.itemName || ''}(${li.feeAmount != null ? '¥' + formatMoneyDisplay(li.feeAmount) : '—'})`)
|
||||
?.map(
|
||||
(li) =>
|
||||
`${li.companyName || ''} — ${li.itemName || ''}(${li.feeAmount != null ? '¥' + formatMoneyDisplay(li.feeAmount) : '—'})`
|
||||
)
|
||||
.join('、') || '无'
|
||||
)
|
||||
}
|
||||
@@ -733,8 +1051,7 @@
|
||||
if (!filtered.length) return []
|
||||
|
||||
return filtered.map((task, index) => {
|
||||
const title =
|
||||
filtered.length > 1 ? `${sectionTitle}(${index + 1})` : sectionTitle
|
||||
const title = filtered.length > 1 ? `${sectionTitle}(${index + 1})` : sectionTitle
|
||||
const fields: ViewField[] = [
|
||||
{ label: '检测项目', value: task.itemName || '—' },
|
||||
{ label: '项目编码', value: task.itemCode || '—' },
|
||||
@@ -813,9 +1130,7 @@
|
||||
return String(v)
|
||||
}
|
||||
|
||||
function formatCheckResults(
|
||||
items?: Array<{ itemName?: string; checked?: boolean }>
|
||||
) {
|
||||
function formatCheckResults(items?: Array<{ itemName?: string; checked?: boolean }>) {
|
||||
if (!items?.length) return '—'
|
||||
return items.map((x) => `${x.itemName || ''}${x.checked ? '(✓)' : '(×)'}`).join('、')
|
||||
}
|
||||
@@ -880,18 +1195,36 @@
|
||||
{ label: '协议号', value: d.protocolNo },
|
||||
{
|
||||
label: '协议状态',
|
||||
value: protocolStatusLabelBySource(String(d.protocolStatus || ''), d.receptionSource || 'cell_storage'),
|
||||
tagType: protocolStatusTagTypeBySource(String(d.protocolStatus || ''), d.receptionSource || 'cell_storage')
|
||||
value: protocolStatusLabelBySource(
|
||||
String(d.protocolStatus || ''),
|
||||
d.receptionSource || 'cell_storage'
|
||||
),
|
||||
tagType: protocolStatusTagTypeBySource(
|
||||
String(d.protocolStatus || ''),
|
||||
d.receptionSource || 'cell_storage'
|
||||
)
|
||||
},
|
||||
{ label: '样本编码', value: d.sampleCode },
|
||||
{
|
||||
label: '来源类型',
|
||||
value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''),
|
||||
value:
|
||||
r.sourceTypeLabel ||
|
||||
d.receptionSourceLabel ||
|
||||
receptionSourceLabel(d.receptionSource || ''),
|
||||
tagType: 'primary' as const
|
||||
},
|
||||
{ label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' }
|
||||
]),
|
||||
{ label: '状态', value: workOrderStatusLabel(d.workOrderStatus), tagType: d.workOrderStatus === 'ywc' ? 'success' : d.workOrderStatus === 'ypz' ? 'warning' : 'info' },
|
||||
{
|
||||
label: '状态',
|
||||
value: workOrderStatusLabel(d.workOrderStatus),
|
||||
tagType:
|
||||
d.workOrderStatus === 'ywc'
|
||||
? 'success'
|
||||
: d.workOrderStatus === 'ypz'
|
||||
? 'warning'
|
||||
: 'info'
|
||||
},
|
||||
{ label: '备注', value: d.remark || '—', span: true },
|
||||
{ label: '创建时间', value: d.createTime || '—' }
|
||||
]
|
||||
@@ -914,8 +1247,15 @@
|
||||
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
|
||||
{
|
||||
label: '结论',
|
||||
value: r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || '')),
|
||||
tagType: r.acceptConclusion === 'js' ? 'success' : r.acceptConclusion === 'jj' ? 'danger' : 'info'
|
||||
value:
|
||||
r.acceptConclusionLabel ||
|
||||
acceptConclusionLabel(String(r.acceptConclusion || '')),
|
||||
tagType:
|
||||
r.acceptConclusion === 'js'
|
||||
? 'success'
|
||||
: r.acceptConclusion === 'jj'
|
||||
? 'danger'
|
||||
: 'info'
|
||||
},
|
||||
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
@@ -925,7 +1265,14 @@
|
||||
...(viewTasks.value.some((t) => t.taskType === 'sysz')
|
||||
? buildTaskTypeSections(viewTasks.value, 'sysz', '实验室项目')
|
||||
: d.hasLab
|
||||
? [buildConfiguredOnlySection('实验室项目', '已配置项目', formatConfiguredLabItems(d), '检测附件')]
|
||||
? [
|
||||
buildConfiguredOnlySection(
|
||||
'实验室项目',
|
||||
'已配置项目',
|
||||
formatConfiguredLabItems(d),
|
||||
'检测附件'
|
||||
)
|
||||
]
|
||||
: []),
|
||||
...(viewTasks.value.some((t) => t.taskType === 'dsfjc')
|
||||
? buildTaskTypeSections(viewTasks.value, 'dsfjc', '第三方项目')
|
||||
@@ -958,6 +1305,7 @@
|
||||
keyword: searchForm.keyword || undefined,
|
||||
workOrderStatus: searchForm.workOrderStatus || undefined,
|
||||
workOrderType: activeTypeTab.value === 'all' ? undefined : activeTypeTab.value,
|
||||
bizType: searchForm.bizType || undefined,
|
||||
createTimeStart: createTimeRange.value?.[0] || undefined,
|
||||
createTimeEnd: createTimeRange.value?.[1] || undefined
|
||||
})
|
||||
@@ -975,6 +1323,7 @@
|
||||
function handleReset() {
|
||||
searchForm.keyword = ''
|
||||
searchForm.workOrderStatus = ''
|
||||
searchForm.bizType = ''
|
||||
createTimeRange.value = getDefaultLast30DaysRange()
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
@@ -1058,6 +1407,14 @@
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 全部工单 Tab 无操作列,略增高行高便于阅读多列信息 */
|
||||
:deep(.list-table.list-table--all-tab) {
|
||||
td.el-table__cell > .cell {
|
||||
min-height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.work-order-type-tabs {
|
||||
padding: 0 16px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
Reference in new issue
Block a user