feat(web-admin): align sample-freight page cancel UX with two-button rules

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zlwuandCursor committed 2026-10-12 14:07:39 +08:00
1 parent 8c0cbb8ab8
commit f659ec602e
1 file changed
+66 -111
@@ -79,7 +79,7 @@
@click="handleBatchUnlink"
>
<el-icon><Link /></el-icon>
取消关联
取消运费
</el-button>
<el-button
class="btn-ghost"
@@ -238,7 +238,7 @@
</el-button>
<el-button class="btn-op-item" @click="openUnlinkDialog(row.freightId)">
<el-icon><Link /></el-icon>
取消关联
取消运费
</el-button>
</div>
</template>
@@ -261,72 +261,28 @@
<FreightConfigDialog ref="configDialogRef" @success="loadData" />
<FreightEditDialog ref="editDialogRef" @success="loadData" />
<el-dialog
<FreightUnlinkDialog
v-model="unlinkVisible"
title="取消关联"
width="720px"
class="dialog-form-layout"
align-center
append-to-body
modal-class="protocol-dialog-mask"
destroy-on-close
:close-on-click-modal="false"
>
<p class="unlink-hint">勾选要从该运费批次解除关联的样本:</p>
<el-table
:data="unlinkItems"
stripe
class="list-table"
max-height="360"
row-key="sampleId"
@selection-change="(rows: UnlinkItem[]) => (unlinkSelected = rows)"
>
<el-table-column type="selection" min-width="48" align="center" header-align="center" />
<el-table-column label="来源" min-width="100" align="center" header-align="center">
<template #default="{ row }">
<el-tag :type="row.sampleSource === 'cell_storage' ? 'success' : 'warning'" size="small">
{{ row.sampleSource === 'cell_storage' ? '细胞存储' : '科研捐赠' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="protocolNo" label="协议单号" min-width="140" show-overflow-tooltip />
<el-table-column prop="sampleCode" label="样本编码" min-width="120" show-overflow-tooltip />
<el-table-column
prop="sampleTypeName"
label="样本类型"
min-width="110"
show-overflow-tooltip
/>
</el-table>
<template #footer>
<el-button class="btn-ghost" @click="unlinkVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button
class="btn-main"
type="primary"
:loading="unlinkSubmitting"
@click="confirmUnlink"
>
<el-icon><Check /></el-icon>
确定解除
</el-button>
</template>
</el-dialog>
:items="unlinkDialogItems"
:default-selected-keys="unlinkDefaultKeys"
:submitting="unlinkSubmitting"
@confirm="confirmUnlinkFromDialog"
/>
</div>
</template>
<script setup lang="ts">
import { Check, Close, Edit, Link, Plus, RefreshLeft, Search } from '@element-plus/icons-vue'
import { Edit, Link, Plus, RefreshLeft, Search } from '@element-plus/icons-vue'
import { fetchDictDataOptions } from '@/api/admin'
import {
fetchSampleFreightDetail,
fetchSampleFreightList,
fetchUnlinkSampleFreight,
type SampleFreight,
type SampleFreightItem
type SampleFreight
} from '@/api/sample-freight'
import FreightUnlinkDialog, {
type FreightUnlinkRow
} from '@/components/business/freight-unlink-dialog/index.vue'
import { formatDisplayOrDash } from '@/utils/datetime-display'
import { formatMoneyDisplay } from '@/utils/money-format'
import {
@@ -340,7 +296,6 @@
defineOptions({ name: 'CellStorageSampleFreight' })
type FlatRow = SampleFreightFlatRow
type UnlinkItem = SampleFreightItem
const loading = ref(false)
const flatRows = ref<FlatRow[]>([])
@@ -351,9 +306,8 @@
const editDialogRef = ref<InstanceType<typeof FreightEditDialog>>()
const unlinkVisible = ref(false)
const unlinkFreightId = ref('')
const unlinkItems = ref<UnlinkItem[]>([])
const unlinkSelected = ref<UnlinkItem[]>([])
const unlinkDialogItems = ref<FreightUnlinkRow[]>([])
const unlinkDefaultKeys = ref<string[]>([])
const unlinkSubmitting = ref(false)
const searchForm = reactive({
@@ -462,63 +416,40 @@
openEditById(selectedFreightIds.value[0])
}
function toUnlinkRow(it: NonNullable<SampleFreight['items']>[number], freightId: string): FreightUnlinkRow {
return {
...it,
freightId,
rowKey: `${it.sampleSource}:${it.sampleId}`
}
}
async function openUnlinkDialog(freightId: string) {
unlinkFreightId.value = freightId
unlinkSelected.value = []
try {
const res = await fetchSampleFreightDetail(freightId)
const data = ((res as any)?.data || res) as SampleFreight
unlinkItems.value = data.items || []
unlinkDialogItems.value = (data.items || []).map((it) => toUnlinkRow(it, freightId))
unlinkDefaultKeys.value = unlinkDialogItems.value.map((r) => r.rowKey)
unlinkVisible.value = true
} catch {
ElMessage.error('加载关联样本失败')
}
}
async function confirmUnlink() {
if (!unlinkSelected.value.length) {
ElMessage.warning('请勾选要解除关联的样本')
return
}
async function confirmUnlinkFromDialog(rows: FreightUnlinkRow[]) {
unlinkSubmitting.value = true
try {
await fetchUnlinkSampleFreight(
unlinkFreightId.value,
unlinkSelected.value.map((x) => ({
sampleSource: x.sampleSource,
sampleId: x.sampleId,
protocolId: x.protocolId,
sampleCode: x.sampleCode
}))
)
unlinkVisible.value = false
await loadData()
} finally {
unlinkSubmitting.value = false
}
}
async function handleBatchUnlink() {
if (!selectedFreightIds.value.length) return
try {
await ElMessageBox.confirm(
`将解除已选 ${selectedFreightIds.value.length} 笔运费下的全部样本关联,是否继续?`,
'批量取消关联',
{ type: 'warning', confirmButtonText: '确定', cancelButtonText: '取消' }
)
} catch {
return
}
loading.value = true
try {
for (const id of selectedFreightIds.value) {
const res = await fetchSampleFreightDetail(id)
const data = ((res as any)?.data || res) as SampleFreight
const items = data.items || []
if (!items.length) continue
const byFreight = new Map<string, FreightUnlinkRow[]>()
for (const row of rows) {
const fid = String(row.freightId || '').trim()
if (!fid) continue
if (!byFreight.has(fid)) byFreight.set(fid, [])
byFreight.get(fid)!.push(row)
}
for (const [fid, list] of byFreight) {
await fetchUnlinkSampleFreight(
id,
items.map((x) => ({
fid,
list.map((x) => ({
sampleSource: x.sampleSource,
sampleId: x.sampleId,
protocolId: x.protocolId,
@@ -526,7 +457,37 @@
}))
)
}
unlinkVisible.value = false
await loadData()
} catch (e: any) {
ElMessage.error(e?.message || '取消运费失败')
} finally {
unlinkSubmitting.value = false
}
}
async function handleBatchUnlink() {
if (!selectedFreightIds.value.length) return
loading.value = true
try {
const byKey = new Map<string, FreightUnlinkRow>()
for (const id of selectedFreightIds.value) {
const res = await fetchSampleFreightDetail(id)
const data = ((res as any)?.data || res) as SampleFreight
for (const it of data.items || []) {
const row = toUnlinkRow(it, id)
byKey.set(row.rowKey, row)
}
}
unlinkDialogItems.value = [...byKey.values()]
unlinkDefaultKeys.value = unlinkDialogItems.value.map((r) => r.rowKey)
if (!unlinkDialogItems.value.length) {
ElMessage.warning('所选运费下没有关联样本')
return
}
unlinkVisible.value = true
} catch {
ElMessage.error('加载关联样本失败')
} finally {
loading.value = false
}
@@ -549,12 +510,6 @@
color: var(--mint-text-muted, #64748b);
}
.unlink-hint {
margin: 0 0 10px;
font-size: 13px;
color: var(--mint-text-muted, #64748b);
}
/* 按运费批次交替底色(同批次多样本行同色) */
:deep(.freight-batch-table) {
.el-table__body tr.freight-batch-even > td.el-table__cell {