feat: 新增 ele 邮件管理模块
							parent
							
								
									e8e3d020f4
								
							
						
					
					
						commit
						d84b72e2a6
					
				|  | @ -0,0 +1,207 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailAccountApi } from '#/api/system/mail/account'; | ||||||
|  | 
 | ||||||
|  | import { useAccess } from '@vben/access'; | ||||||
|  | 
 | ||||||
|  | import { z } from '#/adapter/form'; | ||||||
|  | import { DICT_TYPE, getDictOptions } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | const { hasAccessByCodes } = useAccess(); | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'id', | ||||||
|  |       component: 'Input', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'mail', | ||||||
|  |       label: '邮箱', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入邮箱', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'username', | ||||||
|  |       label: '用户名', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入用户名', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'password', | ||||||
|  |       label: '密码', | ||||||
|  |       component: 'InputPassword', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入密码', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'host', | ||||||
|  |       label: 'SMTP 服务器域名', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入 SMTP 服务器域名', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'port', | ||||||
|  |       label: 'SMTP 服务器端口', | ||||||
|  |       component: 'InputNumber', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入 SMTP 服务器端口', | ||||||
|  |         min: 0, | ||||||
|  |         max: 65_535, | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'sslEnable', | ||||||
|  |       label: '是否开启 SSL', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), | ||||||
|  |         buttonStyle: 'solid', | ||||||
|  |         optionType: 'button', | ||||||
|  |       }, | ||||||
|  |       rules: z.boolean().default(true), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'starttlsEnable', | ||||||
|  |       label: '是否开启 STARTTLS', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean'), | ||||||
|  |         buttonStyle: 'solid', | ||||||
|  |         optionType: 'button', | ||||||
|  |       }, | ||||||
|  |       rules: z.boolean().default(false), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'mail', | ||||||
|  |       label: '邮箱', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入邮箱', | ||||||
|  |         clearable: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'username', | ||||||
|  |       label: '用户名', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入用户名', | ||||||
|  |         clearable: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns<T = SystemMailAccountApi.MailAccount>( | ||||||
|  |   onActionClick: OnActionClickFn<T>, | ||||||
|  | ): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'id', | ||||||
|  |       title: '编号', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'mail', | ||||||
|  |       title: '邮箱', | ||||||
|  |       minWidth: 160, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'username', | ||||||
|  |       title: '用户名', | ||||||
|  |       minWidth: 160, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'host', | ||||||
|  |       title: 'SMTP 服务器域名', | ||||||
|  |       minWidth: 150, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'port', | ||||||
|  |       title: 'SMTP 服务器端口', | ||||||
|  |       minWidth: 130, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sslEnable', | ||||||
|  |       title: '是否开启 SSL', | ||||||
|  |       minWidth: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'starttlsEnable', | ||||||
|  |       title: '是否开启 STARTTLS', | ||||||
|  |       minWidth: 145, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.INFRA_BOOLEAN_STRING }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'createTime', | ||||||
|  |       title: '创建时间', | ||||||
|  |       minWidth: 180, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'operation', | ||||||
|  |       title: '操作', | ||||||
|  |       minWidth: 130, | ||||||
|  |       align: 'center', | ||||||
|  |       fixed: 'right', | ||||||
|  |       cellRender: { | ||||||
|  |         attrs: { | ||||||
|  |           nameField: 'mail', | ||||||
|  |           nameTitle: '邮箱账号', | ||||||
|  |           onClick: onActionClick, | ||||||
|  |         }, | ||||||
|  |         name: 'CellOperation', | ||||||
|  |         options: [ | ||||||
|  |           { | ||||||
|  |             code: 'edit', | ||||||
|  |             show: hasAccessByCodes(['system:mail-account:update']), | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             code: 'delete', | ||||||
|  |             show: hasAccessByCodes(['system:mail-account:delete']), | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,127 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { | ||||||
|  |   OnActionClickParams, | ||||||
|  |   VxeTableGridOptions, | ||||||
|  | } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailAccountApi } from '#/api/system/mail/account'; | ||||||
|  | 
 | ||||||
|  | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { Plus } from '@vben/icons'; | ||||||
|  | 
 | ||||||
|  | import { ElButton, ElLoading, ElMessage } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { | ||||||
|  |   deleteMailAccount, | ||||||
|  |   getMailAccountPage, | ||||||
|  | } from '#/api/system/mail/account'; | ||||||
|  | import { DocAlert } from '#/components/doc-alert'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import Form from './modules/form.vue'; | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Form, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 创建邮箱账号 */ | ||||||
|  | function onCreate() { | ||||||
|  |   formModalApi.setData(null).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑邮箱账号 */ | ||||||
|  | function onEdit(row: SystemMailAccountApi.MailAccount) { | ||||||
|  |   formModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除邮箱账号 */ | ||||||
|  | async function onDelete(row: SystemMailAccountApi.MailAccount) { | ||||||
|  |   const loadingInstance = ElLoading.service({ | ||||||
|  |     text: $t('ui.actionMessage.deleting', [row.mail]), | ||||||
|  |     fullscreen: true, | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteMailAccount(row.id as number); | ||||||
|  |     ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.mail])); | ||||||
|  |     onRefresh(); | ||||||
|  |   } catch { | ||||||
|  |     // 异常处理 | ||||||
|  |   } finally { | ||||||
|  |     loadingInstance.close(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 表格操作按钮的回调函数 */ | ||||||
|  | function onActionClick({ | ||||||
|  |   code, | ||||||
|  |   row, | ||||||
|  | }: OnActionClickParams<SystemMailAccountApi.MailAccount>) { | ||||||
|  |   switch (code) { | ||||||
|  |     case 'delete': { | ||||||
|  |       onDelete(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 'edit': { | ||||||
|  |       onEdit(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(onActionClick), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getMailAccountPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: { code: 'query' }, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<SystemMailAccountApi.MailAccount>, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <Page auto-content-height> | ||||||
|  |     <template #doc> | ||||||
|  |       <DocAlert title="邮件配置" url="https://doc.iocoder.cn/mail" /> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <FormModal @success="onRefresh" /> | ||||||
|  |     <Grid table-title="邮箱账号列表"> | ||||||
|  |       <template #toolbar-tools> | ||||||
|  |         <ElButton | ||||||
|  |           type="primary" | ||||||
|  |           @click="onCreate" | ||||||
|  |           v-access:code="['system:mail-account:create']" | ||||||
|  |         > | ||||||
|  |           <Plus class="size-5" /> | ||||||
|  |           {{ $t('ui.actionTitle.create', ['邮箱账号']) }} | ||||||
|  |         </ElButton> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|  |   </Page> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { SystemMailAccountApi } from '#/api/system/mail/account'; | ||||||
|  | 
 | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { ElMessage } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createMailAccount, | ||||||
|  |   getMailAccount, | ||||||
|  |   updateMailAccount, | ||||||
|  | } from '#/api/system/mail/account'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<SystemMailAccountApi.MailAccount>(); | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   return formData.value?.id | ||||||
|  |     ? $t('ui.actionTitle.edit', ['邮箱账号']) | ||||||
|  |     : $t('ui.actionTitle.create', ['邮箱账号']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     formItemClass: 'col-span-2', | ||||||
|  |     labelWidth: 80, | ||||||
|  |   }, | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = | ||||||
|  |       (await formApi.getValues()) as SystemMailAccountApi.MailAccount; | ||||||
|  |     try { | ||||||
|  |       await (formData.value?.id | ||||||
|  |         ? updateMailAccount(data) | ||||||
|  |         : createMailAccount(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       ElMessage.success($t('ui.actionMessage.operationSuccess')); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<SystemMailAccountApi.MailAccount>(); | ||||||
|  |     if (!data || !data.id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = await getMailAccount(data.id); | ||||||
|  |       // 设置到 values | ||||||
|  |       await formApi.setValues(formData.value); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="getTitle"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,150 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailLogApi } from '#/api/system/mail/log'; | ||||||
|  | 
 | ||||||
|  | import { useAccess } from '@vben/access'; | ||||||
|  | 
 | ||||||
|  | import { getSimpleMailAccountList } from '#/api/system/mail/account'; | ||||||
|  | import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | const { hasAccessByCodes } = useAccess(); | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'sendTime', | ||||||
|  |       label: '发送时间', | ||||||
|  |       component: 'RangePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         ...getRangePickerDefaultProps(), | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'userId', | ||||||
|  |       label: '用户编号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请输入用户编号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'userType', | ||||||
|  |       label: '用户类型', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.USER_TYPE, 'number'), | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请选择用户类型', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'sendStatus', | ||||||
|  |       label: '发送状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.SYSTEM_MAIL_SEND_STATUS, 'number'), | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请选择发送状态', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'accountId', | ||||||
|  |       label: '邮箱账号', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         api: async () => await getSimpleMailAccountList(), | ||||||
|  |         labelField: 'mail', | ||||||
|  |         valueField: 'id', | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请选择邮箱账号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'templateId', | ||||||
|  |       label: '模板编号', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请输入模板编号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns<T = SystemMailLogApi.MailLog>( | ||||||
|  |   onActionClick: OnActionClickFn<T>, | ||||||
|  | ): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'id', | ||||||
|  |       title: '编号', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sendTime', | ||||||
|  |       title: '发送时间', | ||||||
|  |       minWidth: 180, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'toMail', | ||||||
|  |       title: '收件邮箱', | ||||||
|  |       minWidth: 160, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'templateTitle', | ||||||
|  |       title: '邮件标题', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'templateContent', | ||||||
|  |       title: '邮件内容', | ||||||
|  |       minWidth: 300, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'fromMail', | ||||||
|  |       title: '发送邮箱', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'sendStatus', | ||||||
|  |       title: '发送状态', | ||||||
|  |       minWidth: 120, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'templateCode', | ||||||
|  |       title: '模板编码', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'operation', | ||||||
|  |       title: '操作', | ||||||
|  |       minWidth: 80, | ||||||
|  |       align: 'center', | ||||||
|  |       fixed: 'right', | ||||||
|  |       cellRender: { | ||||||
|  |         attrs: { | ||||||
|  |           nameField: 'toMail', | ||||||
|  |           nameTitle: '邮件日志', | ||||||
|  |           onClick: onActionClick, | ||||||
|  |         }, | ||||||
|  |         name: 'CellOperation', | ||||||
|  |         options: [ | ||||||
|  |           { | ||||||
|  |             code: 'detail', | ||||||
|  |             text: '查看', | ||||||
|  |             show: hasAccessByCodes(['system:mail-log:query']), | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,85 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { | ||||||
|  |   OnActionClickParams, | ||||||
|  |   VxeTableGridOptions, | ||||||
|  | } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailLogApi } from '#/api/system/mail/log'; | ||||||
|  | 
 | ||||||
|  | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { getMailLogPage } from '#/api/system/mail/log'; | ||||||
|  | import { DocAlert } from '#/components/doc-alert'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import Detail from './modules/detail.vue'; | ||||||
|  | 
 | ||||||
|  | const [DetailModal, detailModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Detail, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 查看邮件日志 */ | ||||||
|  | function onDetail(row: SystemMailLogApi.MailLog) { | ||||||
|  |   detailModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 表格操作按钮的回调函数 */ | ||||||
|  | function onActionClick({ | ||||||
|  |   code, | ||||||
|  |   row, | ||||||
|  | }: OnActionClickParams<SystemMailLogApi.MailLog>) { | ||||||
|  |   switch (code) { | ||||||
|  |     case 'detail': { | ||||||
|  |       onDetail(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(onActionClick), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getMailLogPage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: { code: 'query' }, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<SystemMailLogApi.MailLog>, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <Page auto-content-height> | ||||||
|  |     <template #doc> | ||||||
|  |       <DocAlert title="邮件配置" url="https://doc.iocoder.cn/mail" /> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <DetailModal @success="onRefresh" /> | ||||||
|  |     <Grid table-title="邮件日志列表"> | ||||||
|  |       <template #toolbar-tools> </template> | ||||||
|  |     </Grid> | ||||||
|  |   </Page> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,93 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { SystemMailLogApi } from '#/api/system/mail/log'; | ||||||
|  | 
 | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { formatDateTime } from '@vben/utils'; | ||||||
|  | 
 | ||||||
|  | import { ElDescriptions, ElDescriptionsItem } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { DictTag } from '#/components/dict-tag'; | ||||||
|  | import { DICT_TYPE } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | const formData = ref<SystemMailLogApi.MailLog>(); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<SystemMailLogApi.MailLog>(); | ||||||
|  |     if (!data || !data.id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = data; | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal | ||||||
|  |     title="邮件日志详情" | ||||||
|  |     class="w-1/2" | ||||||
|  |     :show-cancel-button="false" | ||||||
|  |     :show-confirm-button="false" | ||||||
|  |   > | ||||||
|  |     <div class="p-4"> | ||||||
|  |       <ElDescriptions :column="2" border :label-style="{ width: '140px' }"> | ||||||
|  |         <ElDescriptionsItem label="编号">{{ formData?.id }}</ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="创建时间"> | ||||||
|  |           {{ formatDateTime(formData?.createTime || '') }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="收件邮箱"> | ||||||
|  |           {{ formData?.toMail }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="发送邮箱"> | ||||||
|  |           {{ formData?.fromMail }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="用户编号"> | ||||||
|  |           {{ formData?.userId }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="用户类型"> | ||||||
|  |           {{ formData?.userType }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="模板编号"> | ||||||
|  |           {{ formData?.templateId }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="模板编码"> | ||||||
|  |           {{ formData?.templateCode }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="邮件标题" :span="2"> | ||||||
|  |           {{ formData?.templateTitle }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="邮件内容" :span="2"> | ||||||
|  |           <!-- eslint-disable-next-line vue/no-v-html --> | ||||||
|  |           <div v-html="formData?.templateContent"></div> | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="发送状态"> | ||||||
|  |           <DictTag | ||||||
|  |             :type="DICT_TYPE.SYSTEM_MAIL_SEND_STATUS" | ||||||
|  |             :value="formData?.sendStatus" | ||||||
|  |           /> | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="发送时间"> | ||||||
|  |           {{ formatDateTime(formData?.sendTime || '') }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="发送消息编号"> | ||||||
|  |           {{ formData?.sendMessageId }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |         <ElDescriptionsItem label="发送异常"> | ||||||
|  |           {{ formData?.sendException }} | ||||||
|  |         </ElDescriptionsItem> | ||||||
|  |       </ElDescriptions> | ||||||
|  |     </div> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,278 @@ | ||||||
|  | import type { VbenFormSchema } from '#/adapter/form'; | ||||||
|  | import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailTemplateApi } from '#/api/system/mail/template'; | ||||||
|  | 
 | ||||||
|  | import { useAccess } from '@vben/access'; | ||||||
|  | 
 | ||||||
|  | import { z } from '#/adapter/form'; | ||||||
|  | import { getSimpleMailAccountList } from '#/api/system/mail/account'; | ||||||
|  | import { | ||||||
|  |   CommonStatusEnum, | ||||||
|  |   DICT_TYPE, | ||||||
|  |   getDictOptions, | ||||||
|  |   getRangePickerDefaultProps, | ||||||
|  | } from '#/utils'; | ||||||
|  | 
 | ||||||
|  | const { hasAccessByCodes } = useAccess(); | ||||||
|  | 
 | ||||||
|  | /** 新增/修改的表单 */ | ||||||
|  | export function useFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'id', | ||||||
|  |       component: 'Input', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '模板名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入模板名称', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'code', | ||||||
|  |       label: '模板编码', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入模板编码', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'accountId', | ||||||
|  |       label: '邮箱账号', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         api: async () => await getSimpleMailAccountList(), | ||||||
|  |         labelField: 'mail', | ||||||
|  |         valueField: 'id', | ||||||
|  |         placeholder: '请选择邮箱账号', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'nickname', | ||||||
|  |       label: '发送人名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入发送人名称', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'title', | ||||||
|  |       label: '模板标题', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入模板标题', | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'content', | ||||||
|  |       label: '模板内容', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入模板内容', | ||||||
|  |         height: 300, | ||||||
|  |       }, | ||||||
|  |       rules: 'required', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '开启状态', | ||||||
|  |       component: 'RadioGroup', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |         buttonStyle: 'solid', | ||||||
|  |         optionType: 'button', | ||||||
|  |       }, | ||||||
|  |       rules: z.number().default(CommonStatusEnum.ENABLE), | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'remark', | ||||||
|  |       label: '备注', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入备注', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 发送邮件表单 */ | ||||||
|  | export function useSendMailFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'templateParams', | ||||||
|  |       label: '模板参数', | ||||||
|  |       component: 'Input', | ||||||
|  |       dependencies: { | ||||||
|  |         triggerFields: [''], | ||||||
|  |         show: () => false, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'content', | ||||||
|  |       label: '模板内容', | ||||||
|  |       component: 'Textarea', | ||||||
|  |       componentProps: { | ||||||
|  |         disabled: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'mail', | ||||||
|  |       label: '收件邮箱', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         placeholder: '请输入收件邮箱', | ||||||
|  |       }, | ||||||
|  |       rules: z.string().email('请输入正确的邮箱'), | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的搜索表单 */ | ||||||
|  | export function useGridFormSchema(): VbenFormSchema[] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       fieldName: 'status', | ||||||
|  |       label: '开启状态', | ||||||
|  |       component: 'Select', | ||||||
|  |       componentProps: { | ||||||
|  |         options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'), | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请选择开启状态', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'code', | ||||||
|  |       label: '模板编码', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请输入模板编码', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'name', | ||||||
|  |       label: '模板名称', | ||||||
|  |       component: 'Input', | ||||||
|  |       componentProps: { | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请输入模板名称', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'accountId', | ||||||
|  |       label: '邮箱账号', | ||||||
|  |       component: 'ApiSelect', | ||||||
|  |       componentProps: { | ||||||
|  |         api: async () => await getSimpleMailAccountList(), | ||||||
|  |         labelField: 'mail', | ||||||
|  |         valueField: 'id', | ||||||
|  |         allowClear: true, | ||||||
|  |         placeholder: '请选择邮箱账号', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       fieldName: 'createTime', | ||||||
|  |       label: '创建时间', | ||||||
|  |       component: 'RangePicker', | ||||||
|  |       componentProps: { | ||||||
|  |         ...getRangePickerDefaultProps(), | ||||||
|  |         allowClear: true, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 列表的字段 */ | ||||||
|  | export function useGridColumns<T = SystemMailTemplateApi.MailTemplate>( | ||||||
|  |   onActionClick: OnActionClickFn<T>, | ||||||
|  |   getAccountMail?: (accountId: number) => string | undefined, | ||||||
|  | ): VxeTableGridOptions['columns'] { | ||||||
|  |   return [ | ||||||
|  |     { | ||||||
|  |       field: 'id', | ||||||
|  |       title: '编号', | ||||||
|  |       minWidth: 100, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'code', | ||||||
|  |       title: '模板编码', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'name', | ||||||
|  |       title: '模板名称', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'title', | ||||||
|  |       title: '模板标题', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'accountId', | ||||||
|  |       title: '邮箱账号', | ||||||
|  |       minWidth: 120, | ||||||
|  |       formatter: (row) => getAccountMail?.(row.cellValue) || '-', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'nickname', | ||||||
|  |       title: '发送人名称', | ||||||
|  |       minWidth: 120, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'status', | ||||||
|  |       title: '开启状态', | ||||||
|  |       minWidth: 100, | ||||||
|  |       cellRender: { | ||||||
|  |         name: 'CellDict', | ||||||
|  |         props: { type: DICT_TYPE.COMMON_STATUS }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'createTime', | ||||||
|  |       title: '创建时间', | ||||||
|  |       minWidth: 180, | ||||||
|  |       formatter: 'formatDateTime', | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       field: 'operation', | ||||||
|  |       title: '操作', | ||||||
|  |       minWidth: 150, | ||||||
|  |       align: 'center', | ||||||
|  |       fixed: 'right', | ||||||
|  |       cellRender: { | ||||||
|  |         attrs: { | ||||||
|  |           nameField: 'name', | ||||||
|  |           nameTitle: '邮件模板', | ||||||
|  |           onClick: onActionClick, | ||||||
|  |         }, | ||||||
|  |         name: 'CellOperation', | ||||||
|  |         options: [ | ||||||
|  |           { | ||||||
|  |             code: 'edit', | ||||||
|  |             show: hasAccessByCodes(['system:mail-template:update']), | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             code: 'delete', | ||||||
|  |             show: hasAccessByCodes(['system:mail-template:delete']), | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             code: 'send', | ||||||
|  |             text: '测试', | ||||||
|  |             show: hasAccessByCodes(['system:mail-template:send-mail']), | ||||||
|  |           }, | ||||||
|  |         ], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   ]; | ||||||
|  | } | ||||||
|  | @ -0,0 +1,157 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { | ||||||
|  |   OnActionClickParams, | ||||||
|  |   VxeTableGridOptions, | ||||||
|  | } from '#/adapter/vxe-table'; | ||||||
|  | import type { SystemMailAccountApi } from '#/api/system/mail/account'; | ||||||
|  | import type { SystemMailTemplateApi } from '#/api/system/mail/template'; | ||||||
|  | 
 | ||||||
|  | import { onMounted, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { Page, useVbenModal } from '@vben/common-ui'; | ||||||
|  | import { Plus } from '@vben/icons'; | ||||||
|  | 
 | ||||||
|  | import { ElButton, ElLoading, ElMessage } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||||
|  | import { getSimpleMailAccountList } from '#/api/system/mail/account'; | ||||||
|  | import { | ||||||
|  |   deleteMailTemplate, | ||||||
|  |   getMailTemplatePage, | ||||||
|  | } from '#/api/system/mail/template'; | ||||||
|  | import { DocAlert } from '#/components/doc-alert'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useGridColumns, useGridFormSchema } from './data'; | ||||||
|  | import Form from './modules/form.vue'; | ||||||
|  | import SendForm from './modules/send-form.vue'; | ||||||
|  | 
 | ||||||
|  | const accountList = ref<SystemMailAccountApi.MailAccount[]>([]); | ||||||
|  | 
 | ||||||
|  | /** 获取邮箱账号 */ | ||||||
|  | const getAccountMail = (accountId: number) => { | ||||||
|  |   return accountList.value.find((account) => account.id === accountId)?.mail; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const [FormModal, formModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: Form, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [SendModal, sendModalApi] = useVbenModal({ | ||||||
|  |   connectedComponent: SendForm, | ||||||
|  |   destroyOnClose: true, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 刷新表格 */ | ||||||
|  | function onRefresh() { | ||||||
|  |   gridApi.query(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 创建邮件模板 */ | ||||||
|  | function onCreate() { | ||||||
|  |   formModalApi.setData(null).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 编辑邮件模板 */ | ||||||
|  | function onEdit(row: SystemMailTemplateApi.MailTemplate) { | ||||||
|  |   formModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 发送测试邮件 */ | ||||||
|  | function onSend(row: SystemMailTemplateApi.MailTemplate) { | ||||||
|  |   sendModalApi.setData(row).open(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 删除邮件模板 */ | ||||||
|  | async function onDelete(row: SystemMailTemplateApi.MailTemplate) { | ||||||
|  |   const loadingInstance = ElLoading.service({ | ||||||
|  |     text: $t('ui.actionMessage.deleting', [row.name]), | ||||||
|  |     fullscreen: true, | ||||||
|  |   }); | ||||||
|  |   try { | ||||||
|  |     await deleteMailTemplate(row.id as number); | ||||||
|  |     ElMessage.success($t('ui.actionMessage.deleteSuccess', [row.name])); | ||||||
|  |     onRefresh(); | ||||||
|  |   } finally { | ||||||
|  |     loadingInstance.close(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** 表格操作按钮的回调函数 */ | ||||||
|  | function onActionClick({ | ||||||
|  |   code, | ||||||
|  |   row, | ||||||
|  | }: OnActionClickParams<SystemMailTemplateApi.MailTemplate>) { | ||||||
|  |   switch (code) { | ||||||
|  |     case 'delete': { | ||||||
|  |       onDelete(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 'edit': { | ||||||
|  |       onEdit(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     case 'send': { | ||||||
|  |       onSend(row); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const [Grid, gridApi] = useVbenVxeGrid({ | ||||||
|  |   formOptions: { | ||||||
|  |     schema: useGridFormSchema(), | ||||||
|  |   }, | ||||||
|  |   gridOptions: { | ||||||
|  |     columns: useGridColumns(onActionClick, getAccountMail), | ||||||
|  |     height: 'auto', | ||||||
|  |     keepSource: true, | ||||||
|  |     proxyConfig: { | ||||||
|  |       ajax: { | ||||||
|  |         query: async ({ page }, formValues) => { | ||||||
|  |           return await getMailTemplatePage({ | ||||||
|  |             pageNo: page.currentPage, | ||||||
|  |             pageSize: page.pageSize, | ||||||
|  |             ...formValues, | ||||||
|  |           }); | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |     rowConfig: { | ||||||
|  |       keyField: 'id', | ||||||
|  |     }, | ||||||
|  |     toolbarConfig: { | ||||||
|  |       refresh: { code: 'query' }, | ||||||
|  |       search: true, | ||||||
|  |     }, | ||||||
|  |   } as VxeTableGridOptions<SystemMailTemplateApi.MailTemplate>, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 初始化 */ | ||||||
|  | onMounted(async () => { | ||||||
|  |   accountList.value = await getSimpleMailAccountList(); | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <Page auto-content-height> | ||||||
|  |     <template #doc> | ||||||
|  |       <DocAlert title="邮件配置" url="https://doc.iocoder.cn/mail" /> | ||||||
|  |     </template> | ||||||
|  | 
 | ||||||
|  |     <FormModal @success="onRefresh" /> | ||||||
|  |     <SendModal /> | ||||||
|  |     <Grid table-title="邮件模板列表"> | ||||||
|  |       <template #toolbar-tools> | ||||||
|  |         <ElButton | ||||||
|  |           type="primary" | ||||||
|  |           @click="onCreate" | ||||||
|  |           v-access:code="['system:mail-template:create']" | ||||||
|  |         > | ||||||
|  |           <Plus class="size-5" /> | ||||||
|  |           {{ $t('ui.actionTitle.create', ['邮件模板']) }} | ||||||
|  |         </ElButton> | ||||||
|  |       </template> | ||||||
|  |     </Grid> | ||||||
|  |   </Page> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,89 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { SystemMailTemplateApi } from '#/api/system/mail/template'; | ||||||
|  | 
 | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { ElMessage } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { | ||||||
|  |   createMailTemplate, | ||||||
|  |   getMailTemplate, | ||||||
|  |   updateMailTemplate, | ||||||
|  | } from '#/api/system/mail/template'; | ||||||
|  | import { $t } from '#/locales'; | ||||||
|  | 
 | ||||||
|  | import { useFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<SystemMailTemplateApi.MailTemplate>(); | ||||||
|  | const getTitle = computed(() => { | ||||||
|  |   return formData.value?.id | ||||||
|  |     ? $t('ui.actionTitle.edit', ['邮件模板']) | ||||||
|  |     : $t('ui.actionTitle.create', ['邮件模板']); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     formItemClass: 'col-span-2', | ||||||
|  |     labelWidth: 80, | ||||||
|  |   }, | ||||||
|  |   layout: 'horizontal', | ||||||
|  |   schema: useFormSchema(), | ||||||
|  |   showDefaultActions: false, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const [Modal, modalApi] = useVbenModal({ | ||||||
|  |   async onConfirm() { | ||||||
|  |     const { valid } = await formApi.validate(); | ||||||
|  |     if (!valid) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     // 提交表单 | ||||||
|  |     const data = | ||||||
|  |       (await formApi.getValues()) as SystemMailTemplateApi.MailTemplate; | ||||||
|  |     try { | ||||||
|  |       await (formData.value?.id | ||||||
|  |         ? updateMailTemplate(data) | ||||||
|  |         : createMailTemplate(data)); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       ElMessage.success($t('ui.actionMessage.operationSuccess')); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 加载数据 | ||||||
|  |     const data = modalApi.getData<SystemMailTemplateApi.MailTemplate>(); | ||||||
|  |     if (!data || !data.id) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     modalApi.lock(); | ||||||
|  |     try { | ||||||
|  |       formData.value = await getMailTemplate(data.id); | ||||||
|  |       // 设置到 values | ||||||
|  |       await formApi.setValues(formData.value); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal :title="getTitle"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
|  | @ -0,0 +1,109 @@ | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import type { SystemMailTemplateApi } from '#/api/system/mail/template'; | ||||||
|  | 
 | ||||||
|  | import { ref } from 'vue'; | ||||||
|  | 
 | ||||||
|  | import { useVbenModal } from '@vben/common-ui'; | ||||||
|  | 
 | ||||||
|  | import { ElMessage } from 'element-plus'; | ||||||
|  | 
 | ||||||
|  | import { useVbenForm } from '#/adapter/form'; | ||||||
|  | import { sendMail } from '#/api/system/mail/template'; | ||||||
|  | 
 | ||||||
|  | import { useSendMailFormSchema } from '../data'; | ||||||
|  | 
 | ||||||
|  | const emit = defineEmits(['success']); | ||||||
|  | const formData = ref<SystemMailTemplateApi.MailTemplate>(); | ||||||
|  | 
 | ||||||
|  | const [Form, formApi] = useVbenForm({ | ||||||
|  |   commonConfig: { | ||||||
|  |     componentProps: { | ||||||
|  |       class: 'w-full', | ||||||
|  |     }, | ||||||
|  |     formItemClass: 'col-span-2', | ||||||
|  |     labelWidth: 80, | ||||||
|  |   }, | ||||||
|  |   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 paramsObj: Record<string, string> = {}; | ||||||
|  |     if (formData.value?.params) { | ||||||
|  |       formData.value.params.forEach((param: string) => { | ||||||
|  |         paramsObj[param] = values[`param_${param}`]; | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |     const data: SystemMailTemplateApi.MailSendReqVO = { | ||||||
|  |       mail: values.mail, | ||||||
|  |       templateCode: formData.value?.code || '', | ||||||
|  |       templateParams: paramsObj, | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     // 提交表单 | ||||||
|  |     try { | ||||||
|  |       await sendMail(data); | ||||||
|  |       // 关闭并提示 | ||||||
|  |       await modalApi.close(); | ||||||
|  |       emit('success'); | ||||||
|  |       ElMessage.success('邮件发送成功'); | ||||||
|  |     } catch (error) { | ||||||
|  |       console.error('发送邮件失败', error); | ||||||
|  |     } finally { | ||||||
|  |       modalApi.unlock(); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   async onOpenChange(isOpen: boolean) { | ||||||
|  |     if (!isOpen) { | ||||||
|  |       formData.value = undefined; | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     // 获取数据 | ||||||
|  |     const data = modalApi.getData<SystemMailTemplateApi.MailTemplate>(); | ||||||
|  |     if (!data) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     formData.value = data; | ||||||
|  |     // 更新 form schema | ||||||
|  |     const schema = buildFormSchema(); | ||||||
|  |     formApi.setState({ schema }); | ||||||
|  |     // 设置到 values | ||||||
|  |     await formApi.setValues({ | ||||||
|  |       content: data.content, | ||||||
|  |     }); | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | /** 动态构建表单 schema */ | ||||||
|  | const buildFormSchema = () => { | ||||||
|  |   const schema = useSendMailFormSchema(); | ||||||
|  |   if (formData.value?.params) { | ||||||
|  |     formData.value.params?.forEach((param: string) => { | ||||||
|  |       schema.push({ | ||||||
|  |         fieldName: `param_${param}`, | ||||||
|  |         label: `参数 ${param}`, | ||||||
|  |         component: 'Input', | ||||||
|  |         componentProps: { | ||||||
|  |           placeholder: `请输入参数 ${param}`, | ||||||
|  |         }, | ||||||
|  |         rules: 'required', | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |   return schema; | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <template> | ||||||
|  |   <Modal title="测试发送邮件"> | ||||||
|  |     <Form class="mx-4" /> | ||||||
|  |   </Modal> | ||||||
|  | </template> | ||||||
		Loading…
	
		Reference in New Issue
	
	 puhui999
						puhui999