From 2ac7af9fc842c976d1c28058a4b4d8cfbcc41291 Mon Sep 17 00:00:00 2001 From: dap1 <15891557205@163.com> Date: Tue, 13 Jun 2023 20:25:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=91=E9=80=81=E9=82=AE=E4=BB=B6?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E7=9A=84=E6=9F=A5=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enums/appEnum.ts | 3 +- src/views/system/mail/account/account.data.ts | 20 +++- src/views/system/mail/log/MailLogModal.vue | 30 +++++ src/views/system/mail/log/index.vue | 38 ++++++- src/views/system/mail/log/mailLog.data.ts | 106 ++++++++++++++++-- .../system/mail/template/SendMailModal.vue | 92 +++++++++------ src/views/system/mail/template/index.vue | 4 +- .../system/mail/template/template.data.ts | 44 ++++++-- 8 files changed, 270 insertions(+), 67 deletions(-) create mode 100644 src/views/system/mail/log/MailLogModal.vue diff --git a/src/enums/appEnum.ts b/src/enums/appEnum.ts index b7c40e2f..de2bbd64 100644 --- a/src/enums/appEnum.ts +++ b/src/enums/appEnum.ts @@ -69,5 +69,6 @@ export enum IconEnum { ADD_FOLD = 'ant-design:folder-add-outlined', LOG = 'ant-design:exception-outlined', PASSWORD = 'ant-design:key-outlined', - SETTING = 'ant-design:setting-outlined' + SETTING = 'ant-design:setting-outlined', + SEND = 'ant-design:send-outlined' } diff --git a/src/views/system/mail/account/account.data.ts b/src/views/system/mail/account/account.data.ts index 69364b51..e15919b7 100644 --- a/src/views/system/mail/account/account.data.ts +++ b/src/views/system/mail/account/account.data.ts @@ -71,7 +71,16 @@ export const formSchema: FormSchema[] = [ label: '邮箱', field: 'mail', required: true, - component: 'Input' + component: 'Input', + helpMessage: '填写发件邮箱地址', + rules: [ + { + required: true, + message: '请输入正确的邮箱地址', + pattern: /^\w{3,}(\.\w+)*@[A-z0-9]+(\.[A-z]{2,5}){1,2}$/, + trigger: 'blur' + } + ] }, { label: '用户名', @@ -80,10 +89,11 @@ export const formSchema: FormSchema[] = [ component: 'Input' }, { - label: '密码', + label: '密码/授权码', field: 'password', required: true, - component: 'InputPassword' + component: 'InputPassword', + helpMessage: '填写邮件密码, 部分邮件商需要填写授权码' }, { label: 'SMTP 服务器域名', @@ -95,14 +105,14 @@ export const formSchema: FormSchema[] = [ label: 'SMTP 服务器端口', field: 'port', required: true, - component: 'Input' + component: 'InputNumber' }, { label: '是否开启 SSL', field: 'sslEnable', required: true, defaultValue: false, - component: 'Switch', + component: 'RadioButtonGroup', componentProps: { options: getDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING, 'boolean') } diff --git a/src/views/system/mail/log/MailLogModal.vue b/src/views/system/mail/log/MailLogModal.vue new file mode 100644 index 00000000..f740230d --- /dev/null +++ b/src/views/system/mail/log/MailLogModal.vue @@ -0,0 +1,30 @@ +<template> + <BasicModal v-bind="$attrs" title="发送邮件详情" @register="registerModalInner" @ok="closeModal" width="800px"> + <Description @register="registerDescription" /> + </BasicModal> +</template> + +<script setup lang="ts"> +import { BasicModal, useModalInner } from '@/components/Modal' +import { Description, useDescription } from '@/components/Description/index' +import { ref } from 'vue' +import { logSchema } from './mailLog.data' + +defineOptions({ name: 'MailLogModal' }) + +const logData = ref() +const [registerModalInner, { closeModal }] = useModalInner((record: Recordable) => { + logData.value = record +}) + +const [registerDescription] = useDescription({ + column: 1, + schema: logSchema, + data: logData, + labelStyle: { + width: '100px' + } +}) +</script> + +<style scoped></style> diff --git a/src/views/system/mail/log/index.vue b/src/views/system/mail/log/index.vue index fd82ef2b..4652530b 100644 --- a/src/views/system/mail/log/index.vue +++ b/src/views/system/mail/log/index.vue @@ -1,15 +1,35 @@ <template> <div> - <BasicTable @register="registerTable" /> + <BasicTable @register="registerTable"> + <template #bodyCell="{ column, record }"> + <template v-if="column.key === 'action'"> + <TableAction + :actions="[ + { + icon: IconEnum.VIEW, + label: t('action.detail'), + onClick: handleShowInfo.bind(null, record) + } + ]" + /> + </template> + </template> + </BasicTable> + <MailLogModal @register="registerModal" /> </div> </template> <script lang="ts" setup> -import { BasicTable, useTable } from '@/components/Table' +import { useI18n } from '@/hooks/web/useI18n' +import { IconEnum } from '@/enums/appEnum' +import { BasicTable, useTable, TableAction } from '@/components/Table' import { getMailAccountPage } from '@/api/system/mail/log' import { columns, searchFormSchema } from './mailLog.data' +import { useModal } from '@/components/Modal' +import MailLogModal from './MailLogModal.vue' defineOptions({ name: 'SystemOperateLog' }) +const { t } = useI18n() const [registerTable] = useTable({ title: '邮件日志列表', api: getMailAccountPage, @@ -17,6 +37,18 @@ const [registerTable] = useTable({ formConfig: { labelWidth: 120, schemas: searchFormSchema }, useSearchForm: true, showTableSetting: true, - showIndexColumn: false + showIndexColumn: false, + actionColumn: { + width: 140, + title: t('common.action'), + dataIndex: 'action', + fixed: 'right' + } }) + +const [registerModal, { openModal }] = useModal() + +function handleShowInfo(record: Recordable) { + openModal(true, record) +} </script> diff --git a/src/views/system/mail/log/mailLog.data.ts b/src/views/system/mail/log/mailLog.data.ts index 7106cca1..65b901a5 100644 --- a/src/views/system/mail/log/mailLog.data.ts +++ b/src/views/system/mail/log/mailLog.data.ts @@ -1,6 +1,8 @@ import { BasicColumn, FormSchema, useRender } from '@/components/Table' import { DICT_TYPE, getDictOptions } from '@/utils/dict' import { getSimpleMailAccountList } from '@/api/system/mail/account' +import { DescItem } from '@/components/Description/index' +import { h } from 'vue' export const columns: BasicColumn[] = [ { @@ -8,14 +10,6 @@ export const columns: BasicColumn[] = [ dataIndex: 'id', width: 100 }, - { - title: '发送时间', - dataIndex: 'sendTime', - width: 180, - customRender: ({ text }) => { - return useRender.renderDate(text) - } - }, { title: '接收邮箱', dataIndex: 'toMail', @@ -42,7 +36,15 @@ export const columns: BasicColumn[] = [ { title: '模板编号', dataIndex: 'templateId', - width: 180 + width: 100 + }, + { + title: '发送时间', + dataIndex: 'sendTime', + width: 180, + customRender: ({ text }) => { + return useRender.renderDate(text) + } } ] @@ -101,3 +103,89 @@ export const searchFormSchema: FormSchema[] = [ colProps: { span: 8 } } ] + +export const logSchema: DescItem[] = [ + { + field: 'sendStatus', + label: '发送状态', + labelMinWidth: 80, + render(value) { + return useRender.renderDict(value, DICT_TYPE.SYSTEM_MAIL_SEND_STATUS) + } + }, + { + field: 'sendException', + label: '异常信息', + labelMinWidth: 80, + show: (data) => data && data.sendException && data.sendException.length > 0, + render(value) { + return h('span', { style: { fontWeight: 'bold' } }, value) + } + }, + { + field: 'sendTime', + label: '发送时间', + render(value) { + return useRender.renderDate(value) + } + }, + { + field: 'userId', + label: '用户类型', + render(_, data) { + const { userId, userType } = data + const uidTag = useRender.renderTag('uid: ' + userId) + const typeTag = useRender.renderDict(userType, DICT_TYPE.USER_TYPE) + return h('span', {}, [typeTag, uidTag]) + } + }, + { + field: 'fromMail', + label: '发件邮箱' + }, + { + field: 'toMail', + label: '收件邮箱' + }, + { + field: 'templateNickname', + label: '发件昵称' + }, + { + field: 'templateTitle', + label: '邮件标题' + }, + { + field: 'templateContent', + label: '邮件内容', + render(value) { + return h('div', { innerHTML: value }) + } + }, + { + field: 'templateParams', + label: '邮件参数', + render(value) { + return useRender.renderJsonPreview(value) + } + }, + { + field: 'sendMessageId', + label: '返回ID' + }, + { + field: 'templateCode', + label: '模板编码' + }, + { + field: 'templateId', + label: '模板编号' + }, + { + field: 'createTime', + label: '记录时间', + render(value) { + return useRender.renderDate(value) + } + } +] diff --git a/src/views/system/mail/template/SendMailModal.vue b/src/views/system/mail/template/SendMailModal.vue index b0716bff..3b7ab712 100644 --- a/src/views/system/mail/template/SendMailModal.vue +++ b/src/views/system/mail/template/SendMailModal.vue @@ -1,39 +1,42 @@ <template> - <BasicModal v-bind="$attrs" title="测试发送邮件" @register="innerRegister" @ok="submit"> - <BasicForm @register="register" :schemas="reactiveSchemas" /> + <BasicModal v-bind="$attrs" title="发送邮件" @register="innerRegister" @ok="submit" @cancel="resetForm" width="600px"> + <BasicForm @register="register" /> </BasicModal> </template> <script setup lang="ts"> import { BasicModal, useModalInner } from '@/components/Modal' import { BasicForm, FormSchema, useForm } from '@/components/Form' -import { reactive, ref } from 'vue' import { MailTemplate } from '@/api/system/mail/template' import { sendMail } from '@/api/system/mail/template' import { useMessage } from '@/hooks/web/useMessage' -import { baseSendSchemas } from './template.data' +import { baseSendSchemas, keyPrefix } from './template.data' defineOptions({ name: 'SendMailModal' }) -const { createMessage } = useMessage() -let reactiveSchemas: FormSchema[] = reactive([]) -const templateCode = ref<string>('') +const [register, { setFieldsValue, getFieldsValue, validateFields, resetFields, clearValidate, appendSchemaByField, removeSchemaByField }] = + useForm({ + labelWidth: 120, + schemas: baseSendSchemas, + baseColProps: { + span: 24 + }, + showSubmitButton: false, + showResetButton: false + }) -const [register, { setFieldsValue, getFieldsValue, validateFields, resetFields, clearValidate, setProps }] = useForm({ - labelWidth: 100, - baseColProps: { - span: 24 - }, - showSubmitButton: false, - showResetButton: false -}) +// 存储动态生成的字段信息 后续需要进行移除 +let dyFields: string[] = [] -const [innerRegister, { changeLoading, closeModal }] = useModalInner((data: MailTemplate) => { - resetForm() +const [innerRegister, { changeLoading, changeOkLoading, closeModal }] = useModalInner(async (data: MailTemplate) => { + // 打开时进行清空 + await resetForm() + const dyschemas: FormSchema[] = [] data.params.forEach((item) => { + // 这里加上前缀 防止和content/mail字段重名 + const field = keyPrefix + item const dySchema: FormSchema = { - // 这里加上前缀 防止和content/mail字段重名 - field: `key-${item}`, + field, label: `参数{${item}} `, component: 'Input', componentProps: { @@ -41,48 +44,63 @@ const [innerRegister, { changeLoading, closeModal }] = useModalInner((data: Mail }, required: true } - reactiveSchemas.push(dySchema) + dyschemas.push(dySchema) + dyFields.push(field) }) - const { content, code } = data - setFieldsValue({ content }) - templateCode.value = code + setFieldsValue(data) + // 添加动态参数到末尾 + appendSchemaByField(dyschemas, undefined) }) +function modalLoading(status: boolean) { + changeOkLoading(status) + changeLoading(status) +} + +/** + * 移除动态生成的表单元素 + */ +async function removeDySchemas() { + await removeSchemaByField(dyFields) + dyFields = [] +} + +const { createMessage } = useMessage() const submit = async () => { try { - setProps({ disabled: true }) - changeLoading(true) + modalLoading(true) await validateFields() const fields = getFieldsValue() const data = { mail: fields.mail, - templateCode: templateCode.value, + templateCode: fields.code, templateParams: {} } Object.keys(fields).forEach((key) => { - if (key === 'content' || key === 'mail') { + // 这几个是固定的字段 不用处理 + const fixedKeys = ['mail', 'code', 'content'] + if (fixedKeys.includes(key)) { return } - // 去掉 - 后的key - const realKey = key.split('-')[1] + // 去掉前缀后的key + const realKey = key.split(keyPrefix)[1] data.templateParams[realKey] = fields[key] }) await sendMail(data) createMessage.success(`发送邮件到[${fields.mail}]成功`) closeModal() + } catch (e) { } finally { - setProps({ disabled: false }) - changeLoading(false) + modalLoading(false) } } -const resetForm = () => { - // 这里需要每次清空动态表单 - reactiveSchemas.splice(0, reactiveSchemas.length) - reactiveSchemas.push(...baseSendSchemas) +async function resetForm() { + // 这里需要清空动态表单 + await removeDySchemas() // 清除上一次的表单校验和参数 - resetFields() - clearValidate() + await resetFields() + await clearValidate() } </script> diff --git a/src/views/system/mail/template/index.vue b/src/views/system/mail/template/index.vue index 67bd9e72..4a701bd7 100644 --- a/src/views/system/mail/template/index.vue +++ b/src/views/system/mail/template/index.vue @@ -11,8 +11,8 @@ <TableAction :actions="[ { - icon: IconEnum.EDIT, - label: t('action.test'), + icon: IconEnum.SEND, + label: t('action.send'), auth: 'system:mail-template:send-mail', onClick: handleSend.bind(null, record) }, diff --git a/src/views/system/mail/template/template.data.ts b/src/views/system/mail/template/template.data.ts index 8d750529..10a35fd7 100644 --- a/src/views/system/mail/template/template.data.ts +++ b/src/views/system/mail/template/template.data.ts @@ -1,6 +1,8 @@ import { getSimpleMailAccountList } from '@/api/system/mail/account' import { BasicColumn, FormSchema, useRender } from '@/components/Table' import { DICT_TYPE, getDictOptions } from '@/utils/dict' +import { ScrollContainer } from '@/components/Container' +import { h } from 'vue' export const columns: BasicColumn[] = [ { @@ -65,7 +67,7 @@ export const searchFormSchema: FormSchema[] = [ colProps: { span: 8 } }, { - label: '邮箱账号', + label: '发件邮箱', field: 'accountId', component: 'ApiSelect', componentProps: { @@ -112,10 +114,11 @@ export const formSchema: FormSchema[] = [ label: '模板编码', field: 'code', required: true, - component: 'Input' + component: 'Input', + helpMessage: '建议使用下划线/数字/字母命名' }, { - label: '邮箱账号', + label: '发件邮箱', field: 'accountId', required: true, component: 'ApiSelect', @@ -132,19 +135,22 @@ export const formSchema: FormSchema[] = [ label: '发送人名称', field: 'nickname', required: true, - component: 'Input' + component: 'Input', + helpMessage: '发件人的名称, 如:系统发件人' }, { label: '模板标题', field: 'title', required: true, - component: 'Input' + component: 'Input', + helpMessage: '邮件的标题' }, { label: '模板内容', field: 'content', component: 'Editor', - required: true + required: true, + helpMessage: '{}括号中的内容作为模板参数' }, { label: '开启状态', @@ -163,17 +169,35 @@ export const formSchema: FormSchema[] = [ ] // 发送邮件 + +// 这里加上前缀 防止和表单其他字段重名 +export const keyPrefix = 'key$-' export const baseSendSchemas: FormSchema[] = [ + { + field: 'code', + label: '编码', + component: 'Input', + show: () => false + }, { field: 'content', component: 'Editor', label: '模板内容 ', required: false, defaultValue: '', - componentProps: { - options: { - readonly: true - } + render({ model }) { + let content: string = model.content + Object.keys(model).forEach((key) => { + if (!key.startsWith(keyPrefix)) { + return + } + const realKey = key.split(keyPrefix)[1] + content = content.replace(`{${realKey}}`, model[key]) + }) + return h(ScrollContainer, { + innerHTML: content, + style: { border: '1px solid #e8e8e8', borderRadius: '4px', padding: '10px' } + }) } }, {