增加电子凭证核销详情
This commit is contained in:
@@ -168,6 +168,8 @@ function mapAdminKuaishouIndustryVoucher(voucher: {
|
|||||||
send_callback_attempt_count?: unknown
|
send_callback_attempt_count?: unknown
|
||||||
send_callback_last_error?: unknown
|
send_callback_last_error?: unknown
|
||||||
send_callback_sent_at?: unknown
|
send_callback_sent_at?: unknown
|
||||||
|
consume_serial_num?: unknown
|
||||||
|
consume_details_json?: unknown
|
||||||
consumed_at?: unknown
|
consumed_at?: unknown
|
||||||
destroyed_at?: unknown
|
destroyed_at?: unknown
|
||||||
}) {
|
}) {
|
||||||
@@ -180,6 +182,8 @@ function mapAdminKuaishouIndustryVoucher(voucher: {
|
|||||||
sendCallbackAttemptCount: Number(voucher.send_callback_attempt_count || 0) || 0,
|
sendCallbackAttemptCount: Number(voucher.send_callback_attempt_count || 0) || 0,
|
||||||
sendCallbackLastError: String(voucher.send_callback_last_error || '').trim(),
|
sendCallbackLastError: String(voucher.send_callback_last_error || '').trim(),
|
||||||
sendCallbackSentAt: voucher.send_callback_sent_at || null,
|
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,
|
consumedAt: voucher.consumed_at || null,
|
||||||
destroyedAt: voucher.destroyed_at || null,
|
destroyedAt: voucher.destroyed_at || null,
|
||||||
}
|
}
|
||||||
@@ -382,11 +386,32 @@ function mapKuaishouIndustryVoucherContext(value: unknown): JsonRecord | null {
|
|||||||
sendCallbackLastError: String(record.sendCallbackLastError || '').trim(),
|
sendCallbackLastError: String(record.sendCallbackLastError || '').trim(),
|
||||||
sendCallbackAttemptCount: Number(record.sendCallbackAttemptCount || 0) || 0,
|
sendCallbackAttemptCount: Number(record.sendCallbackAttemptCount || 0) || 0,
|
||||||
sendCallbackSentAt: record.sendCallbackSentAt || null,
|
sendCallbackSentAt: record.sendCallbackSentAt || null,
|
||||||
|
consumeSerialNum: String(record.consumeSerialNum || '').trim(),
|
||||||
|
consumeDetails: normalizeAdminJsonArray(record.consumeDetails),
|
||||||
consumedAt: record.consumedAt || null,
|
consumedAt: record.consumedAt || null,
|
||||||
destroyedAt: record.destroyedAt || 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(
|
function resolveKuaishouIndustryVoucherForTask(
|
||||||
task: TaskRow,
|
task: TaskRow,
|
||||||
taskContext: JsonRecord,
|
taskContext: JsonRecord,
|
||||||
|
|||||||
@@ -212,6 +212,8 @@ export interface AdminTaskDetail {
|
|||||||
sendCallbackLastError: string
|
sendCallbackLastError: string
|
||||||
sendCallbackAttemptCount: number
|
sendCallbackAttemptCount: number
|
||||||
sendCallbackSentAt: string | null
|
sendCallbackSentAt: string | null
|
||||||
|
consumeSerialNum: string
|
||||||
|
consumeDetails: Array<Record<string, unknown>>
|
||||||
consumedAt: string | null
|
consumedAt: string | null
|
||||||
destroyedAt: string | null
|
destroyedAt: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,34 @@
|
|||||||
gap: var(--space-3);
|
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 {
|
.section-copy {
|
||||||
margin: 0 0 var(--space-3);
|
margin: 0 0 var(--space-3);
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import AdminTaskActionBar from './components/AdminTaskActionBar.vue'
|
|||||||
import AdminTaskEventsSection from './components/AdminTaskEventsSection.vue'
|
import AdminTaskEventsSection from './components/AdminTaskEventsSection.vue'
|
||||||
import AdminTaskHeroSection from './components/AdminTaskHeroSection.vue'
|
import AdminTaskHeroSection from './components/AdminTaskHeroSection.vue'
|
||||||
import AdminTaskInfoSection from './components/AdminTaskInfoSection.vue'
|
import AdminTaskInfoSection from './components/AdminTaskInfoSection.vue'
|
||||||
|
import AdminTaskKuaishouIndustryVoucherSection from './components/AdminTaskKuaishouIndustryVoucherSection.vue'
|
||||||
import AdminTaskManualDispatchSection from './components/AdminTaskManualDispatchSection.vue'
|
import AdminTaskManualDispatchSection from './components/AdminTaskManualDispatchSection.vue'
|
||||||
import AdminTaskRedeemResolutionSection from './components/AdminTaskRedeemResolutionSection.vue'
|
import AdminTaskRedeemResolutionSection from './components/AdminTaskRedeemResolutionSection.vue'
|
||||||
import AdminTaskScreenshotSection from './components/AdminTaskScreenshotSection.vue'
|
import AdminTaskScreenshotSection from './components/AdminTaskScreenshotSection.vue'
|
||||||
@@ -122,6 +123,14 @@ onMounted(loadDetail)
|
|||||||
:claim-link-invalid="claimLinkInvalid"
|
:claim-link-invalid="claimLinkInvalid"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AdminTaskKuaishouIndustryVoucherSection
|
||||||
|
v-if="detail.kuaishouIndustryVoucher"
|
||||||
|
:detail="detail"
|
||||||
|
:action-loading="actionLoading"
|
||||||
|
@resend-industry-code="submitKuaishouIndustryResendCode"
|
||||||
|
@consume-industry-voucher="submitKuaishouIndustryConsume"
|
||||||
|
/>
|
||||||
|
|
||||||
<AdminTaskKuaishouCloudPanel
|
<AdminTaskKuaishouCloudPanel
|
||||||
v-if="isKuaishouCloudTask && kuaishouCloudFlow"
|
v-if="isKuaishouCloudTask && kuaishouCloudFlow"
|
||||||
:detail="detail"
|
: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