feat:【ele】【mp】模版消息的新增

This commit is contained in:
YunaiV
2025-11-26 19:08:09 +08:00
parent 672a6c9ccc
commit 3cb0afe319
4 changed files with 468 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
import { requestClient } from '#/api/request';
export namespace MpMessageTemplateApi {
/** 消息模板信息 */
export interface MessageTemplate {
id: number;
accountId: number;
appId: string;
templateId: string;
title: string;
content: string;
example: string;
primaryIndustry: string;
deputyIndustry: string;
createTime?: Date;
}
/** 发送消息模板请求 */
export interface MessageTemplateSendVO {
id: number;
userId: number;
data?: Record<string, string>;
url?: string;
miniProgramAppId?: string;
miniProgramPagePath?: string;
miniprogram?: string;
}
}
/** 查询消息模板列表 */
export function getMessageTemplateList(params: { accountId: number }) {
return requestClient.get<MpMessageTemplateApi.MessageTemplate[]>(
'/mp/message-template/list',
{ params },
);
}
/** 删除消息模板 */
export function deleteMessageTemplate(id: number) {
return requestClient.delete('/mp/message-template/delete', {
params: { id },
});
}
/** 同步公众号模板 */
export function syncMessageTemplate(accountId: number) {
return requestClient.post('/mp/message-template/sync', null, {
params: { accountId },
});
}
/** 发送消息模板 */
export function sendMessageTemplate(
data: MpMessageTemplateApi.MessageTemplateSendVO,
) {
return requestClient.post('/mp/message-template/send', data);
}

View File

@@ -0,0 +1,143 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import { getUserPage } from '#/api/mp/user';
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'accountId',
label: '公众号',
component: 'Input',
},
];
}
/** 发送消息模板表单 */
export function useSendFormSchema(accountId?: number): VbenFormSchema[] {
return [
{
fieldName: 'id',
label: '模板编号',
component: 'Input',
componentProps: {
disabled: true,
},
},
{
fieldName: 'title',
label: '模板标题',
component: 'Input',
componentProps: {
disabled: true,
},
},
{
fieldName: 'userId',
label: '用户',
component: 'ApiSelect',
componentProps: {
api: async () => {
if (!accountId) {
return [];
}
const data = await getUserPage({
pageNo: 1,
pageSize: 100,
accountId,
});
return (data.list || []).map((user) => ({
label: user.nickname || user.openid,
value: user.id,
}));
},
filterable: true,
placeholder: '请选择用户',
},
rules: 'required',
},
{
fieldName: 'data',
label: '模板数据',
component: 'Textarea',
componentProps: {
rows: 4,
placeholder:
'请输入模板数据JSON 格式),例如:{"keyword1": {"value": "测试内容"}}',
},
},
{
fieldName: 'url',
label: '跳转链接',
component: 'Input',
componentProps: {
placeholder: '请输入跳转链接',
},
},
{
fieldName: 'miniProgramAppId',
label: '小程序 appId',
component: 'Input',
componentProps: {
placeholder: '请输入小程序 appId',
},
},
{
fieldName: 'miniProgramPagePath',
label: '小程序页面路径',
component: 'Input',
componentProps: {
placeholder: '请输入小程序页面路径',
},
},
];
}
/** 表格列配置 */
export function useGridColumns(): VxeGridPropTypes.Columns {
return [
{
title: '公众号模板 ID',
field: 'templateId',
minWidth: 400,
},
{
title: '标题',
field: 'title',
minWidth: 150,
},
{
title: '模板内容',
field: 'content',
minWidth: 400,
},
{
title: '模板示例',
field: 'example',
minWidth: 200,
},
{
title: '一级行业',
field: 'primaryIndustry',
minWidth: 120,
},
{
title: '二级行业',
field: 'deputyIndustry',
minWidth: 120,
},
{
title: '创建时间',
field: 'createTime',
formatter: 'formatDateTime',
minWidth: 180,
},
{
title: '操作',
width: 140,
fixed: 'right',
slots: { default: 'actions' },
},
];
}

View File

