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