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.
441 lines
15 KiB
441 lines
15 KiB
import {useTranslation} from '@/i18n.ts'
|
|
import {Badge, Button, Divider, Form, Popconfirm, Space, Tag, Tooltip} from 'antd'
|
|
import {useAtom, useAtomValue} from 'jotai'
|
|
import React, {useEffect, useMemo, useState} from 'react'
|
|
import Action from '@/components/action/Action.tsx'
|
|
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 {
|
|
deleteTemplateAtom,
|
|
saveOrUpdateTemplateAtom,
|
|
templateAtom,
|
|
templateListAtom,
|
|
templateSearchAtom
|
|
} from "@/store/message/template.ts";
|
|
import {coverType} from "@/types/message/template.ts";
|
|
|
|
const i18nPrefix = 'mdwMessage.list'
|
|
|
|
const MdwMessage = () => {
|
|
|
|
const {styles, cx} = useStyle()
|
|
const {t} = useTranslation()
|
|
const [form] = Form.useForm()
|
|
const [filterForm] = Form.useForm()
|
|
const {mutate: saveOrUpdate, isPending: isSubmitting, isSuccess} = useAtomValue(saveOrUpdateTemplateAtom)
|
|
const [search, setSearch] = useAtom(templateSearchAtom)
|
|
const [currentTemplate, setAppPackage] = useAtom(templateAtom)
|
|
const {data, isFetching, isLoading, refetch} = useAtomValue(templateListAtom)
|
|
const {mutate: deleteAppPackage, isPending: isDeleting} = useAtomValue(deleteTemplateAtom)
|
|
|
|
const [open, setOpen] = useState(false)
|
|
const [openFilter, setFilterOpen] = useState(false)
|
|
const [searchKey, setSearchKey] = useState(search?.title)
|
|
|
|
const [templateField, setTemplateField] = useState<string[]>([]);
|
|
|
|
const [templateTitle, setTemplateTitle] = useState('');
|
|
const [templateContent, setTemplateContent] = useState('');
|
|
|
|
const [templateType, setTemplateType] = useState('')
|
|
useEffect(() => {
|
|
setTemplateType(currentTemplate?.type)
|
|
|
|
if (form.getFieldValue('id') === 0) {
|
|
setTemplateField(['name']);
|
|
} else {
|
|
setTemplateField(currentTemplate?.fields.split(","));
|
|
}
|
|
}, [open]);
|
|
|
|
const handleChange = () => {
|
|
// 使用正则表达式匹配 ${var} 格式的变量
|
|
const regex = /\${\.([a-zA-Z0-9_]+)}/g;
|
|
const matches = [...(templateTitle + templateContent).matchAll(regex)];
|
|
|
|
// 提取变量名
|
|
const variables = Array.from(new Set(matches.map(match => match[1])));
|
|
setTemplateField(variables);
|
|
};
|
|
|
|
useEffect(() => {
|
|
handleChange()
|
|
}, [templateTitle, templateContent]);
|
|
|
|
const handleContentChange = (e) => {
|
|
const value = e.target.value;
|
|
setTemplateContent(value)
|
|
};
|
|
|
|
const titheHandleContentChange = (e) => {
|
|
const value = e.target.value;
|
|
setTemplateTitle(value)
|
|
};
|
|
|
|
const typeHandlerChange = (value) => {
|
|
if (value !== 'EMAIL') {
|
|
setTemplateTitle('')
|
|
form.setFieldsValue({'title': undefined})
|
|
}
|
|
setTemplateType(value)
|
|
}
|
|
|
|
const drawerColumns = useMemo(() => {
|
|
return [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
hideInTable: true,
|
|
hideInSearch: true,
|
|
formItemProps: {hidden: true}
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.name`, '模板名称'),
|
|
dataIndex: 'name',
|
|
valueType: 'text',
|
|
fieldProps: {
|
|
maxLength: 50,
|
|
showCount: true,
|
|
},
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
message: t('message.required', '模板名称必填')
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.type`, '模板类型'),
|
|
dataIndex: 'type',
|
|
valueType: 'select',
|
|
fieldProps: {
|
|
options: [
|
|
{label: '短信', value: 'SMS'},
|
|
{label: '邮件', value: 'EMAIL'},
|
|
{label: 'Telegram', value: 'TG'}
|
|
],
|
|
allowClear: false,
|
|
onChange: typeHandlerChange
|
|
},
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.title`, '模板标题'),
|
|
dataIndex: 'title',
|
|
valueType: 'text',
|
|
fieldProps: {
|
|
maxLength: 100,
|
|
showCount: true,
|
|
onChange: titheHandleContentChange, // 监听输入事件
|
|
},
|
|
formItemProps: {
|
|
tooltip: '支持邮件类型',
|
|
hidden: templateType != 'EMAIL',
|
|
rules: [
|
|
{
|
|
required: templateType == 'EMAIL',
|
|
message: t('message.required', '模板内容必填')
|
|
}
|
|
]
|
|
},
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.content`, '模板内容'),
|
|
dataIndex: 'content',
|
|
valueType: 'textarea',
|
|
fieldProps: {
|
|
defaultValue: "你好,我叫${.name}",
|
|
maxLength: 1000,
|
|
showCount: true,
|
|
rows: 15,
|
|
onChange: handleContentChange, // 监听输入事件
|
|
},
|
|
formItemProps: {
|
|
rules: [
|
|
{
|
|
required: true,
|
|
message: t('message.required', '模板内容必填')
|
|
}
|
|
]
|
|
}
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.fields`, '识别到的变量'),
|
|
dataIndex: 'fields',
|
|
renderFormItem: () => {
|
|
return (
|
|
<>
|
|
{
|
|
templateField.map((variable, index) => (
|
|
<Tag key={index} color="blue" style={{marginRight: 8}}>
|
|
{variable}
|
|
</Tag>
|
|
))
|
|
}
|
|
</>
|
|
);
|
|
}
|
|
},
|
|
] as ProColumns[]
|
|
}, [isDeleting, currentTemplate, search, templateField, templateType])
|
|
|
|
const columns = useMemo(() => {
|
|
return [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
hideInTable: true,
|
|
hideInSearch: true,
|
|
formItemProps: {hidden: true}
|
|
},
|
|
{
|
|
title: t(`${i18nPrefix}.columns.name`, '模板名称'),
|
|
dataIndex: 'name',
|
|
},
|
|
{
|
|
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.option`, '操作'),
|
|
key: 'option',
|
|
valueType: 'option',
|
|
fixed: 'right',
|
|
render: (_, record) => [
|
|
<Action key="edit"
|
|
as={'a'}
|
|
onClick={() => {
|
|
form.setFieldsValue(record)
|
|
setOpen(true)
|
|
}}>{t('actions.edit')}</Action>,
|
|
<Divider type={'vertical'}/>,
|
|
<Popconfirm
|
|
key={'del_confirm'}
|
|
disabled={isDeleting}
|
|
onConfirm={() => {
|
|
deleteAppPackage(record.id)
|
|
}}
|
|
title={t('message.deleteConfirm')}>
|
|
<a key="del">
|
|
{t('actions.delete', '删除')}
|
|
</a>
|
|
</Popconfirm>,
|
|
]
|
|
}
|
|
] as ProColumns[]
|
|
}, [isDeleting, currentTemplate, search])
|
|
|
|
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}
|
|
onRow={(record) => {
|
|
return {
|
|
className: cx({
|
|
// 'ant-table-row-selected': currentAppPackage?.id === record.id
|
|
}),
|
|
onClick: () => {
|
|
setAppPackage(record)
|
|
}
|
|
}
|
|
}}
|
|
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,
|
|
}
|
|
})
|
|
},
|
|
}}
|
|
/>
|
|
<BetaSchemaForm
|
|
grid={true}
|
|
shouldUpdate={false}
|
|
width={1000}
|
|
form={form}
|
|
layout={'vertical'}
|
|
scrollToFirstError={true}
|
|
title={t(`${i18nPrefix}.title_${form.getFieldValue('id') !== 0 ? 'edit' : 'add'}`, form.getFieldValue('id') !== 0 ? '模板编辑' : '模板添加')}
|
|
layoutType={'DrawerForm'}
|
|
open={open}
|
|
drawerProps={{
|
|
maskClosable: false,
|
|
}}
|
|
onOpenChange={(open) => {
|
|
setOpen(open)
|
|
}}
|
|
loading={isSubmitting}
|
|
onValuesChange={() => {
|
|
|
|
}}
|
|
onFinish={async (values) => {
|
|
saveOrUpdate({...values, 'fields': templateField.join()})
|
|
}}
|
|
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
|