This commit is contained in:
zlwu committed 2026-05-17 09:05:10 +08:00
1 parent 8e2dd8b9f0
commit e5ba8f097a
7 files changed
+353 -14

No files matched your search

+1 -1
View File
@@ -1 +1 @@
92212
25041
+15 -1
View File
@@ -253,12 +253,14 @@ def _audit_to_dict(
a: BizProtocolAudit,
*,
protocol_no: str = "",
auditor_user_name: str = "",
) -> Dict[str, Any]:
return {
"id": a.id,
"collectionProtocolId": a.collection_protocol_id,
"protocolNo": protocol_no,
"auditorUserId": a.auditor_user_id or "",
"auditorUserName": auditor_user_name or "",
"auditRemark": a.audit_remark or "",
"auditStatus": a.audit_status or "",
"tenantNo": a.tenant_no,
@@ -636,7 +638,19 @@ def list_audits(
.limit(page_size)
.all()
)
out = [_audit_to_dict(a, protocol_no=pn or "") for a, pn in rows]
auditor_ids = list({a.auditor_user_id for a, _ in rows if a.auditor_user_id})
auditor_name_map: Dict[str, str] = {}
if auditor_ids:
for u in db.query(SysUser.id, SysUser.nick_name).filter(SysUser.id.in_(auditor_ids)).all():
auditor_name_map[u.id] = u.nick_name or ""
out = [
_audit_to_dict(
a,
protocol_no=pn or "",
auditor_user_name=auditor_name_map.get(a.auditor_user_id or "", ""),
)
for a, pn in rows
]
return out, int(total)
+3 -2
View File
@@ -51,12 +51,13 @@ export function fetchCollectionProtocolAuditList(params?: {
export function fetchSubmitProtocolAudit(
protocolId: string,
data: { auditStatus: string; auditRemark?: string }
data: { auditStatus: string; auditRemark?: string },
options?: { showSuccessMessage?: boolean }
) {
return request.post({
url: `/web/admin/collection-protocols/${protocolId}/audit`,
data,
showSuccessMessage: true
showSuccessMessage: options?.showSuccessMessage ?? true
})
}
+2 -1
View File
@@ -21,5 +21,6 @@ export async function withFullscreenLoading<T>(
export const fullscreenLoadingText = {
save: '正在保存,请稍候…',
delete: '正在删除,请稍候…',
batchDelete: '正在批量删除,请稍候…'
batchDelete: '正在批量删除,请稍候…',
batchAudit: '正在提交审核,请稍候…'
} as const
@@ -56,6 +56,10 @@
<el-icon><Plus /></el-icon>
新增
</el-button>
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="openAuditDialog()">
<el-icon><CircleCheck /></el-icon>
审核
</el-button>
<el-button class="btn-ghost" :disabled="!selectedRows.length" @click="handleBatchDelete">
<el-icon><Delete /></el-icon>
批量删除
@@ -208,7 +212,7 @@
/>
<el-table-column
label="操作"
width="320"
min-width="380"
fixed="right"
align="center"
header-align="center"
@@ -217,6 +221,9 @@
<el-button class="btn-op-item" @click="handleView(row)"
><el-icon><View /></el-icon>查看</el-button
>
<el-button class="btn-op-item" @click="openAuditDialog(row)"
><el-icon><CircleCheck /></el-icon>审核</el-button
>
<el-button class="btn-op-item primary" @click="handleEdit(row)"
><el-icon><Edit /></el-icon>编辑</el-button
>
@@ -247,8 +254,20 @@
@saved="onFormSaved"
/>
<!-- 审核弹窗 -->
<ProtocolAuditDialog
v-model="auditDialogVisible"
:targets="auditTargets"
@submitted="onAuditSubmitted"
/>
<!-- 查看弹窗 -->
<ProtocolViewDialog v-model="viewVisible" :protocol-id="viewProtocolId" :list-row="viewRow" />
<ProtocolViewDialog
v-model="viewVisible"
:protocol-id="viewProtocolId"
:list-row="viewRow"
show-audit-history
/>
</section>
</div>
</template>
@@ -261,10 +280,14 @@
Edit,
Delete,
Search,
RefreshLeft
RefreshLeft,
CircleCheck
} from '@element-plus/icons-vue'
import ProtocolFormDialog from '../protocol/ProtocolFormDialog.vue'
import ProtocolViewDialog from '../protocol/ProtocolViewDialog.vue'
import ProtocolAuditDialog, {
type ProtocolAuditTarget
} from '../protocol/ProtocolAuditDialog.vue'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
import {
fetchCollectionProtocolList,
@@ -372,6 +395,28 @@
loadData()
}
/* ====== 审核 ====== */
const auditDialogVisible = ref(false)
const auditTargets = ref<ProtocolAuditTarget[]>([])
function openAuditDialog(row?: CollectionProtocolRow) {
const rows = row ? [row] : selectedRows.value
if (!rows.length) {
ElMessage.warning('请先选择要审核的协议单')
return
}
auditTargets.value = rows.map((r) => ({
id: String(r.id),
protocolNo: r.protocolNo != null ? String(r.protocolNo) : undefined
}))
auditDialogVisible.value = true
}
function onAuditSubmitted() {
selectedRows.value = []
loadData()
}
/* ====== 删除 ====== */
function handleDelete(row: CollectionProtocolRow) {
ElMessageBox.confirm(`确认删除协议号「${row.protocolNo}」?`, '删除确认', {
@@ -0,0 +1,165 @@
<!-- 采集协议单 — 提交审核(支持批量,写入 biz_protocol_audit) -->
<template>
<el-dialog
:model-value="modelValue"
:title="dialogTitle"
width="560px"
class="dialog-form-layout"
align-center
destroy-on-close
:close-on-click-modal="false"
@update:model-value="emit('update:modelValue', $event)"
@closed="resetForm"
>
<p v-if="targets.length > 1" class="audit-target-hint">
将对已选
<strong>{{ targets.length }}</strong> 条协议单提交相同审核结果,每条各记一条审核记录。
</p>
<p v-else-if="targets.length === 1" class="audit-target-hint">
协议号:<strong>{{ targets[0].protocolNo || '—' }}</strong>
</p>
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
class="biz-dialog-form"
@submit.prevent
>
<el-form-item label="审核状态" prop="auditStatus">
<el-radio-group v-model="form.auditStatus">
<el-radio value="shtg">审核通过</el-radio>
<el-radio value="shth">审核退回</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="审核备注">
<el-input
v-model="form.auditRemark"
type="textarea"
:rows="3"
placeholder="可选"
maxlength="1000"
show-word-limit
/>
</el-form-item>
</el-form>
<template #footer>
<el-button class="btn-ghost" @click="emit('update:modelValue', false)">
<el-icon><Close /></el-icon>
取消
</el-button>
<el-button class="btn-main" type="primary" :loading="submitting" @click="handleSubmit">
<el-icon><Check /></el-icon>
确定
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import type { FormInstance, FormRules } from 'element-plus'
import { ElMessage } from 'element-plus'
import { Check, Close } from '@element-plus/icons-vue'
import { fetchSubmitProtocolAudit } from '@/api/collection-protocol'
import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
export type ProtocolAuditTarget = {
id: string
protocolNo?: string
}
defineOptions({ name: 'ProtocolAuditDialog' })
const props = defineProps<{
modelValue: boolean
targets: ProtocolAuditTarget[]
}>()
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
(e: 'submitted'): void
}>()
const formRef = ref<FormInstance>()
const submitting = ref(false)
const form = reactive({
auditStatus: 'shtg',
auditRemark: ''
})
const rules: FormRules = {
auditStatus: [{ required: true, message: '请选择审核状态', trigger: 'change' }]
}
const dialogTitle = computed(() =>
props.targets.length > 1 ? `批量审核(${props.targets.length} 条)` : '协议单审核'
)
function resetForm() {
form.auditStatus = 'shtg'
form.auditRemark = ''
formRef.value?.clearValidate()
}
async function handleSubmit() {
const valid = await formRef.value?.validate().catch(() => false)
if (!valid || !props.targets.length) return
const payload = {
auditStatus: form.auditStatus,
auditRemark: form.auditRemark.trim() || undefined
}
const rows = [...props.targets]
const isBatch = rows.length > 1
submitting.value = true
try {
await withFullscreenLoading(
async () => {
let ok = 0
const failed: string[] = []
for (const row of rows) {
try {
await fetchSubmitProtocolAudit(String(row.id), payload, {
showSuccessMessage: !isBatch
})
ok += 1
} catch {
failed.push(row.protocolNo || String(row.id))
}
}
if (isBatch) {
if (failed.length) {
ElMessage.warning(`成功 ${ok} 条,失败 ${failed.length} 条`)
} else {
ElMessage.success(`批量审核成功,共 ${ok} 条`)
}
}
if (ok > 0) {
emit('update:modelValue', false)
emit('submitted')
}
},
isBatch ? fullscreenLoadingText.batchAudit : fullscreenLoadingText.save
)
} finally {
submitting.value = false
}
}
</script>
<style scoped lang="scss">
.audit-target-hint {
margin: 0 0 16px;
font-size: 13px;
line-height: 1.5;
color: var(--mint-text-muted, #64748b);
strong {
color: #1e293b;
font-weight: 600;
}
}
</style>
@@ -318,6 +318,65 @@
</div>
<el-empty v-else description="暂无缴费明细" :image-size="48" />
</div>
<div v-if="showAuditHistory" id="view-section-audit" class="form-tab-pane">
<h3 class="detail-section-title">审核记录</h3>
<el-table
v-loading="auditLoading"
class="list-table sub-view-table"
:data="auditRecords"
stripe
empty-text="暂无审核记录"
>
<el-table-column
type="index"
label="序号"
width="68"
align="center"
header-align="center"
/>
<el-table-column
prop="createTime"
label="审核时间"
min-width="170"
align="center"
header-align="center"
/>
<el-table-column
prop="auditStatus"
label="审核状态"
width="120"
align="center"
header-align="center"
>
<template #default="{ row }">
<span
class="tag"
:class="String(row.auditStatus) === 'shtg' ? 'success' : 'warning'"
>
{{ auditStatusLabel(String(row.auditStatus || '')) }}
</span>
</template>
</el-table-column>
<el-table-column
prop="auditRemark"
label="审核备注"
min-width="200"
align="center"
header-align="center"
show-overflow-tooltip
/>
<el-table-column
prop="auditorUserName"
label="审核人"
min-width="120"
align="center"
header-align="center"
>
<template #default="{ row }">{{ auditorDisplay(row) }}</template>
</el-table-column>
</el-table>
</div>
</template>
</div>
@@ -352,6 +411,7 @@
import { Close } from '@element-plus/icons-vue'
import ProtocolAttachmentReadonly from './ProtocolAttachmentReadonly.vue'
import {
fetchCollectionProtocolAuditList,
fetchCollectionProtocolDetail,
type CollectionProtocolRow
} from '@/api/collection-protocol'
@@ -365,17 +425,22 @@
fetchUserList
} from '@/api/admin'
const props = defineProps<{
modelValue: boolean
protocolId: string | null
listRow?: CollectionProtocolRow | null
}>()
const props = withDefaults(
defineProps<{
modelValue: boolean
protocolId: string | null
listRow?: CollectionProtocolRow | null
/** 展示审核记录 Tab(采集协议单审核页) */
showAuditHistory?: boolean
}>(),
{ showAuditHistory: false }
)
const emit = defineEmits<{
(e: 'update:modelValue', val: boolean): void
}>()
const formTabs = [
const baseFormTabs = [
{ key: 'basic', label: '基本信息' },
{ key: 'party', label: '客户信息' },
{ key: 'collection', label: '采集信息' },
@@ -383,8 +448,18 @@
{ key: 'payment', label: '缴费信息' }
] as const
const formTabs = computed(() => {
const tabs: Array<{ key: string; label: string }> = [...baseFormTabs]
if (props.showAuditHistory) {
tabs.push({ key: 'audit', label: '审核记录' })
}
return tabs
})
const loading = ref(false)
const auditLoading = ref(false)
const bundle = ref<Record<string, any> | null>(null)
const auditRecords = ref<Array<Record<string, unknown>>>([])
const activeTab = ref('basic')
const sampleTypeOptions = ref<Array<{ id: string; typeName: string }>>([])
@@ -476,6 +551,18 @@
return m[s] || s || '—'
}
function auditStatusLabel(s: string) {
if (s === 'shtg') return '审核通过'
if (s === 'shth') return '审核退回'
return s || '—'
}
function auditorDisplay(row: Record<string, unknown>) {
const name = String(row.auditorUserName || '').trim()
if (name) return name
return dash(row.auditorUserId)
}
function payModeLabel(s: string) {
if (s === 'dj') return '趸交'
if (s === 'fq') return '分期'
@@ -643,14 +730,39 @@
await Promise.all(tasks)
}
async function loadAuditRecords() {
if (!props.showAuditHistory || !props.protocolId) {
auditRecords.value = []
return
}
auditLoading.value = true
try {
const pno =
String(protocol.value?.protocolNo || props.listRow?.protocolNo || '').trim() || undefined
const res = await fetchCollectionProtocolAuditList({
page: 1,
pageSize: 100,
keyword: pno
})
const pid = props.protocolId
auditRecords.value = (res.list || []).filter(
(row) => String(row.collectionProtocolId || '') === pid
)
} finally {
auditLoading.value = false
}
}
async function loadDetail() {
if (!props.protocolId) return
loading.value = true
bundle.value = null
auditRecords.value = []
try {
const data = await fetchCollectionProtocolDetail(props.protocolId)
bundle.value = data as Record<string, any>
await loadLookups(data as Record<string, any>)
await loadAuditRecords()
} finally {
loading.value = false
}
@@ -667,6 +779,7 @@
function handleClosed() {
bundle.value = null
auditRecords.value = []
activeTab.value = 'basic'
}