增加电子凭证核销详情
This commit is contained in:
@@ -168,6 +168,8 @@ function mapAdminKuaishouIndustryVoucher(voucher: {
|
||||
send_callback_attempt_count?: unknown
|
||||
send_callback_last_error?: unknown
|
||||
send_callback_sent_at?: unknown
|
||||
consume_serial_num?: unknown
|
||||
consume_details_json?: unknown
|
||||
consumed_at?: unknown
|
||||
destroyed_at?: unknown
|
||||
}) {
|
||||
@@ -180,6 +182,8 @@ function mapAdminKuaishouIndustryVoucher(voucher: {
|
||||
sendCallbackAttemptCount: Number(voucher.send_callback_attempt_count || 0) || 0,
|
||||
sendCallbackLastError: String(voucher.send_callback_last_error || '').trim(),
|
||||
sendCallbackSentAt: voucher.send_callback_sent_at || null,
|
||||
consumeSerialNum: String(voucher.consume_serial_num || '').trim(),
|
||||
consumeDetails: normalizeAdminJsonArray(voucher.consume_details_json),
|
||||
consumedAt: voucher.consumed_at || null,
|
||||
destroyedAt: voucher.destroyed_at || null,
|
||||
}
|
||||
@@ -382,11 +386,32 @@ function mapKuaishouIndustryVoucherContext(value: unknown): JsonRecord | null {
|
||||
sendCallbackLastError: String(record.sendCallbackLastError || '').trim(),
|
||||
sendCallbackAttemptCount: Number(record.sendCallbackAttemptCount || 0) || 0,
|
||||
sendCallbackSentAt: record.sendCallbackSentAt || null,
|
||||
consumeSerialNum: String(record.consumeSerialNum || '').trim(),
|
||||
consumeDetails: normalizeAdminJsonArray(record.consumeDetails),
|
||||
consumedAt: record.consumedAt || null,
|
||||
destroyedAt: record.destroyedAt || null,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeAdminJsonArray(value: unknown): JsonRecord[] {
|
||||
if (Array.isArray(value)) {
|
||||
return value.filter((item): item is JsonRecord =>
|
||||
Boolean(item && typeof item === 'object' && !Array.isArray(item)),
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '[]'))
|
||||
return Array.isArray(parsed)
|
||||
? parsed.filter((item): item is JsonRecord =>
|
||||
Boolean(item && typeof item === 'object' && !Array.isArray(item)),
|
||||
)
|
||||
: []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function resolveKuaishouIndustryVoucherForTask(
|
||||
task: TaskRow,
|
||||
taskContext: JsonRecord,
|
||||
|
||||
@@ -212,6 +212,8 @@ export interface AdminTaskDetail {
|
||||
sendCallbackLastError: string
|
||||
sendCallbackAttemptCount: number
|
||||
sendCallbackSentAt: string | null
|
||||
consumeSerialNum: string
|
||||
consumeDetails: Array<Record<string, unknown>>
|
||||
consumedAt: string | null
|
||||
destroyedAt: string | null
|
||||
}
|
||||
|
||||
@@ -121,6 +121,34 @@
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.section-headline {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
margin-bottom: var(--space-3);
|
||||
}
|
||||
|
||||
.section-headline h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.section-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-2);
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.voucher-detail-block {
|
||||
margin-top: var(--space-4);
|
||||
}
|
||||
|
||||
.voucher-detail-block h4 {
|
||||
margin: 0 0 var(--space-3);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.section-copy {
|
||||
margin: 0 0 var(--space-3);
|
||||
color: var(--text-secondary);
|
||||
|
||||
@@ -13,6 +13,7 @@ import AdminTaskActionBar from './components/AdminTaskActionBar.vue'
|
||||
import AdminTaskEventsSection from './components/AdminTaskEventsSection.vue'
|
||||
import AdminTaskHeroSection from './components/AdminTaskHeroSection.vue'
|
||||
import AdminTaskInfoSection from './components/AdminTaskInfoSection.vue'
|
||||
import AdminTaskKuaishouIndustryVoucherSection from './components/AdminTaskKuaishouIndustryVoucherSection.vue'
|
||||
import AdminTaskManualDispatchSection from './components/AdminTaskManualDispatchSection.vue'
|
||||
import AdminTaskRedeemResolutionSection from './components/AdminTaskRedeemResolutionSection.vue'
|
||||
import AdminTaskScreenshotSection from './components/AdminTaskScreenshotSection.vue'
|
||||
@@ -122,6 +123,14 @@ onMounted(loadDetail)
|
||||
:claim-link-invalid="claimLinkInvalid"
|
||||
/>
|
||||
|
||||
<AdminTaskKuaishouIndustryVoucherSection
|
||||
v-if="detail.kuaishouIndustryVoucher"
|
||||
:detail="detail"
|
||||
:action-loading="actionLoading"
|
||||
@resend-industry-code="submitKuaishouIndustryResendCode"
|
||||
@consume-industry-voucher="submitKuaishouIndustryConsume"
|
||||
/>
|
||||
|
||||
<AdminTaskKuaishouCloudPanel
|
||||
v-if="isKuaishouCloudTask && kuaishouCloudFlow"
|
||||
:detail="detail"
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<script setup lang="ts">
|
||||
import AdminStatusTag from '@/components/admin/AdminStatusTag.vue'
|
||||
import type { AdminTaskDetail } from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
interface Props {
|
||||
detail: AdminTaskDetail
|
||||
actionLoading: boolean
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
resendIndustryCode: []
|
||||
consumeIndustryVoucher: []
|
||||
}>()
|
||||
|
||||
function formatConsumeDetailTime(value: unknown) {
|
||||
const parsed = Number(value || 0)
|
||||
if (Number.isFinite(parsed) && parsed > 0) {
|
||||
return formatAdminDateTime(new Date(parsed).toISOString())
|
||||
}
|
||||
|
||||
return formatAdminDateTime(value as string | null)
|
||||
}
|
||||
|
||||
function formatValue(value: unknown) {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return '-'
|
||||
}
|
||||
|
||||
if (typeof value === 'object') {
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
|
||||
return String(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-card v-if="detail.kuaishouIndustryVoucher" shadow="never" class="section-card">
|
||||
<div class="section-headline">
|
||||
<h3>电子凭证与核销</h3>
|
||||
<div class="section-actions">
|
||||
<el-button
|
||||
v-if="detail.operations.canResendKuaishouIndustryVoucherCode"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
@click="emit('resendIndustryCode')"
|
||||
>
|
||||
重发发码回调
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="detail.operations.canConsumeKuaishouIndustryVoucher"
|
||||
:loading="actionLoading"
|
||||
round
|
||||
type="warning"
|
||||
plain
|
||||
@click="emit('consumeIndustryVoucher')"
|
||||
>
|
||||
手动核销
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="平台订单号">
|
||||
{{ detail.kuaishouIndustryVoucher.oid || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="券序号">
|
||||
{{ detail.kuaishouIndustryVoucher.unitIndex || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="核销码">
|
||||
{{ detail.kuaishouIndustryVoucher.voucherCode || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="券状态">
|
||||
<AdminStatusTag :status="detail.kuaishouIndustryVoucher.status || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="发码回调">
|
||||
<AdminStatusTag :status="detail.kuaishouIndustryVoucher.sendCallbackStatus || ''" />
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调次数">
|
||||
{{ detail.kuaishouIndustryVoucher.sendCallbackAttemptCount || 0 }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调时间">
|
||||
{{ formatAdminDateTime(detail.kuaishouIndustryVoucher.sendCallbackSentAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="回调异常">
|
||||
{{ detail.kuaishouIndustryVoucher.sendCallbackLastError || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="核销流水">
|
||||
{{ detail.kuaishouIndustryVoucher.consumeSerialNum || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="核销时间">
|
||||
{{ formatAdminDateTime(detail.kuaishouIndustryVoucher.consumedAt) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="销毁时间">
|
||||
{{ formatAdminDateTime(detail.kuaishouIndustryVoucher.destroyedAt) }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="voucher-detail-block">
|
||||
<h4>核销明细</h4>
|
||||
<el-table
|
||||
v-if="detail.kuaishouIndustryVoucher.consumeDetails.length > 0"
|
||||
:data="detail.kuaishouIndustryVoucher.consumeDetails"
|
||||
size="small"
|
||||
>
|
||||
<el-table-column label="流水号" min-width="160">
|
||||
<template #default="{ row }">{{ formatValue(row.serialNum) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="核销类型" width="120">
|
||||
<template #default="{ row }">{{ formatValue(row.consumeType) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="时间" width="180">
|
||||
<template #default="{ row }">{{ formatConsumeDetailTime(row.consumeTime) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="来源" width="150">
|
||||
<template #default="{ row }">{{ formatValue(row.source) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="门店/物流">
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
[row.storeName, row.storeAddress, row.expressCode, row.expressNo]
|
||||
.map(formatValue)
|
||||
.filter((item) => item !== '-')
|
||||
.join(' / ') || '-'
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-else description="暂无核销明细" :image-size="64" />
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<style scoped src="../AdminTaskDetail.css"></style>
|
||||
Reference in New Issue
Block a user