265 lines
6.4 KiB
TypeScript
265 lines
6.4 KiB
TypeScript
import type {
|
|
MemberMessageReachChannel,
|
|
MemberMessageReachDetailDto,
|
|
MemberMessageReachStatus,
|
|
MemberMessageScheduleType,
|
|
MemberMessageTemplateCategory,
|
|
SaveMemberMessageReachPayload,
|
|
SaveMemberMessageTemplatePayload,
|
|
} from '#/api/member/message-reach';
|
|
import type {
|
|
MessageReachEditorForm,
|
|
MessageReachFilterForm,
|
|
MessageTemplateEditorForm,
|
|
MessageTemplateFilterForm,
|
|
} from '#/views/member/message-reach/types';
|
|
|
|
import dayjs from 'dayjs';
|
|
|
|
/** 状态文案。 */
|
|
export const MESSAGE_STATUS_TEXT_MAP: Record<MemberMessageReachStatus, string> =
|
|
{
|
|
draft: '草稿',
|
|
pending: '待发送',
|
|
sending: '发送中',
|
|
sent: '已发送',
|
|
failed: '发送失败',
|
|
};
|
|
|
|
/** 状态标签色。 */
|
|
export const MESSAGE_STATUS_COLOR_MAP: Record<
|
|
MemberMessageReachStatus,
|
|
string
|
|
> = {
|
|
draft: 'default',
|
|
pending: 'processing',
|
|
sending: 'warning',
|
|
sent: 'success',
|
|
failed: 'error',
|
|
};
|
|
|
|
/** 渠道文案。 */
|
|
export const MESSAGE_CHANNEL_TEXT_MAP: Record<
|
|
MemberMessageReachChannel,
|
|
string
|
|
> = {
|
|
inapp: '站内信',
|
|
sms: '短信',
|
|
'wechat-mini': '微信模板',
|
|
};
|
|
|
|
/** 渠道标签色。 */
|
|
export const MESSAGE_CHANNEL_COLOR_MAP: Record<
|
|
MemberMessageReachChannel,
|
|
string
|
|
> = {
|
|
inapp: 'blue',
|
|
sms: 'green',
|
|
'wechat-mini': 'orange',
|
|
};
|
|
|
|
/** 模板分类文案。 */
|
|
export const MESSAGE_TEMPLATE_CATEGORY_TEXT_MAP: Record<
|
|
MemberMessageTemplateCategory,
|
|
string
|
|
> = {
|
|
marketing: '营销',
|
|
notice: '通知',
|
|
recall: '召回',
|
|
};
|
|
|
|
/** 模板分类颜色。 */
|
|
export const MESSAGE_TEMPLATE_CATEGORY_COLOR_MAP: Record<
|
|
MemberMessageTemplateCategory,
|
|
string
|
|
> = {
|
|
marketing: 'magenta',
|
|
notice: 'blue',
|
|
recall: 'red',
|
|
};
|
|
|
|
/** 收件状态文案。 */
|
|
export const MESSAGE_RECIPIENT_STATUS_TEXT_MAP: Record<
|
|
'failed' | 'pending' | 'sent',
|
|
string
|
|
> = {
|
|
pending: '待发送',
|
|
sent: '已发送',
|
|
failed: '发送失败',
|
|
};
|
|
|
|
/** 收件状态颜色。 */
|
|
export const MESSAGE_RECIPIENT_STATUS_COLOR_MAP: Record<
|
|
'failed' | 'pending' | 'sent',
|
|
string
|
|
> = {
|
|
pending: 'processing',
|
|
sent: 'success',
|
|
failed: 'error',
|
|
};
|
|
|
|
/** 格式化百分比。 */
|
|
export function formatPercent(value: null | number | undefined) {
|
|
const amount = Number(value ?? 0);
|
|
if (!Number.isFinite(amount)) {
|
|
return '0%';
|
|
}
|
|
|
|
const digits = amount % 1 === 0 ? 0 : 1;
|
|
return `${amount.toFixed(digits)}%`;
|
|
}
|
|
|
|
/** 格式化数量。 */
|
|
export function formatInteger(value: null | number | undefined) {
|
|
const amount = Number(value ?? 0);
|
|
if (!Number.isFinite(amount)) {
|
|
return '0';
|
|
}
|
|
return Math.round(amount).toLocaleString('zh-CN');
|
|
}
|
|
|
|
/** 格式化时间。 */
|
|
export function formatDateTime(value?: string) {
|
|
if (!value) {
|
|
return '—';
|
|
}
|
|
|
|
const parsed = dayjs(value);
|
|
if (!parsed.isValid()) {
|
|
return value;
|
|
}
|
|
return parsed.format('YYYY-MM-DD HH:mm');
|
|
}
|
|
|
|
/** 解析消息发送时间。 */
|
|
export function resolveMessageTime(
|
|
sentAt?: string,
|
|
scheduledAt?: string,
|
|
): string {
|
|
if (sentAt) {
|
|
return formatDateTime(sentAt);
|
|
}
|
|
if (scheduledAt) {
|
|
return formatDateTime(scheduledAt);
|
|
}
|
|
return '—';
|
|
}
|
|
|
|
/** 将列表筛选映射为查询参数。 */
|
|
export function mapMessageFilterToQuery(form: MessageReachFilterForm) {
|
|
const keyword = form.keyword.trim();
|
|
return {
|
|
status: form.status || undefined,
|
|
channel: form.channel || undefined,
|
|
keyword: keyword || undefined,
|
|
};
|
|
}
|
|
|
|
/** 将模板筛选映射为查询参数。 */
|
|
export function mapTemplateFilterToQuery(form: MessageTemplateFilterForm) {
|
|
const keyword = form.keyword.trim();
|
|
return {
|
|
category: form.category || undefined,
|
|
keyword: keyword || undefined,
|
|
};
|
|
}
|
|
|
|
/** 将详情映射为编辑表单。 */
|
|
export function mapDetailToEditorForm(
|
|
detail: MemberMessageReachDetailDto,
|
|
form: MessageReachEditorForm,
|
|
) {
|
|
form.messageId = detail.messageId;
|
|
form.templateId = detail.templateId;
|
|
form.title = detail.title;
|
|
form.content = detail.content;
|
|
form.channels = [...detail.channels];
|
|
form.audienceType = detail.audienceType;
|
|
form.audienceTags = [...detail.audienceTags];
|
|
form.scheduleType = detail.scheduleType;
|
|
form.scheduledAt =
|
|
detail.scheduleType === 'scheduled' && detail.scheduledAt
|
|
? dayjs(detail.scheduledAt)
|
|
: null;
|
|
}
|
|
|
|
/** 重置消息编辑表单。 */
|
|
export function resetMessageEditorForm(form: MessageReachEditorForm) {
|
|
form.messageId = '';
|
|
form.templateId = undefined;
|
|
form.title = '';
|
|
form.content = '';
|
|
form.channels = ['inapp'];
|
|
form.audienceType = 'all';
|
|
form.audienceTags = [];
|
|
form.scheduleType = 'immediate';
|
|
form.scheduledAt = null;
|
|
}
|
|
|
|
/** 重置模板编辑表单。 */
|
|
export function resetTemplateEditorForm(form: MessageTemplateEditorForm) {
|
|
form.templateId = '';
|
|
form.name = '';
|
|
form.category = 'notice';
|
|
form.content = '';
|
|
}
|
|
|
|
/** 消息编辑表单转保存请求。 */
|
|
export function mapMessageEditorFormToSavePayload(
|
|
form: MessageReachEditorForm,
|
|
submitAction: 'draft' | 'send',
|
|
): SaveMemberMessageReachPayload {
|
|
const payload: SaveMemberMessageReachPayload = {
|
|
messageId: form.messageId || undefined,
|
|
templateId: form.templateId || undefined,
|
|
title: form.title.trim(),
|
|
content: form.content.trim(),
|
|
channels: [...form.channels],
|
|
audienceType: form.audienceType,
|
|
audienceTags: [...form.audienceTags],
|
|
scheduleType: form.scheduleType,
|
|
scheduledAt:
|
|
form.scheduleType === 'scheduled' && form.scheduledAt
|
|
? form.scheduledAt.toISOString()
|
|
: undefined,
|
|
submitAction,
|
|
};
|
|
return payload;
|
|
}
|
|
|
|
/** 模板编辑表单转保存请求。 */
|
|
export function mapTemplateEditorFormToSavePayload(
|
|
form: MessageTemplateEditorForm,
|
|
): SaveMemberMessageTemplatePayload {
|
|
return {
|
|
templateId: form.templateId || undefined,
|
|
name: form.name.trim(),
|
|
category: form.category,
|
|
content: form.content.trim(),
|
|
};
|
|
}
|
|
|
|
/** 切换渠道。 */
|
|
export function toggleChannel(
|
|
channels: MemberMessageReachChannel[],
|
|
channel: MemberMessageReachChannel,
|
|
) {
|
|
if (channels.includes(channel)) {
|
|
return channels.filter((item) => item !== channel);
|
|
}
|
|
return [...channels, channel];
|
|
}
|
|
|
|
/** 切换标签。 */
|
|
export function toggleTag(tags: string[], tag: string) {
|
|
if (tags.includes(tag)) {
|
|
return tags.filter((item) => item !== tag);
|
|
}
|
|
return [...tags, tag];
|
|
}
|
|
|
|
/** 解析时间类型。 */
|
|
export function resolveScheduleType(value: string): MemberMessageScheduleType {
|
|
return value === 'scheduled' ? 'scheduled' : 'immediate';
|
|
}
|