Merge remote-tracking branch 'origin/dev/2025a' into dev/2025a

# Conflicts:
#	src/hooks/useEmail.js
#	src/views/orders/components/MailBox.jsx
dev/ckeditor
Lei OT 10 months ago
commit 06209430da

@ -1,13 +1,14 @@
import { WhatsAppOutlined, FileAddOutlined, MailOutlined, PhoneOutlined, UserOutlined, FieldNumberOutlined, CompassOutlined, CalendarOutlined, EditOutlined, CheckOutlined } from '@ant-design/icons' import { WhatsAppOutlined, FileAddOutlined, MailOutlined, PhoneOutlined, UserOutlined, FieldNumberOutlined, CompassOutlined, CalendarOutlined, EditOutlined, CheckOutlined } from '@ant-design/icons'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { App, Flex, Select, Tooltip, Tabs, Button, Divider, Typography, Skeleton, Checkbox } from 'antd' import { App, Flex, Select, Tooltip, Divider, Typography, Skeleton, Checkbox } from 'antd'
import { useOrderStore, OrderLabelDefaultOptions, OrderStatusDefaultOptions, remindStatusOptions } from '@/stores/OrderStore' import { useOrderStore, fetchSetRemindStateAction, OrderLabelDefaultOptions, OrderStatusDefaultOptions, remindStatusOptions } from '@/stores/OrderStore'
import { copy } from '@/utils/commons' import { copy, isEmpty } from '@/utils/commons'
const OrderProfile = ({ coliSN, ...props }) => { const OrderProfile = ({ coliSN, ...props }) => {
const { notification, message } = App.useApp() const { notification, message } = App.useApp()
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const orderLabelOptions = copy(OrderLabelDefaultOptions) const orderLabelOptions = copy(OrderLabelDefaultOptions)
orderLabelOptions.unshift({ value: 0, label: '未设置', disabled: true }) orderLabelOptions.unshift({ value: 0, label: '未设置', disabled: true })
@ -20,13 +21,11 @@ const OrderProfile = ({ coliSN, ...props }) => {
s.appendOrderComment, s.appendOrderComment,
]) ])
const [orderRemindState, setOrderRemindState] = useState(orderDetail.remindstate)
useEffect(() => { useEffect(() => {
if (coliSN) { if (coliSN) {
setLoading(true) setLoading(true)
fetchOrderDetail(coliSN) fetchOrderDetail(coliSN)
.then((result) => {
console.info(result)
})
.finally(() => setLoading(false)) .finally(() => setLoading(false))
.catch((reason) => { .catch((reason) => {
notification.error({ notification.error({
@ -40,9 +39,27 @@ const OrderProfile = ({ coliSN, ...props }) => {
return () => {} return () => {}
}, [coliSN]) }, [coliSN])
const regularText = () => { const handleSetRemindState = async (checkedValue) => {
if (orderDetail.buytime > 0) return '(R' + orderDetail.buytime + ')' const state = checkedValue.filter((v) => v !== orderRemindState)
return '' const oldState = orderRemindState
try {
if (isEmpty(state)) {
setOrderRemindState(null)
} else {
setOrderRemindState(state[0])
}
await fetchSetRemindStateAction({ coli_sn: coliSN, remindstate: state })
message.success('设置成功')
} catch (error) {
notification.warning({ message: '设置失败', description: error.message, placement: 'top', duration: 60 })
setOrderRemindState(oldState)
}
}
const getCustomerName = () => {
if (orderDetail.buytime > 0) return customerDetail.name + '(R' + orderDetail.buytime + ')'
return customerDetail.name
} }
return ( return (
@ -62,14 +79,14 @@ const OrderProfile = ({ coliSN, ...props }) => {
})} })}
/> */} /> */}
<Skeleton active loading={loading}> <Skeleton active loading={loading}>
<Flex gap='small' vertical={true} justify='space-between'> <Flex gap='small' vertical={true} justify='space-between' className='p-2'>
<Typography.Text> <Typography.Text>
<FieldNumberOutlined className='pr-1' /> <FieldNumberOutlined className='pr-1' />
{orderDetail.order_no} {orderDetail.order_no}
</Typography.Text> </Typography.Text>
<Typography.Text> <Typography.Text>
<UserOutlined className=' pr-1' /> <UserOutlined className=' pr-1' />
{customerDetail.name + regularText()} {getCustomerName()}
</Typography.Text> </Typography.Text>
<Typography.Text> <Typography.Text>
<CompassOutlined className=' pr-1' /> <CompassOutlined className=' pr-1' />
@ -83,11 +100,7 @@ const OrderProfile = ({ coliSN, ...props }) => {
<MailOutlined className='pr-1' /> <MailOutlined className='pr-1' />
{customerDetail.email} {customerDetail.email}
</Typography.Text> </Typography.Text>
<Typography.Link <Typography.Link href={`/order/chat/${coliSN}`}>
href='#'
onClick={() => {
console.info('whatsapp clicked.')
}}>
<WhatsAppOutlined className=' pr-1' /> <WhatsAppOutlined className=' pr-1' />
{customerDetail.whatsapp_phone_number} {customerDetail.whatsapp_phone_number}
</Typography.Link> </Typography.Link>
@ -116,7 +129,20 @@ const OrderProfile = ({ coliSN, ...props }) => {
width: '100%', width: '100%',
}} }}
variant='underlined' variant='underlined'
onSelect={(value) => {}} onSelect={(value) => {
setOrderPropValue(coliSN, 'orderlabel', value)
.then(() => {
message.success('设置成功')
})
.catch((reason) => {
notification.error({
message: '设置出错',
description: reason.message,
placement: 'top',
duration: 60,
})
})
}}
value={orderDetail.tags} value={orderDetail.tags}
options={orderLabelOptions} options={orderLabelOptions}
/> />
@ -128,7 +154,20 @@ const OrderProfile = ({ coliSN, ...props }) => {
width: '100%', width: '100%',
}} }}
variant='underlined' variant='underlined'
onSelect={(value) => {}} onSelect={(value) => {
setOrderPropValue(coliSN, 'orderstatus', value)
.then(() => {
message.success('设置成功')
})
.catch((reason) => {
notification.error({
message: '设置出错',
description: reason.message,
placement: 'top',
duration: 60,
})
})
}}
value={orderDetail.states} value={orderDetail.states}
options={orderStatusOptions} options={orderStatusOptions}
/> />
@ -137,7 +176,7 @@ const OrderProfile = ({ coliSN, ...props }) => {
<Divider orientation='left'> <Divider orientation='left'>
<Typography.Text strong>催信</Typography.Text> <Typography.Text strong>催信</Typography.Text>
</Divider> </Divider>
<Checkbox.Group key='substatus' className='px-2' options={remindStatusOptions} /> <Checkbox.Group key='substatus' className='px-2' value={[orderRemindState]} options={remindStatusOptions} onChange={handleSetRemindState} />
<Divider orientation='left'> <Divider orientation='left'>
<Typography.Text strong>表单信息</Typography.Text> <Typography.Text strong>表单信息</Typography.Text>

@ -170,6 +170,17 @@ export const useEmailList = (mailboxDirNode) => {
const markAsRead = useCallback( const markAsRead = useCallback(
async (sn_list) => { async (sn_list) => {
// 优化性能的话,需要更新 mailList 数据,
// 但是更新 mailList 会造成页面全部刷新
// 所以还是先用 refresh()
// const updatedMailList = mailList.map(mail => {
// if (sn_list.includes(mail.MAI_SN)) {
// return { ...mail, MOI_ReadState: 1 };
// }
// return mail;
// });
// setMailList(updatedMailList);
// setLoading(true)
await updateEmailAction({ await updateEmailAction({
opi_sn: opi_sn, opi_sn: opi_sn,
mai_sn_list: sn_list, mai_sn_list: sn_list,

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ReloadOutlined, ReadOutlined, CheckSquareOutlined, RightOutlined, LeftOutlined, ExpandOutlined } from '@ant-design/icons' import { ReloadOutlined, ReadOutlined, RightOutlined, LeftOutlined, ExpandOutlined } from '@ant-design/icons'
import { Flex, Button, Tooltip, List, Form, Row, Col, Dropdown, Input, Checkbox, DatePicker, Switch, Breadcrumb, Skeleton } from 'antd' import { Flex, Button, Tooltip, List, Form, Row, Col, Input, Checkbox, DatePicker, Switch, Breadcrumb, Skeleton } 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'
@ -12,7 +12,7 @@ const { RangePicker } = DatePicker
const PAGE_SIZE = 50 // const PAGE_SIZE = 50 //
const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => { const MailBox = ({ mailboxDir, onMailItemClick, ...props }) => {
const DATE_RANGE_PRESETS = [ const DATE_RANGE_PRESETS = [
{ {
label: '本周', label: '本周',
@ -100,27 +100,21 @@ const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => {
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
return ( return (
<li <li className={`flex border border-solid border-t-0 border-x-0 border-gray-200 hover:bg-neutral-50 active:bg-gray-200 p-2 ${props.currentActiveMailItem === item.key ? 'bg-neutral-100' : ''}`}>
className={`flex border border-solid border-t-0 border-x-0 border-gray-200 hover:bg-neutral-50 active:bg-gray-200 p-2 ${props.currentActiveMailItem === item.key ? 'bg-neutral-100' : ''}`}> <div className=''>
<div className=''>
<Checkbox <Checkbox
checked={selectedItems.some((i) => i.MAI_SN === item.MAI_SN)} checked={selectedItems.some((i) => i.MAI_SN === item.MAI_SN)}
onClick={e => { onClick={(e) => {
console.info(item, 'checked: ' + e.target.checked) const isChecked = e.target.checked
const isChecked = e.target.checked; const updatedSelection = isChecked ? [...selectedItems, item] : selectedItems.filter((item) => item.MAI_SN !== item.MAI_SN)
const updatedSelection = isChecked setSelectedItems(updatedSelection)
? [...selectedItems, item] }}></Checkbox>
: selectedItems.filter((item) => item.MAI_SN !== item.MAI_SN) </div>
setSelectedItems(updatedSelection) <div
console.info('selectedItems: ', updatedSelection) className='flex-1 pl-2'
} onClick={() => {
}></Checkbox> onMailItemClick(item)
</div> }}>
<div className='flex-1 pl-2'
onClick={() => {
console.info('item: ', item)
onMailItemClick(item)
}}>
<Flex gap='small' vertical={true} justify='space-between' className='cursor-pointer'> <Flex gap='small' vertical={true} justify='space-between' className='cursor-pointer'>
<div> <div>
{orderNumber} {orderNumber}
@ -141,40 +135,38 @@ const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => {
<Flex wrap gap='middle' justify={'center'} className='min-w-40'> <Flex wrap gap='middle' justify={'center'} className='min-w-40'>
<Tooltip title='全选'> <Tooltip title='全选'>
<Checkbox <Checkbox
indeterminate={ indeterminate={selectedItems.length > 0 && selectedItems.length < pagination.pagedList.length}
selectedItems.length > 0 && checked={pagination.pagedList.length === 0 ? false : pagination.pagedList.every((item) => selectedItems.some((selected) => selected.MAI_SN === item.MAI_SN))}
selectedItems.length < pagination.pagedList.length onChange={(e) => {
} const isChecked = e.target.checked
checked={pagination.pagedList.length === 0 ? false : pagination.pagedList.every((item) =>
selectedItems.some((selected) => selected.MAI_SN === item.MAI_SN)
)}
onChange={(e) => {
const isChecked = e.target.checked;
if (isChecked) { if (isChecked) {
setSelectedItems((prev) => [ setSelectedItems((prev) => [...prev, ...pagination.pagedList])
...prev,
...pagination.pagedList,
]);
} else { } else {
setSelectedItems([]); setSelectedItems([])
} }
}} }}></Checkbox>
></Checkbox>
</Tooltip> </Tooltip>
<Tooltip title='标记已读'> <Tooltip title='标记已读'>
<Button shape='circle' type='text' size='small' icon={<MailOpenIcon />} <Button
onClick={() => { shape='circle'
console.info('markAsRead: ', selectedItems.map((item) => item.MAI_SN)) type='text'
size='small'
icon={<MailOpenIcon />}
onClick={() => {
markAsRead(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([])) markAsRead(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}} }}
/> />
</Tooltip> </Tooltip>
<Tooltip title='已处理'> <Tooltip title='已处理'>
<Button shape='circle' type='text' size='small' icon={<MailCheckIcon />} <Button
onClick={() => { shape='circle'
console.info('markAsProcessed: ', selectedItems.map((item) => item.MAI_SN)) type='text'
size='small'
icon={<MailCheckIcon />}
onClick={() => {
markAsProcessed(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([])) markAsProcessed(selectedItems.map((item) => item.MAI_SN)).then(() => setSelectedItems([]))
}} /> }}
/>
</Tooltip> </Tooltip>
<Tooltip title='刷新'> <Tooltip title='刷新'>
<Button shape='circle' type='text' size='small' icon={<ReloadOutlined />} onClick={refresh} /> <Button shape='circle' type='text' size='small' icon={<ReloadOutlined />} onClick={refresh} />
@ -219,51 +211,51 @@ const MailBox = ({ mailboxDir, onMailItemClick, onSelect, ...props }) => {
</Form> </Form>
</div> </div>
<div className='bg-white overflow-y-auto' style={{ height: 'calc(100vh - 198px)' }}> <div className='bg-white overflow-y-auto px-2' style={{ height: 'calc(100vh - 198px)' }}>
<Skeleton active loading={loading}> <Skeleton active loading={loading}>
<List <List
loading={loading} loading={loading}
header={ header={
<Flex align='center' justify='space-between'>
<Breadcrumb
items={props.breadcrumb.map((bc) => {
return {
title: (
<>
<MailboxDirIcon type={bc?.iconIndex} />
<span>{bc.title}</span>
</>
),
}
})}
/>
<Flex align='center' justify='space-between'> <Flex align='center' justify='space-between'>
<span>已选: {selectedItems.length} </span> <Breadcrumb
<span> items={props.breadcrumb.map((bc) => {
{(pagination.current - 1) * PAGE_SIZE + 1}-{Math.min(pagination.current * PAGE_SIZE, pagination.total)} of {pagination.total} return {
</span> title: (
<Button <>
icon={<LeftOutlined />} <MailboxDirIcon type={bc?.iconIndex} />
type='text' <span>{bc.title}</span>
onClick={() => { </>
prePage() ),
}} }
iconPosition={'end'}></Button> })}
<Button />
icon={<RightOutlined />} <Flex align='center' justify='space-between'>
type='text' <span>已选: {selectedItems.length} </span>
onClick={() => { <span>
nextPage() {(pagination.current - 1) * PAGE_SIZE + 1}-{Math.min(pagination.current * PAGE_SIZE, pagination.total)} of {pagination.total}
}} </span>
iconPosition={'end'}></Button> <Button
icon={<LeftOutlined />}
type='text'
onClick={() => {
prePage()
}}
iconPosition={'end'}></Button>
<Button
icon={<RightOutlined />}
type='text'
onClick={() => {
nextPage()
}}
iconPosition={'end'}></Button>
</Flex>
</Flex> </Flex>
</Flex> }
} itemLayout='vertical'
itemLayout='vertical' pagination={false}
pagination={false} dataSource={pagination.pagedList}
dataSource={pagination.pagedList} renderItem={mailItemRender}
renderItem={mailItemRender} />
/>
</Skeleton> </Skeleton>
</div> </div>
</> </>

Loading…
Cancel
Save