You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

467 lines
16 KiB

import {useTranslation} from '@/i18n.ts'
import {Badge, Button, Divider, Form, Input, Select, Space, Table, TableColumnsType, Tooltip} from 'antd'
import {useAtom, useAtomValue} from 'jotai'
import React, {useEffect, useMemo, useState} from 'react'
import {BetaSchemaForm, ProColumns, ProFormColumnsType,} from '@ant-design/pro-components'
import ListPageLayout from '@/layout/ListPageLayout.tsx'
import {useStyle} from './style.ts'
import {FilterOutlined} from '@ant-design/icons'
import {getValueCount, unSetColumnRules} from '@/utils'
import {Table as ProTable} from '@/components/table'
import {msgListAtom, msgSearchAtom, saveMsgAtom} from "@/store/message/my.ts";
import {templateAllListAtom} from "@/store/message/template.ts";
import {coverType, IMsgTemplate} from "@/types/message/template.ts";
import dayjs from "dayjs";
const i18nPrefix = 'msgMy.list'
const MdwMessage = () => {
const {styles} = useStyle()
const {t} = useTranslation()
const [form] = Form.useForm()
const [filterForm] = Form.useForm()
const {mutate: saveOrUpdate, isPending: isSubmitting, isSuccess} = useAtomValue(saveMsgAtom)
const [search, setSearch] = useAtom(msgSearchAtom)
const {data, isFetching, isLoading, refetch} = useAtomValue(msgListAtom)
const {data: templateList} = useAtomValue(templateAllListAtom)
const [open, setOpen] = useState(false)
const [openFilter, setFilterOpen] = useState(false)
const [currentTemplate, setCurrentTemplate] = useState<IMsgTemplate>()
const [searchKey, setSearchKey] = useState(search?.title)
const [templateType, setTemplateType] = useState('')
const templateChange = (index: number) => {
if (templateList && index !== undefined) {
// key转换
const result = templateList[index].fields.split(',').map(item => {
return {
field_key: item,
field_value: ''
};
});
setCurrentTemplate(templateList[index])
form.setFieldsValue({...templateList[index], 'fieldList': result})
setTemplateType(templateList[index].type)
} else {
form.resetFields()
setTemplateType('')
setCurrentTemplate(undefined)
}
}
const handleInputChange = (index, e) => {
form.getFieldValue("fieldList")[index].field_value = e.target.value
}
const drawerColumns = useMemo(() => {
return [
{
title: 'ID',
dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
formItemProps: {hidden: true}
},
{
title: t(`${i18nPrefix}.columns.template`, '选择模板'),
valueType: 'select',
fieldProps: {
allowClear: true,
},
renderFormItem: () => {
return <Select onChange={templateChange}>
{
templateList?.map((template, index) => (
<Select.Option key={index} value={index}>
{template.name}
</Select.Option>
))
}
</Select>
},
},
{
title: t(`${i18nPrefix}.columns.type`, '通道类型'),
dataIndex: 'type',
valueType: 'select',
fieldProps: {
options: [
{label: '短信', value: 'SMS'},
{label: '邮件', value: 'EMAIL'},
{label: 'Telegram', value: 'TG'}
],
allowClear: false,
disabled: true,
},
formItemProps: {
rules: [
{
required: true,
}
]
}
},
{
title: t(`${i18nPrefix}.columns.token`, 'Telegram BOT API Token'),
dataIndex: 'token',
valueType: 'text',
fieldProps: {
placeholder: "请输入 @BotFather 获取到的BOT的API Token"
},
formItemProps: {
hidden: templateType != 'TG',
rules: [
{
required: templateType == 'TG',
}
]
},
},
{
title: t(`${i18nPrefix}.columns.email_from`, '发件人邮箱'),
dataIndex: 'email_from',
valueType: 'text',
fieldProps: {
maxLength: 100,
showCount: true,
},
formItemProps: {
hidden: templateType != 'EMAIL',
rules: [
{
required: templateType == 'EMAIL',
message: t('message.required', '模板内容必填')
}
]
},
},
{
title: t(`${i18nPrefix}.columns.title`, '标题'),
dataIndex: 'title',
valueType: 'text',
fieldProps: {
maxLength: 100,
showCount: true,
disabled: true,
},
formItemProps: {
hidden: templateType != 'EMAIL',
rules: [
{
required: templateType == 'EMAIL',
message: t('message.required', '模板内容必填')
}
]
},
},
{
title: t(`${i18nPrefix}.columns.content`, '正文'),
dataIndex: 'content',
valueType: 'textarea',
fieldProps: {
maxLength: 1000,
showCount: true,
rows: 15,
disabled: true,
},
formItemProps: {
rules: [
{
required: true,
message: t('message.required', '内容必填')
}
]
}
},
{
title: t(`${i18nPrefix}.columns.fieldList`, '填充变量'),
dataIndex: 'fieldList',
formItemProps: {
hidden: currentTemplate === undefined,
},
renderFormItem: (_, config) => {
return (
<>
{
config.value?.map((variable, index) => (
<div key={index} style={{marginBottom: 8}}>
<Input
addonBefore={variable.field_key}
onChange={(e) => handleInputChange(index, e)}
/>
</div>
))
}
</>
);
},
},
{
title: t(`${i18nPrefix}.columns.dest`, '收件人(多个收件人用英文逗号隔开;如果类型是Telegram,请填写User ID或Chat ID)'),
dataIndex: 'dest',
valueType: 'textarea',
fieldProps: {
maxLength: 1000,
showCount: true,
rows: 5,
placeholder: 'aaa@qq.com,bbb@gmail.com,-1001953214222,-1001953214333',
},
formItemProps: {
rules: [
{
required: true,
message: t('message.required', '内容必填')
}
]
}
},
] as ProColumns[]
}, [search, templateType, templateList])
const columns = useMemo(() => {
return [
{
title: 'ID',
dataIndex: 'id',
hideInTable: true,
hideInSearch: true,
formItemProps: {hidden: true}
},
{
title: t(`${i18nPrefix}.columns.type`, '类型'),
dataIndex: 'type',
render: (_, record) => {
return <div>{coverType(record.type)}</div>
}
},
{
title: t(`${i18nPrefix}.columns.title`, '标题'),
dataIndex: 'title',
},
{
title: t(`${i18nPrefix}.columns.content`, '正文'),
dataIndex: 'content',
},
{
title: t(`${i18nPrefix}.columns.send_at`, '预计发送时间'),
dataIndex: 'send_at',
render: (_, record) => {
return <div>{record.send_at == 0 ? "立即" : dayjs(record.send_at).format('YYYY-MM-DD HH:mm:ss')}</div>
}
},
] as ProColumns[]
}, [search, currentTemplate])
const expandedRowRender = (record, index) => {
const expandedColumns: TableColumnsType = [
{
title: "收件人",
dataIndex: 'dest',
},
{
title: t(`${i18nPrefix}.columns.status`, '状态'),
dataIndex: 'status',
render: (_text, record) => {
return <Badge
status={['default', 'success', 'error'][record.status] as any}
text={['未处理', '发送成功', '发送失败'][record.status]}/>
}
},
{
title: t(`${i18nPrefix}.columns.send_at`, '发送时间'),
dataIndex: 'send_at',
render: (_, record) => {
return <div>{record.send_at == 0 ? 0 : dayjs(record.send_at * 1000).format('YYYY-MM-DD HH:mm:ss')}</div>
}
},
];
return <Table columns={expandedColumns} dataSource={record.dest} pagination={false}/>;
}
useEffect(() => {
setSearchKey(search?.title)
filterForm.setFieldsValue(search)
}, [search])
useEffect(() => {
if (isSuccess) {
setOpen(false)
}
}, [isSuccess])
return (
<ListPageLayout className={styles.container}>
<ProTable
rowKey="id"
headerTitle={t(`${i18nPrefix}.title`, '消息管理')}
toolbar={{
search: {
loading: isFetching && !!search?.title,
onSearch: (value: string) => {
setSearch(prev => ({
...prev,
title: value
}))
},
allowClear: true,
onChange: (e) => {
setSearchKey(e.target?.value)
},
value: searchKey,
placeholder: t(`${i18nPrefix}.placeholder`, '输入模板名称')
},
actions: [
<Tooltip key={'filter'} title={t(`${i18nPrefix}.filter.tooltip`, '高级查询')}>
<Badge count={getValueCount(search)}>
<Button
onClick={() => {
setFilterOpen(true)
}}
icon={<FilterOutlined/>} shape={'circle'} size={'small'}/>
</Badge>
</Tooltip>,
<Divider type={'vertical'} key={'divider'}/>,
<Button key={'add'}
onClick={() => {
form.resetFields()
form.setFieldsValue({
id: 0,
})
setOpen(true)
}}
type={'primary'}>{t(`${i18nPrefix}.add`, '发送消息')}</Button>
]
}}
scroll={{
x: columns.length * 200,
y: 'calc(100vh - 290px)'
}}
search={false}
dateFormatter="string"
loading={isLoading || isFetching}
dataSource={data?.rows ?? []}
columns={columns}
options={{
reload: () => {
refetch()
},
}}
pagination={{
total: data?.total,
pageSize: search.pageSize,
current: search.page,
onShowSizeChange: (current: number, size: number) => {
setSearch({
...search,
pageSize: size,
page: current
})
},
onChange: (current, pageSize) => {
setSearch(prev => {
return {
...prev,
page: current,
pageSize: pageSize,
}
})
},
}}
expandable={{
expandedRowRender,
}}
/>
<BetaSchemaForm
grid={true}
shouldUpdate={false}
width={1000}
form={form}
layout={'vertical'}
scrollToFirstError={true}
title={t(`${i18nPrefix}.title_add}`, '发送消息')}
layoutType={'DrawerForm'}
open={open}
drawerProps={{
maskClosable: false,
}}
onOpenChange={(open) => {
setOpen(open)
}}
loading={isSubmitting}
onValuesChange={() => {
}}
onFinish={async (values) => {
saveOrUpdate({...values, "code": currentTemplate?.code})
}}
columns={drawerColumns as ProFormColumnsType[]}/>
<BetaSchemaForm
title={t(`${i18nPrefix}.filter.title`, '模板高级查询')}
grid={true}
shouldUpdate={false}
width={500}
form={filterForm}
open={openFilter}
onOpenChange={open => {
setFilterOpen(open)
}}
layout={'vertical'}
scrollToFirstError={true}
layoutType={'DrawerForm'}
drawerProps={{
maskClosable: false,
mask: false,
}}
submitter={{
searchConfig: {
resetText: t(`${i18nPrefix}.filter.reset`, '清空'),
submitText: t(`${i18nPrefix}.filter.submit`, '查询'),
},
onReset: () => {
filterForm.resetFields()
},
render: (props,) => {
return (
<div style={{textAlign: 'right'}}>
<Space>
<Button onClick={() => {
props.reset()
}}>{props.searchConfig?.resetText}</Button>
<Button type="primary"
onClick={() => {
props.submit()
}}
>{props.searchConfig?.submitText}</Button>
</Space>
</div>
)
},
}}
onValuesChange={() => {
}}
onFinish={async (values) => {
//处理,变成数组
Object.keys(values).forEach(key => {
if (typeof values[key] === 'string' && values[key].includes(',')) {
values[key] = values[key].split(',')
}
})
setSearch(values)
}}
columns={unSetColumnRules(columns.filter(item => !item.hideInSearch)) as ProFormColumnsType[]}/>
</ListPageLayout>
)
}
export default MdwMessage