This commit is contained in:
269
src/components/business/audit/AuditResourceSearchDialog.vue
Normal file
269
src/components/business/audit/AuditResourceSearchDialog.vue
Normal file
@@ -0,0 +1,269 @@
|
||||
<template>
|
||||
<ElDialog
|
||||
v-model="visible"
|
||||
title="精确注册资源"
|
||||
width="min(880px, 92vw)"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
>
|
||||
<div v-loading="loading" class="result-list">
|
||||
<ElCard
|
||||
v-for="row in items"
|
||||
:key="`${row.resource_type}:${row.resource_id || row.resource_key}`"
|
||||
shadow="never"
|
||||
class="resource-card"
|
||||
>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<strong>业务信息</strong>
|
||||
<ElButton link type="primary" :disabled="!row.resource_id" @click="openTimeline(row)">
|
||||
<template #icon>
|
||||
<ElIcon><Clock /></ElIcon>
|
||||
</template>
|
||||
审计时间线
|
||||
</ElButton>
|
||||
</div>
|
||||
</template>
|
||||
<div v-if="snapshotDetails(row).length" class="snapshot-grid">
|
||||
<div v-for="item in snapshotDetails(row)" :key="item.label" class="snapshot-item">
|
||||
<span>{{ item.label }}</span>
|
||||
<strong>{{ item.value }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="snapshot-item">
|
||||
<span>业务标识</span>
|
||||
<strong>{{ row.resource_key || '-' }}</strong>
|
||||
</div>
|
||||
</ElCard>
|
||||
<ElEmpty v-if="!loading && !items.length" description="未找到匹配的注册资源" />
|
||||
</div>
|
||||
|
||||
<div v-if="total > pageSize" class="pagination">
|
||||
<ElPagination
|
||||
v-model:current-page="page"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
@current-change="search(false)"
|
||||
/>
|
||||
</div>
|
||||
</ElDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { Clock } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { AuditService } from '@/api/modules'
|
||||
import type { AuditResourceCandidate, AuditSearchResourceType } from '@/types/api'
|
||||
import { openAuditInvestigation } from './investigationController'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
resourceType: AuditSearchResourceType
|
||||
initialKeyword?: string
|
||||
}>()
|
||||
const emit = defineEmits<{ 'update:modelValue': [value: boolean] }>()
|
||||
|
||||
const visible = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
})
|
||||
const keyword = ref('')
|
||||
const loading = ref(false)
|
||||
const page = ref(1)
|
||||
const pageSize = 10
|
||||
const total = ref(0)
|
||||
const items = ref<AuditResourceCandidate[]>([])
|
||||
let searchSequence = 0
|
||||
const resourceTypeLabels: Record<AuditSearchResourceType, string> = {
|
||||
iot_card: 'IoT卡',
|
||||
device: '设备',
|
||||
shop: '店铺',
|
||||
order: '订单',
|
||||
refund: '退款单'
|
||||
}
|
||||
|
||||
const resourceTypeLabel = computed(() => resourceTypeLabels[props.resourceType])
|
||||
|
||||
interface SnapshotField {
|
||||
label: string
|
||||
keys: string[]
|
||||
format?: (value: unknown) => string
|
||||
}
|
||||
|
||||
const textValue = (value: unknown) => String(value ?? '-')
|
||||
const refundStatusValue = (value: unknown) =>
|
||||
({ 1: '待审批', 2: '已通过', 3: '已拒绝', 4: '已退回' })[Number(value)] || textValue(value)
|
||||
const centAmountValue = (value: unknown) => {
|
||||
const amount = Number(value)
|
||||
return Number.isFinite(amount) ? `¥${(amount / 100).toFixed(2)}` : textValue(value)
|
||||
}
|
||||
const snapshotFields: Record<AuditSearchResourceType, SnapshotField[]> = {
|
||||
iot_card: [
|
||||
{ label: 'ICCID', keys: ['iccid', 'iccid_19', 'iccid_20'] },
|
||||
{ label: '虚拟号', keys: ['virtual_no'] },
|
||||
{ label: 'MSISDN', keys: ['msisdn'] },
|
||||
{ label: '运营商', keys: ['carrier_name', 'carrier_type'] }
|
||||
],
|
||||
device: [
|
||||
{ label: '虚拟号', keys: ['virtual_no'] },
|
||||
{ label: 'IMEI', keys: ['imei'] },
|
||||
{ label: 'SN', keys: ['sn', 'serial_no'] },
|
||||
{ label: '设备编号', keys: ['device_no'] }
|
||||
],
|
||||
shop: [
|
||||
{ label: '店铺编号', keys: ['shop_code'] },
|
||||
{ label: '店铺名称', keys: ['shop_name', 'name'] },
|
||||
{ label: '联系人', keys: ['contact_name'] },
|
||||
{ label: '联系电话', keys: ['contact_phone', 'phone'] }
|
||||
],
|
||||
order: [
|
||||
{ label: '订单号', keys: ['order_no'] },
|
||||
{ label: '资产编号', keys: ['asset_identifier'] },
|
||||
{ label: '订单状态', keys: ['status_name'] },
|
||||
{ label: '订单金额', keys: ['total_amount', 'order_amount'], format: centAmountValue }
|
||||
],
|
||||
refund: [
|
||||
{ label: '退款单号', keys: ['refund_no'] },
|
||||
{ label: '订单号', keys: ['order_no'] },
|
||||
{ label: '资产编号', keys: ['asset_identifier'] },
|
||||
{
|
||||
label: '申请退款金额',
|
||||
keys: ['requested_refund_amount'],
|
||||
format: centAmountValue
|
||||
},
|
||||
{ label: '状态', keys: ['status_name', 'status'], format: refundStatusValue }
|
||||
]
|
||||
}
|
||||
|
||||
const snapshotDetails = (row: AuditResourceCandidate) => {
|
||||
const snapshot = row.identity_snapshot || {}
|
||||
return snapshotFields[row.resource_type].flatMap((field) => {
|
||||
const key = field.keys.find((candidate) => {
|
||||
const value = snapshot[candidate]
|
||||
return value !== undefined && value !== null && value !== ''
|
||||
})
|
||||
if (!key) return []
|
||||
const value = snapshot[key]
|
||||
return [{ label: field.label, value: field.format?.(value) || textValue(value) }]
|
||||
})
|
||||
}
|
||||
const search = async (resetPage: boolean) => {
|
||||
const exactKeyword = keyword.value.trim()
|
||||
if (!exactKeyword) {
|
||||
ElMessage.warning(`当前记录缺少可用于搜索${resourceTypeLabel.value}的业务标识`)
|
||||
return
|
||||
}
|
||||
if (resetPage) page.value = 1
|
||||
const sequence = ++searchSequence
|
||||
loading.value = true
|
||||
try {
|
||||
const data = (
|
||||
await AuditService.searchResources({
|
||||
resource_type: props.resourceType,
|
||||
keyword: exactKeyword,
|
||||
page: page.value,
|
||||
page_size: pageSize
|
||||
})
|
||||
).data
|
||||
if (sequence === searchSequence) {
|
||||
items.value = data.items || []
|
||||
total.value = data.total
|
||||
}
|
||||
} catch {
|
||||
if (sequence === searchSequence) ElMessage.error('注册资源搜索失败')
|
||||
} finally {
|
||||
if (sequence === searchSequence) loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const openTimeline = (row: AuditResourceCandidate) => {
|
||||
if (!row.resource_id) return
|
||||
visible.value = false
|
||||
openAuditInvestigation({
|
||||
mode: 'resource',
|
||||
resourceType: row.resource_type,
|
||||
id: row.resource_id
|
||||
})
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(open) => {
|
||||
if (!open) {
|
||||
searchSequence++
|
||||
loading.value = false
|
||||
items.value = []
|
||||
total.value = 0
|
||||
return
|
||||
}
|
||||
keyword.value = props.initialKeyword?.trim() || ''
|
||||
page.value = 1
|
||||
items.value = []
|
||||
total.value = 0
|
||||
if (keyword.value) void search(true)
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.result-list {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.resource-card + .resource-card {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.snapshot-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.snapshot-item {
|
||||
min-width: 0;
|
||||
padding: 12px 14px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
|
||||
span,
|
||||
strong {
|
||||
display: block;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-bottom: 6px;
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 600px) {
|
||||
.snapshot-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user