Browse Source

!23 feat: 发送邮件日志的查看功能

Merge pull request !23 from 二次元虎哥/master
main
xingyu 2 years ago committed by Gitee
parent
commit
25fa7e21ae
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
  1. 3
      src/enums/appEnum.ts
  2. 20
      src/views/system/mail/account/account.data.ts
  3. 30
      src/views/system/mail/log/MailLogModal.vue
  4. 38
      src/views/system/mail/log/index.vue
  5. 106
      src/views/system/mail/log/mailLog.data.ts
  6. 92
      src/views/system/mail/template/SendMailModal.vue
  7. 4
      src/views/system/mail/template/index.vue
  8. 44
      src/views/system/mail/template/template.data.ts
  9. 2
      src/views/system/sms/log/smsLog.data.ts

3
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'
}

20
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')
}

30
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>

38
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>

106
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)
}
}
]

92
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>

4
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)
},

44
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' }
})
}
},
{

2
src/views/system/sms/log/smsLog.data.ts

@ -84,7 +84,7 @@ export const searchFormSchema: FormSchema[] = [
field: 'channelId',
component: 'ApiSelect',
componentProps: {
options: getSimpleSmsChannels(),
api: getSimpleSmsChannels,
fieldNames: {
label: 'signature',
key: 'id',