feat: 完成会员消息触达模块页面与交互
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type {
|
||||
MemberMessageTemplateDto,
|
||||
SaveMemberMessageReachPayload,
|
||||
} from '#/api/member/message-reach';
|
||||
import type {
|
||||
MemberMessageReachTabKey,
|
||||
MessageReachDetailViewModel,
|
||||
MessageReachEditorForm,
|
||||
} from '#/views/member/message-reach/types';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
deleteMemberMessageReachApi,
|
||||
saveMemberMessageReachApi,
|
||||
} from '#/api/member/message-reach';
|
||||
|
||||
import {
|
||||
mapDetailToEditorForm,
|
||||
mapMessageEditorFormToSavePayload,
|
||||
resetMessageEditorForm,
|
||||
toggleTag,
|
||||
} from './helpers';
|
||||
|
||||
interface CreateMessageActionsOptions {
|
||||
activeTab: Ref<MemberMessageReachTabKey>;
|
||||
audienceEstimateCount: Ref<number>;
|
||||
canManage: Ref<boolean>;
|
||||
detail: Ref<MessageReachDetailViewModel | null>;
|
||||
detailDrawerMessageId: Ref<string>;
|
||||
form: MessageReachEditorForm;
|
||||
isDetailDrawerOpen: Ref<boolean>;
|
||||
isMessageDrawerOpen: Ref<boolean>;
|
||||
isMessageSubmitting: Ref<boolean>;
|
||||
messageDrawerMode: Ref<'create' | 'edit'>;
|
||||
loadMessageDetail: (
|
||||
messageId: string,
|
||||
) => Promise<MessageReachDetailViewModel | null>;
|
||||
loadMessageList: () => Promise<void>;
|
||||
loadStats: () => Promise<void>;
|
||||
}
|
||||
|
||||
export function createMessageActions(options: CreateMessageActionsOptions) {
|
||||
function setMessageDrawerOpen(value: boolean) {
|
||||
options.isMessageDrawerOpen.value = value;
|
||||
}
|
||||
|
||||
function setMessageTitle(value: string) {
|
||||
options.form.title = value;
|
||||
}
|
||||
|
||||
function setMessageContent(value: string) {
|
||||
options.form.content = value;
|
||||
}
|
||||
|
||||
function setMessageChannel(channel: 'inapp' | 'sms' | 'wechat-mini') {
|
||||
if (
|
||||
options.form.channels.length === 1 &&
|
||||
options.form.channels[0] === channel
|
||||
) {
|
||||
return;
|
||||
}
|
||||
options.form.channels = [channel];
|
||||
}
|
||||
|
||||
function setAudienceType(value: 'all' | 'tag') {
|
||||
options.form.audienceType = value;
|
||||
if (value === 'all') {
|
||||
options.form.audienceTags = [];
|
||||
}
|
||||
}
|
||||
|
||||
function toggleAudienceTag(value: string) {
|
||||
options.form.audienceTags = toggleTag(options.form.audienceTags, value);
|
||||
}
|
||||
|
||||
function setScheduleType(value: 'immediate' | 'scheduled') {
|
||||
options.form.scheduleType = value;
|
||||
if (value === 'immediate') {
|
||||
options.form.scheduledAt = null;
|
||||
}
|
||||
}
|
||||
|
||||
function setScheduledAt(value: Dayjs | null) {
|
||||
options.form.scheduledAt = value;
|
||||
}
|
||||
|
||||
function switchToTemplateTab() {
|
||||
options.activeTab.value = 'template';
|
||||
options.isMessageDrawerOpen.value = false;
|
||||
}
|
||||
|
||||
async function openCreateMessageDrawer() {
|
||||
if (!options.canManage.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetMessageEditorForm(options.form);
|
||||
options.audienceEstimateCount.value = 0;
|
||||
options.messageDrawerMode.value = 'create';
|
||||
options.isMessageDrawerOpen.value = true;
|
||||
}
|
||||
|
||||
async function openEditMessageDrawer(messageId: string) {
|
||||
if (!options.canManage.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = await options.loadMessageDetail(messageId);
|
||||
if (!detail) {
|
||||
return;
|
||||
}
|
||||
|
||||
mapDetailToEditorForm(detail, options.form);
|
||||
options.audienceEstimateCount.value = detail.estimatedReachCount;
|
||||
options.messageDrawerMode.value = 'edit';
|
||||
options.isMessageDrawerOpen.value = true;
|
||||
}
|
||||
|
||||
async function openDetailDrawer(messageId: string) {
|
||||
options.detailDrawerMessageId.value = messageId;
|
||||
options.isDetailDrawerOpen.value = true;
|
||||
await options.loadMessageDetail(messageId);
|
||||
}
|
||||
|
||||
async function refreshDetailIfNeeded(messageId: string) {
|
||||
if (!options.isDetailDrawerOpen.value) {
|
||||
return;
|
||||
}
|
||||
if (options.detailDrawerMessageId.value !== messageId) {
|
||||
return;
|
||||
}
|
||||
await options.loadMessageDetail(messageId);
|
||||
}
|
||||
|
||||
function useTemplateToCreateMessage(template: MemberMessageTemplateDto) {
|
||||
if (!options.canManage.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
resetMessageEditorForm(options.form);
|
||||
options.form.templateId = template.templateId;
|
||||
options.form.title = template.name;
|
||||
options.form.content = template.content;
|
||||
options.audienceEstimateCount.value = 0;
|
||||
options.messageDrawerMode.value = 'create';
|
||||
options.isMessageDrawerOpen.value = true;
|
||||
options.activeTab.value = 'list';
|
||||
}
|
||||
|
||||
function validateMessagePayload(payload: SaveMemberMessageReachPayload) {
|
||||
if (!payload.title) {
|
||||
message.warning('请输入消息标题');
|
||||
return false;
|
||||
}
|
||||
if (!payload.content) {
|
||||
message.warning('请输入消息内容');
|
||||
return false;
|
||||
}
|
||||
if (payload.channels.length === 0) {
|
||||
message.warning('请至少选择一个推送渠道');
|
||||
return false;
|
||||
}
|
||||
if (payload.audienceType === 'tag' && payload.audienceTags.length === 0) {
|
||||
message.warning('请选择目标标签');
|
||||
return false;
|
||||
}
|
||||
if (payload.scheduleType === 'scheduled' && !payload.scheduledAt) {
|
||||
message.warning('请选择定时发送时间');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
async function submitMessage(submitAction: 'draft' | 'send') {
|
||||
if (!options.canManage.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = mapMessageEditorFormToSavePayload(
|
||||
options.form,
|
||||
submitAction,
|
||||
);
|
||||
if (!validateMessagePayload(payload)) {
|
||||
return;
|
||||
}
|
||||
|
||||
options.isMessageSubmitting.value = true;
|
||||
try {
|
||||
const result = await saveMemberMessageReachApi(payload);
|
||||
message.success(
|
||||
submitAction === 'send' ? '发送任务已提交' : '草稿已保存',
|
||||
);
|
||||
options.isMessageDrawerOpen.value = false;
|
||||
await Promise.all([options.loadStats(), options.loadMessageList()]);
|
||||
await refreshDetailIfNeeded(result.messageId);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error(submitAction === 'send' ? '发送失败' : '保存草稿失败');
|
||||
} finally {
|
||||
options.isMessageSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function removeMessage(messageId: string) {
|
||||
if (!options.canManage.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
Modal.confirm({
|
||||
title: '确认删除消息?',
|
||||
content: '删除后无法恢复,且会取消未执行的发送任务。',
|
||||
okText: '删除',
|
||||
okType: 'danger',
|
||||
cancelText: '取消',
|
||||
async onOk() {
|
||||
try {
|
||||
await deleteMemberMessageReachApi({ messageId });
|
||||
message.success('删除成功');
|
||||
if (options.detailDrawerMessageId.value === messageId) {
|
||||
options.isDetailDrawerOpen.value = false;
|
||||
options.detailDrawerMessageId.value = '';
|
||||
options.detail.value = null;
|
||||
}
|
||||
await Promise.all([options.loadStats(), options.loadMessageList()]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
message.error('删除失败');
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function setDetailDrawerOpen(value: boolean) {
|
||||
options.isDetailDrawerOpen.value = value;
|
||||
if (!value) {
|
||||
options.detailDrawerMessageId.value = '';
|
||||
options.detail.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
openCreateMessageDrawer,
|
||||
openDetailDrawer,
|
||||
openEditMessageDrawer,
|
||||
removeMessage,
|
||||
setAudienceType,
|
||||
setDetailDrawerOpen,
|
||||
setMessageContent,
|
||||
setMessageDrawerOpen,
|
||||
setMessageChannel,
|
||||
setMessageTitle,
|
||||
setScheduleType,
|
||||
setScheduledAt,
|
||||
submitMessage,
|
||||
switchToTemplateTab,
|
||||
toggleAudienceTag,
|
||||
useTemplateToCreateMessage,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user