feat: 邮箱目录: 数量

dev/ckeditor
Lei OT 2 weeks ago
parent 3f9cc81b30
commit 812bf19c26

@ -165,7 +165,8 @@ const todoTypes = {
} }
/** /**
* 顾问的邮箱目录 * 顾问的邮箱目录
* @param {object} { opi_sn, year, by_start_date, by_success, important, if_want_book, if_thinking } * @param {object} params { opi_sn, year, by_start_date, by_success, important, if_want_book, if_thinking }
* @param {boolean} retOrder 是否直接返回订单列表 -- 忽略
*/ */
export const getEmailDirAction = async (params = { opi_sn: '' }, retOrder=false) => { export const getEmailDirAction = async (params = { opi_sn: '' }, retOrder=false) => {
const defaultParams = { opi_sn: 0, year: dayjs().year(), by_start_date: -1, by_success: -1, important: -1, if_want_book: -1, if_thinking: -1 } const defaultParams = { opi_sn: 0, year: dayjs().year(), by_start_date: -1, by_success: -1, important: -1, if_want_book: -1, if_thinking: -1 }
@ -177,6 +178,35 @@ 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) => {
const defaultParams = {
opi_sn: 0,
// date1: dayjs().subtract(1, 'year').startOf('year').format(DATE_FORMAT),
date1: dayjs().subtract(180, 'days').format(DATE_FORMAT),
date2: dayjs().format(DATEEND_FORMAT)
}
const { errcode, result } = await fetchJSON(`${API_HOST_V3}/dir_count`, {...defaultParams, ...params})
const ret = errcode !== 0 ? { [`${params.opi_sn}`]: {} } : { [`${params.opi_sn}`]: result }
// 更新数量
if (update !== false) {
const readCacheDir = await readIndexDB(Number(params.opi_sn), 'dirs', 'mailbox');
const mailboxDir = isEmpty(readCacheDir) ? [] : readCacheDir.tree.filter(node => node._raw.IsTrue === 1);
const _MapDir = new Map(mailboxDir.map((obj) => [obj.key, obj]))
Object.keys(result).map(dirKey => {
_MapDir.set(Number(dirKey), {..._MapDir.get(Number(dirKey)), count: result[dirKey]});
})
console.log(_MapDir, '_MapDir')
const _newToUpdate = Array.from(_MapDir.values());
const _MapRoot = new Map((readCacheDir?.tree || []).map((obj) => [obj.key, obj]))
_newToUpdate.forEach((row) => {
_MapRoot.set(row.key, row)
})
const _newRoot = Array.from(_MapRoot.values())
writeIndexDB([{ key: Number(params.opi_sn), tree: _newRoot }], 'dirs', 'mailbox')
}
return ret;
};
export const getTodoOrdersAction = async (params) => { export const getTodoOrdersAction = async (params) => {
const opi_arr = params.opisn.split(',') const opi_arr = params.opisn.split(',')
const defaultStickyTree = opi_arr.reduce( const defaultStickyTree = opi_arr.reduce(
@ -211,7 +241,7 @@ export const getTodoOrdersAction = async (params) => {
{}, {},
) )
const { errcode, result } = await fetchJSON(`${API_HOST}/getwlorder`, params) const { errcode, result } = await fetchJSON(`${API_HOST}/getwlorder`, params)
// 取后一个状态, 因此翻转两次 // 订单重复时, 取后一个状态, 因此翻转两次
const _result_unique = uniqWith(result.reverse(), (a, b) => a.COLI_SN === b.COLI_SN).reverse(); const _result_unique = uniqWith(result.reverse(), (a, b) => a.COLI_SN === b.COLI_SN).reverse();
const orderList = errcode === 0 ? _result_unique : [] const orderList = errcode === 0 ? _result_unique : []
const byOPI = groupBy(orderList, 'OPI_SN') const byOPI = groupBy(orderList, 'OPI_SN')
@ -286,7 +316,9 @@ 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 mailboxDirByOPI = mailboxDir.reduce((a, c) => ({...a, ...c}), {}); const mailBoxCount = await Promise.all(userIdStr.split(',').map(_opi => getMailboxCountAction({ opi_sn: _opi })));
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 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] || [])] }))
writeIndexDB(rootTree, 'dirs', 'mailbox') writeIndexDB(rootTree, 'dirs', 'mailbox')
const _mapped = groupBy(rootTree, 'key') const _mapped = groupBy(rootTree, 'key')
@ -304,7 +336,8 @@ export const queryEmailListAction = async ({ opi_sn = '', pagesize = 10, last_id
vkey: 0, vkey: 0,
vparent: 0, vparent: 0,
order_source_type: 0, order_source_type: 0,
mai_senddate1: dayjs().subtract(1, 'year').startOf('year').format(DATE_FORMAT), // mai_senddate1: dayjs().subtract(1, 'year').startOf('year').format(DATE_FORMAT),
mai_senddate1: dayjs().subtract(180, 'days').format(DATE_FORMAT),
mai_senddate2: dayjs().format(DATEEND_FORMAT), mai_senddate2: dayjs().format(DATEEND_FORMAT),
...omitEmpty({ ...omitEmpty({
...node, ...node,

@ -54,7 +54,7 @@ export const useEmailDetail = (mai_sn=0, data={}, oid=0, markRead=false) => {
const refresh = useCallback(() => { const refresh = useCallback(() => {
setRefreshTrigger(prev => prev + 1); setRefreshTrigger(prev => prev + 1);
}, []); }, []);
console.log(maiSN, 'mailSN', mai_sn) // console.log(maiSN, 'mailSN', mai_sn)
// const [updateMessageItem] = useConversationStore(state => [state.updateMessageItem]); // const [updateMessageItem] = useConversationStore(state => [state.updateMessageItem]);

@ -179,7 +179,7 @@ const websocketSlice = (set, get) => ({
logWebsocket(data, 'I'); logWebsocket(data, 'I');
// olog('websocket Messages ----', data); // olog('websocket Messages ----', data);
// console.log(data); // console.log(data);
const { updateMessageItem, sentOrReceivedNewMessage, addGlobalNotify, setWai, addToConversationList } = get(); const { updateMessageItem, sentOrReceivedNewMessage, addGlobalNotify, setWai, addToConversationList, updateMailboxCount } = get()
const { errcode, errmsg, result } = data; const { errcode, errmsg, result } = data;
if (!result) { if (!result) {
@ -195,6 +195,14 @@ const websocketSlice = (set, get) => ({
const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj); const msgRender = receivedMsgTypeMapped[resultType].contentToRender(msgObj);
const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj); const msgUpdate = receivedMsgTypeMapped[resultType].contentToUpdate(msgObj);
// console.log('msgRender msgUpdate', msgRender, msgUpdate); // console.log('msgRender msgUpdate', msgRender, msgUpdate);
if (['email.updated', 'email.inbound.received',].includes(resultType)) {
updateMailboxCount({ opi_sn: msgObj.opi_sn })
if (!isEmpty(msgRender)) {
const msgNotify = receivedMsgTypeMapped[resultType].contentToNotify(msgObj);
addGlobalNotify(msgNotify);
}
return false;
}
if ([ if ([
'whatsapp.message.updated', 'message', 'error', 'whatsapp.message.updated', 'message', 'error',
'email.updated', 'wai.message.updated', 'email.updated', 'wai.message.updated',

@ -1,5 +1,5 @@
import { getEmailDirAction, getRootMailboxDirAction } from '@/actions/EmailActions' import { getEmailDirAction, getMailboxCountAction, getRootMailboxDirAction } from '@/actions/EmailActions'
import { buildTree, isEmpty, 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';
/** /**
@ -85,18 +85,14 @@ const emailSlice = (set, get) => ({
setMailboxNestedDirsActive: (dir) => { setMailboxNestedDirsActive: (dir) => {
return set(() => ({ mailboxNestedDirsActive: dir })) return set(() => ({ mailboxNestedDirsActive: dir }))
}, },
updateMailboxNestedDirs: (dirs) => { updateCurrentMailboxNestedDirs: (dirs) => {
const { mailboxNestedDirsActive } = get();
const keep = mailboxNestedDirsActive.filter((ele) => isEmpty(ele.parent))
return set(() => ({ mailboxNestedDirsActive: [...keep, ...dirs] }))
},
addMailboxNestedDirs: dirs => {
const { mailboxNestedDirsActive } = get() const { mailboxNestedDirsActive } = get()
const _Map = new Map(mailboxNestedDirsActive.map((obj) => [obj.key, obj])) const _Map = new Map(mailboxNestedDirsActive.map((obj) => [obj.key, obj]))
dirs.forEach((row) => { dirs.forEach((row) => {
_Map.set(row.key, row) _Map.set(row.key, row)
}) })
const _newValue = sortArrayByOrder(Array.from(_Map.values()), 'key', ['search-orders']) // const _newValue = sortArrayByOrder(Array.from(_Map.values()), 'key', ['search-orders'])
const _newValue = Array.from(_Map.values())
return set(() => ({ mailboxNestedDirsActive: _newValue })) return set(() => ({ mailboxNestedDirsActive: _newValue }))
}, },
@ -119,33 +115,46 @@ const emailSlice = (set, get) => ({
return set(() => ({ mailboxActiveCOLI: coli })) return set(() => ({ mailboxActiveCOLI: coli }))
}, },
getOPIEmailDir: async (opi_sn = 0, userIdStr='') => { getOPIEmailDir: async (opi_sn = 0, userIdStr = '', refreshNow = false) => {
// console.log('🌐requesting opi dir', opi_sn, typeof opi_sn) // console.log('🌐requesting opi dir', opi_sn, typeof opi_sn)
const { setMailboxNestedDirsActive } = get() const { setMailboxNestedDirsActive, updateMailboxCount } = get()
const readCache = await readIndexDB(Number(opi_sn), 'dirs', 'mailbox') const readCache = await readIndexDB(Number(opi_sn), 'dirs', 'mailbox')
// console.log(readCache); // console.log(readCache);
let isNeedRefresh = false let isNeedRefresh = refreshNow
if (!isEmpty(readCache)) { if (!isEmpty(readCache)) {
setMailboxNestedDirsActive(readCache?.tree || []) setMailboxNestedDirsActive(readCache?.tree || [])
isNeedRefresh = Date.now() - readCache.timestamp > 1 * 60 * 60 * 1000 isNeedRefresh = refreshNow || Date.now() - readCache.timestamp > 1 * 60 * 60 * 1000
// isNeedRefresh = true; // test: 0 // isNeedRefresh = true; // test: 0
} }
if (isEmpty(readCache) || isNeedRefresh) { if (isEmpty(readCache) || isNeedRefresh) {
// > {4} 更新 // > {4} 更新
const rootTree = await getRootMailboxDirAction({ opi_sn, userIdStr }) const rootTree = await getRootMailboxDirAction({ opi_sn, userIdStr: String(userIdStr || opi_sn) })
// console.log('empty', opi_sn, userIdStr, isEmpty(readCache), isNeedRefresh, rootTree); // console.log('empty', opi_sn, userIdStr, isEmpty(readCache), isNeedRefresh, rootTree);
setMailboxNestedDirsActive(rootTree) setMailboxNestedDirsActive(rootTree)
} else {
// 只更新数量
updateMailboxCount({ opi_sn })
} }
return false return false
}, },
async initMailbox({ opi_sn, dei_sn, userId, userIdStr }) { // 更新数量
updateMailboxCount: async ({ opi_sn }) => {
const { setMailboxNestedDirsActive } = get()
await getMailboxCountAction({ opi_sn }, true)
const readCache = await readIndexDB(Number(opi_sn), 'dirs', 'mailbox')
if (!isEmpty(readCache)) {
setMailboxNestedDirsActive(readCache?.tree || [])
}
},
async initMailbox({ opi_sn, dei_sn, userIdStr }) {
olog('initMailbox ---- ')
const { setCurrentMailboxOPI, setCurrentMailboxDEI, getOPIEmailDir } = get() const { setCurrentMailboxOPI, setCurrentMailboxDEI, getOPIEmailDir } = 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) getOPIEmailDir(opi_sn, userIdStr, true)
}, },
}) })
export default emailSlice export default emailSlice

@ -1,7 +1,7 @@
import useAuthStore from '@/stores/AuthStore' import useAuthStore from '@/stores/AuthStore'
import { pick } from '@/utils/commons' import { pick } from '@/utils/commons'
import { UnorderedListOutlined, LeftOutlined } from '@ant-design/icons' import { UnorderedListOutlined, LeftOutlined } from '@ant-design/icons'
import { Flex, Segmented, Tree, Typography, Layout, Splitter, Button, Tooltip } from 'antd' import { Flex, Segmented, Tree, Typography, Layout, Splitter, Button, Tooltip, Badge } from 'antd'
import { useEffect, useMemo, useState } from 'react' import { useEffect, useMemo, useState } from 'react'
import EmailDetailInline from '../Conversations/Online/Components/EmailDetailInline' import EmailDetailInline from '../Conversations/Online/Components/EmailDetailInline'
import OrderProfile from '@/components/OrderProfile' import OrderProfile from '@/components/OrderProfile'
@ -134,11 +134,13 @@ function Follow() {
<Flex justify='start' align='start' vertical className='h-full'> <Flex justify='start' align='start' vertical className='h-full'>
<Segmented className='w-full' block shape='round' options={accountDEI} value={currentMailboxDEI} onChange={handleSwitchAccount} /> <Segmented className='w-full' block shape='round' options={accountDEI} value={currentMailboxDEI} onChange={handleSwitchAccount} />
<div className='overflow-y-auto flex-auto w-full [&_.ant-tree-switcher]:me-0 [&_.ant-tree-node-content-wrapper]:px-0 [&_.ant-tree-node-content-wrapper]:text-ellipsis [&_.ant-tree-node-content-wrapper]:overflow-hidden [&_.ant-tree-node-content-wrapper]:whitespace-nowrap'> <div className='overflow-y-auto flex-auto w-full [&_.ant-tree-switcher]:me-0 [&_.ant-tree-node-content-wrapper]:px-0 [&_.ant-tree-node-content-wrapper]:text-ellipsis [&_.ant-tree-node-content-wrapper]:overflow-hidden [&_.ant-tree-node-content-wrapper]:whitespace-nowrap'>
<Tree className='[&_.ant-typography-ellipsis]:max-w-44 [&_.ant-typography-ellipsis]:min-w-36' <Tree
className='[&_.ant-typography-ellipsis]:max-w-44 [&_.ant-typography-ellipsis]:min-w-36'
key='sticky-today' key='sticky-today'
blockNode blockNode
showIcon showIcon
showLine autoExpandParent={true} showLine
autoExpandParent={true}
expandAction={'doubleClick'} expandAction={'doubleClick'}
onSelect={handleTreeSelectGetMails} onSelect={handleTreeSelectGetMails}
selectedKeys={[mailboxActiveNode.key]} selectedKeys={[mailboxActiveNode.key]}
@ -147,7 +149,12 @@ function Follow() {
defaultExpandedKeys={expandTree} defaultExpandedKeys={expandTree}
treeData={mailboxNestedDirsActive} treeData={mailboxNestedDirsActive}
icon={(node) => <MailboxDirIcon type={node?.iconIndex} />} icon={(node) => <MailboxDirIcon type={node?.iconIndex} />}
titleRender={(node) => <Typography.Text ellipsis={{ tooltip: node.title }} className={`${node?._raw?.IsSuccess===1 ? 'text-primary' : ''}`}>{node.title}</Typography.Text>} titleRender={(node) => (
<Typography.Text ellipsis={{ tooltip: node.title }} className={`${node?._raw?.IsSuccess === 1 ? 'text-primary' : ''}`}>
{node.title}
<Badge size={'small'} count={node.count} offset={[3, 0]} style={{backgroundColor: "transparent", color: '#1ba784'}} overflowCount={999} />
</Typography.Text>
)}
/> />
</div> </div>
</Flex> </Flex>
@ -163,8 +170,13 @@ function Follow() {
</Splitter> </Splitter>
</Layout.Content> </Layout.Content>
<Tooltip title={(collapsed ? '展开' : '收起')+'订单信息'} placement='left' > <Tooltip title={(collapsed ? '展开' : '收起') + '订单信息'} placement='left'>
<Button icon={collapsed ? <LeftOutlined /> : <UnorderedListOutlined />} onClick={() => setCollapsed(!collapsed)} className={`absolute z-10 rounded-none ${collapsed ? 'right-1 top-36 rounded-l-xl' : 'right-8 top-20 rounded-l'}`} size={collapsed ? 'small' : 'middle'} /> <Button
icon={collapsed ? <LeftOutlined /> : <UnorderedListOutlined />}
onClick={() => setCollapsed(!collapsed)}
className={`absolute z-10 rounded-none ${collapsed ? 'right-1 top-36 rounded-l-xl' : 'right-8 top-20 rounded-l'}`}
size={collapsed ? 'small' : 'middle'}
/>
</Tooltip> </Tooltip>
<Layout.Sider <Layout.Sider
width='280' width='280'

@ -8,7 +8,7 @@ import useConversationStore from '@/stores/ConversationStore'
const MailOrderSearchModal = ({ ...props }) => { const MailOrderSearchModal = ({ ...props }) => {
const [currentMailboxOPI] = useConversationStore((state) => [state.currentMailboxOPI]) const [currentMailboxOPI] = useConversationStore((state) => [state.currentMailboxOPI])
const [addMailboxNestedDirs, updateMailboxNestedDirs, setMailboxActiveNode] = useConversationStore((state) => [state.addMailboxNestedDirs, state.updateMailboxNestedDirs, state.setMailboxActiveNode]) const [updateCurrentMailboxNestedDirs, setMailboxActiveNode] = useConversationStore((state) => [state.updateCurrentMailboxNestedDirs, state.setMailboxActiveNode])
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const [form] = Form.useForm() const [form] = Form.useForm()
@ -31,7 +31,7 @@ const MailOrderSearchModal = ({ ...props }) => {
if (isEmpty(valuesToSub.coli_id)) { if (isEmpty(valuesToSub.coli_id)) {
const { coli_id, sourcetype, ...mailboxParams} = valuesToSub; const { coli_id, sourcetype, ...mailboxParams} = valuesToSub;
result = await getEmailDirAction({ ...mailboxParams, opi_sn: currentMailboxOPI }, false) result = await getEmailDirAction({ ...mailboxParams, opi_sn: currentMailboxOPI }, false)
updateMailboxNestedDirs(result[`${currentMailboxOPI}`]) updateCurrentMailboxNestedDirs(result[`${currentMailboxOPI}`])
} else { } else {
const htOrderParams = pick(valuesToSub, ['coli_id', 'sourcetype',]) const htOrderParams = pick(valuesToSub, ['coli_id', 'sourcetype',])
result = await queryHTOrderListAction({...htOrderParams, opi_sn: currentMailboxOPI}) result = await queryHTOrderListAction({...htOrderParams, opi_sn: currentMailboxOPI})
@ -42,7 +42,7 @@ const MailOrderSearchModal = ({ ...props }) => {
_raw: { COLI_SN: 0, IsTrue: 0 }, _raw: { COLI_SN: 0, IsTrue: 0 },
children: result.map((o) => ({ key: `search-${o.COLI_SN}`, title: `${o.COLI_ID}`, iconIndex: 13, parent: 'search-orders', parentTitle: '搜索结果', parentIconIndex: 'search', _raw: { ...o, VKey: o.COLI_SN, VName: o.COLI_ID, VParent: 'search-orders', IsTrue: 0, ApplyDate: '', OrderSourceType: htOrderParams.sourcetype, parent: 'search-orders' }, })), children: result.map((o) => ({ key: `search-${o.COLI_SN}`, title: `${o.COLI_ID}`, iconIndex: 13, parent: 'search-orders', parentTitle: '搜索结果', parentIconIndex: 'search', _raw: { ...o, VKey: o.COLI_SN, VName: o.COLI_ID, VParent: 'search-orders', IsTrue: 0, ApplyDate: '', OrderSourceType: htOrderParams.sourcetype, parent: 'search-orders' }, })),
} }
addMailboxNestedDirs([addToTree]); updateCurrentMailboxNestedDirs([addToTree]);
setMailboxActiveNode(addToTree); setMailboxActiveNode(addToTree);
} }
setLoading(false) setLoading(false)

Loading…
Cancel
Save