From 51faae66dbd7aac41ff9624c0e1dbc0046044652 Mon Sep 17 00:00:00 2001 From: cs Date: Mon, 9 Sep 2024 23:32:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B6=88=E6=81=AF=E6=A8=A1=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/system/message/index.tsx | 454 +++++++++++++++++++++++++++++++++++++ src/pages/system/message/style.ts | 26 +++ src/service/message/message.ts | 17 ++ src/store/system/message.ts | 85 +++++++ src/types/system/message.ts | 25 ++ 5 files changed, 607 insertions(+) create mode 100644 src/pages/system/message/index.tsx create mode 100644 src/pages/system/message/style.ts create mode 100644 src/service/message/message.ts create mode 100644 src/store/system/message.ts create mode 100644 src/types/system/message.ts diff --git a/src/pages/system/message/index.tsx b/src/pages/system/message/index.tsx new file mode 100644 index 0000000..327c0e2 --- /dev/null +++ b/src/pages/system/message/index.tsx @@ -0,0 +1,454 @@ +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' +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/system/message.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(['name']); + const [titleTemplateField, setTitleTemplateField] = useState([]); + + const [allTemplateField, setAllTemplateField] = useState([]); + + useEffect(() => { + const combinedFields = [...templateField, ...titleTemplateField]; + const uniqueFields = Array.from(new Set(combinedFields)); + setAllTemplateField(uniqueFields); + }, [templateField, titleTemplateField]); + + const [templateType, setTemplateType] = useState('') + useEffect(() => { + setTemplateType(currentTemplate?.type) + }, [open]); + + const handleContentChange = (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]))); + + // 更新变量状态 + 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]))); + + // 更新变量状态 + setTitleTemplateField(variables); + + }; + + const typeHandlerChange = (value) => { + console.log(value) + 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.field`, '识别到的变量'), + dataIndex: 'field', + renderFormItem: () => { + return ( + <> + { + allTemplateField.map((variable, index) => ( + + {variable} + + )) + } + + ); + } + }, + { + title: t(`${i18nPrefix}.columns.dest`, '收件人(多个收件人用英文逗号隔开,如果类型是TG,则填token)'), + dataIndex: 'dest', + valueType: 'textarea', + fieldProps: { + maxLength: 1000, + showCount: true, + rows: 15, + placeholder: 'aaa@qq.com,bbb@gmail.com', + }, + }, + ] as ProColumns[] + }, [isDeleting, currentTemplate, search, allTemplateField, 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.title`, '模板标题'), + dataIndex: 'title', + }, + { + title: t(`${i18nPrefix}.columns.content`, '模板内容'), + dataIndex: 'content', + }, + { + title: t(`${i18nPrefix}.columns.option`, '操作'), + key: 'option', + valueType: 'option', + fixed: 'right', + render: (_, record) => [ + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.edit')}, + , + { + deleteAppPackage(record.id) + }} + title={t('message.deleteConfirm')}> + + {t('actions.delete', '删除')} + + , + , + { + form.setFieldsValue(record) + setOpen(true) + }}>{t('actions.sendMsg', '发送消息')}, + ] + } + ] as ProColumns[] + }, [isDeleting, currentTemplate, search]) + + 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} + 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, + } + }) + }, + }} + /> + { + setOpen(open) + }} + loading={isSubmitting} + onValuesChange={() => { + + }} + onFinish={async (values) => { + saveOrUpdate({...values, 'fields': allTemplateField}) + }} + 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/system/message/style.ts new file mode 100644 index 0000000..8774450 --- /dev/null +++ b/src/pages/system/message/style.ts @@ -0,0 +1,26 @@ +import { createStyles } from '@/theme' + +export const useStyle = createStyles(({ token, css, cx, prefixCls }, props: any) => { + 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 new file mode 100644 index 0000000..0e9a05c --- /dev/null +++ b/src/service/message/message.ts @@ -0,0 +1,17 @@ +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/store/system/message.ts b/src/store/system/message.ts new file mode 100644 index 0000000..7d95344 --- /dev/null +++ b/src/store/system/message.ts @@ -0,0 +1,85 @@ +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 { message } from 'antd' +import { t } from 'i18next' + +type SearchParams = IPage & { + key?: string + + [key: string]: any +} + +export const templateAIdsAtom = atom(0) + +export const templateAtom = atom(undefined as unknown as IMsgTemplate) + +export const templateSearchAtom = atom({ + key: '', + pageSize: 10, + page: 1, +} as SearchParams) + +export const templatePageAtom = atom({ + pageSize: 10, + page: 1, +}) + +export const templateListAtom = atomWithQuery((get) => { + return { + queryKey: [ 'templateList', get(templateSearchAtom) ], + queryFn: async ({ queryKey: [ , params ] }) => { + const list = await mdwMessage.list(params as SearchParams) + return list.data + } + } +}) + +export const templateFieldAtom = atomWithQuery(() => { + return { + queryKey: [ 'templateField' ], + queryFn: async ({ queryKey: [ , params ] }) => { + const list = await mdwMessage.fieldList(params) + return list.data + } + } +}) + +export const deleteTemplateAtom = atomWithMutation((get) => { + return { + mutationKey: [ 'deleteTemplate' ], + mutationFn: async (ids: number) => { + return await mdwMessage.delete(ids ?? get(templateAIdsAtom) as number) + }, + onSuccess: (res) => { + message.success('message.deleteSuccess') + //更新列表 + get(queryClientAtom).invalidateQueries({ queryKey: [ 'templateList', get(templateSearchAtom) ] }) + return res + } + } +}) + +export const saveOrUpdateTemplateAtom = atomWithMutation((get) => { + + return { + mutationKey: [ 'updateTemplate' ], + mutationFn: async (data) => { + //data.status = data.status ? '1' : '0' + if (data.id === 0) { + return await mdwMessage.add(data) + } + return await mdwMessage.update(data) + }, + onSuccess: (res) => { + const isAdd = !!res.data?.id + message.success(t(isAdd ? 'message.saveSuccess' : 'message.editSuccess', '保存成功')) + + get(queryClientAtom).invalidateQueries({ queryKey: [ 'templateList', get(templateSearchAtom) ] }) + + return res + } + } +}) diff --git a/src/types/system/message.ts b/src/types/system/message.ts new file mode 100644 index 0000000..a94ac4f --- /dev/null +++ b/src/types/system/message.ts @@ -0,0 +1,25 @@ +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 +}