浏览代码

feat(qc): add outbound QC work order tab with list, filters and action buttons

zlwu 5 月之前
父节点
当前提交
b20c9f7bd7
共有 1 个文件被更改,包括 434 次插入 和 77 次删除
  1. 434 77
      web-admin/src/views/quality-inspection/qc-work-order/index.vue

+ 434 - 77
web-admin/src/views/quality-inspection/qc-work-order/index.vue

@@ -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;