@@ -0,0 +1,165 @@
<script lang="ts" setup>
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { MpMessageTemplateApi } from '#/api/mp/messageTemplate';
import { confirm, DocAlert, Page, useVbenModal } from '@vben/common-ui';
import { ElLoading, ElMessage } from 'element-plus';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
deleteMessageTemplate,
getMessageTemplateList,
syncMessageTemplate,
} from '#/api/mp/messageTemplate';
import { $t } from '#/locales';
import { WxAccountSelect } from '#/views/mp/components';
import { useGridColumns, useGridFormSchema } from './data';
import SendForm from './modules/send-form.vue';
const [SendFormModal, sendFormModalApi] = useVbenModal({
connectedComponent: SendForm,
destroyOnClose: true,
});
/** 刷新表格 */
function handleRefresh() {
gridApi.query();
}
/** 公众号变化时查询数据 */
function handleAccountChange(accountId: number) {
gridApi.formApi.setValues({ accountId });
gridApi.formApi.submitForm();
}
/** 同步模板 */
async function handleSync() {
const formValues = await gridApi.formApi.getValues();
const accountId = formValues.accountId;
if (!accountId) {
ElMessage.warning('请先选择公众号');
return;
}
await confirm('是否确认同步消息模板?');
const loadingInstance = ElLoading.service({
text: '正在同步消息模板...',
});
try {
await syncMessageTemplate(accountId);
ElMessage.success('同步消息模板成功');
handleRefresh();
} finally {
loadingInstance.close();
}
}
/** 发送消息 */
function handleSend(row: MpMessageTemplateApi.MessageTemplate) {
sendFormModalApi.setData(row).open();
}
/** 删除模板 */
async function handleDelete(row: MpMessageTemplateApi.MessageTemplate) {
const loadingInstance = ElLoading.service({
text: $t('ui.actionMessage.deleting', [row.title]),
});
try {
await deleteMessageTemplate(row.id);
ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.title]));
handleRefresh();
} finally {
loadingInstance.close();
}
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
height: 'auto',
keepSource: true,
proxyConfig: {
ajax: {
query: async (_params, formValues) => {
return await getMessageTemplateList({
accountId: formValues.accountId,
});
},
},
autoLoad: false,
},
pagerConfig: {
enabled: false,
},
rowConfig: {
keyField: 'id',
isHover: true,
},
toolbarConfig: {
refresh: true,
search: true,
},
} as VxeTableGridOptions<MpMessageTemplateApi.MessageTemplate>,
});
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="模版消息"
url="https://doc.iocoder.cn/mp/message-template/"
/>
</template>
<SendFormModal @success="handleRefresh" />
<Grid table-title="公众号消息模板列表">
<template #form-accountId>
<WxAccountSelect @change="handleAccountChange" />
</template>
<template #toolbar-tools>
<TableAction
:actions="[
{
label: '同步',
type: 'primary',
icon: 'lucide:refresh-ccw',
auth: ['mp:message-template:sync'],
onClick: handleSync,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
:actions="[
{
label: '发送',
type: 'primary',
link: true,
icon: 'lucide:send',
auth: ['mp:message-template:send'],
onClick: handleSend.bind(null, row),
},
{
label: $t('common.delete'),
type: 'danger',
link: true,
icon: ACTION_ICON.DELETE,
auth: ['mp:message-template:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.title]),
confirm: handleDelete.bind(null, row),
},
},
]"
/>
</template>
</Grid>
</Page>
</template>

View File

@@ -0,0 +1,103 @@
<script lang="ts" setup>
import type { MpMessageTemplateApi } from '#/api/mp/messageTemplate';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { ElMessage } from 'element-plus';
import { useVbenForm } from '#/adapter/form';
import { sendMessageTemplate } from '#/api/mp/messageTemplate';
import { useSendFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<MpMessageTemplateApi.MessageTemplate>();
const [Form, formApi] = useVbenForm({
commonConfig: {
componentProps: {
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 120,
},
layout: 'horizontal',
showDefaultActions: false,
});
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
if (!valid) {
return;
}
modalApi.lock();
// 构建发送请求
const values = await formApi.getValues();
const sendData: MpMessageTemplateApi.MessageTemplateSendVO = {
id: formData.value?.id || 0,
userId: values.userId,
data: values.data || undefined,
url: values.url || undefined,
miniProgramAppId: values.miniProgramAppId || undefined,
miniProgramPagePath: values.miniProgramPagePath || undefined,
};
// 如果填写了小程序信息,需要拼接成 miniprogram 字段
if (sendData.miniProgramAppId && sendData.miniProgramPagePath) {
sendData.miniprogram = JSON.stringify({
appid: sendData.miniProgramAppId,
pagepath: sendData.miniProgramPagePath,
});
}
// 如果填写了 data 字段
if (sendData.data && typeof sendData.data === 'string') {
try {
sendData.data = JSON.parse(sendData.data);
} catch {
ElMessage.error('模板数据格式不正确,请输入有效的 JSON 格式');
modalApi.unlock();
return;
}
}
// 提交表单
try {
await sendMessageTemplate(sendData);
// 关闭并提示
await modalApi.close();
emit('success');
ElMessage.success('发送成功');
} finally {
modalApi.unlock();
}
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formData.value = undefined;
return;
}
// 获取数据
const data = modalApi.getData<MpMessageTemplateApi.MessageTemplate>();
if (!data) {
return;
}
formData.value = data;
// 更新 form schema
const schema = useSendFormSchema(data.accountId);
formApi.setState({ schema });
// 设置到 values
await formApi.setValues({
id: data.id,
title: data.title,
});
},
});
</script>
<template>
<Modal class="w-[600px]" title="发送消息模板">
<Form class="mx-4" />
</Modal>
</template>