diff --git a/src/global.d.ts b/src/global.d.ts index 44baa73..dedf0fe 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -33,6 +33,14 @@ export type IPageResult = IPage & { total: number } +export type IListAllResult = { + list: T[] +} + +export type IBoolResult = { + success: boolean +} + export type IApiResult = { code: number; data: T; diff --git a/src/pages/message/my/index.tsx b/src/pages/message/my/index.tsx new file mode 100644 index 0000000..f1f4080 --- /dev/null +++ b/src/pages/message/my/index.tsx @@ -0,0 +1,476 @@ +import {useTranslation} from '@/i18n.ts' +import {Badge, Button, Divider, Form, Input, Select, Space, 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"; + +const i18nPrefix = 'msgMy.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(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() + const [searchKey, setSearchKey] = useState(search?.title) + + const [templateType, setTemplateType] = useState('') + const typeHandlerChange = (value: string) => { + if (value !== 'EMAIL') { + setTemplateTitle('') + form.setFieldsValue({'title': undefined}) + } + setTemplateType(value) + } + + 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) + setTemplateTitle(templateList[index].title) + setTemplateContent(templateList[index].content) + } else { + form.resetFields() + setTemplateType('') + setCurrentTemplate(undefined) + } + } + + const [templateTitle, setTemplateTitle] = useState(''); + const [templateContent, setTemplateContent] = useState(''); + + 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 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]))); + const result = variables.map(item => { + return { + field_key: item, + field_value: '' + }; + }); + form.setFieldsValue({'fieldList': result}) + }; + + 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 + } + }, + { + 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: { + maxLength: 1000, + showCount: true, + rows: 15, + onChange: handleContentChange, + }, + 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) => ( +
+ handleInputChange(index, e)} + /> +
+ )) + } + + ); + }, + }, + { + title: t(`${i18nPrefix}.columns.dest`, '收件人(多个收件人用英文逗号隔开,如果类型是TG,则填token)'), + dataIndex: 'dest', + valueType: 'textarea', + fieldProps: { + maxLength: 1000, + showCount: true, + rows: 5, + placeholder: 'aaa@qq.com,bbb@gmail.com', + }, + 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
{coverType(record.type)}
+ } + }, + { + title: t(`${i18nPrefix}.columns.title`, '标题'), + dataIndex: 'title', + }, + { + title: t(`${i18nPrefix}.columns.content`, '正文'), + dataIndex: 'content', + }, + { + title: t(`${i18nPrefix}.columns.dest`, '收件人'), + dataIndex: 'dest', + }, + { + title: t(`${i18nPrefix}.columns.status`, '状态'), + dataIndex: 'status', + render: (_text, record) => { + return + } + }, + { + title: t(`${i18nPrefix}.columns.send_at`, '发送时间'), + dataIndex: 'send_at', + }, + // { + // title: t(`${i18nPrefix}.columns.option`, '操作'), + // key: 'option', + // valueType: 'option', + // fixed: 'right', + // render: (_, record) => [ + // { + // deleteAppPackage(record.id) + // }} + // title={t('message.deleteConfirm')}> + // + // {t('actions.delete', '删除')} + // + // , + // ] + // } + ] as ProColumns[] + }, [search, currentTemplate]) + + useEffect(() => { + + setSearchKey(search?.title) + filterForm.setFieldsValue(search) + + }, [search]) + + useEffect(() => { + if (isSuccess) { + setOpen(false) + } + }, [isSuccess]) + + return ( + + { + setSearch(prev => ({ + ...prev, + title: value + })) + }, + allowClear: true, + onChange: (e) => { + setSearchKey(e.target?.value) + }, + value: searchKey, + placeholder: t(`${i18nPrefix}.placeholder`, '输入模板名称') + }, + actions: [ + + + + ] + }} + 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, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + onValuesChange={() => { + + }} + onFinish={async (values) => { + saveOrUpdate({...values, "template_id": currentTemplate?.id}) + }} + columns={drawerColumns as ProFormColumnsType[]}/> + { + 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 ( +
+ + + + +
+ ) + }, + + }} + 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[]}/> +
+ ) +} + +export default MdwMessage \ No newline at end of file diff --git a/src/pages/system/message/style.ts b/src/pages/message/my/style.ts similarity index 100% rename from src/pages/system/message/style.ts rename to src/pages/message/my/style.ts diff --git a/src/pages/system/message/index.tsx b/src/pages/message/template/index.tsx similarity index 90% rename from src/pages/system/message/index.tsx rename to src/pages/message/template/index.tsx index 327c0e2..c481efc 100644 --- a/src/pages/system/message/index.tsx +++ b/src/pages/message/template/index.tsx @@ -5,7 +5,7 @@ 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' +import {useStyle} from './style.ts' import {FilterOutlined} from '@ant-design/icons' import {getValueCount, unSetColumnRules} from '@/utils' import {Table as ProTable} from '@/components/table' @@ -15,7 +15,8 @@ import { templateAtom, templateListAtom, templateSearchAtom -} from "@/store/system/message.ts"; +} from "@/store/message/template.ts"; +import {coverType} from "@/types/message/template.ts"; const i18nPrefix = 'mdwMessage.list' @@ -35,57 +36,50 @@ const MdwMessage = () => { const [openFilter, setFilterOpen] = useState(false) const [searchKey, setSearchKey] = useState(search?.title) - const [templateField, setTemplateField] = useState(['name']); - const [titleTemplateField, setTitleTemplateField] = useState([]); + const [templateField, setTemplateField] = useState([]); - const [allTemplateField, setAllTemplateField] = useState([]); - - useEffect(() => { - const combinedFields = [...templateField, ...titleTemplateField]; - const uniqueFields = Array.from(new Set(combinedFields)); - setAllTemplateField(uniqueFields); - }, [templateField, titleTemplateField]); + const [templateTitle, setTemplateTitle] = useState(''); + const [templateContent, setTemplateContent] = useState(''); const [templateType, setTemplateType] = useState('') useEffect(() => { setTemplateType(currentTemplate?.type) - }, [open]); - const handleContentChange = (e) => { - const value = e.target.value; + 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 = [...value.matchAll(regex)]; + const matches = [...(templateTitle + templateContent).matchAll(regex)]; // 提取变量名 const variables = Array.from(new Set(matches.map(match => match[1]))); - - // 更新变量状态 setTemplateField(variables); }; - const titheHandleContentChange = (e) => { - const value = e.target.value; - - // 使用正则表达式匹配 ${var} 格式的变量 - const regex = /\${\.([a-zA-Z0-9_]+)}/g; - const matches = [...value.matchAll(regex)]; - - // 提取变量名 - const variables = Array.from(new Set(matches.map(match => match[1]))); + useEffect(() => { + handleChange() + }, [templateTitle, templateContent]); - // 更新变量状态 - setTitleTemplateField(variables); + const handleContentChange = (e) => { + const value = e.target.value; + setTemplateContent(value) + }; + const titheHandleContentChange = (e) => { + const value = e.target.value; + setTemplateTitle(value) }; const typeHandlerChange = (value) => { - console.log(value) setTemplateType(value) } - const drawerColumns = useMemo(() => { return [ { @@ -174,13 +168,13 @@ const MdwMessage = () => { } }, { - title: t(`${i18nPrefix}.columns.field`, '识别到的变量'), - dataIndex: 'field', + title: t(`${i18nPrefix}.columns.fields`, '识别到的变量'), + dataIndex: 'fields', renderFormItem: () => { return ( <> { - allTemplateField.map((variable, index) => ( + templateField.map((variable, index) => ( {variable} @@ -202,7 +196,7 @@ const MdwMessage = () => { }, }, ] as ProColumns[] - }, [isDeleting, currentTemplate, search, allTemplateField, templateType]) + }, [isDeleting, currentTemplate, search, templateField, templateType]) const columns = useMemo(() => { return [ @@ -218,6 +212,13 @@ const MdwMessage = () => { dataIndex: 'name', }, { + title: t(`${i18nPrefix}.columns.type`, '模板类型'), + dataIndex: 'type', + render: (_, record) => { + return
{coverType(record.type)}
+ } + }, + { title: t(`${i18nPrefix}.columns.title`, '模板标题'), dataIndex: 'title', }, @@ -249,13 +250,6 @@ const MdwMessage = () => { {t('actions.delete', '删除')} , - , - { - form.setFieldsValue(record) - setOpen(true) - }}>{t('actions.sendMsg', '发送消息')}, ] } ] as ProColumns[] @@ -384,7 +378,7 @@ const MdwMessage = () => { }} onFinish={async (values) => { - saveOrUpdate({...values, 'fields': allTemplateField}) + saveOrUpdate({...values, 'fields': templateField.join()}) }} columns={drawerColumns as ProFormColumnsType[]}/> { + const prefix = `${prefixCls}-${token?.proPrefix}-appPackage-list-page` + + const container = css` + .ant-table-cell{ + .ant-tag{ + padding-inline: 3px; + margin-inline-end: 3px; + } + } + .ant-table-empty { + .ant-table-body{ + height: calc(100vh - 350px) + } + } + .ant-pro-table-highlight{ + + } + ` + + return { + container: cx(prefix, props?.className, container), + } +}) \ No newline at end of file diff --git a/src/service/message/message.ts b/src/service/message/message.ts deleted file mode 100644 index 0e9a05c..0000000 --- a/src/service/message/message.ts +++ /dev/null @@ -1,17 +0,0 @@ -import request from '@/request.ts' -import { IApiResult, IPageResult } from '@/global' -import { IMsgFieldRes, IMsgTemplate } from '@/types/system/message.ts' -import { createCURD } from '@/service/base.ts' - -const mdwMessage = { - ...createCURD('/mdw-msg/template'), - list: async (params: any) => { - return await request.get>(`mdw-msg/template/list`, { ...params }) - }, - - fieldList: async (params: any) => { - return await request.get>(`mdw-msg/template/fieldList`, { ...params }) - }, -} - -export default mdwMessage \ No newline at end of file diff --git a/src/service/message/my.ts b/src/service/message/my.ts new file mode 100644 index 0000000..70962bf --- /dev/null +++ b/src/service/message/my.ts @@ -0,0 +1,13 @@ +import request from '@/request.ts' +import { IPageResult } from '@/global' +import { createCURD } from '@/service/base.ts' +import { IMsgMy } from '@/types/message/my.ts' + +const messageMy = { + ...createCURD('/mdw-msg/msg'), + list: async (params: any) => { + return await request.get>(`mdw-msg/msg/list`, { ...params }) + }, +} + +export default messageMy \ No newline at end of file diff --git a/src/service/message/template.ts b/src/service/message/template.ts new file mode 100644 index 0000000..3c5132b --- /dev/null +++ b/src/service/message/template.ts @@ -0,0 +1,17 @@ +import request from '@/request.ts' +import { IListAllResult, IPageResult } from '@/global' +import { IMsgTemplate } from '@/types/message/template.ts' +import { createCURD } from '@/service/base.ts' + +const messageTemplate = { + ...createCURD('/mdw-msg/template'), + list: async (params: any) => { + return await request.get>(`mdw-msg/template/list`, { ...params }) + }, + + listAll: async () => { + return await request.get>(`mdw-msg/template/listAll`) + }, +} + +export default messageTemplate \ No newline at end of file diff --git a/src/store/message/my.ts b/src/store/message/my.ts new file mode 100644 index 0000000..4a62ded --- /dev/null +++ b/src/store/message/my.ts @@ -0,0 +1,68 @@ +import { atom } from 'jotai/index' +import { IApiResult, IPage } from '@/global' +import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' +import { message } from 'antd' +import { t } from 'i18next' +import { IMsgMy } from '@/types/message/my.ts' +import messageMy from '@/service/message/my.ts' + +type SearchParams = IPage & { + key?: string + + [key: string]: any +} + +export const msgIdsAtom = atom(0) + +export const msgSearchAtom = atom({ + key: '', + pageSize: 10, + page: 1, +} as SearchParams) + +export const msgPageAtom = atom({ + pageSize: 10, + page: 1, +}) + +export const msgListAtom = atomWithQuery((get) => { + return { + queryKey: [ 'msgList', get(msgSearchAtom) ], + queryFn: async ({ queryKey: [ , params ] }) => { + const list = await messageMy.list(params as SearchParams) + return list.data + } + } +}) + +export const deleteMsgAtom = atomWithMutation((get) => { + return { + mutationKey: [ 'deleteMsg' ], + mutationFn: async (ids: number) => { + return await messageMy.delete(ids ?? get(msgIdsAtom) as number) + }, + onSuccess: (res) => { + message.success('message.deleteSuccess') + //更新列表 + get(queryClientAtom).invalidateQueries({ queryKey: [ 'msgList', get(msgSearchAtom) ] }) + return res + } + } +}) + +export const saveMsgAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'saveMsg' ], + mutationFn: async (data) => { + return await messageMy.add(data) + }, + onSuccess: (res) => { + message.success(t('message.saveSuccess', '保存成功')) + + get(queryClientAtom).invalidateQueries({ queryKey: [ 'msgList', get(msgSearchAtom) ] }) + + return res + } + } +}) diff --git a/src/store/system/message.ts b/src/store/message/template.ts similarity index 72% rename from src/store/system/message.ts rename to src/store/message/template.ts index 7d95344..043b122 100644 --- a/src/store/system/message.ts +++ b/src/store/message/template.ts @@ -1,8 +1,8 @@ import { atom } from 'jotai/index' import { IApiResult, IPage } from '@/global' import { atomWithMutation, atomWithQuery, queryClientAtom } from 'jotai-tanstack-query' -import { IMsgTemplate } from '@/types/system/message.ts' -import mdwMessage from '@/service/message/message.ts' +import { IMsgTemplate } from '@/types/message/template.ts' +import messageTemplate from '@/service/message/template.ts' import { message } from 'antd' import { t } from 'i18next' @@ -12,7 +12,7 @@ type SearchParams = IPage & { [key: string]: any } -export const templateAIdsAtom = atom(0) +export const templateIdsAtom = atom(0) export const templateAtom = atom(undefined as unknown as IMsgTemplate) @@ -31,18 +31,18 @@ export const templateListAtom = atomWithQuery((get) => { return { queryKey: [ 'templateList', get(templateSearchAtom) ], queryFn: async ({ queryKey: [ , params ] }) => { - const list = await mdwMessage.list(params as SearchParams) + const list = await messageTemplate.list(params as SearchParams) return list.data } } }) -export const templateFieldAtom = atomWithQuery(() => { +export const templateAllListAtom = atomWithQuery(() => { return { - queryKey: [ 'templateField' ], - queryFn: async ({ queryKey: [ , params ] }) => { - const list = await mdwMessage.fieldList(params) - return list.data + queryKey: [ 'templateAllList'], + queryFn: async ({ queryKey: [ , ] }) => { + const list = await messageTemplate.listAll() + return list.data.list } } }) @@ -51,7 +51,7 @@ export const deleteTemplateAtom = atomWithMutation((get) => { return { mutationKey: [ 'deleteTemplate' ], mutationFn: async (ids: number) => { - return await mdwMessage.delete(ids ?? get(templateAIdsAtom) as number) + return await messageTemplate.delete(ids ?? get(templateIdsAtom) as number) }, onSuccess: (res) => { message.success('message.deleteSuccess') @@ -67,11 +67,10 @@ export const saveOrUpdateTemplateAtom = atomWithMutation { - //data.status = data.status ? '1' : '0' if (data.id === 0) { - return await mdwMessage.add(data) + return await messageTemplate.add(data) } - return await mdwMessage.update(data) + return await messageTemplate.update(data) }, onSuccess: (res) => { const isAdd = !!res.data?.id diff --git a/src/types/message/my.ts b/src/types/message/my.ts new file mode 100644 index 0000000..7e4c193 --- /dev/null +++ b/src/types/message/my.ts @@ -0,0 +1,11 @@ +export interface IMsgMy { + id: number + template_id: number + title: string + send_content: string + dest: string + type: string + status: number + error_message: string + send_at: number +} \ No newline at end of file diff --git a/src/types/message/template.ts b/src/types/message/template.ts new file mode 100644 index 0000000..7ff2eec --- /dev/null +++ b/src/types/message/template.ts @@ -0,0 +1,25 @@ +export interface IMsgTemplate { + id: number + name: string + content: string + title: string + dest: string + type: string + fields: string +} + +export const coverType = (type: string) => { + let typeText = '' + switch (type) { + case 'SMS': + typeText = '短信' + break + case 'EMAIL': + typeText = '邮件' + break + case 'TG': + typeText = 'Telegram' + break + } + return typeText +} diff --git a/src/types/system/message.ts b/src/types/system/message.ts deleted file mode 100644 index a94ac4f..0000000 --- a/src/types/system/message.ts +++ /dev/null @@ -1,25 +0,0 @@ -export interface IMsgTemplate { - id: number - name: string - content: string - title: string - dest: string - type: string -} - -export interface IMsgFieldRes { - list: IMsgField[] -} - - -export interface IMsgField { - template_id: string - field_key: string - field_value: string -} - -export interface IMdwMsgReq { - id: string - name: string - content: string -} diff --git a/vite.config.ts b/vite.config.ts index 556523a..8efc084 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,7 @@ import jotaiReactRefresh from "jotai/babel/plugin-react-refresh"; //import { TanStackRouterVite } from '@tanstack/router-vite-plugin' export const downLoadUrl = "http://127.0.0.1:8000"; const proxyMap = { + '/api/v1/mdw-msg': 'http://127.0.0.1:8848', "/api/v1/package": "http://154.88.7.8:45321", "/api/v1/movie": "http://47.113.117.106:10000", //'/api/v1/certold': 'http://192.168.31.41:8000',