Files
TakeoutSaaS.TenantUI/apps/web-antd/src/views/member/message-reach/components/MessageReachDetailDrawer.vue

274 lines
8.1 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.
<script setup lang="ts">
import type { TableColumnType } from 'ant-design-vue';
import type {
MemberMessageReachChannel,
MemberMessageReachDetailDto,
MemberMessageReachRecipientDto,
} from '#/api/member/message-reach';
import { Button, Drawer, Empty, Spin, Table, Tag } from 'ant-design-vue';
import {
formatDateTime,
formatInteger,
formatPercent,
MESSAGE_CHANNEL_COLOR_MAP,
MESSAGE_CHANNEL_TEXT_MAP,
MESSAGE_RECIPIENT_STATUS_COLOR_MAP,
MESSAGE_RECIPIENT_STATUS_TEXT_MAP,
MESSAGE_STATUS_COLOR_MAP,
MESSAGE_STATUS_TEXT_MAP,
} from '../composables/message-reach-page/helpers';
defineProps<{
detail: MemberMessageReachDetailDto | null;
loading: boolean;
open: boolean;
}>();
const emit = defineEmits<{
(event: 'close'): void;
}>();
const recipientColumns: TableColumnType<MemberMessageReachRecipientDto>[] = [
{
title: '会员ID',
dataIndex: 'memberId',
key: 'memberId',
width: 150,
},
{
title: '渠道',
dataIndex: 'channel',
key: 'channel',
width: 120,
},
{
title: '状态',
dataIndex: 'status',
key: 'status',
width: 110,
},
{
title: '联系方式',
key: 'contact',
width: 180,
},
{
title: '发送时间',
dataIndex: 'sentAt',
key: 'sentAt',
width: 170,
},
{
title: '已读时间',
dataIndex: 'readAt',
key: 'readAt',
width: 170,
},
{
title: '转化时间',
dataIndex: 'convertedAt',
key: 'convertedAt',
width: 170,
},
{
title: '错误信息',
dataIndex: 'errorMessage',
key: 'errorMessage',
ellipsis: true,
},
];
function resolveContact(record: Record<string, any>) {
const channel = String(record.channel || '');
if (channel === 'sms') {
return String(record.mobile || '--');
}
if (channel === 'wechat-mini') {
return String(record.openId || '--');
}
return String(record.memberId || '--');
}
function resolveChannelColor(channel: unknown) {
return (
MESSAGE_CHANNEL_COLOR_MAP[channel as MemberMessageReachChannel] || 'default'
);
}
function resolveChannelText(channel: unknown) {
return (
MESSAGE_CHANNEL_TEXT_MAP[channel as MemberMessageReachChannel] || '未知渠道'
);
}
function resolveRecipientStatusColor(status: unknown) {
return MESSAGE_RECIPIENT_STATUS_COLOR_MAP[
status as 'failed' | 'pending' | 'sent'
];
}
function resolveRecipientStatusText(status: unknown) {
return MESSAGE_RECIPIENT_STATUS_TEXT_MAP[
status as 'failed' | 'pending' | 'sent'
];
}
</script>
<template>
<Drawer :open="open" width="820" title="消息详情" @close="emit('close')">
<Spin :spinning="loading">
<template v-if="detail">
<section class="mmr-detail-section">
<div class="mmr-detail-title-row">
<div class="mmr-detail-title">{{ detail.title }}</div>
<Tag :color="MESSAGE_STATUS_COLOR_MAP[detail.status]">
{{ MESSAGE_STATUS_TEXT_MAP[detail.status] }}
</Tag>
</div>
<div class="mmr-detail-meta">
<div>
<span class="label">推送渠道</span>
<span class="channels">
<Tag
v-for="item in detail.channels"
:key="`detail-channel-${item}`"
:color="resolveChannelColor(item)"
>
{{ resolveChannelText(item) }}
</Tag>
</span>
</div>
<div>
<span class="label">目标人群</span>
<span>{{ detail.audienceText }}</span>
</div>
<div>
<span class="label">定时发送</span>
<span>{{ formatDateTime(detail.scheduledAt) }}</span>
</div>
<div>
<span class="label">实际发送</span>
<span>{{ formatDateTime(detail.sentAt) }}</span>
</div>
</div>
</section>
<section class="mmr-detail-section">
<div class="mmr-detail-stat-grid">
<div class="mmr-detail-stat-item">
<div class="label">预计触达</div>
<div class="value">
{{ formatInteger(detail.estimatedReachCount) }}
</div>
</div>
<div class="mmr-detail-stat-item">
<div class="label">发送成功</div>
<div class="value">{{ formatInteger(detail.sentCount) }}</div>
</div>
<div class="mmr-detail-stat-item">
<div class="label">已读人数</div>
<div class="value">{{ formatInteger(detail.readCount) }}</div>
</div>
<div class="mmr-detail-stat-item">
<div class="label">转化人数</div>
<div class="value">
{{ formatInteger(detail.convertedCount) }}
</div>
</div>
<div class="mmr-detail-stat-item">
<div class="label">打开率</div>
<div class="value">{{ formatPercent(detail.openRate) }}</div>
</div>
<div class="mmr-detail-stat-item">
<div class="label">转化率</div>
<div class="value">
{{ formatPercent(detail.conversionRate) }}
</div>
</div>
</div>
</section>
<section class="mmr-detail-section">
<div class="mmr-detail-section-title">消息内容</div>
<div class="mmr-detail-content">{{ detail.content }}</div>
<div v-if="detail.lastError" class="mmr-detail-error">
最后错误{{ detail.lastError }}
</div>
</section>
<section class="mmr-detail-section">
<div class="mmr-detail-section-title">收件明细</div>
<div class="mmr-recipient-table">
<Table
:row-key="
(record: MemberMessageReachRecipientDto) =>
`${record.memberId}-${record.channel}-${record.sentAt || ''}`
"
:columns="recipientColumns"
:data-source="detail.recipients"
:pagination="{ pageSize: 8, showSizeChanger: false }"
size="small"
:scroll="{ x: 1040 }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'channel'">
<Tag :color="resolveChannelColor(record.channel)">
{{ resolveChannelText(record.channel) }}
</Tag>
</template>
<template v-else-if="column.key === 'status'">
<Tag :color="resolveRecipientStatusColor(record.status)">
{{ resolveRecipientStatusText(record.status) }}
</Tag>
</template>
<template v-else-if="column.key === 'contact'">
<span>{{ resolveContact(record) }}</span>
</template>
<template v-else-if="column.key === 'sentAt'">
<span class="mmr-time-text">{{
formatDateTime(record.sentAt)
}}</span>
</template>
<template v-else-if="column.key === 'readAt'">
<span class="mmr-time-text">{{
formatDateTime(record.readAt)
}}</span>
</template>
<template v-else-if="column.key === 'convertedAt'">
<span class="mmr-time-text">{{
formatDateTime(record.convertedAt)
}}</span>
</template>
<template v-else-if="column.key === 'errorMessage'">
<span
:title="record.errorMessage || '--'"
class="mmr-error-text"
>
{{ record.errorMessage || '--' }}
</span>
</template>
</template>
</Table>
</div>
</section>
</template>
<Empty v-else description="暂无消息详情" />
</Spin>
<template #footer>
<Button @click="emit('close')">关闭</Button>
</template>
</Drawer>
</template>