feat: 完成会员消息触达模块页面与交互

This commit is contained in:
2026-03-04 11:36:49 +08:00
parent 058ec9c142
commit 3056b56082
26 changed files with 3485 additions and 0 deletions

View File

@@ -0,0 +1,396 @@
import type { MemberMessageReachTabKey } from '../types';
import type {
MemberMessageReachChannel,
MemberMessageReachDetailDto,
MemberMessageReachStatus,
MemberMessageTemplateCategory,
} from '#/api/member/message-reach';
import { computed, onActivated, onMounted, reactive, ref, watch } from 'vue';
import { useAccessStore } from '@vben/stores';
import {
createDefaultMessageReachEditorForm,
createDefaultMessageReachFilterForm,
createDefaultMessageReachPager,
createDefaultMessageReachStats,
createDefaultMessageTemplateEditorForm,
createDefaultMessageTemplateFilterForm,
createDefaultMessageTemplatePager,
} from '../types';
import {
MEMBER_MESSAGE_REACH_MANAGE_PERMISSION,
MEMBER_MESSAGE_REACH_VIEW_PERMISSION,
MESSAGE_REACH_TAB_OPTIONS,
} from './message-reach-page/constants';
import { createDataActions } from './message-reach-page/data-actions';
import {
resetMessageEditorForm,
resetTemplateEditorForm,
} from './message-reach-page/helpers';
import { createMessageActions } from './message-reach-page/message-actions';
import { createTemplateActions } from './message-reach-page/template-actions';
export function useMemberMessageReachPage() {
const accessStore = useAccessStore();
const activeTab = ref<MemberMessageReachTabKey>('list');
const stats = ref(createDefaultMessageReachStats());
const isStatsLoading = ref(false);
const messageFilterForm = reactive(createDefaultMessageReachFilterForm());
const messagePager = ref(createDefaultMessageReachPager());
const isMessageLoading = ref(false);
const templateFilterForm = reactive(createDefaultMessageTemplateFilterForm());
const templatePager = ref(createDefaultMessageTemplatePager());
const isTemplateLoading = ref(false);
const detail = ref<MemberMessageReachDetailDto | null>(null);
const isDetailLoading = ref(false);
const isDetailDrawerOpen = ref(false);
const detailDrawerMessageId = ref('');
const messageDrawerMode = ref<'create' | 'edit'>('create');
const form = reactive(createDefaultMessageReachEditorForm());
const isMessageDrawerOpen = ref(false);
const isMessageSubmitting = ref(false);
const audienceEstimateCount = ref(0);
const isEstimatingAudience = ref(false);
const templateEditorMode = ref<'create' | 'edit'>('create');
const templateForm = reactive(createDefaultMessageTemplateEditorForm());
const isTemplateEditorOpen = ref(false);
const isTemplateEditorLoading = ref(false);
const isTemplateSubmitting = ref(false);
const accessCodeSet = computed(
() => new Set((accessStore.accessCodes ?? []).map(String)),
);
const canManage = computed(() =>
accessCodeSet.value.has(MEMBER_MESSAGE_REACH_MANAGE_PERMISSION),
);
const canView = computed(
() =>
canManage.value ||
accessCodeSet.value.has(MEMBER_MESSAGE_REACH_VIEW_PERMISSION),
);
const messageDrawerTitle = computed(() =>
messageDrawerMode.value === 'create' ? '创建消息' : '编辑消息',
);
const templateEditorTitle = computed(() =>
templateEditorMode.value === 'create' ? '新建模板' : '编辑模板',
);
const templateEditorSubmitText = computed(() =>
templateEditorMode.value === 'create' ? '创建' : '保存',
);
const {
estimateAudience,
loadMessageDetail,
loadMessageList,
loadStats,
loadTemplateList,
} = createDataActions({
audienceEstimateCount,
detail,
isDetailLoading,
isEstimatingAudience,
isMessageLoading,
isStatsLoading,
isTemplateLoading,
messageFilterForm,
messagePager,
stats,
templateFilterForm,
templatePager,
});
const {
openCreateMessageDrawer,
openDetailDrawer,
openEditMessageDrawer,
removeMessage,
setAudienceType,
setDetailDrawerOpen,
setMessageContent,
setMessageDrawerOpen,
setMessageChannel,
setMessageTitle,
setScheduleType,
setScheduledAt,
submitMessage,
switchToTemplateTab,
toggleAudienceTag,
useTemplateToCreateMessage,
} = createMessageActions({
activeTab,
audienceEstimateCount,
canManage,
detail,
detailDrawerMessageId,
form,
isDetailDrawerOpen,
isMessageDrawerOpen,
isMessageSubmitting,
messageDrawerMode,
loadMessageDetail,
loadMessageList,
loadStats,
});
const {
openCreateTemplateModal,
openEditTemplateModal,
removeTemplate,
setTemplateCategory,
setTemplateContent,
setTemplateEditorOpen,
setTemplateName,
submitTemplate,
} = createTemplateActions({
canManage,
form: templateForm,
isTemplateEditorLoading,
isTemplateEditorOpen,
isTemplateSubmitting,
loadTemplateList,
mode: templateEditorMode,
});
function setActiveTab(value: MemberMessageReachTabKey) {
activeTab.value = value;
}
function setMessageStatusFilter(value: string) {
messageFilterForm.status = (value || undefined) as
| MemberMessageReachStatus
| undefined;
}
function setMessageChannelFilter(value: string) {
messageFilterForm.channel = (value || undefined) as
| MemberMessageReachChannel
| undefined;
}
function setMessageKeyword(value: string) {
messageFilterForm.keyword = value;
}
async function applyMessageFilters() {
messagePager.value = {
...messagePager.value,
page: 1,
};
await loadMessageList();
}
async function resetMessageFilters() {
messageFilterForm.status = undefined;
messageFilterForm.channel = undefined;
messageFilterForm.keyword = '';
messagePager.value = {
...messagePager.value,
page: 1,
};
await loadMessageList();
}
async function handleMessagePageChange(page: number, pageSize: number) {
messagePager.value = {
...messagePager.value,
page,
pageSize,
};
await loadMessageList();
}
function setTemplateCategoryFilter(value: string) {
templateFilterForm.category = (value || undefined) as
| MemberMessageTemplateCategory
| undefined;
}
function setTemplateKeyword(value: string) {
templateFilterForm.keyword = value;
}
async function applyTemplateFilters() {
templatePager.value = {
...templatePager.value,
page: 1,
};
await loadTemplateList();
}
async function resetTemplateFilters() {
templateFilterForm.category = undefined;
templateFilterForm.keyword = '';
templatePager.value = {
...templatePager.value,
page: 1,
};
await loadTemplateList();
}
async function handleTemplatePageChange(page: number, pageSize: number) {
templatePager.value = {
...templatePager.value,
page,
pageSize,
};
await loadTemplateList();
}
async function setAudienceTypeAndEstimate(value: 'all' | 'tag') {
setAudienceType(value);
if (value === 'all') {
audienceEstimateCount.value = 0;
return;
}
await estimateAudience('tag', form.audienceTags);
}
async function toggleAudienceTagAndEstimate(tag: string) {
toggleAudienceTag(tag);
if (form.audienceType !== 'tag') {
return;
}
await estimateAudience('tag', form.audienceTags);
}
async function submitDraftMessage() {
await submitMessage('draft');
}
async function submitSendMessage() {
await submitMessage('send');
}
function clearByPermission() {
stats.value = createDefaultMessageReachStats();
messagePager.value = createDefaultMessageReachPager();
templatePager.value = createDefaultMessageTemplatePager();
detail.value = null;
isDetailDrawerOpen.value = false;
detailDrawerMessageId.value = '';
resetMessageEditorForm(form);
audienceEstimateCount.value = 0;
isMessageDrawerOpen.value = false;
resetTemplateEditorForm(templateForm);
isTemplateEditorOpen.value = false;
}
async function bootstrapPageData() {
await Promise.all([loadStats(), loadMessageList(), loadTemplateList()]);
}
watch(canView, async (value, oldValue) => {
if (value === oldValue) {
return;
}
if (!value) {
clearByPermission();
return;
}
await bootstrapPageData();
});
onMounted(async () => {
if (!canView.value) {
clearByPermission();
return;
}
await bootstrapPageData();
});
onActivated(() => {
if (!canView.value) {
return;
}
if (
messagePager.value.totalCount === 0 &&
templatePager.value.totalCount === 0
) {
void bootstrapPageData();
}
});
return {
activeTab,
applyMessageFilters,
applyTemplateFilters,
audienceEstimateCount,
canManage,
canView,
detail,
form,
handleMessagePageChange,
handleTemplatePageChange,
isDetailDrawerOpen,
isDetailLoading,
isEstimatingAudience,
isMessageDrawerOpen,
isMessageLoading,
isMessageSubmitting,
isStatsLoading,
isTemplateEditorLoading,
isTemplateEditorOpen,
isTemplateLoading,
isTemplateSubmitting,
messageDrawerTitle,
messageFilterForm,
messagePager,
openCreateMessageDrawer,
openCreateTemplateModal,
openDetailDrawer,
openEditMessageDrawer,
openEditTemplateModal,
removeMessage,
removeTemplate,
resetMessageFilters,
resetTemplateFilters,
setActiveTab,
setAudienceTypeAndEstimate,
setDetailDrawerOpen,
setMessageChannel,
setMessageChannelFilter,
setMessageContent,
setMessageDrawerOpen,
setMessageKeyword,
setMessageStatusFilter,
setMessageTitle,
setScheduleType,
setScheduledAt,
setTemplateCategory,
setTemplateCategoryFilter,
setTemplateContent,
setTemplateEditorOpen,
setTemplateKeyword,
setTemplateName,
stats,
submitDraftMessage,
submitSendMessage,
submitTemplate,
switchToTemplateTab,
tabOptions: MESSAGE_REACH_TAB_OPTIONS,
templateEditorSubmitText,
templateEditorTitle,
templateFilterForm,
templateForm,
templatePager,
toggleAudienceTagAndEstimate,
useTemplateToCreateMessage,
};
}