diff --git a/src/actions/EmailActions.js b/src/actions/EmailActions.js index bad97d2..e5444a7 100644 --- a/src/actions/EmailActions.js +++ b/src/actions/EmailActions.js @@ -3,6 +3,7 @@ import { API_HOST, API_HOST_V3, DATE_FORMAT, DATEEND_FORMAT, DATETIME_FORMAT, EM import { buildTree, groupBy, isEmpty, objectMapper, omitEmpty, uniqWith } from '@/utils/commons'; import { readIndexDB, writeIndexDB } from '@/utils/indexedDB'; import dayjs from 'dayjs'; +import { internalEventEmitter } from '@/utils/EventEmitterService'; export const parseHTMLString = (html, needText = false) => { const parser = new DOMParser() @@ -152,13 +153,13 @@ export const fetchEmailBindOrderAction = async (params) => { const todoTypes = { - // 1新订单;2未读消息;3需一催;4需二催;5需三催;6未处理邮件;入境提醒coli_ordertype=7,余款提醒coli_ordertype=8 + // 1新订单;2WhatsApp未读消息;3需一催;4需二催;5需三催;6未处理邮件;入境提醒coli_ordertype=7,余款提醒coli_ordertype=8 1: '新订单', - 2: '未读', + 2: '未读WhatsApp', 3: '一催', 4: '二催', 5: '三催', - 6: '未处理', + 6: '老邮件', 7: '入境提醒', 8: '余款提醒', } @@ -213,7 +214,7 @@ export const getTodoOrdersAction = async (params) => { const orderList = errcode === 0 ? _result_unique : [] const byOPI = groupBy(orderList, 'OPI_SN') const byState = Object.keys(byOPI).reduce((acc, key) => { - const sticky = groupBy(byOPI[key], (ele) => ([1, 2, 6].includes(ele.coli_ordertype) ? 0 : [3, 4, 5].includes(ele.coli_ordertype) ? 1 : 2)) + const sticky = groupBy(byOPI[key], (ele) => ([1, 6].includes(ele.coli_ordertype) ? 0 : [2, 3, 4, 5].includes(ele.coli_ordertype) ? 1 : 2)) const treeNode = [ { key: key + '-today', @@ -315,17 +316,56 @@ export const queryEmailListAction = async ({ opi_sn = '', pagesize = 10, last_id if (!isEmpty(ret)) { const listids = [...new Set(ret.map(ele => ele.MAI_SN))]; writeIndexDB([{key: cacheKey, data: listids}], 'maillist', 'mailbox') - writeIndexDB(ret.map(ele => ({ data: ele, key: ele.MAI_SN})), 'listrow', 'mailbox') + writeIndexDB(ret.map(ele => ({ data: {...ele, listKey: cacheKey }, key: ele.MAI_SN})), 'listrow', 'mailbox') } return ret; } +export const EMAIL_CHANNEL_NAME = 'mailbox_changes'; +let emailChangesChannel = null; +export function getEmailChangesChannel() { + if (!emailChangesChannel) { + emailChangesChannel = new BroadcastChannel(EMAIL_CHANNEL_NAME) + } + return emailChangesChannel +} + +const updateEmailKeyMap = { read: 'MOI_ReadState' }; +const updateEmailKeyFun = { + read: async (params) => { + const readCache = await readIndexDB(params.mai_sn_list, 'listrow', 'mailbox') + const updateField = Object.keys(params.set).reduce((a, c) => ({ ...a, [updateEmailKeyMap[c]]: params.set[c] }), {}) + writeIndexDB( + params.mai_sn_list.map((ele) => ({ data: { ...(readCache.get(ele).data || {}), ...updateField }, key: ele })), + 'listrow', + 'mailbox', + ) + const listKey = readCache.get(params.mai_sn_list[0])?.data?.listKey || ''; + // 通知邮件列表数据更新 + const notificationPayload = { type: 'listrow', listKey, affectKeys: params.mai_sn_list } + // - 多个tab + const channel = getEmailChangesChannel() + channel.postMessage(notificationPayload) + // - 当前tab + // console.log(`[EmailDetail] Emitted internal`, EMAIL_CHANNEL_NAME, notificationPayload); + internalEventEmitter.emit(EMAIL_CHANNEL_NAME, notificationPayload); + }, +} /** * 更新邮件属性 */ export const updateEmailAction = async (params = { opi_sn: 0, mai_sn_list: [], set: {} }) => { + if (isEmpty(params.mai_sn_list)) { + throw new Error('没有需要更新的邮件'); + } const { errcode, result } = await postJSON(`${API_HOST_V3}/mail_update`, params) - return errcode === 0 ? {} : result + if (errcode === 0 ) { + for (const [key, value] of Object.entries(params.set)) { + const updateFun = updateEmailKeyFun[key] || (() => {}); + updateFun(params) + } + } + return errcode === 0 ? result : {} } /** diff --git a/src/hooks/useEmail.js b/src/hooks/useEmail.js index 12968a1..156d93d 100644 --- a/src/hooks/useEmail.js +++ b/src/hooks/useEmail.js @@ -1,11 +1,12 @@ import { useState, useEffect, useCallback } from 'react' import { isEmpty, objectMapper, olog, } from '@/utils/commons' import { readIndexDB } from '@/utils/indexedDB' -import { getEmailDetailAction, postResendEmailAction, getSalesSignatureAction, getEmailOrderAction, queryEmailListAction, getEmailTemplateAction, saveEmailDraftOrSendAction, updateEmailAction } from '@/actions/EmailActions' +import { getEmailDetailAction, postResendEmailAction, getSalesSignatureAction, getEmailOrderAction, queryEmailListAction, getEmailTemplateAction, saveEmailDraftOrSendAction, updateEmailAction, getEmailChangesChannel, EMAIL_CHANNEL_NAME } from '@/actions/EmailActions' import { App } from 'antd' import useConversationStore from '@/stores/ConversationStore'; import { msgStatusRenderMapped } from '@/channel/bubbleMsgUtils'; import { POPUP_FEATURES } from '@/config'; +import { internalEventEmitter } from '@/utils/EventEmitterService'; export const useEmailSignature = (opi_sn) => { @@ -41,7 +42,7 @@ export const useEmailSignature = (opi_sn) => { * - If `number`: 直接传递, 直接获取订单详情 * - If `false`: 不需要获取订单信息 */ -export const useEmailDetail = (mai_sn, data={}, oid=0) => { +export const useEmailDetail = (mai_sn=0, data={}, oid=0) => { const {notification} = App.useApp() const [loading, setLoading] = useState(false) const [mailData, setMailData] = useState({ loading, info: { MAI_COLI_SN: 0 }, content: '', attachments: [], AttachList: [] }) @@ -128,7 +129,9 @@ export const useEmailDetail = (mai_sn, data={}, oid=0) => { try { const { id: savedID } = await saveEmailDraftOrSendAction(body, isDraft) setMaiSN(savedID) - refresh() + if (isDraft) { + refresh() + } return savedID } catch (error) { console.error(error); @@ -151,31 +154,46 @@ export const useEmailList = (mailboxDirNode) => { const [mailList, setMailList] = useState([]) const [error, setError] = useState(null) const [isFreshData, setIsFreshData] = useState(false) - const [refreshTrigger, setRefreshTrigger] = useState(0); + const [refreshTrigger, setRefreshTrigger] = useState(0) const refresh = useCallback(() => { - setRefreshTrigger(prev => prev + 1); - }, []); + setRefreshTrigger((prev) => prev + 1) + }, []) const { OPI_SN: opi_sn, COLI_SN, VKey, VParent, ApplyDate, OrderSourceType, IsTrue } = mailboxDirNode - const markAsRead = useCallback((sn_list) => { - updateEmailAction({ - opi_sn: opi_sn, - mai_sn_list: sn_list, - set: { read: 1} - }); - refresh() - }, []); + const markAsRead = useCallback( + async (sn_list) => { + await updateEmailAction({ + opi_sn: opi_sn, + mai_sn_list: sn_list, + set: { read: 1 }, + }) + }, + [VKey], + ) + + const markAsProcessed = useCallback( + async (sn_list) => { + await updateEmailAction({ + opi_sn: opi_sn, + mai_sn_list: sn_list, + set: { processed: 1 }, + }) + }, + [VKey], + ) - const markAsProcessed = useCallback((sn_list) => { - updateEmailAction({ - opi_sn: opi_sn, - mai_sn_list: sn_list, - set: { processed: 1} - }); - refresh() - }, []); + const loadMailListFromCache = useCallback(async (payload) => { + const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}` + const readCacheIDList = await readIndexDB(cacheKey, 'maillist', 'mailbox') + if (!isEmpty(readCacheIDList)) { + const readCacheListRowsMap = await readIndexDB(readCacheIDList.data, 'listrow', 'mailbox') + const _x = readCacheIDList.data.map((ele) => readCacheListRowsMap.get(ele).data || {}) + setMailList(_x) + setLoading(false) + } + }, [VKey]) const getMailList = useCallback(async () => { console.log('getMailList', mailboxDirNode) @@ -191,15 +209,11 @@ export const useEmailList = (mailboxDirNode) => { setError(null) setIsFreshData(false) - const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}` - const readCacheIDList = await readIndexDB(cacheKey, 'maillist', 'mailbox') - if (!isEmpty(readCacheIDList)) { - const readCacheListRowsMap = await readIndexDB(readCacheIDList.data, 'listrow', 'mailbox') - const _x = readCacheIDList.data.map((ele) => readCacheListRowsMap.get(ele).data || {}) - setMailList(_x) - setLoading(false) - } + // const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}` try { + // 1. 先从缓存读取 + await loadMailListFromCache(); + // 2. 从接口获取最新的列表 const nodeParam = { coli_sn: COLI_SN, order_source_type: OrderSourceType, vkey: VKey, vparent: VParent, mai_senddate1: ApplyDate } const x = await queryEmailListAction({ opi_sn, node: nodeParam }) // 配合List的结构 @@ -217,6 +231,32 @@ export const useEmailList = (mailboxDirNode) => { useEffect(() => { getMailList() + // --- Setup Internal Event Listener --- + const handleInternalUpdate = (event) => { + console.log(`[useEmailList] Received internal event. `, event.detail) + if (event.detail && event.detail.type === 'listrow') { + loadMailListFromCache() + } + } + internalEventEmitter.on(EMAIL_CHANNEL_NAME, handleInternalUpdate) + + // --- Setup BroadcastChannel Listener --- + const channel = getEmailChangesChannel() + const handleMessage = (event) => { + console.log(`[useEmailList] Received channel event. `, event.data) + const cacheKey = isEmpty(COLI_SN) ? `dir-${VKey}` : `order-${VKey}` + if (event.data.type === 'listrow' && cacheKey === event.data.listKey) { + // cacheKey 不相同时, 不需要更新; 邮箱目录不相同 + loadMailListFromCache(event.data) + } + } + channel.addEventListener('message', handleMessage) + + // Cleanup + return () => { + internalEventEmitter.off(EMAIL_CHANNEL_NAME, handleInternalUpdate) + channel.removeEventListener('message', handleMessage) + } }, [getMailList]) return { loading, isFreshData, error, mailList, refresh, markAsRead, markAsProcessed } diff --git a/src/stores/EmailSlice.js b/src/stores/EmailSlice.js index 6234e55..a322525 100644 --- a/src/stores/EmailSlice.js +++ b/src/stores/EmailSlice.js @@ -118,7 +118,7 @@ const emailSlice = (set, get) => ({ if (isEmpty(readCache) || isNeedRefresh) { // > {4} 更新 const rootTree = await getRootMailboxDirAction({ opi_sn, userIdStr }) - console.log('empty', opi_sn, userIdStr, isEmpty(readCache), isNeedRefresh, rootTree); + // console.log('empty', opi_sn, userIdStr, isEmpty(readCache), isNeedRefresh, rootTree); setMailboxNestedDirsActive(rootTree) } return false diff --git a/src/utils/EventEmitterService.js b/src/utils/EventEmitterService.js new file mode 100644 index 0000000..df591ed --- /dev/null +++ b/src/utils/EventEmitterService.js @@ -0,0 +1,20 @@ +class EventEmitterService extends EventTarget { + constructor() { + super(); + // console.log('Internal EventEmitterService created.'); // For debugging + } + + emit(eventName, detail) { + this.dispatchEvent(new CustomEvent(eventName, { detail })); + } + + on(eventName, handler) { + this.addEventListener(eventName, handler); + } + + off(eventName, handler) { + this.removeEventListener(eventName, handler); + } +} + +export const internalEventEmitter = new EventEmitterService(); diff --git a/src/views/Conversations/Online/Components/EmailDetailInline.jsx b/src/views/Conversations/Online/Components/EmailDetailInline.jsx index 11f9b1b..f6adab1 100644 --- a/src/views/Conversations/Online/Components/EmailDetailInline.jsx +++ b/src/views/Conversations/Online/Components/EmailDetailInline.jsx @@ -138,10 +138,10 @@ const EmailDetailInline = ({ mailID, emailMsg = {}, disabled = false, variant, s let btns = [] - const showDoneBtn = mailData.info?.MAI_Direction !== 1 ? true : false - if (showDoneBtn) { - // btns.push() - } + // const showDoneBtn = mailData.info?.MAI_Direction !== 1 ? true : false + // if (showDoneBtn) { + // btns.push() + // } // 没有关联订单的邮件`绑定订单` if (showBindBtn) { btns.push( setShowBindBtn(false)} {...{ conversationid, mai_sn, showBindBtn }} />) diff --git a/src/views/orders/components/MailBox.jsx b/src/views/orders/components/MailBox.jsx index 562fa49..83fab0c 100644 --- a/src/views/orders/components/MailBox.jsx +++ b/src/views/orders/components/MailBox.jsx @@ -165,7 +165,7 @@ const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => {