Files
zkxw/web-admin/src/views/basic-info/sample-code/index.vue
T
2026-05-20 20:41:47 +08:00

799 lines
26 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 样本编码管理 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<header class="page-head">
<h1>样本编码</h1>
<p class="page-desc">维护样本编码池及是否下发标记。</p>
</header>
<section class="panel">
<div class="panel-top">
<h2>样本编码列表</h2>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="关键词">
<el-input v-model="searchForm.keyword" placeholder="样本编码/备注" clearable />
</el-form-item>
<el-form-item label="编码类型">
<el-select
v-model="searchForm.sampleCodeType"
placeholder="全部"
clearable
style="width: 100%"
>
<el-option label="造血编码 (ZKZX)" value="zxbm" />
<el-option label="脐带编码 (ZKQD)" value="qdbm" />
</el-select>
</el-form-item>
<el-form-item label="下发对象">
<el-select
v-model="searchForm.issuedUserId"
placeholder="全部"
filterable
clearable
style="width: 100%"
>
<el-option
v-for="opt in userOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint">模糊查询。</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>
</div>
</div>
</el-form>
</div>
<div class="list-toolbar">
<div class="list-toolbar-row">
<div class="list-toolbar-selected">
已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
</div>
<el-button class="btn-main" type="primary" @click="handleAdd">
<el-icon><Plus /></el-icon>
新增样本编码
</el-button>
<el-button class="btn-ghost" @click="openBatchGenerate">
<el-icon><DocumentAdd /></el-icon>
批量生成
</el-button>
<el-button
class="btn-ghost"
:disabled="selectedRows.length === 0"
@click="openBatchIssue"
>
<el-icon><Promotion /></el-icon>
批量下发
</el-button>
<el-button
class="btn-ghost"
:disabled="selectedRows.length === 0"
@click="handleBatchRevoke"
>
<el-icon><RefreshRight /></el-icon>
批量收回
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="selectedRows.length === 0"
@click="handleBatchDelete"
>
批量删除
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="handleSelectionChange"
@row-dblclick="handleView"
>
<el-table-column type="selection" width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
width="68"
align="center"
header-align="center"
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
/>
<el-table-column
prop="sampleCode"
label="样本编码"
min-width="160"
align="center"
header-align="center"
/>
<el-table-column
prop="sampleCodeType"
label="编码类型"
width="100"
align="center"
header-align="center"
>
<template #default="{ row }">{{ sampleCodeTypeLabel(row.sampleCodeType) }}</template>
</el-table-column>
<el-table-column
prop="issued"
label="是否下发"
width="80"
align="center"
header-align="center"
>
<template #default="{ row }">
<span>{{ row.issued === 1 ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column
prop="issuedUserName"
label="下发对象"
min-width="100"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.issuedUserName || '-' }}</template>
</el-table-column>
<el-table-column
prop="issuedTime"
label="下发时间"
min-width="180"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.issuedTime || '-' }}</template>
</el-table-column>
<el-table-column
prop="remark"
label="备注"
min-width="120"
align="center"
header-align="center"
show-overflow-tooltip
>
<template #default="{ row }">{{ row.remark || '-' }}</template>
</el-table-column>
<el-table-column
prop="createTime"
label="创建时间"
min-width="180"
align="center"
header-align="center"
/>
<el-table-column
label="操作"
width="320"
fixed="right"
align="center"
header-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 primary" @click="handleEdit(row)">编辑</el-button>
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
@current-change="(val) => ((pagination.page = val), loadData())"
/>
</div>
</div>
<!-- 批量生成弹窗 -->
<el-dialog
v-model="batchGenVisible"
title="批量生成样本编码"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form
ref="batchGenFormRef"
:model="batchGenForm"
:rules="batchGenRules"
label-position="top"
scroll-to-error
>
<div class="form-grid-2">
<el-form-item label="编码类型" prop="sampleCodeType">
<el-select v-model="batchGenForm.sampleCodeType" placeholder="请选择" style="width: 100%">
<el-option label="造血编码 (ZKZX)" value="zxbm" />
<el-option label="脐带编码 (ZKQD)" value="qdbm" />
</el-select>
</el-form-item>
<el-form-item label="中间段" prop="middle">
<el-input v-model="batchGenForm.middle" placeholder="如 34" />
</el-form-item>
<el-form-item label="开始序号" prop="seqStart">
<el-input
type="number"
step="any"
v-model="batchGenForm.seqStart"
:min="0"
:max="99999"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="结束序号" prop="seqEnd">
<el-input
type="number"
step="any"
v-model="batchGenForm.seqEnd"
:min="0"
:max="99999"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="序号位数" prop="digitCount">
<template #label
>序号位数<span class="form-label-tip">补零后的总位数</span></template
>
<el-input
type="number"
step="any"
v-model="batchGenForm.digitCount"
:min="1"
:max="10"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="预览" class="form-col-span-2">
<span class="text-sm text-gray-500">{{ batchGenPreview }}</span>
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="batchGenVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="batchGenLoading" @click="handleBatchGenerate">
<el-icon><Check /></el-icon>
生成
</el-button>
</template>
</el-dialog>
<!-- 批量下发弹窗 -->
<el-dialog
v-model="batchIssueVisible"
title="批量下发样本编码"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form
ref="batchIssueFormRef"
:model="batchIssueForm"
:rules="batchIssueRules"
label-position="top"
scroll-to-error
>
<el-form-item label="下发用户" prop="issuedUserId">
<el-select
v-model="batchIssueForm.issuedUserId"
placeholder="请选择用户"
filterable
style="width: 100%"
>
<el-option
v-for="opt in userOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item label="已选编码">
<span class="text-sm text-gray-500">
已选 {{ selectedRows.length }} 条,下发后"是否下发"将变为"是"
</span>
</el-form-item>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="batchIssueVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="batchIssueLoading" @click="handleBatchIssue">
<el-icon><Check /></el-icon>
确定下发
</el-button>
</template>
</el-dialog>
<el-dialog
v-model="dialogVisible"
:title="dialogTitle"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
class="biz-dialog-form"
scroll-to-error
>
<div class="form-grid-2">
<el-form-item label="样本编码" prop="sampleCode">
<el-input
v-model="formData.sampleCode"
placeholder="租户内唯一,最多32位"
maxlength="32"
/>
</el-form-item>
<el-form-item v-if="!isEdit" label="编码类型" prop="sampleCodeType">
<el-select
v-model="formData.sampleCodeType"
placeholder="请选择"
style="width: 100%"
>
<el-option label="造血编码 (ZKZX)" value="zxbm" />
<el-option label="脐带编码 (ZKQD)" value="qdbm" />
</el-select>
</el-form-item>
<el-form-item v-else label="编码类型">
<span class="text-sm text-gray-500">{{ sampleCodeTypeLabel(formData.sampleCodeType) }}</span>
</el-form-item>
<el-form-item label="是否下发">
<el-switch
v-model="formData.issued"
:active-value="1"
:inactive-value="0"
inline-prompt
active-text="是"
inactive-text="否"
/>
</el-form-item>
<el-form-item v-if="formData.issued === 1" label="下发用户" prop="issuedUserId">
<el-select
v-model="formData.issuedUserId"
placeholder="请选择用户"
filterable
style="width: 100%"
>
<el-option
v-for="opt in userOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="formData.issued === 1 && formData.issuedTime" label="下发时间">
<span class="text-sm text-gray-500">{{ formData.issuedTime }}</span>
</el-form-item>
<el-form-item label="备注" class="form-col-span-2">
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="可选" />
</el-form-item>
</div>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="dialogVisible = false">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
<SystemViewDialog
v-model:visible="viewVisible"
:title="viewRow?.sampleCode || '样本编码详情'"
:icon="Ticket"
:meta="`编码类型:${sampleCodeTypeLabel(viewRow?.sampleCodeType || '')}${viewRow?.createTime ? ' · ' + viewRow.createTime : ''}`"
:sections="viewSections"
/>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
Plus,
Close,
Check,
View,
Ticket,
RefreshLeft,
Search,
DocumentAdd,
Promotion,
RefreshRight
} from '@element-plus/icons-vue'
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import {
fetchSampleCodeList,
fetchCreateSampleCode,
fetchUpdateSampleCode,
fetchDeleteSampleCode,
fetchUserOptions,
fetchBatchGenerateSampleCodes,
fetchBatchIssueSampleCodes,
fetchBatchRevokeSampleCodes
} from '@/api/admin'
import { sampleCodeTypeLabel } from '@/utils/status-tag-registry'
defineOptions({ name: 'BizSampleCode' })
const searchForm = reactive<{ keyword: string; sampleCodeType: string; issuedUserId: string }>({
keyword: '',
sampleCodeType: '',
issuedUserId: ''
})
const tableData = ref<Api.BizSampleCode.SampleCodeItem[]>([])
const selectedRows = ref<Api.BizSampleCode.SampleCodeItem[]>([])
const loading = ref(false)
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
const dialogVisible = ref(false)
const dialogTitle = ref('新增样本编码')
const isEdit = ref(false)
const submitLoading = ref(false)
const formRef = ref()
const editingId = ref<string | null>(null)
// ---- 查看弹窗 ----
const viewVisible = ref(false)
const viewRow = ref<Api.BizSampleCode.SampleCodeItem | null>(null)
const viewSections = computed(() => {
const row = viewRow.value
if (!row) return []
return [
{
title: '基本信息',
fields: [
{ label: '样本编码', value: row.sampleCode },
{ label: '编码类型', value: sampleCodeTypeLabel(row.sampleCodeType) },
{ label: '是否下发', value: row.issued === 1 ? '是' : '否' },
{ label: '下发对象', value: row.issuedUserName || '-' },
{ label: '下发时间', value: row.issuedTime || '-' },
{ label: '创建时间', value: row.createTime },
{ label: '备注', value: row.remark, span: true }
]
}
]
})
function handleView(row: Api.BizSampleCode.SampleCodeItem) {
viewRow.value = row
viewVisible.value = true
}
// --- 批量生成 ---
const batchGenVisible = ref(false)
const batchGenLoading = ref(false)
const batchGenFormRef = ref()
const batchGenForm = reactive({
sampleCodeType: 'zxbm',
middle: '34',
seqStart: 1,
seqEnd: 100,
digitCount: 6,
})
const batchGenRules = {
sampleCodeType: [{ required: true, message: '请选择编码类型', trigger: 'change' }],
middle: [{ required: true, message: '请输入中间段', trigger: 'blur' }],
seqStart: [{ required: true, message: '请输入开始序号', trigger: 'blur' }],
seqEnd: [{ required: true, message: '请输入结束序号', trigger: 'blur' }],
digitCount: [{ required: true, message: '请输入序号位数', trigger: 'blur' }],
}
const typePrefixMap: Record<string, string> = { zxbm: 'ZKZX', qdbm: 'ZKQD' }
const batchGenPreview = computed(() => {
const t = batchGenForm.sampleCodeType || ''
const m = batchGenForm.middle || ''
const s = batchGenForm.seqStart ?? 0
const e = batchGenForm.seqEnd ?? 0
const d = batchGenForm.digitCount ?? 6
const prefix = typePrefixMap[t] || ''
if (!prefix || e < s) return '—'
const first = `${prefix}${m}${String(s).padStart(d, '0')}`
const last = `${prefix}${m}${String(e).padStart(d, '0')}`
return `${first} ~ ${last}(共 ${e - s + 1} 条)`
})
// --- 批量下发 ---
const batchIssueVisible = ref(false)
const batchIssueLoading = ref(false)
const batchIssueFormRef = ref()
const batchIssueForm = reactive({
issuedUserId: '',
})
const batchIssueRules = {
issuedUserId: [{ required: true, message: '请选择下发用户', trigger: 'change' }],
}
const userOptions = ref<Array<{ label: string; value: string }>>([])
const formData = reactive({
sampleCode: '',
sampleCodeType: '',
issued: 0,
issuedUserId: '',
issuedUserName: '',
issuedTime: '',
orderNum: 0,
remark: ''
})
const formRules = {
sampleCode: [{ required: true, message: '请输入样本编码', trigger: 'blur' }],
sampleCodeType: [{ required: true, message: '请选择编码类型', trigger: 'change' }],
issuedUserId: [
{
validator: (_rule: unknown, value: string, callback: (err?: Error) => void) => {
if (formData.issued === 1 && !(value || '').trim()) {
callback(new Error('请选择下发用户'))
return
}
callback()
},
trigger: 'change'
}
]
}
const handleSelectionChange = (rows: Api.BizSampleCode.SampleCodeItem[]) => {
selectedRows.value = rows
}
async function loadData() {
loading.value = true
try {
const res = await fetchSampleCodeList({
page: pagination.page,
pageSize: pagination.pageSize,
keyword: searchForm.keyword || undefined,
sampleCodeType: searchForm.sampleCodeType || undefined,
issuedUserId: searchForm.issuedUserId || undefined
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
searchForm.keyword = ''
searchForm.sampleCodeType = ''
searchForm.issuedUserId = ''
pagination.page = 1
loadData()
}
function resetForm() {
formData.sampleCode = ''
formData.sampleCodeType = 'zxbm'
formData.issued = 0
formData.issuedUserId = ''
formData.issuedUserName = ''
formData.issuedTime = ''
formData.orderNum = 0
formData.remark = ''
editingId.value = null
}
async function loadUserOptions() {
if (userOptions.value.length) return
try {
userOptions.value = await fetchUserOptions()
} catch {
/* ignore */
}
}
async function handleAdd() {
isEdit.value = false
dialogTitle.value = '新增样本编码'
resetForm()
dialogVisible.value = true
formRef.value?.clearValidate?.()
await loadUserOptions()
}
async function handleEdit(row: Api.BizSampleCode.SampleCodeItem) {
isEdit.value = true
dialogTitle.value = '编辑样本编码'
editingId.value = row.id
formData.sampleCode = row.sampleCode || ''
formData.sampleCodeType = row.sampleCodeType || ''
formData.issued = row.issued === 1 ? 1 : 0
formData.issuedUserId = row.issuedUserId || ''
formData.issuedUserName = row.issuedUserName || ''
formData.issuedTime = row.issuedTime || ''
formData.orderNum = row.orderNum ?? 0
formData.remark = row.remark || ''
dialogVisible.value = true
formRef.value?.clearValidate?.()
await loadUserOptions()
}
async function handleSubmit() {
await formRef.value?.validate?.()
submitLoading.value = true
try {
const issueUserOpt =
formData.issued === 1
? userOptions.value.find((u) => u.value === formData.issuedUserId)
: undefined
const payload = {
sampleCode: formData.sampleCode.trim(),
sampleCodeType: !isEdit.value ? formData.sampleCodeType || undefined : undefined,
issued: formData.issued,
issuedUserId: formData.issued === 1 ? formData.issuedUserId || undefined : null,
issuedUserName:
formData.issued === 1
? (formData.issuedUserName || issueUserOpt?.label || '').trim() || undefined
: null,
issuedTime: formData.issuedTime || undefined,
orderNum: formData.orderNum,
remark: formData.remark?.trim() || undefined
}
if (isEdit.value && editingId.value) {
await fetchUpdateSampleCode(editingId.value, payload)
} else {
await fetchCreateSampleCode(payload)
}
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
dialogVisible.value = false
await loadData()
} catch {
/* */
} finally {
submitLoading.value = false
}
}
async function handleDelete(row: Api.BizSampleCode.SampleCodeItem) {
await ElMessageBox.confirm('确定要删除该样本编码吗?', '提示', { type: 'warning' })
await fetchDeleteSampleCode(row.id)
ElMessage.success('删除成功')
await loadData()
}
async function handleBatchDelete() {
const ids = selectedRows.value.map((r) => r.id)
if (!ids.length) return
await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 条吗?`, '提示', { type: 'warning' })
await withFullscreenLoading(async () => {
for (const id of ids) {
await fetchDeleteSampleCode(id)
}
ElMessage.success('批量删除成功')
selectedRows.value = []
await loadData()
}, fullscreenLoadingText.batchDelete)
}
async function handleBatchRevoke() {
const ids = selectedRows.value.map((r) => r.id)
if (!ids.length) return
await ElMessageBox.confirm(
`确定收回选中的 ${ids.length} 条吗?收回后将清除下发信息。`,
'提示',
{ type: 'warning' }
)
await withFullscreenLoading(async () => {
await fetchBatchRevokeSampleCodes(ids)
selectedRows.value = []
await loadData()
}, fullscreenLoadingText.batchDelete)
}
// --- 批量生成 ---
function openBatchGenerate() {
batchGenForm.sampleCodeType = 'zxbm'
batchGenForm.middle = '34'
batchGenForm.seqStart = 1
batchGenForm.seqEnd = 100
batchGenForm.digitCount = 6
batchGenVisible.value = true
}
async function handleBatchGenerate() {
await batchGenFormRef.value?.validate?.()
if (batchGenForm.seqStart > batchGenForm.seqEnd) {
ElMessage.warning('开始序号不能大于结束序号')
return
}
batchGenLoading.value = true
try {
await fetchBatchGenerateSampleCodes({
sampleCodeType: batchGenForm.sampleCodeType,
middle: batchGenForm.middle.trim(),
seqStart: batchGenForm.seqStart,
seqEnd: batchGenForm.seqEnd,
digitCount: batchGenForm.digitCount,
})
batchGenVisible.value = false
await loadData()
} finally {
batchGenLoading.value = false
}
}
// --- 批量下发 ---
async function openBatchIssue() {
if (!selectedRows.value.length) return
batchIssueForm.issuedUserId = ''
batchIssueVisible.value = true
await loadUserOptions()
}
async function handleBatchIssue() {
await batchIssueFormRef.value?.validate?.()
batchIssueLoading.value = true
try {
const ids = selectedRows.value.map(r => r.id)
const userOpt = userOptions.value.find(u => u.value === batchIssueForm.issuedUserId)
await fetchBatchIssueSampleCodes({
ids,
issuedUserId: batchIssueForm.issuedUserId,
issuedUserName: userOpt?.label || undefined,
})
batchIssueVisible.value = false
selectedRows.value = []
await loadData()
} finally {
batchIssueLoading.value = false
}
}
onMounted(async () => {
await loadUserOptions()
loadData()
})
</script>
<style scoped lang="scss">
.biz-dialog-form {
:deep(.el-form-item) {
margin-bottom: 0;
}
}
</style>