Commit 7534953c authored by 李腾's avatar 李腾

feat: 完成售后订单列表逻辑

parent 17b8b15c
......@@ -148,24 +148,24 @@ export default {
},
{
title: '商户管理后台',
path: '/afterSaleManageNew',
name: 'afterSaleManageNew',
path: '/afterSaleManageOld',
name: 'afterSaleManageOld',
icon: 'smile',
component: './AfterSaleManageNew/index',
component: './AfterSaleManageOld/index',
},
{
title: '商户管理后台',
path: '/auditPending',
name: 'auditPending',
icon: 'smile',
component: './AfterSaleManage/Pending',
component: './AfterSaleManageOld/Pending',
},
{
title: '商户管理后台',
path: '/passAudit',
name: 'passAudit',
icon: 'smile',
component: './AfterSaleManage/PassAudit',
component: './AfterSaleManageOld/PassAudit',
},
{
title: '商户管理后台',
......
import React, { useState, useEffect, forwardRef, useImperativeHandle } from 'react';
import { Modal, Tabs, Pagination } from 'antd';
import { Modal, Tabs, Pagination, Spin, notification, Checkbox, Badge } from 'antd';
import { apiGetBussinessMsgList, apiGetBusinessMsgUnReadCount } from '@/services/messageReminder';
import { connect } from 'dva';
import Empty from '../Empty';
import styles from './index.less';
import { CHANNEL_ID } from '@/utils/constants';
const Complex = (props, ref) => {
const INIT_PAGINATION = {
pageSize: 10,
pageNo: 1,
};
const INIT_QUERY_PARAMS = {
type: '0',
readStatus: '',
};
const MessageItem = props => {
const { item, onMark } = props;
/**
* type: 0订单消息,1售后消息
* readStatus: 0未读,1已读
*/
const { readStatus, type } = item;
let message = {};
try {
message = JSON.parse(item.sendContent || '{}');
} catch (e) {
console.error('消息数据格式错误');
}
return (
<div className={styles['complex-list__item']}>
<div className={styles['complex-list__item--header']}>
<span className={styles['order-number']}> 订单编号:{message.orderNo}</span>
<span className={styles.time}>订单时间:{message.createdAt}</span>
{readStatus === 0 ? (
<a className={styles['read-status']} onClick={() => onMark([item.id])}>
标记为已读
</a>
) : (
<span className={styles['read-status--read']}>已读</span>
)}
</div>
<div className={styles['complex-list__item--body']}>
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
</div>
<div className={styles['complex-list__item--footer']}>
<div className={styles.actions}>
<a className={styles.notice}>新的xxx订单,请查看</a>
</div>
</div>
</div>
);
};
const Complex = props => {
const { dispatch, refInstance } = props;
const [visible, setVisible] = useState(false);
const [dataTotal, setDataTotal] = useState(10);
const [pagination, setPagination] = useState({
pageSize: 20,
pageNo: 1,
});
const [loading, setLoading] = useState(false);
const [messageData, setMessageData] = useState([]);
// 未读消息
const [orderUnReadCount, setOrderUnReadCount] = useState(0);
const [afterUnReadCount, setAfterUnReadCount] = useState(0);
const [queryParams, setQueryParams] = useState({ ...INIT_QUERY_PARAMS, ...INIT_PAGINATION });
const userInfo = JSON.parse(localStorage.getItem('user') || '{}');
// 获取信息列表
const getMsgList = async () => {
const params = {
pageSize: queryParams.pageSize,
pageNo: queryParams.pageNo,
};
const data = {
channelId: CHANNEL_ID,
bussinessId: userInfo.supplierCode,
...queryParams,
};
delete data.pageSize;
delete data.pageNo;
setLoading(true);
const res = await apiGetBussinessMsgList(data, params);
setLoading(false);
if (res.code !== '0000') {
notification.error(res.msg);
return;
}
const { content, totalElements } = res.data;
setMessageData(content);
setDataTotal(totalElements);
};
// 获取未读数量
const getMsgReadCount = async () => {
const data = {
channelId: CHANNEL_ID,
bussinessId: userInfo.supplierCode,
};
const res = await apiGetBusinessMsgUnReadCount(data);
if (res.code !== '0000') {
notification.error(res.msg);
return;
}
const { afterSalesUnRead, orderUnRead } = res.data;
setOrderUnReadCount(orderUnRead);
setAfterUnReadCount(afterSalesUnRead);
};
// 分页操作
const onPageChange = (page, size) => {
const current = pagination.pageSize !== size ? 1 : page;
setPagination({
const current = queryParams.pageSize !== size ? 1 : page;
setQueryParams({
...queryParams,
pageNo: current,
pageSize: size,
});
};
useEffect(() => {}, [pagination]);
// 筛选未读/已读
const onReadStatusChange = e => {
let { value } = e.target;
if (queryParams.readStatus === e.target.value) {
value = '';
}
setQueryParams({
...queryParams,
readStatus: value,
});
};
// 切换消息类型
const onTabChange = index => {
setQueryParams({
...queryParams,
...INIT_PAGINATION,
type: index,
});
};
// 标记已读
const onMark = idList => {
if (!idList.length) {
return;
}
const payload = {
channelId: CHANNEL_ID,
bussinessId: userInfo.supplierCode,
idList,
};
dispatch({
type: 'messageReminder/setMarkRead',
payload,
options: {
setLoading,
},
});
};
const open = () => {
setVisible(true);
getMsgReadCount();
};
const close = () => {
setVisible(false);
};
useImperativeHandle(ref, () => ({
// 展开初始化
useEffect(() => {
if (visible) {
getMsgList();
}
}, [visible, queryParams]);
useEffect(() => {
if (!visible) {
setQueryParams({ ...INIT_QUERY_PARAMS, ...INIT_PAGINATION });
}
}, [visible]);
useImperativeHandle(refInstance, () => ({
open,
}));
const modalProps = {
bodyStyle: {
display: 'flex',
flexWrap: 'wrap',
padding: 0,
},
width: '1000px',
height: '600px',
visible,
......@@ -42,95 +201,97 @@ const Complex = (props, ref) => {
onCancel: close,
};
const TabRender = tabProps => {
const { title, count = 0 } = tabProps;
return (
<Modal {...modalProps}>
<Tabs tabPosition="left">
<Tabs.TabPane
tab={
<span>
1111<span style={{ background: 'red' }}>abc</span>
{title}
<Badge size="small" overflowCount={999} count={count} />
</span>
}
key="1"
>
<div className={styles['complex-list']}>
<div className={styles['complex-list__item']}>
<div className={styles['complex-list__item--header']}>
<span className={styles.notice}>新的xxx订单,请查看</span>
<span className={styles.orderNumber}> 订单编号:1562742258351251456</span>
<span className={styles.time}>2022-10-13 12:22:03</span>
</div>
<div className={styles['complex-list__item--body']}>
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
);
};
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
</div>
<div className={styles['complex-list__item--footer']}>
<div className={styles.actions}>
<a>标记为已读</a>
<a>立即查看</a>
</div>
</div>
const FilterRender = filterProps => {
console.log(filterProps);
return (
<div className={styles['filter-box']}>
<div className={styles['filter-box__content']}>
<Checkbox
checked={queryParams.readStatus === '0'}
value="0"
onChange={onReadStatusChange}
>
未读
</Checkbox>
<Checkbox
checked={queryParams.readStatus === '1'}
value="1"
onChange={onReadStatusChange}
>
已读读
</Checkbox>
</div>
<div className={styles['complex-list__item']}>
<div className={styles['complex-list__item--header']}>
<span className={styles.notice}>新的xxx订单,请查看</span>
<span className={styles.orderNumber}> 订单编号:1562742258351251456</span>
<span className={styles.time}>2022-10-13 12:22:03</span>
<div className={styles['filter-box__actions']}>
<a
onClick={() =>
onMark(messageData.filter(message => message.readStatus === 0).map(item => item.id))
}
>
全部标记为已读
</a>
</div>
<div className={styles['complex-list__item--body']}>
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
);
};
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
return (
<Modal {...modalProps}>
<Tabs
className={styles.tabs}
activeKey={queryParams.type}
tabPosition="left"
onChange={onTabChange}
>
<Tabs.TabPane tab={<TabRender title="订单消息" count={orderUnReadCount} />} key="0" />
<Tabs.TabPane tab={<TabRender title="售后消息" count={afterUnReadCount} />} key="1" />
</Tabs>
<div className={styles.good}>
<span className={styles.good__name}>商品名称商品名称商品名称商品名称</span>
<span className={styles.good__count}>x10</span>
</div>
</div>
<div className={styles['complex-list__item--footer']}>
<div className={styles.actions}>
<a>标记为已读</a>
<a>立即查看</a>
</div>
</div>
<div className={styles['tab-pane']}>
<Spin spinning={loading}>
<FilterRender />
<div className={styles['complex-list']}>
{messageData.length ? (
messageData.map(item => <MessageItem key={item.id} item={item} onMark={onMark} />)
) : (
<Empty text="暂无数据" />
)}
</div>
</Spin>
</div>
</Tabs.TabPane>
<Tabs.TabPane tab="售后消息" key="2">
<div className={styles['complex-list']}></div>
</Tabs.TabPane>
</Tabs>
{dataTotal > 0 ? (
<div className={styles.pagination}>
<Pagination
onChange={onPageChange}
total={dataTotal}
showTotal={(total, range) => `第${range[0]}-${range[1]}条 /总共${total}条`}
pageSize={pagination.pageSize}
current={pagination.pageNo}
showSizeChanger
pageSize={queryParams.pageSize}
current={queryParams.pageNo}
/>
</div>
) : (
''
)}
</Modal>
);
};
export default forwardRef(Complex);
const MiddleComponent = connect(({ messageReminder }) => ({
unReadCount: messageReminder.unReadCount,
unReadData: messageReminder.unReadData,
}))(Complex);
// 注意:这里不要在Component上使用ref;换个属性名字比如refInstance;不然会导致覆盖
export default forwardRef((props, ref) => <MiddleComponent {...props} refInstance={ref} />);
.tab-pane {
flex: 1;
}
.filter-box {
display: flex;
margin-top: 15px;
padding-right: 25px;
&__content {
flex: 1;
}
// &__actions{
// }
}
.complex-list {
height: 500px;
margin-top: 20px;
padding-right: 25px;
overflow-y: auto;
border: 1px solid #efefef;
& :first-child {
margin: 0;
}
&__item {
margin-top: 15px;
border: 1px solid #efefef;
&--header {
display: flex;
width: 100%;
height: 40px;
padding: 0 20px;
padding: 0 15px;
line-height: 40px;
background: #f8f8f8;
.orderNumber {
background: #fff;
border-bottom: 1px solid #efefef;
.order-number,
.time {
flex: 1;
padding: 0 20px;
padding-right: 20px;
color: #999;
}
.read-status {
&--read {
color: #999;
}
.notice {
color: green;
}
}
&--body {
......@@ -37,16 +60,30 @@
&--footer {
display: flex;
justify-content: end;
padding: 0 15px;
border-top: 1px solid #efefef;
border-bottom: 1px solid #efefef;
.actions {
display: flex;
width: 100%;
margin-left: 10px;
line-height: 40px;
a {
margin-left: 15px;
position: relative;
display: block;
flex: 1;
color: #ff1515;
font-weight: 400;
&::after {
position: absolute;
top: 50%;
right: 10px;
display: block;
width: 0;
height: 0;
margin-top: -3px;
border: 6px solid transparent;
border-left: 8px solid #999;
content: '';
}
}
}
}
......@@ -54,5 +91,7 @@
}
.pagination {
width: 100%;
padding: 25px;
text-align: right;
}
import React, { useState, useEffect } from 'react';
import { Badge, notification } from 'antd';
import { Badge, notification, Spin } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import classNames from 'classnames';
import { connect } from 'dva';
......@@ -21,21 +21,26 @@ const Horn = props => {
};
const Message = props => {
const { toggle, animationClass, messageData, openComplex, onMark } = props;
const { toggle, animationClass, messageData, openComplex, onMark, loading } = props;
const ReminderItem = args => {
const { item } = args;
const { orderNo, createdAt } = JSON.parse(item.sendContent || '{}');
let message = {};
try {
message = JSON.parse(item.sendContent || '{}');
} catch (e) {
console.error('消息数据格式错误');
}
return (
<div className={styles.item}>
<div className={styles.info}>
<span className={styles['order-number']}>{orderNo}</span>
<span className={styles['order-number']}>{message.orderNo}</span>
<span className={styles['mark-read']} onClick={() => onMark([item.id])}>
标记为已读
</span>
</div>
<div className={styles.time}>
<span>{createdAt}</span>
<span>{message.createdAt}</span>
</div>
<div className={styles.notice}>
<a>您有新的xxxxx订单,请查看</a>
......@@ -55,6 +60,7 @@ const Message = props => {
</div>
</div>
<div className={styles['message-reminder__body']}>
<Spin spinning={loading}>
<div className={styles['message-reminder__body--list']}>
{messageData.length ? (
messageData
......@@ -64,6 +70,7 @@ const Message = props => {
<Empty text="暂无数据" />
)}
</div>
</Spin>
</div>
<div className={styles['message-reminder__footer']}>
<div className={styles['message-reminder__footer--actions']}>
......@@ -87,6 +94,7 @@ const Message = props => {
const Simple = props => {
const { dispatch, unReadCount, complexRef, unReadData } = props;
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [count, setCount] = useState(0);
const [hornClass, setHornClass] = useState('');
const [messageClass, setMessageClass] = useState('');
......@@ -110,7 +118,9 @@ const Simple = props => {
bussinessId: userInfo.supplierCode,
readStatus: 0,
};
setLoading(true);
const res = await apiGetBussinessMsgList(data, params);
setLoading(false);
if (res.code !== '0000') {
notification.error(res.msg);
return;
......@@ -135,6 +145,9 @@ const Simple = props => {
// 标记已读信息
const onMark = idList => {
if (!idList.length) {
return;
}
const payload = {
channelId: CHANNEL_ID,
bussinessId: userInfo.supplierCode,
......@@ -143,6 +156,9 @@ const Simple = props => {
dispatch({
type: 'messageReminder/setMarkRead',
payload,
options: {
setLoading,
},
});
};
......@@ -175,6 +191,7 @@ const Simple = props => {
messageData,
openComplex,
onMark,
loading,
};
// 隐藏消息提醒
......
......@@ -14,6 +14,7 @@ import Authorized from '@/utils/Authorized';
import RightContent from '@/components/GlobalHeader/RightContent';
import MessageReminder from '@/components/MessageReminder';
import { getAuthorityFromRouter } from '@/utils/utils';
import { getSocketUrl } from '@/services/messageReminder';
import logo from '../assets/logo.png';
import style from './BasicLayout.less';
......@@ -60,7 +61,7 @@ const BasicLayout = props => {
const token = window.localStorage.getItem('token');
const channelId = 100001;
const socket = new Socket({
url: `ws://ws-sc.liangkebang.net/ws?token=${token}&channelId=${channelId}`,
url: getSocketUrl({ token, channelId }),
});
socket.connection();
......@@ -69,27 +70,28 @@ const BasicLayout = props => {
});
socket.event.on('message', msg => {
console.log(msg);
dispatch({
type: 'messageReminder/setUnReadData',
payload: JSON.parse(msg.data),
payload: [JSON.parse(msg.data)],
});
});
let a = 100000000000;
setInterval(() => {
a++;
socket.sendMessage([
{
id: a,
channelId: 100001,
bussinessId: 'self_40',
type: 0,
sendContent: `{"name":"商品21111", "orderNo":${a},"createdAt":"2022-10-13 12:12:12", "count":11}`,
readStatus: 0,
createdAt: '2022-10-18 14:05:12',
updatedAt: '2022-10-18 17:15:19',
},
]);
}, 5000);
// let a = 100000000000;
// setInterval(() => {
// a++;
// socket.sendMessage(
// {
// id: a,
// channelId: 100001,
// bussinessId: 'self_40',
// type: 0,
// sendContent: `{"name":"商品21111", "orderNo":${a},"createdAt":"2022-10-13 12:12:12", "count":11}`,
// readStatus: 0,
// createdAt: '2022-10-18 14:05:12',
// updatedAt: '2022-10-18 17:15:19',
// },
// );
// }, 5000);
if (dispatch) {
dispatch({
type: 'settings/getSetting',
......@@ -224,6 +226,7 @@ const BasicLayout = props => {
<MessageReminder.Simple complexRef={messageReminderComplexRef} />
<MessageReminder.Complex ref={messageReminderComplexRef} />
{/* <audio id="myaudio" src="https://img.lkbang.net/10544.b57be67d.mp3" loop="loop" preload="preload" muted="muted" /> */}
</ProLayout>
);
};
......
......@@ -16,9 +16,16 @@ const MessageReminderModel = {
});
});
},
*setMarkRead({ payload, options }, { put, call }) {
// const res = yield call(apiUpdageBusinessMsgStatus, payload)
*setMarkRead({ payload, options = {} }, { put, call }) {
const { setLoading } = options;
if (setLoading) {
setLoading(true);
}
const res = yield call(apiUpdageBusinessMsgStatus, payload);
// console.log(res)
if (setLoading) {
setLoading(false);
}
yield put({
type: 'updateUnReadData',
payload,
......
......@@ -2,7 +2,7 @@
/* eslint-disable guard-for-in */
import React, { useState, useEffect } from 'react';
import { Modal, Timeline, Button } from 'antd';
import styles from '../styles.less';
import styles from '../index.less';
const LogisticsCom = props => {
const { visible, onCancel, data } = props;
......
import React, { useState, forwardRef, useImperativeHandle, useEffect } from 'react';
import { Modal, Timeline, notification, Spin } from 'antd';
import { getLogisticsRecord } from '../services';
const LogisticsRecordModal = (props, ref) => {
// const { } = props;
const [visible, setVisible] = useState(false);
const [result, setResult] = useState({});
const [loading, setLoading] = useState(false);
const getRecordList = async orderNo => {
const tempObj = {
detailList: [],
key: Date.now(),
};
setLoading(true);
const res = await getLogisticsRecord({ orderNo });
setLoading(false);
if (!res) {
notification.info({ message: '暂无物流信息' });
return;
}
const { logisticsName, logisticsBillNo, logisticsList = [] } = res.data;
tempObj.expressCompanyName = logisticsName;
tempObj.deliveryNo = logisticsBillNo;
logisticsList.forEach(v => {
tempObj.detailList = [...tempObj.detailList, ...v.detailList];
});
setResult(tempObj);
};
const open = orderNo => {
setVisible(true);
getRecordList(orderNo);
};
const onCancel = () => {
setVisible(false);
setResult([]);
};
useImperativeHandle(ref, () => ({
open,
}));
const modalProps = {
title: '查看物流',
visible,
onCancel,
destroyOnClose: true,
bodyStyle: { maxHeight: '600px', minHeight: '200px', overflow: 'auto' },
footer: null,
};
const emptyStyle = {
textAlign: 'center',
padding: '30px 0',
color: '#999',
};
return (
<Modal {...modalProps}>
<Spin spinning={loading}>
{result.detailList?.length ? (
<Timeline>
{result?.detailList?.map((item, index) => (
<Timeline.Item color={index > 0 ? 'gray' : 'blue'} key={index.toString()}>
<p>{item.desc}</p>
<p>{item.logisticsTime}</p>
</Timeline.Item>
))}
</Timeline>
) : (
<div style={emptyStyle}>暂无物流信息</div>
)}
</Spin>
</Modal>
);
};
export default forwardRef(LogisticsRecordModal);
const LogisticsCom = props => {
const { modalVisible, onCancel } = props;
const [result, setResult] = useState({});
useEffect(() => {
setResult(props.value);
});
return (
<Modal
destroyOnClose
title={`${
result?.expressCompanyName
? `${result?.expressCompanyName}-${result?.deliveryNo}`
: '物流信息'
}`}
visible={modalVisible}
onCancel={() => onCancel()}
onOk={() => onCancel()}
afterClose={() => setResult({})}
bodyStyle={{ maxHeight: '600px', minHeight: '200px', overflow: 'auto' }}
footer={[]}
>
{result?.detailList?.length ? (
<Timeline>
{result?.detailList?.map((item, index) => (
// eslint-disable-next-line react/no-array-index-key
<Timeline.Item color={index > 0 ? 'gray' : 'blue'} key={index}>
<p>{item.desc}</p>
<p>{item.logisticsTime}</p>
</Timeline.Item>
))}
</Timeline>
) : (
'暂无物流信息'
)}
</Modal>
);
};
import React from 'react';
import React, { useRef } from 'react';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Modal, Input, Cascader, notification, InputNumber } from 'antd';
import { shopAudit } from '../services';
import styles from '../styles.less';
import styles from '../index.less';
import LogisticsRecordModal from './LogisticsRecordModal';
const FormItem = Form.Item;
const { TextArea } = Input;
......@@ -15,7 +17,7 @@ const AuditModal = props => {
form: { getFieldDecorator, getFieldValue, validateFields, resetFields },
formData = {},
} = props;
const logisticsRecordModalRef = useRef();
const handleCancel = isSuccess => {
resetFields();
onCancel(isSuccess);
......@@ -102,6 +104,10 @@ const AuditModal = props => {
});
};
const openLogisticsRecord = () => {
logisticsRecordModalRef.current.open(formData.orderNo);
};
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
......@@ -110,6 +116,7 @@ const AuditModal = props => {
const isAgree = () => auditResult?.[0] === 1;
const isRefuse = () => auditResult && auditResult[0] !== 1;
return (
<>
<Modal
title="售后操作确认"
visible={visible}
......@@ -120,7 +127,7 @@ const AuditModal = props => {
<div className={styles.redTip}>
温馨提示:当前售后类型为用户未收到产品,申请
<span className={styles.redTipBold}>仅退款</span>
,请务必检查此单物流状态后审核。
,请务必检查此单物流状态后审核。<a onClick={openLogisticsRecord}>查看物流</a>
</div>
)}
<Form {...layout} name="formData">
......@@ -237,6 +244,8 @@ const AuditModal = props => {
)}
</Form>
</Modal>
<LogisticsRecordModal ref={logisticsRecordModalRef} />
</>
);
};
......
import React, { useState } from 'react';
import { Modal } from 'antd';
import style from '../styles.less';
import style from '../index.less';
export default props => {
const { visible, onCancel, data } = props;
......
import { Tag, Badge } from 'antd';
import { Tag, Badge, Statistic, Button, Popconfirm } from 'antd';
import React from 'react';
import moment from 'moment';
import { SEARCH_TYPE } from '@/components/FormSearch';
const { Countdown } = Statistic;
export const TAB_MAPPING_DATA = {
'': {},
1: {
label: '仅退款(未发货)待审核',
type: 0,
dealStatus: 14,
},
2: {
label: '仅退款待审核',
type: 1,
dealStatus: 14,
},
3: {
label: '退货退款待审核',
type: 2,
dealStatus: 14,
},
4: {
label: '退货入库待审核',
dealStatus: 40,
},
5: {
label: '已完成',
dealStatus: 70,
},
};
// 售后类型
const AFTER_TYPE = [
{
value: 0,
name: '仅退款(未发货)',
},
{
value: 1,
name: '仅退款',
},
{
value: 2,
name: '退货退款',
},
];
// 售后状态
const AFTER_STATUS = [
{
value: 14,
name: '商户审核中',
},
{
value: 16,
name: '商户审核拒绝',
},
{
value: 30,
name: '待填写退货物流信息',
},
{
value: 40,
name: '待退货入库',
},
{
value: 50,
name: '退货拒收',
},
{
value: 70,
name: '退款成功',
},
{
value: 99,
name: '用户撤销',
},
];
export const getFormConfig = (props = {}) => {
const { setTableParams, setCurrentTab, tableParams, actionRef } = props;
return {
formConfig: [
{
type: SEARCH_TYPE.INPUT,
label: '订单ID',
bindKey: 'orderNo',
column: 5,
},
{
type: SEARCH_TYPE.INPUT,
label: '售后单ID',
bindKey: 'serviceNo',
column: 5,
},
{
type: SEARCH_TYPE.SELECT,
label: '售后状态',
column: 5,
bindKey: 'dealStatus',
options: AFTER_STATUS,
originOptions: {
allowClear: true,
},
},
{
type: SEARCH_TYPE.SELECT,
label: '售后类型',
column: 5,
bindKey: 'type',
options: AFTER_TYPE,
originOptions: {
allowClear: true,
},
},
{
type: SEARCH_TYPE.INPUT,
label: '收货人姓名',
column: 5,
bindKey: 'receiverName',
},
{
type: SEARCH_TYPE.INPUT,
label: '收货人手机号',
column: 5,
bindKey: 'receiverPhone',
},
{
type: SEARCH_TYPE.DATE_PICKER,
label: '订单开始日期',
column: 5,
bindKey: 'startDate',
},
{
type: SEARCH_TYPE.DATE_PICKER,
label: '订单结束日期',
column: 5,
bindKey: 'endDate',
},
],
btnConfig: [
{
label: '筛选',
clickType: 'submit',
onClick: ({ params }) => {
// 参数相同,直接执行刷新
if (JSON.stringify(params) === JSON.stringify(tableParams)) {
actionRef.current.reload();
return;
}
setTableParams(params);
const { type, dealStatus } = params;
let hasMatchingKey = '';
Object.keys(TAB_MAPPING_DATA).forEach(key => {
const item = TAB_MAPPING_DATA[key];
if (type === item.type && dealStatus === item.dealStatus) {
hasMatchingKey = key;
}
});
setCurrentTab(hasMatchingKey);
},
},
{
label: '重置',
type: '',
clickType: 'reset',
onClick: () => {
setTableParams({});
},
},
],
};
};
export const appealType = {
1: '已申诉',
0: '未申诉',
};
export const columnSticData = [
export const getColumns = props => {
const {
openAudit,
viewAppeal,
viewProofs,
openLogistics,
viewDetail,
viewLog,
handleCom,
refund,
reject,
canEditable,
} = props;
return [
{
title: '审核倒计时',
dataIndex: 'serviceTime',
key: 'serviceTime',
hideInSearch: true,
width: 150,
render: (val, record) => {
const serviceTime = moment(record.approvalEndTime).valueOf();
return (
<Countdown
format="HH时mm分ss秒"
value={serviceTime}
valueStyle={{ color: 'red', fontSize: '14px' }}
/>
);
},
},
{
title: '售后状态',
dataIndex: 'serviceStatus',
hideInSearch: true,
width: 120,
},
{
title: '售后类型',
dataIndex: 'serviceType',
......@@ -18,12 +230,6 @@ export const columnSticData = [
return <span>退货退款</span>;
},
},
{
title: '订单ID',
dataIndex: 'orderNo',
hideInTable: true,
width: 200,
},
{
title: '售后单ID',
dataIndex: 'serviceNo',
......@@ -42,56 +248,19 @@ export const columnSticData = [
width: 300,
},
{
title: '售后状态',
dataIndex: 'dealStatus',
hideInTable: true,
valueEnum: {
0: '待审核',
10: '三方审核中',
11: '三方审核通过',
12: '三方审核拒绝',
13: '客服审核通过',
14: '商户审核中',
15: '商户审核通过',
16: '商户审核拒绝',
20: '审核拒绝',
21: '申诉中',
30: '待填写退货物流信息',
40: '待退货入库',
50: '退货拒收',
60: '待退款',
61: '退货处理中',
70: '售后成功',
99: '用户取消',
},
width: 100,
},
{
title: '售后类型',
dataIndex: 'type',
hideInTable: true,
width: 120,
valueEnum: {
1: '仅退款',
2: '退货退款',
},
},
{
title: '收货人姓名',
title: '收货人信息',
dataIndex: 'receiverName',
width: 200,
render: (_, record) => {
const { receiverPhone, receiveAddress } = record;
return (
<>
<p>{_}</p>
<p>{receiverPhone}</p>
<p>{receiveAddress}</p>
</>
);
},
{
title: '收货人手机号',
dataIndex: 'receiverPhone',
width: 200,
},
{
title: '收货人地址',
dataIndex: 'receiveAddress',
width: 200,
hideInSearch: true,
},
{
title: '订单开始时间',
......@@ -149,31 +318,182 @@ export const columnSticData = [
hideInSearch: true,
width: 200,
},
];
export const columnPassAudit = [
...columnSticData,
{
title: '商家退货地址',
title: '商家退货信息',
dataIndex: 'merchantAddress',
hideInSearch: true,
width: 200,
render: (_, record) => {
const { expressCompanyName, deliveryNo } = record;
return (
<>
<p>{_}</p>
<p>{expressCompanyName}</p>
<p>{deliveryNo}</p>
</>
);
},
},
{
title: '退回物流',
dataIndex: 'expressCompanyName',
title: '售后凭证',
dataIndex: 'proofs',
hideInSearch: true,
width: 150,
width: 100,
render: (val, r) => <a onClick={() => viewProofs(r.proofs)}>查看凭证</a>,
},
{
title: '退回物流单号',
dataIndex: 'deliveryNo',
title: '售后申诉',
dataIndex: 'appealFlag',
valueEnum: appealType,
hideInSearch: true,
width: 200,
width: 120,
render: (appealFlag, r) => {
if (appealFlag) {
return <a onClick={() => viewAppeal(r)}>已申诉</a>;
}
return <div>未申诉</div>;
},
},
{
title: '售后状态',
dataIndex: 'serviceStatus',
title: '操作',
hideInSearch: true,
width: 120,
dataIndex: 'action',
width: 250,
fixed: 'right',
render: (val, record) => {
const {
status,
supplierType,
serviceType,
intercept,
showRefuse,
showRefund,
showLogistics,
showRefunded,
} = record;
// 是否是服务类商品
const isServiceGoods = !['vip', 'self'].includes(supplierType);
// 按钮通用属性
const btnProps = {
className: 'mr10 mt10',
type: 'primary',
};
const refundBtnProps = {
title: '确定允许退款?',
okText: '确认',
cancelText: '取消',
key: 'pop',
};
// 允许退款/已退款按钮
const refundBtn = (
<Popconfirm {...refundBtnProps} onConfirm={() => refund(record)} disabled={!showRefund}>
<Button key="link1" disabled={!showRefund}>
{showRefunded ? '已退款' : '允许退款'}
</Button>
</Popconfirm>
);
// 审核按钮
let auditBtn = (
<Button key="link2" onClick={() => openAudit(record)} {...btnProps}>
审核
</Button>
);
// 驳回按钮
let refuseBtn = (
<Button key="link3" onClick={() => reject(record)} {...btnProps} disabled={!showRefuse}>
{' '}
驳回{' '}
</Button>
);
// 物流拦截按钮
let logisticsInterceptionBtn = (
<Button
key="link7"
onClick={() => openLogistics(record)}
{...btnProps}
disabled={serviceType !== 1 || (serviceType === 1 && intercept)}
>
{' '}
物流拦截{' '}
</Button>
);
// 订单详情
const detailBtn = (
<Button key="link4" onClick={() => viewDetail(record)} {...btnProps}>
订单详情
</Button>
);
// 查看物流
const viewLogisticsBtn = (
<Button
key="link5"
onClick={() => handleCom(record)}
{...btnProps}
disabled={!showLogistics}
>
{' '}
退货物流{' '}
</Button>
);
// 查看记录
const viewLogBtn = (
<Button key="link6" onClick={() => viewLog(record)} {...btnProps}>
{' '}
查看记录{' '}
</Button>
);
if (!canEditable) {
auditBtn = '';
refuseBtn = '';
logisticsInterceptionBtn = '';
}
// 服务商品 || 实物商品-仅退款未发货 serviceType = 0
if (isServiceGoods || serviceType === 0) {
// 待商户审核14
if ([14].includes(status)) {
return [auditBtn, detailBtn, viewLogBtn];
}
// 拒绝16, 处理成功70
if ([16, 70].includes(status)) {
return [detailBtn, viewLogBtn];
}
}
// 实物商品-仅退款 serviceType = 1
if (serviceType === 1) {
// 待商户审核14
if ([14].includes(status)) {
return [auditBtn, detailBtn, viewLogBtn, logisticsInterceptionBtn];
}
// 拒绝16/处理成功70
if ([16, 70].includes(status)) {
return [detailBtn, viewLogBtn];
}
}
// 实物商品-退货退款 serviceType = 2
if (serviceType === 2) {
// 待商户审核14
if ([14].includes(status)) {
return [auditBtn, detailBtn, viewLogBtn];
}
// 待退货入库40
if ([40].includes(status)) {
return [refundBtn, refuseBtn, viewLogisticsBtn, detailBtn, viewLogBtn];
}
// 拒绝16/待填写退货物流信息30/处理成功70
if ([16, 30, 70].includes(status)) {
return [viewLogisticsBtn, detailBtn, viewLogBtn];
}
}
return [detailBtn, viewLogBtn];
},
];
},
];
};
import { Tabs } from 'antd';
import React from 'react';
import React, { useRef, useState, useEffect } from 'react';
import { Tabs, notification, Form, Modal } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import { AFTER_SALE_ORDER } from '@/../config/permission.config';
import { connect } from 'dva';
import Pending from './Pending';
import PassAudit from './PassAudit';
import { FormSearch } from '@/components/FormSearch';
import moment from 'moment';
import AuditModal from './components/auditModal';
import DetailTable from './components/detailTable';
import ProofsModal from './components/proofsModal';
import AppealDetail from '@/pages/afterSale/components/detail';
import AfterLog from './components/AfterLog';
import RejectModal from './components/rejectModal';
import LogisticsCom from '../orderManage/pendingDeliveryOrder/components/LogisticsCom';
import { getColumns, getFormConfig, TAB_MAPPING_DATA } from './data.js';
import { getDetail } from '@/pages/afterSale/appeal/services';
import {
searchList,
logisticsIntercept,
orderDetail,
getOpLog,
auditInfoApi,
trackInfo,
shopCheck,
getAfterPendingNum,
} from './services';
import styles from './index.less';
const { TabPane } = Tabs;
const { confirm } = Modal;
function AfterSale(props) {
const AfterSale = props => {
const { permissions } = props;
const canEditable = permissions[AFTER_SALE_ORDER.EDITABLE];
const actionRef = useRef();
const formRef = useRef();
const [tableParams, setTableParams] = useState({});
const [currentTab, setCurrentTab] = useState('');
const [appealDetailModal, setAppealDetailModal] = useState(false);
const [selectedRow, setSelectedRow] = useState({});
const [tabInfoData, setTabInfoData] = useState({});
// 申诉
const [proofsData, setProofsData] = useState([]);
const [proofsVisible, setProofsVisible] = useState(false);
// 详情
const [detailVisible, setDetailVisible] = useState(false);
const [detailInfo, setDetailInfo] = useState([]);
// 售后操作记录
const [afterVisible, setAfterVisible] = useState(false);
const [afterList, setAfterList] = useState([]);
// 审核
const [visible, setVisible] = useState(false);
const [auditInfo, setAuditInfo] = useState({});
// 查看物流
const [LogisticsComList, setLogisticsComList] = useState({});
const [logisticsComModalVisible, setLogisticsComModalVisible] = useState(false);
// 驳回
const [serviceNoInfo, setServiceNoInfo] = useState({});
const [rejectVisible, setRejectVisible] = useState(false);
// 关闭modal
const closeModal = isReload => {
if (isReload === true) {
// eslint-disable-next-line no-unused-expressions
actionRef.current?.reload?.();
}
setVisible(false);
setDetailVisible(false);
setProofsVisible(false);
setAppealDetailModal(false);
setAfterVisible(false);
setLogisticsComModalVisible(false);
};
// 查看申诉详情
const viewAppeal = async r => {
const detailData = await getDetail({ appealNo: r.appealNo });
setAppealDetailModal(true);
setSelectedRow(detailData);
};
// 查看凭证
const viewProofs = proofs => {
if (!proofs) {
notification.warning({ message: '该订单没有凭证' });
return;
}
const list = proofs.replace(/(\uff1b|\uff0c|\u003b)/g, ',').split(',');
setProofsData(list);
setProofsVisible(true);
};
// 审核
const openAudit = async ({ serviceNo, serviceType, orderNo }) => {
const data = await auditInfoApi({ serviceNo });
setAuditInfo({ ...data?.data, serviceNo, serviceType, orderNo });
setVisible(true);
};
// 查看物流
const handleCom = async ({ expressCompanyCode, deliveryNo }) => {
const tempObj = {
detailList: [],
key: Date.now(),
};
const data = await trackInfo({ expressCompanyCode, logisticsNo: deliveryNo });
if (!data) {
notification.info({ message: '暂无物流信息' });
return;
}
tempObj.expressCompanyName = data.logisticsName;
tempObj.deliveryNo = data.logisticsBillNo;
if (data.logisticsList?.length) {
data.logisticsList.forEach(v => {
tempObj.detailList = [...tempObj.detailList, ...v.detailList];
});
}
setLogisticsComModalVisible(true);
setLogisticsComList(tempObj);
};
// 物流拦截
const openLogistics = r => {
confirm({
title: '温馨提示',
okText: '确认拦截',
cancelText: '取消拦截',
content: (
<div>
请48小时内自行联系物流公司进行物流拦截,系统监测拦截成功后
<span className={styles.redTipBold}>自动同意</span>退款
</div>
),
async onOk() {
const data = await logisticsIntercept({ serviceNo: r.serviceNo });
if (data.businessCode === '0000') {
notification.success({ message: '拦截成功' });
actionRef.current.reload();
} else {
notification.error({ message: data.msg || '拦截失败' });
}
},
onCancel() {
console.log('Cancel');
},
});
};
// 查看
const viewDetail = async ({ serviceNo }) => {
const res = await orderDetail({ serviceNo });
const data = res.data || [];
setDetailInfo(data);
setDetailVisible(true);
};
// 查看售后操作日志
const viewLog = async r => {
const data = await getOpLog(r.serviceNo);
if (data?.data?.length) {
setAfterList(data.data);
setAfterVisible(true);
}
};
// 已退款/允许退款
const refund = async ({ serviceNo }) => {
const data = await shopCheck({
serviceNo,
auditResult: 1,
});
if (data.businessCode === '0000') {
notification.success({ message: '操作成功' });
closeModal(true);
}
};
// 驳回
const reject = async ({ serviceNo }) => {
setServiceNoInfo(serviceNo);
setRejectVisible(true);
};
const [form] = Form.useForm();
const formConfig = getFormConfig({
actionRef,
tableParams,
setTableParams,
setCurrentTab,
});
const FormSearchProps = {
form,
initialValues: {},
formOptions: {},
...formConfig,
};
// tab选项框回调
const tabChange = tabIndex => {
setCurrentTab(tabIndex);
const { type, dealStatus } = TAB_MAPPING_DATA[tabIndex];
form.setFieldsValue({
dealStatus,
type,
});
setTableParams({ ...tableParams, dealStatus, type });
};
const columns = getColumns({
openAudit,
viewAppeal,
viewProofs,
openLogistics,
viewDetail,
viewLog,
handleCom,
refund,
reject,
canEditable,
});
// 表格属性
const tableProps = {
columns,
params: tableParams,
bordered: true,
scroll: { x: '100%', y: 500 },
rowKey: r => r.serviceNo,
request: async params => {
console.log(params);
console.log('搜索', params);
const { current: page, pageSize: size } = params;
const startDate = params.startDate ? moment(params.startDate).format('YYYY-MM-DD') : '';
const endDate = params.endDate ? moment(params.endDate).format('YYYY-MM-DD') : '';
const requestParams = {
page,
size,
...params,
startDate,
endDate,
};
const res = await searchList(requestParams);
const { records = [], total } = res.data;
return {
data: records,
total,
};
},
toolBarRender: null,
};
const getPendingNum = async () => {
const params = {
startDate: new Date('2020/01/01 00:00:00'),
endDate: new Date(),
};
const res = await getAfterPendingNum(params);
console.log(res);
};
useEffect(() => {
getPendingNum();
}, []);
return (
<PageHeaderWrapper>
<Tabs defaultActiveKey="1">
<TabPane tab="未审核" key="1">
<Pending canEditable={canEditable} />
</TabPane>
<TabPane tab="已审核" key="2">
<PassAudit canEditable={canEditable} />
</TabPane>
<FormSearch {...FormSearchProps} />
<div className={styles['tab-box']}>
<Tabs
activeKey={currentTab}
onChange={tabChange}
size="large"
tabBarStyle={{ padding: '0 30px' }}
>
<TabPane key="" tab="全部"></TabPane>
<TabPane key="1" tab="仅退款(未发货)待审核0"></TabPane>
<TabPane key="2" tab="仅退款待审核8"></TabPane>
<TabPane key="3" tab="退货退款待审核0"></TabPane>
<TabPane key="4" tab="退货入库待审核0"></TabPane>
<TabPane key="5" tab="已完成0"></TabPane>
</Tabs>
</div>
<ProTable
{...tableProps}
actionRef={actionRef}
formRef={formRef}
search={false}
// toolBarRender={false}
/>
<AuditModal visible={visible} onCancel={closeModal} formData={auditInfo} />
<DetailTable visible={detailVisible} onCancel={closeModal} dataSource={detailInfo} />
<ProofsModal visible={proofsVisible} onCancel={closeModal} data={proofsData} />
<LogisticsCom
onSubmit={closeModal}
onCancel={closeModal}
modalVisible={logisticsComModalVisible}
value={LogisticsComList}
key={LogisticsComList.key}
/>
<AppealDetail
data={selectedRow}
modalVisible={appealDetailModal}
onCancel={closeModal}
></AppealDetail>
<AfterLog visible={afterVisible} onCancel={closeModal} data={afterList} />
<RejectModal visible={rejectVisible} onCancel={closeModal} serviceNo={serviceNoInfo} />
</PageHeaderWrapper>
);
}
};
export default connect(({ menu }) => ({
permissions: menu.permissions,
......
......@@ -6,42 +6,50 @@ import _ from 'lodash';
const { kdspApi } = config;
// 分页查询所有数据
export async function searchList(params, queryStatus) {
const param = {
...params,
pageNo: params.current,
pageSize: params.pageSize || 20,
queryStatus,
};
const data = await request.post('/api/kdsp/op/afs/shop/list', {
export const searchList = params =>
request.post('/api/kdsp/op/afs/shop/list', {
prefix: kdspApi,
data: stringify(_.omitBy(param, v => !v)),
data: stringify(_.omitBy(params, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
if (data.data) {
return {
total: data.data.total,
data: data.data.records,
};
}
return {
total: 0,
data: [],
};
}
// export async function searchList(params, queryStatus) {
// const param = {
// ...params,
// pageNo: params.current,
// pageSize: params.pageSize || 20,
// queryStatus,
// };
// const data = await request.post('/api/kdsp/op/afs/shop/list', {
// prefix: kdspApi,
// data: stringify(_.omitBy(param, v => !v)),
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
// });
// if (data.data) {
// return {
// total: data.data.total,
// data: data.data.records,
// };
// }
// return {
// total: 0,
// data: [],
// };
// }
// 售后单详情
export async function orderDetail(params) {
const data = await request.get('/api/kdsp/op/afs/sku', {
export function orderDetail(params) {
return request.get('/api/kdsp/op/afs/sku', {
prefix: kdspApi,
params,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return data.data || [];
}
// 售后审核
export async function shopAudit(params) {
......@@ -93,3 +101,18 @@ export async function getOpLog(params) {
prefix: kdspApi,
});
}
// 查询物流记录信息
export async function getLogisticsRecord(params) {
return request.get(`/api/kdsp/op/logistics/kd100/track-list?orderNo=${params.orderNo}`, {
prefix: kdspApi,
});
}
// 查询售后待办数量
export function getAfterPendingNum(data) {
return request.post('/api/kdsp/op/afs/getPendingNum', {
prefix: kdspApi,
data,
});
}
import { Tag, Badge, Statistic, Button } from 'antd';
import React from 'react';
import moment from 'moment';
import { SEARCH_TYPE } from '@/components/FormSearch';
const { Countdown } = Statistic;
// 商户审核中/商户审核拒绝/待填写退货物流信息/待退货入库/退货拒收/退款成功/用户撤销”
// 仅退款(未发货)/仅退款/退货退款,新增【仅退款(未发货)】即取消订单待审核
// 售后类型
const AFTER_TYPE = [
{
value: '1',
name: '商户审核中',
},
{
value: '2',
name: '商户审核拒绝',
},
{
value: '3',
name: '待填写退货物流信息',
},
{
value: '4',
name: '待退货入库',
},
{
value: '5',
name: '退货拒收',
},
{
value: '5',
name: '退款成功',
},
{
value: '6',
name: '用户撤销”',
},
];
// 售后状态
const AFTER_STATUS = [
{
value: '1',
name: '仅退款(未发货)',
},
{
value: '1',
name: '仅退款',
},
{
value: '1',
name: '退货退款',
},
];
export const getFormConfig = (props = {}) => {
const { setTableParams } = props;
return {
formConfig: [
{
type: SEARCH_TYPE.INPUT,
label: '订单ID',
bindKey: 'orderNo',
column: 5,
},
{
type: SEARCH_TYPE.INPUT,
label: '售后单ID',
bindKey: 'serviceNo',
column: 5,
},
{
type: SEARCH_TYPE.SELECT,
label: '售后状态',
column: 5,
bindKey: 'dealStatus',
options: AFTER_STATUS,
},
{
type: SEARCH_TYPE.SELECT,
label: '售后类型',
column: 5,
bindKey: 'type',
options: AFTER_TYPE,
},
{
type: SEARCH_TYPE.INPUT,
label: '收货人姓名',
column: 5,
bindKey: 'receiverName',
},
{
type: SEARCH_TYPE.INPUT,
label: '收货人手机号',
column: 5,
bindKey: 'receiverPhone',
},
{
type: SEARCH_TYPE.DATE_PICKER,
label: '订单开始日期',
column: 5,
bindKey: 'startDate',
},
{
type: SEARCH_TYPE.DATE_PICKER,
label: '订单结束日期',
column: 5,
bindKey: 'endDate',
},
],
btnConfig: [
{
label: '筛选',
clickType: 'submit',
onClick: ({ type, params }) => {
setTableParams(params);
},
},
{
label: '重置',
type: '',
clickType: 'reset',
onClick: () => {
setTableParams({});
},
},
],
};
};
export const appealType = {
1: '已申诉',
0: '未申诉',
};
export const getColumns = props => {
const {
openAudit,
viewAppeal,
viewProofs,
openLogistics,
viewDetail,
viewLog,
canEditable,
} = props;
return [
{
title: '审核倒计时',
dataIndex: 'serviceTime',
key: 'serviceTime',
hideInSearch: true,
width: 150,
render: (val, record) => {
const serviceTime = moment(record.approvalEndTime).valueOf();
return (
<Countdown
format="HH时mm分ss秒"
value={serviceTime}
valueStyle={{ color: 'red', fontSize: '14px' }}
/>
);
},
},
{
title: '售后状态',
dataIndex: 'serviceStatus',
hideInSearch: true,
width: 120,
},
{
title: '售后类型',
dataIndex: 'serviceType',
hideInSearch: true,
width: 120,
render: serviceType => {
if (+serviceType === 1) {
return <span style={{ color: '#ff1616' }}>仅退款</span>;
}
return <span>退货退款</span>;
},
},
{
title: '订单ID',
dataIndex: 'orderNo',
hideInTable: true,
width: 200,
},
{
title: '售后单ID',
dataIndex: 'serviceNo',
width: 300,
render: (serviceNo, r) => (
<div>
{r.timeout ? <Tag color="red">{serviceNo}</Tag> : serviceNo}
{<Badge count={r.reminderFlag ? '' : ''} size="default" />}
</div>
),
},
{
title: '订单ID',
dataIndex: 'orderNo',
hideInSearch: true,
width: 300,
},
{
title: '售后状态',
dataIndex: 'dealStatus',
hideInTable: true,
valueEnum: {
0: '待审核',
10: '三方审核中',
11: '三方审核通过',
12: '三方审核拒绝',
13: '客服审核通过',
14: '商户审核中',
15: '商户审核通过',
16: '商户审核拒绝',
20: '审核拒绝',
21: '申诉中',
30: '待填写退货物流信息',
40: '待退货入库',
50: '退货拒收',
60: '待退款',
61: '退货处理中',
70: '售后成功',
99: '用户取消',
},
width: 100,
},
{
title: '售后类型',
dataIndex: 'type',
hideInTable: true,
width: 120,
valueEnum: {
1: '仅退款',
2: '退货退款',
},
},
{
title: '收货人姓名',
dataIndex: 'receiverName',
width: 200,
},
{
title: '收货人手机号',
dataIndex: 'receiverPhone',
width: 200,
},
{
title: '收货人地址',
dataIndex: 'receiveAddress',
width: 200,
hideInSearch: true,
},
{
title: '订单开始时间',
width: 120,
dataIndex: 'startDate',
key: 'startDate',
valueType: 'date',
hideInTable: true,
},
{
title: '订单结束时间',
width: 120,
dataIndex: 'endDate',
key: 'endDate',
valueType: 'date',
hideInTable: true,
},
{
title: '售后原因',
dataIndex: 'serviceReason',
hideInSearch: true,
width: 200,
},
{
title: '售后发生时间',
dataIndex: 'serviceTime',
hideInSearch: true,
width: 200,
},
{
title: '超时时间',
dataIndex: 'overtime',
hideInSearch: true,
width: 200,
},
{
title: '是否催办',
dataIndex: 'reminderFlag',
hideInSearch: true,
width: 120,
valueEnum: {
true: '',
false: '',
},
},
{
title: '是否同意售后',
dataIndex: 'isAgree',
hideInSearch: true,
width: 120,
},
{
title: '拒绝原因',
dataIndex: 'refuseReason',
hideInSearch: true,
width: 200,
},
{
title: '售后凭证',
dataIndex: 'proofs',
hideInSearch: true,
width: 100,
render: (val, r) => <a onClick={() => viewProofs(r.proofs)}>查看凭证</a>,
},
{
title: '售后申诉',
dataIndex: 'appealFlag',
valueEnum: appealType,
hideInSearch: true,
width: 120,
render: (appealFlag, r) => {
if (appealFlag) {
return <a onClick={() => viewAppeal(r)}>已申诉</a>;
}
return <div>未申诉</div>;
},
},
{
title: '操作',
hideInSearch: true,
dataIndex: 'action',
width: 250,
fixed: 'right',
render: (val, r) => {
const operations = [
<Button key="link1" onClick={() => openAudit(r)} className="mr10 mt10" type="primary">
审核
</Button>,
<Button
disabled={r.serviceType !== 1 || (r.serviceType === 1 && r.intercept)}
onClick={() => openLogistics(r)}
className="mr10 mt10"
type="primary"
>
物流拦截
</Button>,
<Button className="mr10 mt10" key="link" onClick={() => viewDetail(r)} type="primary">
订单详情
</Button>,
<Button className="mr10 mt10" type="primary" onClick={() => viewLog(r)}>
查看记录
</Button>,
];
// 不可编辑直接隐藏可操作按钮
if (!canEditable) {
return [operations[2], operations[3]];
}
// 服务订单删除物流拦截
if (!['vip', 'self'].includes(r.supplierType)) {
return [operations[0], operations[2], operations[3]];
}
return operations;
},
},
];
};
import React, { useRef, useState } from 'react';
import { Tabs, notification, Form, Modal } from 'antd';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import ProTable from '@ant-design/pro-table';
import { AFTER_SALE_ORDER } from '@/../config/permission.config';
import { connect } from 'dva';
import { FormSearch } from '@/components/FormSearch';
import moment from 'moment';
import AuditModal from './components/auditModal';
import DetailTable from './components/detailTable';
import ProofsModal from './components/proofsModal';
import AppealDetail from '@/pages/afterSale/components/detail';
import AfterLog from './components/AfterLog';
import { getColumns, getFormConfig } from './data.js';
import { getDetail } from '@/pages/afterSale/appeal/services';
import { searchList, logisticsIntercept, orderDetail, getOpLog, auditInfoApi } from './services';
import styles from './index.less';
const { TabPane } = Tabs;
const { confirm } = Modal;
const AfterSale = props => {
const { permissions } = props;
const canEditable = permissions[AFTER_SALE_ORDER.EDITABLE];
const actionRef = useRef();
const formRef = useRef();
const [tableParams, setTableParams] = useState({});
const [appealDetailModal, setAppealDetailModal] = useState(false);
const [selectedRow, setSelectedRow] = useState({});
// 申诉
const [proofsData, setProofsData] = useState([]);
const [proofsVisible, setProofsVisible] = useState(false);
// 详情
const [detailVisible, setDetailVisible] = useState(false);
const [detailInfo, setDetailInfo] = useState([]);
// 售后操作记录
const [afterVisible, setAfterVisible] = useState(false);
const [afterList, setAfterList] = useState([]);
// 审核
const [visible, setVisible] = useState(false);
const [auditInfo, setAuditInfo] = useState({});
// 关闭modal
const closeModal = isReload => {
if (isReload === true) {
// eslint-disable-next-line no-unused-expressions
actionRef.current?.reload?.();
}
setVisible(false);
setDetailVisible(false);
setProofsVisible(false);
setAppealDetailModal(false);
setAfterVisible(false);
};
// 查看申诉详情
const viewAppeal = async r => {
const detailData = await getDetail({ appealNo: r.appealNo });
setAppealDetailModal(true);
setSelectedRow(detailData);
};
// 查看凭证
const viewProofs = proofs => {
if (!proofs) {
notification.warning({ message: '该订单没有凭证' });
return;
}
const list = proofs.replace(/(\uff1b|\uff0c|\u003b)/g, ',').split(',');
setProofsData(list);
setProofsVisible(true);
};
// 审核
const openAudit = async ({ serviceNo, serviceType }) => {
const data = await auditInfoApi({ serviceNo });
setAuditInfo({ ...data?.data, serviceNo, serviceType });
setVisible(true);
};
// 物流拦截
const openLogistics = r => {
confirm({
title: '温馨提示',
okText: '确认拦截',
cancelText: '取消拦截',
content: (
<div>
请48小时内自行联系物流公司进行物流拦截,系统监测拦截成功后
<span className={styles.redTipBold}>自动同意</span>退款
</div>
),
async onOk() {
const data = await logisticsIntercept({ serviceNo: r.serviceNo });
if (data.businessCode === '0000') {
notification.success({ message: '拦截成功' });
actionRef.current.reload();
} else {
notification.error({ message: data.msg || '拦截失败' });
}
},
onCancel() {
console.log('Cancel');
},
});
};
// 查看
const viewDetail = async ({ serviceNo }) => {
const res = await orderDetail({ serviceNo });
const data = res.data || [];
setDetailInfo(data);
setDetailVisible(true);
};
// 查看售后操作日志
const viewLog = async r => {
const data = await getOpLog(r.serviceNo);
if (data?.data?.length) {
setAfterList(data.data);
setAfterVisible(true);
}
};
const tabChange = e => {
console.log(e);
};
const [form] = Form.useForm();
const formConfig = getFormConfig({
setTableParams,
});
const FormSearchProps = {
form,
initialValues: {},
formOptions: {},
...formConfig,
};
const columns = getColumns({
openAudit,
viewAppeal,
viewProofs,
openLogistics,
viewDetail,
viewLog,
canEditable,
});
// 表格属性
const tableProps = {
columns,
params: tableParams,
bordered: true,
scroll: { x: '100%', y: 500 },
rowKey: r => r.serviceNo,
request: async params => {
console.log(params);
console.log('搜索', params);
const { current: page, pageSize: size } = params;
const startDate = params.startDate ? moment(params.startDate).format('YYYY-MM-DD') : '';
const endDate = params.endDate ? moment(params.endDate).format('YYYY-MM-DD') : '';
const requestParams = {
page,
size,
queryStatus: 1,
...params,
startDate,
endDate,
};
const res = await searchList(requestParams);
const { records = [], total } = res.data;
return {
data: records,
total,
};
},
toolBarRender: null,
};
return (
<PageHeaderWrapper>
<FormSearch {...FormSearchProps} />
<div className={styles['tab-box']}>
<Tabs onChange={tabChange} size="large" tabBarStyle={{ padding: '0 30px' }}>
<TabPane key="" tab="全部"></TabPane>
<TabPane key={1} tab="仅退款(未发货)"></TabPane>
<TabPane key={2} tab="待审核0"></TabPane>
<TabPane key={3} tab="仅退款待审核8"></TabPane>
<TabPane key={4} tab="退货退款待审核0"></TabPane>
<TabPane key={5} tab="退货入库待审核0"></TabPane>
<TabPane key={6} tab="已完成0"></TabPane>
</Tabs>
</div>
<ProTable
{...tableProps}
actionRef={actionRef}
formRef={formRef}
search={false}
toolBarRender={false}
/>
<AuditModal visible={visible} onCancel={closeModal} formData={auditInfo} />
<DetailTable visible={detailVisible} onCancel={closeModal} dataSource={detailInfo} />
<ProofsModal visible={proofsVisible} onCancel={closeModal} data={proofsData} />
<AppealDetail
data={selectedRow}
modalVisible={appealDetailModal}
onCancel={closeModal}
></AppealDetail>
<AfterLog visible={afterVisible} onCancel={closeModal} data={afterList} />
</PageHeaderWrapper>
);
};
export default connect(({ menu }) => ({
permissions: menu.permissions,
}))(AfterSale);
......@@ -2,7 +2,7 @@
/* eslint-disable guard-for-in */
import React, { useState, useEffect } from 'react';
import { Modal, Timeline, Button } from 'antd';
import styles from '../index.less';
import styles from '../styles.less';
const LogisticsCom = props => {
const { visible, onCancel, data } = props;
......
......@@ -3,7 +3,7 @@ import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Modal, Input, Cascader, notification, InputNumber } from 'antd';
import { shopAudit } from '../services';
import styles from '../index.less';
import styles from '../styles.less';
const FormItem = Form.Item;
const { TextArea } = Input;
......
import React, { useState } from 'react';
import { Modal } from 'antd';
import style from '../index.less';
import style from '../styles.less';
export default props => {
const { visible, onCancel, data } = props;
......
import { Tag, Badge } from 'antd';
import React from 'react';
export const appealType = {
1: '已申诉',
0: '未申诉',
};
export const columnSticData = [
{
title: '售后类型',
dataIndex: 'serviceType',
hideInSearch: true,
width: 120,
render: serviceType => {
if (+serviceType === 1) {
return <span style={{ color: '#ff1616' }}>仅退款</span>;
}
return <span>退货退款</span>;
},
},
{
title: '订单ID',
dataIndex: 'orderNo',
hideInTable: true,
width: 200,
},
{
title: '售后单ID',
dataIndex: 'serviceNo',
width: 300,
render: (serviceNo, r) => (
<div>
{r.timeout ? <Tag color="red">{serviceNo}</Tag> : serviceNo}
{<Badge count={r.reminderFlag ? '' : ''} size="default" />}
</div>
),
},
{
title: '订单ID',
dataIndex: 'orderNo',
hideInSearch: true,
width: 300,
},
{
title: '售后状态',
dataIndex: 'dealStatus',
hideInTable: true,
valueEnum: {
0: '待审核',
10: '三方审核中',
11: '三方审核通过',
12: '三方审核拒绝',
13: '客服审核通过',
14: '商户审核中',
15: '商户审核通过',
16: '商户审核拒绝',
20: '审核拒绝',
21: '申诉中',
30: '待填写退货物流信息',
40: '待退货入库',
50: '退货拒收',
60: '待退款',
61: '退货处理中',
70: '售后成功',
99: '用户取消',
},
width: 100,
},
{
title: '售后类型',
dataIndex: 'type',
hideInTable: true,
width: 120,
valueEnum: {
1: '仅退款',
2: '退货退款',
},
},
{
title: '收货人姓名',
dataIndex: 'receiverName',
width: 200,
},
{
title: '收货人手机号',
dataIndex: 'receiverPhone',
width: 200,
},
{
title: '收货人地址',
dataIndex: 'receiveAddress',
width: 200,
hideInSearch: true,
},
{
title: '订单开始时间',
width: 120,
dataIndex: 'startDate',
key: 'startDate',
valueType: 'date',
hideInTable: true,
},
{
title: '订单结束时间',
width: 120,
dataIndex: 'endDate',
key: 'endDate',
valueType: 'date',
hideInTable: true,
},
{
title: '售后原因',
dataIndex: 'serviceReason',
hideInSearch: true,
width: 200,
},
{
title: '售后发生时间',
dataIndex: 'serviceTime',
hideInSearch: true,
width: 200,
},
{
title: '超时时间',
dataIndex: 'overtime',
hideInSearch: true,
width: 200,
},
{
title: '是否催办',
dataIndex: 'reminderFlag',
hideInSearch: true,
width: 120,
valueEnum: {
true: '',
false: '',
},
},
{
title: '是否同意售后',
dataIndex: 'isAgree',
hideInSearch: true,
width: 120,
},
{
title: '拒绝原因',
dataIndex: 'refuseReason',
hideInSearch: true,
width: 200,
},
];
export const columnPassAudit = [
...columnSticData,
{
title: '商家退货地址',
dataIndex: 'merchantAddress',
hideInSearch: true,
width: 200,
},
{
title: '退回物流',
dataIndex: 'expressCompanyName',
hideInSearch: true,
width: 150,
},
{
title: '退回物流单号',
dataIndex: 'deliveryNo',
hideInSearch: true,
width: 200,
},
{
title: '售后状态',
dataIndex: 'serviceStatus',
hideInSearch: true,
width: 120,
},
];
import { Tabs } from 'antd';
import React from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import { AFTER_SALE_ORDER } from '@/../config/permission.config';
import { connect } from 'dva';
import Pending from './Pending';
import PassAudit from './PassAudit';
const { TabPane } = Tabs;
function AfterSale(props) {
const { permissions } = props;
const canEditable = permissions[AFTER_SALE_ORDER.EDITABLE];
return (
<PageHeaderWrapper>
<Tabs defaultActiveKey="1">
<TabPane tab="未审核" key="1">
<Pending canEditable={canEditable} />
</TabPane>
<TabPane tab="已审核" key="2">
<PassAudit canEditable={canEditable} />
</TabPane>
</Tabs>
</PageHeaderWrapper>
);
}
export default connect(({ menu }) => ({
permissions: menu.permissions,
}))(AfterSale);
......@@ -6,40 +6,31 @@ import _ from 'lodash';
const { kdspApi } = config;
// 分页查询所有数据
export const searchList = params =>
request.post('/api/kdsp/op/afs/shop/list', {
export async function searchList(params, queryStatus) {
const param = {
...params,
pageNo: params.current,
pageSize: params.pageSize || 20,
queryStatus,
};
const data = await request.post('/api/kdsp/op/afs/shop/list', {
prefix: kdspApi,
data: stringify(_.omitBy(params, v => !v)),
data: stringify(_.omitBy(param, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
// export async function searchList(params, queryStatus) {
// const param = {
// ...params,
// pageNo: params.current,
// pageSize: params.pageSize || 20,
// queryStatus,
// };
// const data = await request.post('/api/kdsp/op/afs/shop/list', {
// prefix: kdspApi,
// data: stringify(_.omitBy(param, v => !v)),
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// },
// });
// if (data.data) {
// return {
// total: data.data.total,
// data: data.data.records,
// };
// }
// return {
// total: 0,
// data: [],
// };
// }
if (data.data) {
return {
total: data.data.total,
data: data.data.records,
};
}
return {
total: 0,
data: [],
};
}
// 售后单详情
export async function orderDetail(params) {
......
import request from '@/utils/request';
import config from '../../config/env.config';
const { msgApi } = config;
const { msgApi, wsApi } = config;
export const getSocketUrl = ({ token, channelId }) =>
`${wsApi}/ws?token=${token}&channelId=${channelId}`;
/**
* @name 商户消息列表
* @see http://yapi.quantgroups.com/project/193/interface/api/41792
......@@ -21,9 +25,6 @@ export function apiGetBussinessMsgList(data, params) {
export function apiGetBusinessMsgUnReadCount(data) {
return request.post('/v1/send/message/getBusinessMsgUnReadCount', {
data,
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
prefix: msgApi,
});
}
......
......@@ -76,13 +76,16 @@ class Socket extends EventEmitter {
if (e.code === '4500') {
this.socket.close();
} else {
this.taskRemindInterval = setInterval(() => {
const reconnect = () => {
clearTimeout(this.taskRemindInterval);
this.taskRemindInterval = setTimeout(() => {
if (!this.connected) {
this.connection();
} else {
clearInterval(this.taskRemindInterval);
reconnect();
}
}, 2000);
}, 5000);
};
reconnect();
}
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment