Files
zkxw/web-admin/src/views/basic-info/collection-protocol-no/index.vue
T
2026-05-20 09:28:36 +08:00

685 lines
22 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 采集协议号管理 -->
<!-- eslint-disable vue/no-v-model-argument -->
<template>
<div class="page-container">
<header class="page-head">
<h1>采集协议号</h1>
<p class="page-desc">维护采集协议号池及是否下发标记。</p>
</header>
<section class="panel">
<div class="panel-top">
<h2>采集协议号列表</h2>
</div>
<div class="filter-block">
<el-form :model="searchForm" class="filter-form" @submit.prevent>
<div class="filter-grid">
<el-form-item label="关键词">
<el-input v-model="searchForm.keyword" placeholder="协议号/备注" clearable />
</el-form-item>
</div>
<div class="filter-actions-row">
<span class="filter-hint">模糊查询。</span>
<div class="filter-actions-end">
<el-button class="btn-ghost" @click="handleReset">重置</el-button>
<el-button class="btn-main" type="primary" @click="handleSearch">搜索</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-button>
<el-button
class="btn-ghost"
:disabled="selectedRows.length === 0"
@click="openBatchIssue"
>
批量下发
</el-button>
<el-button
class="btn-ghost"
:disabled="selectedRows.length === 0"
@click="handleBatchRevoke"
>
批量收回
</el-button>
<el-button
class="btn-ghost"
type="danger"
:disabled="selectedRows.length === 0"
@click="handleBatchDelete"
>
批量删除
</el-button>
</div>
</div>
<div class="list-body list-wrap">
<el-table
class="list-table"
:data="tableData"
v-loading="loading"
stripe
row-key="id"
@selection-change="handleSelectionChange"
@row-dblclick="handleView"
>
<el-table-column type="selection" width="55" align="center" header-align="center" />
<el-table-column
type="index"
label="序号"
width="68"
align="center"
header-align="center"
:index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
/>
<el-table-column
prop="protocolNo"
label="采集协议号"
min-width="160"
align="center"
header-align="center"
/>
<el-table-column
prop="issued"
label="是否下发"
width="80"
align="center"
header-align="center"
>
<template #default="{ row }">
<span>{{ row.issued === 1 ? '是' : '否' }}</span>
</template>
</el-table-column>
<el-table-column
prop="issuedUserName"
label="下发对象"
min-width="100"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.issuedUserName || '-' }}</template>
</el-table-column>
<el-table-column
prop="issuedTime"
label="下发时间"
min-width="180"
align="center"
header-align="center"
>
<template #default="{ row }">{{ row.issuedTime || '-' }}</template>
</el-table-column>
<el-table-column
prop="createTime"
label="创建时间"
min-width="180"
align="center"
header-align="center"
/>
<el-table-column
label="操作"
width="320"
fixed="right"
align="center"
header-align="center"
>
<template #default="{ row }">
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看</el-button>
<el-button class="btn-op-item primary" @click="handleEdit(row)">编辑</el-button>
<el-button class="btn-op-item" @click="handleDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination list-pager-bar">
<el-pagination
:current-page="pagination.page"
:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[10, 20, 50]"
layout="total, sizes, prev, pager, next"
@size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
@current-change="(val) => ((pagination.page = val), loadData())"
/>
</div>
</div>
<!-- 批量生成弹窗 -->
<el-dialog
v-model="batchGenVisible"
title="批量生成协议号"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
>
<el-form
ref="batchGenFormRef"
:model="batchGenForm"
:rules="batchGenRules"
label-position="top"
scroll-to-error
>
<div class="form-grid-2">
<el-form-item label="固定前缀" prop="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="下发用户">
<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>
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Close, Check, View, Tickets } 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 }>({ keyword: '' })
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: row.issuedUserName || '-' },
{ 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 }>>([])
const formData = reactive({
protocolNo: '',
issued: 0,
issuedUserId: '',
issuedUserName: '',
issuedTime: '',
orderNum: 0,
remark: ''
})
const formRules = {
protocolNo: [{ required: true, message: '请输入采集协议号', trigger: 'blur' }]
}
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
})
tableData.value = res.list || []
pagination.total = res.total ?? 0
} finally {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
loadData()
}
function handleReset() {
searchForm.keyword = ''
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
}
function handleAdd() {
isEdit.value = false
dialogTitle.value = '新增协议号'
resetForm()
dialogVisible.value = true
formRef.value?.clearValidate?.()
}
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?.()
if (!userOptions.value.length) {
try {
userOptions.value = await fetchUserOptions()
} catch { /* ignore */ }
}
}
async function handleSubmit() {
await formRef.value?.validate?.()
submitLoading.value = true
try {
const payload = {
protocolNo: formData.protocolNo.trim(),
issued: formData.issued,
issuedUserId: formData.issued === 1 ? formData.issuedUserId || undefined : null,
issuedUserName: formData.issued === 1 ? formData.issuedUserName || 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
// load user options
if (!userOptions.value.length) {
try {
userOptions.value = await fetchUserOptions()
} catch { /* ignore */ }
}
}
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
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped lang="scss">
.biz-dialog-form {
:deep(.el-form-item) {
margin-bottom: 0;
}
}
</style>