feat: 邮件列表

dev/RoosterEditor
Lei OT 5 months ago
parent 98d885400f
commit 75132e14eb

@ -1,5 +1,6 @@
import { fetchJSON, postForm } from '@/utils/request';
import { API_HOST, EMAIL_HOST } from '@/config';
import { isEmpty } from '@/utils/commons';
const parseHTMLString = (html, needText = false) => {
const parser = new DOMParser()
@ -89,11 +90,13 @@ export const getEmailDetailAction = async (params) => {
const { html, bodyContent, bodyText } = mailType === 'text/html' ? parseHTMLString(cleanContent, true) : { html: '', bodyContent: '', bodyText: '' };
const attachments = (isEmpty(result?.AttachList) ? [] : result.AttachList).filter(ele => isEmpty(ele.ATI_ContentID));
return {
info: { ...encodeEmailInfo(result.MailInfo?.[0] || {}), mailType },
content: mailType === 'text/html' ? html : result.MailContent || '',
abstract: bodyText || result.MailContent || '',
attachments: result?.AttachList || [],
attachments,
}
}

@ -12,11 +12,15 @@ export const useFormStore = create(
setMsgHistorySelectMatch: (msgHistorySelectMatch) => set({ msgHistorySelectMatch }),
msgListParams: {},
setMsgListParams: (msgListParams) => set(state => ({ msgListParams: {...state.msgListParams, ...msgListParams} })),
ImageAlbum: [],
setImageAlbum: (ImageAlbum) => set({ ImageAlbum }),
ImagePreviewSrc: '',
setImagePreviewSrc: (ImagePreviewSrc) => set({ ImagePreviewSrc }),
EmailList: [],
setEmailList: (EmailList) => set({ EmailList }),
// 订单跟踪页面
orderFollowForm: {
type: 'today',

@ -9,12 +9,14 @@ import ImageAlbumPreview from './Conversations/History/ImageAlumPreview';
import { flush, pick } from '@/utils/commons';
import { fetchConversationsSearch, CONVERSATION_PAGE_SIZE } from '@/actions/ConversationActions';
import EmailDetail from './Conversations/Online/Components/EmailDetail';
import SupplierEmailDrawer from './Conversations/Online/Components/EmailListDrawer';
const { Sider, Content } = Layout;
const Index = (props) => {
const [formValues, setFormValues] = useFormStore((state) => [state.chatHistoryForm, state.setChatHistoryForm]);
const [selectedConversation, setSelectedConversation] = useFormStore((state) => [state.chatHistorySelectChat, state.setChatHistorySelectChat]);
const [EmailList, ] = useFormStore((state) => [state.EmailList, ]);
const [conversationsListLoading, setConversationsListLoading] = useState(false);
const [conversationsList, setConversationsList] = useState([]);
@ -51,10 +53,13 @@ const Index = (props) => {
const [emailDetail, setEmailDetail] = useState({});
const [initialPosition, setInitialPosition] = useState({})
const [initialSize, setInitialSize] = useState({})
const [emailItem, setEmailItem] = useState({});
const onOpenEmail = (emailMsg) => {
setOpenEmailDetail(true);
setEmailDetail({...emailMsg, order_opi: Number(selectedConversation?.opi_sn || 0)});
// setOpenEmailDetail(true);
// setEmailDetail({...emailMsg, order_opi: Number(selectedConversation?.opi_sn || 0)});
// console.log(emailMsg);
setEmailItem({ MAI_SN: emailMsg.msgtext?.email?.mai_sn, MAI_Subject: emailMsg.msgtext?.email?.subject, SenderReceiver: '', MAI_SendDate: '' })
}
return (
@ -72,6 +77,7 @@ const Index = (props) => {
</Flex>
<ImageAlbumPreview />
<EmailDetail open={openEmailDetail} setOpen={setOpenEmailDetail} emailMsg={emailDetail} key={`history-email-detail-${emailDetail.id}`} disabled {...{initialPosition, initialSize, setInitialPosition, setInitialSize}} />
<SupplierEmailDrawer showExpandBtn={false} list={EmailList} opi_sn={selectedConversation?.opi_sn} currentConversationID={selectedConversation?.sn} oid={selectedConversation?.coli_sn} emailItem={emailItem} />
</Content>
</Layout>
</>

@ -20,6 +20,7 @@ const MessagesList = ({ ...listProps }) => {
const [paramsForMsgList, setParamsForMsgList] = useFormStore((state) => [state.msgListParams, state.setMsgListParams]);
const [selectMatch, setSelectedMatch] = useFormStore((state) => [state.msgHistorySelectMatch, state.setMsgHistorySelectMatch]);
const [setImageAlbumList, setImagePreviewSrc] = useFormStore(useShallow((state) => [state.setImageAlbum, state.setImagePreviewSrc]));
const [ setEmailList] = useFormStore(useShallow((state) => [ state.setEmailList]));
const [chatItemMessages, setChatItemMessages] = useState([]);
const [messageListPreLoading, setMessageListPreLoading] = useState(false);
@ -110,6 +111,8 @@ const MessagesList = ({ ...listProps }) => {
// setParamsForMsgList({ pretime: chatItemMessages[0].msgtime, lasttime: chatItemMessages[chatItemMessages.length - 1].msgtime });
const album = chatItemMessages.filter((ele) => ele.whatsapp_msg_type === 'image').map((ele) => ele.data.uri);
setImageAlbumList(album);
const emailList = chatItemMessages.filter((ele) => ele.msg_source === 'email').map(ele => ({...ele, MAI_SN: ele.msgtext?.email?.mai_sn, MAI_Subject: ele.msgtext?.email?.subject, SenderReceiver: ele.from, MAI_SendDate: ele.msgtime, Direction: ele.msg_direction === 'inbound' ? '收' : '发' })).reverse();
setEmailList(emailList);
}
return () => {};
}, [chatItemMessages]);

@ -1,6 +1,6 @@
import React, { useState, useEffect, useRef } from 'react'
const EmailContent = ({ id, content: MailContent, ...props }) => {
const EmailContent = ({ id, content: MailContent, className='', ...props }) => {
const [iframeHeight, setIframeHeight] = useState(800) // Initial height
const [content, setContent] = useState(MailContent)
const iframeRef = useRef(null)
@ -102,7 +102,7 @@ const EmailContent = ({ id, content: MailContent, ...props }) => {
// }, [content])
return (
<div ref={containerRef} className='space-y-4 w-full'>
<div ref={containerRef} className={`space-y-4 w-full ${className}`}>
<div className='w-full relative'>
<iframe
key={id}

@ -1,24 +1,38 @@
import { useState, useEffect } from 'react'
import { App, Button, Divider, Avatar } from 'antd'
import { LoadingOutlined, ApiOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons';
import { App, Button, Divider, Avatar, List, Flex, Typography } from 'antd'
import { LoadingOutlined, ApiOutlined, FilePdfOutlined, FileOutlined, FileWordOutlined, FileExcelOutlined, FileJpgOutlined, FileImageOutlined, FileTextOutlined, FileGifOutlined, GlobalOutlined, FileZipOutlined } from '@ant-design/icons'
import { EditIcon, ReplyIcon, ResendIcon, ShareForwardIcon } from '@/components/Icons'
import { isEmpty, TagColorStyle } from '@/utils/commons'
import EmailEditorPopup from '../Input/EmailEditorPopup'
import DnDModal from '@/components/DnDModal'
import useStyleStore from '@/stores/StyleStore'
import { useEmailDetail, } from '@/hooks/useEmail';
import { EMAIL_ATTA_HOST } from '@/config';
import EmailBindFormModal from './EmailBind';
import EmailContent from './EmailContent';
import { useEmailDetail } from '@/hooks/useEmail'
import { EMAIL_ATTA_HOST } from '@/config'
import EmailBindFormModal from './EmailBind'
import EmailContent from './EmailContent'
const extTypeMapped = {
txt: { icon: FileTextOutlined },
zip: { icon: FileZipOutlined, color: '#ffe78f' },
pdf: { icon: FilePdfOutlined, color: '#ad0b00' },
doc: { icon: FileWordOutlined, color: '#103f91' },
docx: { icon: FileWordOutlined, color: '#103f91' },
xls: { icon: FileExcelOutlined, color: '#0c7d0c' },
xlsx: { icon: FileExcelOutlined, color: '#0c7d0c' },
jpg: { icon: FileImageOutlined, color: '#1985ff' },
png: { icon: FileImageOutlined, color: '#1985ff' },
gif: { icon: FileGifOutlined, color: '#1985ff' },
html: { icon: GlobalOutlined, color: '#1985ff' },
default: { icon: FileOutlined }, // rtf bmp
}
/**
* @property {*} emailMsg - 邮件数据. { conversationid, actionId, order_opi, coli_sn, msgOrigin: { from, to, id, email: { subject, mai_sn, } } }
*/
const EmailDetailInline = ({ mailID, emailMsg={}, disabled=false, ...props }) => {
const EmailDetailInline = ({ mailID, emailMsg = {}, disabled = false, ...props }) => {
// console.log('emailDetail', emailMsg);
const {notification, message} = App.useApp()
const { notification, message } = App.useApp()
const { conversationid, actionId, order_opi, coli_sn } = emailMsg
const { mai_sn, id } = emailMsg.msgOrigin?.email || emailMsg.msgOrigin || {}
@ -43,7 +57,7 @@ const EmailDetailInline = ({ mailID, emailMsg={}, disabled=false, ...props }) =>
}
const { loading, mailData, orderDetail, postEmailResend } = useEmailDetail(mailID)
const [showBindBtn, setShowBindBtn] = useState(false);
const [showBindBtn, setShowBindBtn] = useState(false)
useEffect(() => {
setShowBindBtn(isEmpty(mailData.info?.MAI_COLI_SN))
return () => {}
@ -58,13 +72,19 @@ const EmailDetailInline = ({ mailID, emailMsg={}, disabled=false, ...props }) =>
// setOpen(false)
} catch (err) {
notification.error({
message: "请求失败",
message: '请求失败',
description: err.message,
placement: "top",
placement: 'top',
duration: 3,
});
})
}
}
const FileTypeIcon = ({fileName}) => {
const ext = fileName.split('.').pop() || 'default';
const Com = extTypeMapped[ext]?.icon || FileOutlined;
const color = extTypeMapped[ext]?.color || 'inherit';
return <Com style={{ color: color }} size={36} />;
};
/**
* 根据状态, 显示操作
@ -73,15 +93,15 @@ const EmailDetailInline = ({ mailID, emailMsg={}, disabled=false, ...props }) =>
* * 失败: 重发
* todo: disabled 不显示
*/
const ActionBtns = ({className, ...props}) => {
const ActionBtns = ({ className, ...props }) => {
const { status } = mailData.info
let btns = []
// ``
if (showBindBtn) {
btns.push(<EmailBindFormModal key={'bind'} onBoundSuccess={() => setShowBindBtn(false)} {...{conversationid, mai_sn, showBindBtn}} />)
btns.push(<Divider key='divider1' type='vertical' />);
btns.push(<EmailBindFormModal key={'bind'} onBoundSuccess={() => setShowBindBtn(false)} {...{ conversationid, mai_sn, showBindBtn }} />)
btns.push(<Divider key='divider1' type='vertical' />)
}
switch (status) {
@ -92,90 +112,113 @@ const EmailDetailInline = ({ mailID, emailMsg={}, disabled=false, ...props }) =>
btns.push(
<Button key={'reply'} onClick={() => onOpenEditor(emailMsg.msgOrigin, 'reply')} size='small' type='text' icon={<ReplyIcon className='text-indigo-500' />}>
回复
</Button>
</Button>,
)
btns.push(
<Button key={'forward'} onClick={() => onOpenEditor(emailMsg.msgOrigin, 'forward')} size='small' type='text' icon={<ShareForwardIcon className='text-primary' />}>
转发
</Button>
</Button>,
)
break
case 'failed':
btns.push(
<Button key={'resend'} onClick={() => handleResend()} size='small' type='text' icon={<ResendIcon className='text-orange-500' />}>
重发
</Button>
</Button>,
)
btns.push(
<Button key={'edit'} onClick={() => onOpenEditor({...(emailMsg.msgOrigin || {}), content: mailData.content}, 'edit')} size='small' type='text' icon={<EditIcon className='text-indigo-500' />}>
<Button
key={'edit'}
onClick={() => onOpenEditor({ ...(emailMsg.msgOrigin || {}), content: mailData.content }, 'edit')}
size='small'
type='text'
icon={<EditIcon className='text-indigo-500' />}>
编辑
</Button>
</Button>,
)
break
default:
break
}
return (
<div className={`flex justify-end items-center gap-1 w-full ${className || ''}`}>
{btns}
</div>
)
return <div className={`flex justify-end items-center gap-1 w-full ${className || ''}`}>{btns}</div>
}
return (
<>
<div className='email-container flex flex-col gap-2 *:p-2 *:rounded-sm *:border-b *:border-gray-200 *:shadow-1md'>
<div className=' font-bold'>{loading ? <LoadingOutlined className='mr-1' /> : null}{mailData.info?.MAI_Subject || emailMsg?.msgOrigin?.subject}</div>
<div>
<div className={['flex flex-wrap justify-end', window.innerWidth < 600 ? 'flex-col' : 'flex-row '].join(' ')}>
<div className=' flex-auto basis-0 flex flex-wrap gap-2 mb-2 items-center'>
<Avatar className='' style={TagColorStyle(mailData.info?.MAI_From, true)}>
{(mailData.info?.MAI_From || '').substring(0, 1)}
</Avatar>
<div className=' flex flex-col '>
{/* <span className=' font-bold text-base'>{mailData.info?.fromName}</span> */}
<span className='text-neutral-500 text-wrap break-words break-all '>{mailData.info?.MAI_From}</span>
</div>
</div>
<div className=' ml-auto flex flex-col justify-start gap-1 items-end'>
<ActionBtns />
<div className='text-xs '>{mailData.info?.MAI_SendDate || emailMsg.localDate}</div>
<div className='email-container flex flex-col gap-2 *:p-2 *:rounded-sm *:border-b *:border-gray-200 *:shadow-1md'>
<div className=' font-bold'>
{loading ? <LoadingOutlined className='mr-1' /> : null}
{mailData.info?.MAI_Subject || emailMsg?.msgOrigin?.subject}
</div>
<div>
<div className={['flex flex-wrap justify-end', window.innerWidth < 600 ? 'flex-col' : 'flex-row '].join(' ')}>
<div className=' flex-auto basis-0 flex flex-wrap gap-2 mb-2 items-center'>
<Avatar className='' style={TagColorStyle(mailData.info?.MAI_From, true)}>
{(mailData.info?.MAI_From || '').substring(0, 1)}
</Avatar>
<div className=' flex flex-col '>
{/* <span className=' font-bold text-base'>{mailData.info?.fromName}</span> */}
<span className='text-neutral-500 text-wrap break-words break-all '>{mailData.info?.MAI_From}</span>
</div>
</div>
<div className=' ml-auto flex flex-col justify-start gap-1 items-end'>
<ActionBtns />
<div className='text-xs '>{mailData.info?.MAI_SendDate || emailMsg.localDate}</div>
</div>
</div>
<div className='text-sm'>
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{ textAlignLast: 'justify' }}>
收件人
</span>
{mailData.info?.MAI_To}
</div>
{mailData.info?.MAI_CS && (
<div className='text-sm'>
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{ textAlignLast: 'justify' }}>
抄送
</span>
{mailData.info.MAI_CS}
</div>
)}
{mailData.info?.bcc && (
<div className='text-sm'>
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{textAlignLast: 'justify'}}>收件人</span>
{mailData.info?.MAI_To}
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{ textAlignLast: 'justify' }}>
密送
</span>
{mailData.info.bcc}
</div>
{mailData.info?.MAI_CS && (
<div className='text-sm'>
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{textAlignLast: 'justify'}}>抄送</span>
{mailData.info.MAI_CS}
</div>
)}
{mailData.info?.bcc && (
<div className='text-sm'>
<span className='text-neutral-500 pr-2 w-12 inline-block text-justify' style={{textAlignLast: 'justify'}}>密送</span>
{mailData.info.bcc}
</div>
)}
<Divider className='my-2' />
<Flex className=' divide-y-0 divide-x divide-gray-200 divide-solid gap-0'>
{mailData.info?.mailType === 'text/html' ? (
<EmailContent content={mailData.content} id={mailID} key={mailID} />
) : (
<div className='mt-2 whitespace-pre-wrap' dangerouslySetInnerHTML={{ __html: mailData.content }}></div>
)}
{mailData.attachments.length > 0 && (
<div className='mt-2 *:ml-2'>
<span>{mailData.attachments.length}个附件</span>
<div className='flex flex-wrap gap-2'>
{mailData.attachments.map((atta) => (
<a href={`${EMAIL_ATTA_HOST}${atta.ATI_ServerFile}`} key={atta.ATI_SN} target='_blank' rel='noreferrer'>
{atta.ATI_Name}
</a>
))}
</div>
</div>
<div className='w-48 overflow-hidden'>
<span>&nbsp;附件 ({mailData.attachments.length})</span>
<List
dataSource={mailData.attachments || []}
size='small' className='[&_.ant-list-item]:p-1 [&_.ant-list-item]:justify-start'
renderItem={(atta) => (
<List.Item>
<FileTypeIcon fileName={atta.ATI_Name} />
<Typography.Text ellipsis={{ tooltip: { title: atta.ATI_Name, placement: 'left' } }} className='text-inherit'>
<a href={`${EMAIL_ATTA_HOST}${atta.ATI_ServerFile}`} key={atta.ATI_SN} target='_blank' rel='noreferrer'>
{atta.ATI_Name}
</a>
</Typography.Text>
</List.Item>
)}
/>
</div>
)}
<Divider className='my-2' />
{mailData.info?.mailType === 'text/html' ? <EmailContent content={mailData.content} id={mailID} key={mailID} /> : <div className='mt-2 whitespace-pre-wrap' dangerouslySetInnerHTML={{ __html: mailData.content }}></div>}
</div>
</Flex>
</div>
</div>
<EmailEditorPopup
open={openEmailEditor}
setOpen={setOpenEmailEditor}

@ -3,10 +3,10 @@ import { App, Button, Card, Empty, Flex, Select, Spin, Typography, Divider, Moda
import { CloseOutlined, DownloadOutlined } from '@ant-design/icons';
import dayjs from 'dayjs'
import { InboxIcon, SendPlaneFillIcon, ExpandIcon } from '@/components/Icons'
import EmailDetailInline from '../Components/EmailDetailInline'
import EmailDetailInline from './EmailDetailInline'
import { debounce, isEmpty } from '@/utils/commons'
const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationID, opi_sn, oid, emailItem: clickItem, ...props }) => {
const EmailListDrawer = ({ showExpandBtn=true, title, list: otherEmailList, currentConversationID, opi_sn, oid, emailItem: clickItem, ...props }) => {
const [open, setOpen] = useState(false)
const [selectedEmail, setSelectedEmail] = useState({})
const searchInputRef = useRef(null)
@ -45,10 +45,18 @@ const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationI
setSelectedEmail(emailMsg)
};
const [pageCurrent, setPageCurrent] = useState(1);
const onChangePagination = (page, size) => {
setPageCurrent(page)
}
useEffect(() => {
if (!isEmpty(clickItem)) {
onClickEmailItem(clickItem)
setOpen(true);
const itemIndex = dataSource.findIndex((ele) => ele.MAI_SN === clickItem.MAI_SN);
const page = Math.ceil(itemIndex / 8) || 1;
setPageCurrent(page);
}
return () => {}
@ -56,7 +64,7 @@ const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationI
return (
<>
<Button
{showExpandBtn ? <Button
icon={<ExpandIcon />}
type={'primary'}
className='ml-2'
@ -65,7 +73,7 @@ const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationI
onClick={() => {
setOpen(true)
}}
/>
/> : null}
<Drawer
zIndex={3}
mask={false}
@ -91,7 +99,7 @@ const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationI
dataSource={dataSource}
className=''
pagination={{
pageSize: 8,
pageSize: 8, current: pageCurrent, onChange: onChangePagination,
// showLessItems: true,
showSizeChanger: false,
size: 'small',
@ -131,4 +139,4 @@ const SupplierEmailDrawer = ({ title, list: otherEmailList, currentConversationI
</>
)
}
export default SupplierEmailDrawer
export default EmailListDrawer

@ -16,7 +16,7 @@ import { useConversationNewItem } from "@/hooks/useConversation";
import EmailDetail from './../Components/EmailDetail';
import { postEditConversationItemColiAction } from "@/actions/ConversationActions";
import useStyleStore from '@/stores/StyleStore';
import SupplierEmailDrawer from "../Components/SupplierEmailDrawer";
import SupplierEmailDrawer from "../Components/EmailListDrawer";
const CustomerProfile = () => {
const navigate = useNavigate();

Loading…
Cancel
Save