提交
This commit is contained in:
1 parent
7145c6c683
commit
c137b46939
311 files changed
+41432
-30702
No files matched your search
@@ -0,0 +1,836 @@
|
||||
<!-- 细胞样本基础信息维护 · 样本编码 Tab -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="sample-code-tab">
|
||||
<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" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-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="编码类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sampleCodeTypeLabel(row.sampleCodeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="issued"
|
||||
label="是否下发"
|
||||
min-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="操作"
|
||||
min-width="120"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</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"
|
||||
/>
|
||||
</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,
|
||||
More,
|
||||
Edit,
|
||||
Delete
|
||||
} 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: 'SampleCodeTab' })
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUserOptions()
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.biz-dialog-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,16 +2,13 @@
|
||||
<!-- 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>
|
||||
<h2>细胞样本基础信息维护</h2>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="panel-tabs">
|
||||
<el-tab-pane label="细胞样本类型" name="type">
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
@@ -113,17 +110,32 @@
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
min-width="120"
|
||||
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>
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -209,6 +221,12 @@
|
||||
code-label="类型编码"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="样本编码" name="sample-code" lazy>
|
||||
<SampleCodeTab />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -223,7 +241,10 @@
|
||||
View,
|
||||
Collection,
|
||||
RefreshLeft,
|
||||
Search
|
||||
Search,
|
||||
More,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
@@ -233,9 +254,12 @@
|
||||
fetchDeleteCellSampleType
|
||||
} from '@/api/admin'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import SampleCodeTab from './SampleCodeTab.vue'
|
||||
|
||||
defineOptions({ name: 'BizCellSampleType' })
|
||||
|
||||
const route = useRoute()
|
||||
const activeTab = ref('type')
|
||||
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
|
||||
const tableData = ref<Api.BizCellSampleType.CellSampleTypeItem[]>([])
|
||||
const selectedRows = ref<Api.BizCellSampleType.CellSampleTypeItem[]>([])
|
||||
@@ -382,6 +406,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.BizCellSampleType.CellSampleTypeItem) {
|
||||
await ElMessageBox.confirm('确定要删除该类型吗?', '提示', { type: 'warning' })
|
||||
await fetchDeleteCellSampleType(row.id)
|
||||
@@ -413,6 +444,10 @@
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
const tab = route.query.tab
|
||||
if (tab === 'sample-code') {
|
||||
activeTab.value = 'sample-code'
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -1,781 +1,18 @@
|
||||
<!-- 采集协议号管理 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<!-- 旧「采集协议号管理」菜单入口:重定向至细胞采集基础信息维护 · 采集协议号 Tab -->
|
||||
<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.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" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="采集协议号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="issued"
|
||||
label="是否下发"
|
||||
min-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 }">{{ resolveIssuedUserName(row) }}</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="140"
|
||||
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="操作"
|
||||
min-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="prefix">
|
||||
<el-input v-model="batchGenForm.prefix" placeholder="如 ZKZX" />
|
||||
</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="protocolNo">
|
||||
<el-input
|
||||
v-model="formData.protocolNo"
|
||||
placeholder="租户内唯一,最多32位"
|
||||
maxlength="32"
|
||||
/>
|
||||
</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?.protocolNo || '协议号详情'"
|
||||
:icon="Tickets"
|
||||
:meta="viewRow?.createTime ? '创建时间:' + viewRow.createTime : ''"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</section>
|
||||
</div>
|
||||
<div />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Tickets,
|
||||
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 {
|
||||
fetchCollectionProtocolNoList,
|
||||
fetchCreateCollectionProtocolNo,
|
||||
fetchUpdateCollectionProtocolNo,
|
||||
fetchDeleteCollectionProtocolNo,
|
||||
fetchUserOptions,
|
||||
fetchBatchGenerateProtocolNos,
|
||||
fetchBatchIssueProtocolNos,
|
||||
fetchBatchRevokeProtocolNos
|
||||
} from '@/api/admin'
|
||||
|
||||
defineOptions({ name: 'BizCollectionProtocolNo' })
|
||||
|
||||
const searchForm = reactive<{ keyword: string; issuedUserId: string }>({
|
||||
keyword: '',
|
||||
issuedUserId: ''
|
||||
})
|
||||
const tableData = ref<Api.BizCollectionProtocolNo.ProtocolNoItem[]>([])
|
||||
const selectedRows = ref<Api.BizCollectionProtocolNo.ProtocolNoItem[]>([])
|
||||
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 router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// ---- 查看弹窗 ----
|
||||
const viewVisible = ref(false)
|
||||
const viewRow = ref<Api.BizCollectionProtocolNo.ProtocolNoItem | null>(null)
|
||||
const viewSections = computed(() => {
|
||||
const row = viewRow.value
|
||||
if (!row) return []
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '采集协议号', value: row.protocolNo },
|
||||
{ label: '是否下发', value: row.issued === 1 ? '是' : '否' },
|
||||
{ label: '下发对象', value: resolveIssuedUserName(row) },
|
||||
{ label: '下发时间', value: row.issuedTime || '-' },
|
||||
{ label: '创建时间', value: row.createTime },
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
function handleView(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
// --- 批量生成 ---
|
||||
const batchGenVisible = ref(false)
|
||||
const batchGenLoading = ref(false)
|
||||
const batchGenFormRef = ref()
|
||||
const batchGenForm = reactive({
|
||||
prefix: 'ZKZX',
|
||||
middle: '34',
|
||||
seqStart: 1,
|
||||
seqEnd: 100,
|
||||
digitCount: 6
|
||||
})
|
||||
const batchGenRules = {
|
||||
prefix: [{ required: true, message: '请输入固定前缀', trigger: 'blur' }],
|
||||
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 batchGenPreview = computed(() => {
|
||||
const p = batchGenForm.prefix || ''
|
||||
const m = batchGenForm.middle || ''
|
||||
const s = batchGenForm.seqStart ?? 0
|
||||
const e = batchGenForm.seqEnd ?? 0
|
||||
const d = batchGenForm.digitCount ?? 6
|
||||
if (!p || e < s) return '—'
|
||||
const first = `${p}${m}${String(s).padStart(d, '0')}`
|
||||
const last = `${p}${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 }>>([])
|
||||
|
||||
function resolveIssuedUserName(row: Api.BizCollectionProtocolNo.ProtocolNoItem): string {
|
||||
const name = (row.issuedUserName || '').trim()
|
||||
if (name) return name
|
||||
const uid = (row.issuedUserId || '').trim()
|
||||
if (!uid) return '-'
|
||||
const opt = userOptions.value.find((u) => u.value === uid)
|
||||
return (opt?.label || '').trim() || '-'
|
||||
}
|
||||
|
||||
const formData = reactive({
|
||||
protocolNo: '',
|
||||
issued: 0,
|
||||
issuedUserId: '',
|
||||
issuedUserName: '',
|
||||
issuedTime: '',
|
||||
orderNum: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
protocolNo: [{ required: true, message: '请输入采集协议号', trigger: 'blur' }],
|
||||
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.BizCollectionProtocolNo.ProtocolNoItem[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchCollectionProtocolNoList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: searchForm.keyword || 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.issuedUserId = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formData.protocolNo = ''
|
||||
formData.issued = 0
|
||||
formData.issuedUserId = ''
|
||||
formData.issuedUserName = ''
|
||||
formData.issuedTime = ''
|
||||
formData.orderNum = 0
|
||||
formData.remark = ''
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
async function handleAdd() {
|
||||
isEdit.value = false
|
||||
dialogTitle.value = '新增协议号'
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
formRef.value?.clearValidate?.()
|
||||
await loadUserOptions()
|
||||
}
|
||||
|
||||
async function handleEdit(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
isEdit.value = true
|
||||
dialogTitle.value = '编辑协议号'
|
||||
editingId.value = row.id
|
||||
formData.protocolNo = row.protocolNo || ''
|
||||
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 = {
|
||||
protocolNo: formData.protocolNo.trim(),
|
||||
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 fetchUpdateCollectionProtocolNo(editingId.value, payload)
|
||||
} else {
|
||||
await fetchCreateCollectionProtocolNo(payload)
|
||||
}
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
||||
dialogVisible.value = false
|
||||
await loadData()
|
||||
} catch {
|
||||
/* */
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
await ElMessageBox.confirm('确定要删除该协议号吗?', '提示', { type: 'warning' })
|
||||
await fetchDeleteCollectionProtocolNo(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 fetchDeleteCollectionProtocolNo(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 fetchBatchRevokeProtocolNos(ids)
|
||||
selectedRows.value = []
|
||||
await loadData()
|
||||
}, fullscreenLoadingText.batchDelete)
|
||||
}
|
||||
|
||||
// --- 批量生成 ---
|
||||
function openBatchGenerate() {
|
||||
batchGenForm.prefix = 'ZKZX'
|
||||
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 fetchBatchGenerateProtocolNos({
|
||||
prefix: batchGenForm.prefix.trim(),
|
||||
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 fetchBatchIssueProtocolNos({
|
||||
ids,
|
||||
issuedUserId: batchIssueForm.issuedUserId,
|
||||
issuedUserName: userOpt?.label || undefined
|
||||
})
|
||||
batchIssueVisible.value = false
|
||||
selectedRows.value = []
|
||||
await loadData()
|
||||
} finally {
|
||||
batchIssueLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUserOptions() {
|
||||
if (userOptions.value.length) return
|
||||
try {
|
||||
userOptions.value = await fetchUserOptions()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUserOptions()
|
||||
loadData()
|
||||
onMounted(() => {
|
||||
router.replace({
|
||||
name: 'BizCollectionRegion',
|
||||
query: { ...route.query, tab: 'protocol-no' }
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.biz-dialog-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,795 @@
|
||||
<!-- 细胞采集基础信息维护 · 采集协议号 Tab -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<template>
|
||||
<div class="collection-protocol-no-tab">
|
||||
<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.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" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-width="68"
|
||||
align="center"
|
||||
header-align="center"
|
||||
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="protocolNo"
|
||||
label="采集协议号"
|
||||
min-width="160"
|
||||
align="center"
|
||||
header-align="center"
|
||||
/>
|
||||
<el-table-column
|
||||
prop="issued"
|
||||
label="是否下发"
|
||||
min-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 }">{{ resolveIssuedUserName(row) }}</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="140"
|
||||
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="操作"
|
||||
min-width="120"
|
||||
fixed="right"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</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="prefix">
|
||||
<el-input v-model="batchGenForm.prefix" placeholder="如 ZKZX" />
|
||||
</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="protocolNo">
|
||||
<el-input
|
||||
v-model="formData.protocolNo"
|
||||
placeholder="租户内唯一,最多32位"
|
||||
maxlength="32"
|
||||
/>
|
||||
</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?.protocolNo || '协议号详情'"
|
||||
:icon="Tickets"
|
||||
:meta="viewRow?.createTime ? '创建时间:' + viewRow.createTime : ''"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import {
|
||||
Plus,
|
||||
Close,
|
||||
Check,
|
||||
View,
|
||||
Tickets,
|
||||
RefreshLeft,
|
||||
Search,
|
||||
DocumentAdd,
|
||||
Promotion,
|
||||
RefreshRight,
|
||||
More,
|
||||
Edit,
|
||||
Delete
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCollectionProtocolNoList,
|
||||
fetchCreateCollectionProtocolNo,
|
||||
fetchUpdateCollectionProtocolNo,
|
||||
fetchDeleteCollectionProtocolNo,
|
||||
fetchUserOptions,
|
||||
fetchBatchGenerateProtocolNos,
|
||||
fetchBatchIssueProtocolNos,
|
||||
fetchBatchRevokeProtocolNos
|
||||
} from '@/api/admin'
|
||||
|
||||
defineOptions({ name: 'CollectionProtocolNoTab' })
|
||||
|
||||
const searchForm = reactive<{ keyword: string; issuedUserId: string }>({
|
||||
keyword: '',
|
||||
issuedUserId: ''
|
||||
})
|
||||
const tableData = ref<Api.BizCollectionProtocolNo.ProtocolNoItem[]>([])
|
||||
const selectedRows = ref<Api.BizCollectionProtocolNo.ProtocolNoItem[]>([])
|
||||
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.BizCollectionProtocolNo.ProtocolNoItem | null>(null)
|
||||
const viewSections = computed(() => {
|
||||
const row = viewRow.value
|
||||
if (!row) return []
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
fields: [
|
||||
{ label: '采集协议号', value: row.protocolNo },
|
||||
{ label: '是否下发', value: row.issued === 1 ? '是' : '否' },
|
||||
{ label: '下发对象', value: resolveIssuedUserName(row) },
|
||||
{ label: '下发时间', value: row.issuedTime || '-' },
|
||||
{ label: '创建时间', value: row.createTime },
|
||||
{ label: '备注', value: row.remark, span: true }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
function handleView(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
viewRow.value = row
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
// --- 批量生成 ---
|
||||
const batchGenVisible = ref(false)
|
||||
const batchGenLoading = ref(false)
|
||||
const batchGenFormRef = ref()
|
||||
const batchGenForm = reactive({
|
||||
prefix: 'ZKZX',
|
||||
middle: '34',
|
||||
seqStart: 1,
|
||||
seqEnd: 100,
|
||||
digitCount: 6
|
||||
})
|
||||
const batchGenRules = {
|
||||
prefix: [{ required: true, message: '请输入固定前缀', trigger: 'blur' }],
|
||||
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 batchGenPreview = computed(() => {
|
||||
const p = batchGenForm.prefix || ''
|
||||
const m = batchGenForm.middle || ''
|
||||
const s = batchGenForm.seqStart ?? 0
|
||||
const e = batchGenForm.seqEnd ?? 0
|
||||
const d = batchGenForm.digitCount ?? 6
|
||||
if (!p || e < s) return '—'
|
||||
const first = `${p}${m}${String(s).padStart(d, '0')}`
|
||||
const last = `${p}${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 }>>([])
|
||||
|
||||
function resolveIssuedUserName(row: Api.BizCollectionProtocolNo.ProtocolNoItem): string {
|
||||
const name = (row.issuedUserName || '').trim()
|
||||
if (name) return name
|
||||
const uid = (row.issuedUserId || '').trim()
|
||||
if (!uid) return '-'
|
||||
const opt = userOptions.value.find((u) => u.value === uid)
|
||||
return (opt?.label || '').trim() || '-'
|
||||
}
|
||||
|
||||
const formData = reactive({
|
||||
protocolNo: '',
|
||||
issued: 0,
|
||||
issuedUserId: '',
|
||||
issuedUserName: '',
|
||||
issuedTime: '',
|
||||
orderNum: 0,
|
||||
remark: ''
|
||||
})
|
||||
|
||||
const formRules = {
|
||||
protocolNo: [{ required: true, message: '请输入采集协议号', trigger: 'blur' }],
|
||||
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.BizCollectionProtocolNo.ProtocolNoItem[]) => {
|
||||
selectedRows.value = rows
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await fetchCollectionProtocolNoList({
|
||||
page: pagination.page,
|
||||
pageSize: pagination.pageSize,
|
||||
keyword: searchForm.keyword || 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.issuedUserId = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
formData.protocolNo = ''
|
||||
formData.issued = 0
|
||||
formData.issuedUserId = ''
|
||||
formData.issuedUserName = ''
|
||||
formData.issuedTime = ''
|
||||
formData.orderNum = 0
|
||||
formData.remark = ''
|
||||
editingId.value = null
|
||||
}
|
||||
|
||||
async function handleAdd() {
|
||||
isEdit.value = false
|
||||
dialogTitle.value = '新增协议号'
|
||||
resetForm()
|
||||
dialogVisible.value = true
|
||||
formRef.value?.clearValidate?.()
|
||||
await loadUserOptions()
|
||||
}
|
||||
|
||||
async function handleEdit(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
isEdit.value = true
|
||||
dialogTitle.value = '编辑协议号'
|
||||
editingId.value = row.id
|
||||
formData.protocolNo = row.protocolNo || ''
|
||||
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 = {
|
||||
protocolNo: formData.protocolNo.trim(),
|
||||
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 fetchUpdateCollectionProtocolNo(editingId.value, payload)
|
||||
} else {
|
||||
await fetchCreateCollectionProtocolNo(payload)
|
||||
}
|
||||
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
|
||||
dialogVisible.value = false
|
||||
await loadData()
|
||||
} catch {
|
||||
/* */
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.BizCollectionProtocolNo.ProtocolNoItem) {
|
||||
await ElMessageBox.confirm('确定要删除该协议号吗?', '提示', { type: 'warning' })
|
||||
await fetchDeleteCollectionProtocolNo(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 fetchDeleteCollectionProtocolNo(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 fetchBatchRevokeProtocolNos(ids)
|
||||
selectedRows.value = []
|
||||
await loadData()
|
||||
}, fullscreenLoadingText.batchDelete)
|
||||
}
|
||||
|
||||
// --- 批量生成 ---
|
||||
function openBatchGenerate() {
|
||||
batchGenForm.prefix = 'ZKZX'
|
||||
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 fetchBatchGenerateProtocolNos({
|
||||
prefix: batchGenForm.prefix.trim(),
|
||||
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 fetchBatchIssueProtocolNos({
|
||||
ids,
|
||||
issuedUserId: batchIssueForm.issuedUserId,
|
||||
issuedUserName: userOpt?.label || undefined
|
||||
})
|
||||
batchIssueVisible.value = false
|
||||
selectedRows.value = []
|
||||
await loadData()
|
||||
} finally {
|
||||
batchIssueLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadUserOptions() {
|
||||
if (userOptions.value.length) return
|
||||
try {
|
||||
userOptions.value = await fetchUserOptions()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUserOptions()
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.biz-dialog-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,16 +2,13 @@
|
||||
<!-- 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>
|
||||
<h2>细胞采集基础信息维护</h2>
|
||||
</div>
|
||||
|
||||
<el-tabs v-model="activeTab" class="panel-tabs">
|
||||
<el-tab-pane label="采集地区及医院" name="region">
|
||||
<div class="filter-block">
|
||||
<el-form :model="searchForm" class="filter-form" @submit.prevent>
|
||||
<div class="filter-grid">
|
||||
@@ -117,20 +114,35 @@
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
min-width="160"
|
||||
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="openHospitalDialog(row)"
|
||||
>地区医院配置</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)">编辑</el-button>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item primary" @click="openHospitalDialog(row)">
|
||||
地区医院配置
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="view">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -354,6 +366,12 @@
|
||||
:meta="viewRow?.createTime ? '创建时间:' + viewRow.createTime : ''"
|
||||
:sections="viewSections"
|
||||
/>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="采集协议号" name="protocol-no" lazy>
|
||||
<CollectionProtocolNoTab />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
@@ -361,7 +379,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, Location, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, Edit, Delete, More, Location, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import {
|
||||
@@ -374,9 +392,12 @@
|
||||
fetchUpdateHospital,
|
||||
fetchDeleteHospital
|
||||
} from '@/api/admin'
|
||||
import CollectionProtocolNoTab from './CollectionProtocolNoTab.vue'
|
||||
|
||||
defineOptions({ name: 'BizCollectionRegion' })
|
||||
|
||||
const route = useRoute()
|
||||
const activeTab = ref('region')
|
||||
const searchForm = reactive<{ keyword: string }>({ keyword: '' })
|
||||
const tableData = ref<Api.BizCollectionRegion.RegionItem[]>([])
|
||||
const selectedRows = ref<Api.BizCollectionRegion.RegionItem[]>([])
|
||||
@@ -519,6 +540,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'view': handleView(row); break
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.BizCollectionRegion.RegionItem) {
|
||||
await ElMessageBox.confirm('删除地区将同时软删除其下属全部医院,确定继续?', '提示', {
|
||||
type: 'warning'
|
||||
@@ -661,6 +690,10 @@
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
const tab = route.query.tab
|
||||
if (tab === 'protocol-no') {
|
||||
activeTab.value = 'protocol-no'
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
<!-- 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>
|
||||
@@ -139,17 +134,32 @@
|
||||
/>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="320"
|
||||
min-width="120"
|
||||
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>
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item" @click="handleView(row)">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -249,7 +259,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Close, Check, View, User, RefreshLeft, Search } from '@element-plus/icons-vue'
|
||||
import { Plus, Close, Check, View, User, RefreshLeft, Search, More, Edit, Delete } from '@element-plus/icons-vue'
|
||||
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
|
||||
import {
|
||||
fetchCustomerList,
|
||||
@@ -429,6 +439,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.BizCustomer.CustomerItem) {
|
||||
await ElMessageBox.confirm('确定要删除该客户吗?', '提示', { type: 'warning' })
|
||||
await fetchDeleteCustomer(row.id)
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
<!-- 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>
|
||||
@@ -111,24 +106,35 @@
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
min-width="400"
|
||||
min-width="160"
|
||||
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="openItemDialog(row)"
|
||||
><el-icon><Setting /></el-icon>险种价格配置</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleEdit(row)"
|
||||
><el-icon><Edit /></el-icon>编辑</el-button
|
||||
>
|
||||
<el-button class="btn-op-item" @click="handleDelete(row)"
|
||||
><el-icon><Delete /></el-icon>删除</el-button
|
||||
>
|
||||
<div style="display: flex; justify-content: center; gap: 8px">
|
||||
<el-button class="btn-op-item primary" @click="openItemDialog(row)">
|
||||
<el-icon><Setting /></el-icon>险种价格配置
|
||||
</el-button>
|
||||
<el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
|
||||
<el-button class="btn-op-item">
|
||||
<el-icon><More /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="view">
|
||||
<el-icon><View /></el-icon>查看
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="edit">
|
||||
<el-icon><Edit /></el-icon>编辑
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="delete" divided>
|
||||
<el-icon><Delete /></el-icon>删除
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -412,6 +418,7 @@
|
||||
Setting,
|
||||
Edit,
|
||||
Delete,
|
||||
More,
|
||||
OfficeBuilding
|
||||
} from '@element-plus/icons-vue'
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
@@ -654,6 +661,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
function handleDropdownCommand(command: string, row: any) {
|
||||
switch (command) {
|
||||
case 'view': handleView(row); break
|
||||
case 'edit': handleEdit(row); break
|
||||
case 'delete': handleDelete(row); break
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(row: any) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
|
||||
@@ -1,822 +1,18 @@
|
||||
<!-- 样本编码管理 -->
|
||||
<!-- eslint-disable vue/no-v-model-argument -->
|
||||
<!-- 旧「样本编码管理」菜单入口:重定向至细胞样本基础信息维护 · 样本编码 Tab -->
|
||||
<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" min-width="55" align="center" header-align="center" />
|
||||
<el-table-column
|
||||
type="index"
|
||||
label="序号"
|
||||
min-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="编码类型"
|
||||
min-width="100"
|
||||
align="center"
|
||||
header-align="center"
|
||||
>
|
||||
<template #default="{ row }">{{ sampleCodeTypeLabel(row.sampleCodeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="issued"
|
||||
label="是否下发"
|
||||
min-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="操作"
|
||||
min-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>
|
||||
<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 router = useRouter()
|
||||
const route = useRoute()
|
||||
|
||||
// ---- 查看弹窗 ----
|
||||
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
|
||||
}
|
||||
}
|
||||
// 输入检索条件时自动搜索(防抖 300ms)
|
||||
watchDebounced(
|
||||
searchForm,
|
||||
() => {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
},
|
||||
{ debounce: 300, deep: true }
|
||||
)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadUserOptions()
|
||||
loadData()
|
||||
onMounted(() => {
|
||||
router.replace({
|
||||
name: 'BizCellSampleType',
|
||||
query: { ...route.query, tab: 'sample-code' }
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.biz-dialog-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user