feat: 邮件: 删除; perf: 更新数量

dev/ckeditor
Lei OT 2 weeks ago
parent d9686b3ef5
commit aabc409f6d

@ -14,6 +14,25 @@ export const parseHTMLString = (html, needText = false) => {
return needText ? { html, bodyContent, bodyText } : bodyContent return needText ? { html, bodyContent, bodyText } : bodyContent
} }
export const EMAIL_CHANNEL_NAME = 'mailbox_changes';
let emailChangesChannel = null;
export function getEmailChangesChannel() {
if (!emailChangesChannel) {
emailChangesChannel = new BroadcastChannel(EMAIL_CHANNEL_NAME)
}
return emailChangesChannel
}
// 通知邮件列表数据更新
const notifyMailboxUpdate = (payload) => {
const notificationPayload = payload
// - 多个tab
const channel = getEmailChangesChannel()
channel.postMessage(notificationPayload)
// - 当前tab
internalEventEmitter.emit(EMAIL_CHANNEL_NAME, notificationPayload)
}
/** /**
* 获取顾问签名 * 获取顾问签名
* @param {object} { opi_sn } * @param {object} { opi_sn }
@ -178,7 +197,7 @@ export const getEmailDirAction = async (params = { opi_sn: '' }, retOrder=false)
const orderList = groupBy(result, row => `${row.IsTrue}`)?.['0'] || []; const orderList = groupBy(result, row => `${row.IsTrue}`)?.['0'] || [];
return retOrder !== false ? orderList : { [`${params.opi_sn}`]: retTree } return retOrder !== false ? orderList : { [`${params.opi_sn}`]: retTree }
}; };
export const getMailboxCountAction = async (params = { opi_sn: '' }, update = false) => { export const getMailboxCountAction = async (params = { opi_sn: '' }, update = true) => {
const defaultParams = { const defaultParams = {
opi_sn: 0, opi_sn: 0,
// date1: dayjs().subtract(1, 'year').startOf('year').format(DATE_FORMAT), // date1: dayjs().subtract(1, 'year').startOf('year').format(DATE_FORMAT),
@ -190,7 +209,7 @@ export const getMailboxCountAction = async (params = { opi_sn: '' }, update = fa
// 更新数量 // 更新数量
if (update !== false) { if (update !== false) {
const readCacheDir = await readIndexDB(Number(params.opi_sn), 'dirs', 'mailbox'); const readCacheDir = await readIndexDB(Number(params.opi_sn), 'dirs', 'mailbox');
const mailboxDir = isEmpty(readCacheDir) ? [] : readCacheDir.tree.filter(node => node._raw.IsTrue === 1); const mailboxDir = isEmpty(readCacheDir) ? [] : readCacheDir.tree.filter(node => node?._raw?.IsTrue === 1);
const _MapDir = new Map(mailboxDir.map((obj) => [obj.key, obj])) const _MapDir = new Map(mailboxDir.map((obj) => [obj.key, obj]))
Object.keys(result).map(dirKey => { Object.keys(result).map(dirKey => {
_MapDir.set(Number(dirKey), {..._MapDir.get(Number(dirKey)), count: result[dirKey]}); _MapDir.set(Number(dirKey), {..._MapDir.get(Number(dirKey)), count: result[dirKey]});
@ -202,6 +221,7 @@ export const getMailboxCountAction = async (params = { opi_sn: '' }, update = fa
}) })
const _newRoot = Array.from(_MapRoot.values()) const _newRoot = Array.from(_MapRoot.values())
writeIndexDB([{ key: Number(params.opi_sn), tree: _newRoot }], 'dirs', 'mailbox') writeIndexDB([{ key: Number(params.opi_sn), tree: _newRoot }], 'dirs', 'mailbox')
notifyMailboxUpdate({ type: 'dirs', key: Number(params.opi_sn) })
} }
return ret; return ret;
@ -315,7 +335,7 @@ export const getRootMailboxDirAction = async ({ opi_sn = 0, userIdStr = '' } = {
getTodoOrdersAction({ opisn: userIdStr || String(opi_sn), otype: 'today' }), getTodoOrdersAction({ opisn: userIdStr || String(opi_sn), otype: 'today' }),
...(userIdStr.split(',').map(_opi => getEmailDirAction({ opi_sn: _opi }))), ...(userIdStr.split(',').map(_opi => getEmailDirAction({ opi_sn: _opi }))),
]) ])
const mailBoxCount = await Promise.all(userIdStr.split(',').map(_opi => getMailboxCountAction({ opi_sn: _opi }))); const mailBoxCount = await Promise.all(userIdStr.split(',').map(_opi => getMailboxCountAction({ opi_sn: _opi }, false)));
const mailboxDirCountByOPI = mailBoxCount.reduce((a, c) => ({ ...a, ...c, }), {}) const mailboxDirCountByOPI = mailBoxCount.reduce((a, c) => ({ ...a, ...c, }), {})
const mailboxDirByOPI = mailboxDir.reduce((a, c) => ({ ...a, ...(Object.keys(c).reduce((a, opi) => ({...a, [opi]: c[`${opi}`].map((dir) => ({ ...dir, count: mailboxDirCountByOPI[opi][`${dir.key}`] })) }), {} )) }), {}) const mailboxDirByOPI = mailboxDir.reduce((a, c) => ({ ...a, ...(Object.keys(c).reduce((a, opi) => ({...a, [opi]: c[`${opi}`].map((dir) => ({ ...dir, count: mailboxDirCountByOPI[opi][`${dir.key}`] })) }), {} )) }), {})
const rootTree = Object.keys(stickyTree).map((opi) => ({ key: Number(opi), tree: [...stickyTree[opi], ...(mailboxDirByOPI?.[opi] || [])] })) const rootTree = Object.keys(stickyTree).map((opi) => ({ key: Number(opi), tree: [...stickyTree[opi], ...(mailboxDirByOPI?.[opi] || [])] }))
@ -355,15 +375,16 @@ export const queryEmailListAction = async ({ opi_sn = '', pagesize = 10, last_id
return ret; return ret;
} }
export const EMAIL_CHANNEL_NAME = 'mailbox_changes'; const removeFromCurrentList = async (params) => {
let emailChangesChannel = null; const readRow0 = await readIndexDB(params.mai_sn_list[0], 'listrow', 'mailbox')
export function getEmailChangesChannel() { const listKey = readRow0?.data?.listKey || ''
if (!emailChangesChannel) { if (listKey) {
emailChangesChannel = new BroadcastChannel(EMAIL_CHANNEL_NAME) const readCache = await readIndexDB(listKey, 'maillist', 'mailbox')
const updatedMailList = readCache.data.filter((mai_sn) => !params.mai_sn_list.includes(mai_sn))
writeIndexDB([{ key: listKey, data: updatedMailList }], 'maillist', 'mailbox')
notifyMailboxUpdate({ type: 'listrow', listKey, affectKeys: params.mai_sn_list })
} }
return emailChangesChannel
} }
const updateEmailKeyMap = { read: 'MOI_ReadState' }; const updateEmailKeyMap = { read: 'MOI_ReadState' };
const updateEmailKeyFun = { const updateEmailKeyFun = {
read: async (params) => { read: async (params) => {
@ -374,31 +395,13 @@ const updateEmailKeyFun = {
'listrow', 'listrow',
'mailbox', 'mailbox',
) )
const listKey = readCache.get(params.mai_sn_list[0])?.data?.listKey || '';
// 通知邮件列表数据更新 // 通知邮件列表数据更新
const listKey = readCache.get(params.mai_sn_list[0])?.data?.listKey || '';
const notificationPayload = { type: 'listrow', listKey, affectKeys: params.mai_sn_list } const notificationPayload = { type: 'listrow', listKey, affectKeys: params.mai_sn_list }
// - 多个tab notifyMailboxUpdate(notificationPayload)
const channel = getEmailChangesChannel()
channel.postMessage(notificationPayload)
// - 当前tab
internalEventEmitter.emit(EMAIL_CHANNEL_NAME, notificationPayload);
}, },
processed: async (params) => { processed: removeFromCurrentList,
const readRow0 = await readIndexDB(params.mai_sn_list[0], 'listrow', 'mailbox') delete: removeFromCurrentList
const listKey = readRow0?.data?.listKey || '';
if (listKey) {
const readCache = await readIndexDB(listKey, 'maillist', 'mailbox')
const updatedMailList = readCache.data.filter(mai_sn => !params.mai_sn_list.includes(mai_sn));
writeIndexDB([{ key: listKey, data: updatedMailList }], 'maillist', 'mailbox')
// 通知邮件列表数据更新
const notificationPayload = { type: 'listrow', listKey, affectKeys: params.mai_sn_list }
// - 多个tab
const channel = getEmailChangesChannel()
channel.postMessage(notificationPayload)
// - 当前tab
internalEventEmitter.emit(EMAIL_CHANNEL_NAME, notificationPayload);
}
}
} }
/** /**
* 更新邮件属性 * 更新邮件属性
@ -414,6 +417,7 @@ export const updateEmailAction = async (params = { opi_sn: 0, mai_sn_list: [], s
updateFun(params) updateFun(params)
} }
} }
getMailboxCountAction({ opi_sn: params.opi_sn });
return errcode === 0 ? result : {} return errcode === 0 ? result : {}
} }

@ -201,6 +201,17 @@ export const useEmailList = (mailboxDirNode) => {
[VKey], [VKey],
) )
const markAsDeleted = useCallback(
async (sn_list) => {
await updateEmailAction({
opi_sn: opi_sn,
mai_sn_list: sn_list,
set: { delete: 1 },
})
},
[VKey],
)
const loadMailListFromCache = useCallback(async (payload) => { const loadMailListFromCache = useCallback(async (payload) => {
const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}` const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}`
const readCacheIDList = await readIndexDB(cacheKey, 'maillist', 'mailbox') const readCacheIDList = await readIndexDB(cacheKey, 'maillist', 'mailbox')
@ -276,7 +287,7 @@ export const useEmailList = (mailboxDirNode) => {
} }
}, [getMailList]) }, [getMailList])
return { loading, isFreshData, error, mailList, refresh, markAsRead, markAsProcessed } return { loading, isFreshData, error, mailList, refresh, markAsRead, markAsProcessed, markAsDeleted }
} }
const orderMailTypes = new Map([ const orderMailTypes = new Map([

@ -1,6 +1,7 @@
import { getEmailDirAction, getMailboxCountAction, getRootMailboxDirAction } from '@/actions/EmailActions' import { getEmailDirAction, getMailboxCountAction, getRootMailboxDirAction, getEmailChangesChannel, EMAIL_CHANNEL_NAME } from '@/actions/EmailActions'
import { buildTree, isEmpty, olog, sortArrayByOrder } from '@/utils/commons' import { buildTree, isEmpty, olog, sortArrayByOrder } from '@/utils/commons'
import { readIndexDB, writeIndexDB, createIndexedDBStore, clean7DaysMailboxLog } from '@/utils/indexedDB'; import { readIndexDB, writeIndexDB, createIndexedDBStore, clean7DaysMailboxLog } from '@/utils/indexedDB';
import { internalEventEmitter } from '@/utils/EventEmitterService';
/** /**
* Email * Email
@ -141,20 +142,43 @@ const emailSlice = (set, get) => ({
// 更新数量 // 更新数量
updateMailboxCount: async ({ opi_sn }) => { updateMailboxCount: async ({ opi_sn }) => {
const { setMailboxNestedDirsActive } = get() const { setMailboxNestedDirsActive } = get()
await getMailboxCountAction({ opi_sn }, true) await getMailboxCountAction({ opi_sn })
const readCache = await readIndexDB(Number(opi_sn), 'dirs', 'mailbox') // const readCache = await readIndexDB(Number(opi_sn), 'dirs', 'mailbox')
if (!isEmpty(readCache)) { // if (!isEmpty(readCache)) {
setMailboxNestedDirsActive(readCache?.tree || []) // setMailboxNestedDirsActive(readCache?.tree || [])
} // }
}, },
async initMailbox({ opi_sn, dei_sn, userIdStr }) { async initMailbox({ opi_sn, dei_sn, userIdStr }) {
olog('initMailbox ---- ') olog('initMailbox ---- ')
const { setCurrentMailboxOPI, setCurrentMailboxDEI, getOPIEmailDir } = get() const { currentMailboxOPI, setCurrentMailboxOPI, setCurrentMailboxDEI, getOPIEmailDir, setMailboxNestedDirsActive, } = get()
createIndexedDBStore(['dirs', 'maillist', 'listrow', 'mailinfo', 'draft'], 'mailbox') createIndexedDBStore(['dirs', 'maillist', 'listrow', 'mailinfo', 'draft'], 'mailbox')
setCurrentMailboxOPI(opi_sn) setCurrentMailboxOPI(opi_sn)
setCurrentMailboxDEI(dei_sn) setCurrentMailboxDEI(dei_sn)
getOPIEmailDir(opi_sn, userIdStr, true) getOPIEmailDir(opi_sn, userIdStr, true)
// --- Setup Internal Event Listener ---
internalEventEmitter.on(EMAIL_CHANNEL_NAME, async (event) => {
console.log(`🔔Received internal event. `, event.detail)
if (event.detail && event.detail.type === 'dirs') {
const readCache = await readIndexDB(event.detail.key, 'dirs', 'mailbox')
if (!isEmpty(readCache)) {
setMailboxNestedDirsActive(readCache?.tree || [])
}
}
})
// --- Setup BroadcastChannel Listener ---
const channel = getEmailChangesChannel()
channel.addEventListener('message', async (event) => {
console.log(`📣Received channel event. `, event.data)
if (event.data.type === 'dirs' && currentMailboxOPI === event.data.key) {
const readCache = await readIndexDB(event.data.key, 'dirs', 'mailbox')
if (!isEmpty(readCache)) {
setMailboxNestedDirsActive(readCache?.tree || [])
}
}
})
}, },
}) })
export default emailSlice export default emailSlice

@ -245,7 +245,7 @@ const NewEmail = () => {
mai_sn: pageParam.quoteid, mai_sn: pageParam.quoteid,
..._form2 ..._form2
} }
readyToInitialContent = generateMailContent(mailData) readyToInitialContent = '<br>'+ generateMailContent(mailData)
setFileList(mailData.attachments.map(ele => ({ uid: ele.ATI_SN, name: ele.ATI_Name, url: ele.ATI_ServerFile, fullPath: `${EMAIL_ATTA_HOST}${ele.ATI_ServerFile}` }))) setFileList(mailData.attachments.map(ele => ({ uid: ele.ATI_SN, name: ele.ATI_Name, url: ele.ATI_ServerFile, fullPath: `${EMAIL_ATTA_HOST}${ele.ATI_ServerFile}` })))
break break
case 'new': case 'new':

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ReloadOutlined, ReadOutlined, RightOutlined, LeftOutlined, SearchOutlined, MailOutlined } from '@ant-design/icons' import { ReloadOutlined, ReadOutlined, RightOutlined, LeftOutlined, SearchOutlined, MailOutlined, DeleteOutlined } from '@ant-design/icons'
import { Flex, Button, Tooltip, List, Form, Row, Col, Input, Checkbox, DatePicker, Switch, Breadcrumb, Skeleton } from 'antd' import { Flex, Button, Tooltip, List, Form, Row, Col, Input, Checkbox, DatePicker, Switch, Breadcrumb, Skeleton, Popconfirm } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useEmailList } from '@/hooks/useEmail' import { useEmailList } from '@/hooks/useEmail'
import { isEmpty } from '@/utils/commons' import { isEmpty } from '@/utils/commons'
@ -42,7 +42,7 @@ const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
] ]
const [form] = Form.useForm() const [form] = Form.useForm()
const [selectedItems, setSelectedItems] = useState([]) const [selectedItems, setSelectedItems] = useState([])
const { mailList, loading, error, refresh, markAsRead, markAsProcessed } = useEmailList(mailboxDir) const { mailList, loading, error, refresh, markAsRead, markAsProcessed, markAsDeleted } = useEmailList(mailboxDir)
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
current: 1, current: 1,
@ -97,6 +97,8 @@ const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
const mailItemRender = (item) => { const mailItemRender = (item) => {
const isOrderNode = mailboxDir.COLI_SN > 0 const isOrderNode = mailboxDir.COLI_SN > 0
const orderNumber = isEmpty(item.MAI_COLI_ID) || isOrderNode ? '' : item.MAI_COLI_ID + ' - ' const orderNumber = isEmpty(item.MAI_COLI_ID) || isOrderNode ? '' : item.MAI_COLI_ID + ' - '
const folderName = isOrderNode ? `[${item.FDir}]` : ''
const orderMailType = <span className='text-blue-400 text-xs'>{item.MAT_Name}</span>
const countryName = isEmpty(item.CountryCN) ? '' : '[' + item.CountryCN + '] ' const countryName = isEmpty(item.CountryCN) ? '' : '[' + item.CountryCN + '] '
const mailStateClass = item.MOI_ReadState === 0 ? 'font-bold' : '' const mailStateClass = item.MOI_ReadState === 0 ? 'font-bold' : ''
const hasAtta = item.MAI_Attachment !== 0 ? <AttachmentIcon className='text-blue-500' /> : null const hasAtta = item.MAI_Attachment !== 0 ? <AttachmentIcon className='text-blue-500' /> : null
@ -173,6 +175,13 @@ const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
markAsProcessed(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([])) markAsProcessed(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}} }}
>已处理</Button> >已处理</Button>
<Button
size='small' // danger
icon={<DeleteOutlined />}
onClick={() => {
markAsDeleted(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}}
>删除</Button>
<MailOrderSearchModal /> <MailOrderSearchModal />
</div> </div>
<div className='bg-white h-auto p-1 flex gap-1 items-center hidden'> <div className='bg-white h-auto p-1 flex gap-1 items-center hidden'>

Loading…
Cancel
Save