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 }) => {
}
onClick={() => {
console.info('markAsRead: ', selectedItems.map((item) => item.MAI_SN))
- markAsRead(selectedItems.map((item) => item.MAI_SN))
+ markAsRead(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}}
/>
@@ -173,7 +173,7 @@ const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => {
}
onClick={() => {
console.info('markAsProcessed: ', selectedItems.map((item) => item.MAI_SN))
- markAsProcessed(selectedItems.map((item) => item.MAI_SN))
+ markAsProcessed(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}} />