Commit 7c85a234 authored by 李腾's avatar 李腾

feat: 新增订单查询&订单详情页面

parent a02c476b
...@@ -91,6 +91,12 @@ export default { ...@@ -91,6 +91,12 @@ export default {
path: '/', path: '/',
component: './Admin', component: './Admin',
}, },
{
title: '商户管理后台',
path: '/orderManage/queryOrder',
name: 'queryOrder',
component: './orderManage/queryOrder',
},
{ {
title: '商户管理后台', title: '商户管理后台',
path: '/orderManage/pendingDeliveryOrder', path: '/orderManage/pendingDeliveryOrder',
......
...@@ -2,16 +2,16 @@ const isProduction = process.env.NODE_ENV === 'production'; ...@@ -2,16 +2,16 @@ const isProduction = process.env.NODE_ENV === 'production';
const isPre = process.env.PRE_ENV === 'pre'; const isPre = process.env.PRE_ENV === 'pre';
const envAPi = { const envAPi = {
api: '//backstms-yxm.liangkebang.net', api: 'https://backstms-ds.liangkebang.net',
kdspOpApi: 'https://kdsp-operation-yxm.liangkebang.net', kdspOpApi: 'https://kdsp-operation-ds.liangkebang.net',
kdspApi: 'https://sc-op-api-yxm.liangkebang.net', kdspApi: 'https://sc-op-api-ds.liangkebang.net',
goodsApi: 'https://sc-op-api-yxm.liangkebang.net', goodsApi: 'https://sc-op-api-ds.liangkebang.net',
querysApi: 'https://sc-settlement-api-yxm.liangkebang.net', querysApi: 'https://sc-settlement-api-ds.liangkebang.net',
// goodsApi: '//192.168.188.111:7000', // goodsApi: '//192.168.188.111:7000',
prologueDomain: 'https://mall-yxm.liangkebang.net', prologueDomain: 'https://mall-ds.liangkebang.net',
// qiniuHost: 'https://appsync.lkbang.net', // qiniuHost: 'https://appsync.lkbang.net',
qiniuHost: 'https://kdspstatic.q-gp.com/', qiniuHost: 'https://kdspstatic.q-gp.com/',
opapiHost: 'https://opapi-yxm.liangkebang.net', opapiHost: 'https://opapi-ds.liangkebang.net',
}; };
const prodApi = { const prodApi = {
......
import React, { useMemo } from 'react';
import { Spin, Empty } from 'antd';
import style from './index.less';
const CustomTable = props => {
const {
columns,
dataSource,
rowKey,
bordered,
eachRowHeadRender = null,
subDataField = null,
loading = null,
align = 'left',
} = props;
const colLength = columns.length;
const baseColumns = useMemo(() => columns, [columns]);
const baseDataSource = useMemo(() => dataSource, [dataSource]);
const ColElement = () =>
baseColumns.map((column, index) => {
const key = column.dataIndex || index.toString();
const styleWidth = column.width ? { width: column.width } : {};
return <col key={key} style={styleWidth}></col>;
});
/** @name thead内容 */
const TheadElement = () => (
<tr>
{baseColumns.map((column, index) => {
const key = column.dataIndex || index;
return <th key={key}>{column.title}</th>;
})}
</tr>
);
/** @name 获取td元素数据 */
const getTdElement = ({ dataSourceItem, dataSourceIndex, subData, subDataItem, subDataIndex }) =>
baseColumns.map((column, index) => {
const { render, dataIndex, align: itemAlign = null } = column;
const key = dataIndex || index;
// 子集不存在的属性去顶级查找
const currentData =
subDataItem && subDataItem[key] !== undefined ? subDataItem[key] : dataSourceItem[key];
let rowSpan = 1;
// 设置了自动合并 && 只设置第一条数据的值,其他的返回<></</>
if (column.rowSpanMode === 'auto' && subData) {
rowSpan = subData.length;
if (subDataIndex > 0) {
return <></>;
}
}
const renderParams = {
value: currentData,
record: dataSourceItem,
index: dataSourceIndex,
subRecord: subDataItem,
subIndex: subDataIndex,
};
return (
<td rowSpan={rowSpan} key={key} align={itemAlign || align}>
{render ? render(renderParams) : currentData}
</td>
);
});
/** @name 每一行的头部自定义渲染 */
const EachRowHeadElement = dataSourceItem => (
<tr>
<td className={style['thead-render']} colSpan={colLength}>
{' '}
{eachRowHeadRender(dataSourceItem)}
</td>
</tr>
);
/** @name tbody内容 */
const TbodyElement = () =>
baseDataSource.map((dataSourceItem, dataSourceIndex) => {
const subData = subDataField ? dataSourceItem[subDataField] : dataSourceItem;
let tbodyElement = '';
if (Array.isArray(subData)) {
tbodyElement = subData.map((subDataItem, subDataIndex) => {
const key = subDataItem[rowKey] || subDataIndex.toString();
return (
<tr key={key}>
{getTdElement({
dataSourceItem,
dataSourceIndex,
subData,
subDataItem,
subDataIndex,
})}
</tr>
);
});
} else {
const key = subData[rowKey] || dataSourceIndex.toString();
tbodyElement = (
<tr key={key}>{getTdElement({ dataSourceItem, dataSourceIndex, subData })}</tr>
);
}
return (
<React.Fragment key={dataSourceIndex.toString()}>
{eachRowHeadRender ? (
<EachRowHeadElement key={dataSourceIndex.toString()} {...dataSourceItem} />
) : (
''
)}
{tbodyElement}
</React.Fragment>
);
});
const EmptyElement = () => {
if (!dataSource || dataSource.length === 0) {
return (
<div className={style['custom-table-empty']}>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
);
}
return <></>;
};
const memoTable = useMemo(
() => (
<table>
<colgroup>
<ColElement />
</colgroup>
<thead className={style['custom-table-thead']}>
<TheadElement />
</thead>
<tbody className={style['custom-table-tbody']}>
<TbodyElement />
</tbody>
</table>
),
[props.dataSource],
);
return (
<div className={`${style['custom-table']} ${bordered ? style['custom-table-bordered'] : ''}`}>
<Spin spinning={loading}>
{memoTable}
<EmptyElement />
</Spin>
</div>
);
};
export { CustomTable };
.custom-table {
width: 100%;
background: #fff;
table {
width: 100%;
}
}
.custom-table-thead {
background: #fafafa;
> tr > th {
padding: 16px 16px;
overflow-wrap: break-word;
}
}
.custom-table-tbody {
> tr > td {
padding: 16px 16px;
&.thead-render {
padding: 0;
}
}
}
// bordered
.custom-table-bordered {
> table,
.custom-table-tbody,
.custom-table-thead {
> tr > td,
> tr > th {
border: 1px solid #e8e8e8;
}
}
}
// empt
.custom-table-empty {
margin: 0 auto;
overflow: hidden;
text-align: center;
border: 1px solid #e8e8e8;
border-top: none;
}
import React, { useState, forwardRef, useImperativeHandle } from 'react';
import { Modal, Table } from 'antd';
import style from './index.less';
const DetailModal = (props, ref) => {
const [visible, setVisible] = useState(false);
const modalProps = {
visible,
width: '1000px',
title: '订单详情',
footer: null,
onCancel: () => {
setVisible(false);
},
};
/** @module 基本信息 */
const [baseInfo, setBaseInfo] = useState({});
/** @module 商品table */
const [goodData, setGoodData] = useState([]);
const goodColumns = [
{
title: '商品',
dataIndex: 'skuName',
},
{
title: '单价(元)',
dataIndex: 'supplyPrice',
},
{
title: '数量',
dataIndex: 'count',
},
{
title: '小计',
dataIndex: 'skuName',
},
{
title: '售后状态',
dataIndex: 'afterServiceStatus',
},
];
/** @module 券码 */
const [couponData, setCouponData] = useState([]);
const couponColumns = [
{
title: '券码',
render: (_, record, index) => `券码${index + 1}`,
},
{
title: '有效期',
dataIndex: 'price',
},
{
title: '核销时间',
dataIndex: 'count',
},
{
title: '核销人',
dataIndex: 'skuName',
},
{
title: '状态',
dataIndex: 'afterStatus',
},
];
const open = record => {
console.log(record);
setVisible(true);
const {
skuVos,
couponCodeVos,
receiverName,
receiverMobile,
fullAddress,
orderNo,
orderTime,
payTime,
} = record;
setGoodData(skuVos);
setCouponData(couponCodeVos);
setBaseInfo({
receiverName,
receiverMobile,
fullAddress,
orderNo,
orderTime,
payTime,
});
};
useImperativeHandle(ref, () => ({
open,
}));
return (
<Modal {...modalProps}>
<div className={style['order-info']}>
<div className={style['order-info--item']}>
<div className={style['order-info--item__title']}>收货人信息</div>
<ul className={style['order-info--item__ul']}>
<li>姓名:{baseInfo.receiverName}</li>
<li>手机号:{baseInfo.receiverMobile}</li>
<li>地址:{baseInfo.fullAddress}</li>
</ul>
</div>
<div className={style['order-info--item']}>
<div className={style['order-info--item__title']}>订单信息</div>
<ul className={style['order-info--item__ul']}>
<li>订单号:{baseInfo.orderNo}</li>
<li>下单时间:{baseInfo.orderTime}</li>
<li>付款时间:{baseInfo.payTime}</li>
</ul>
</div>
</div>
{/* 商品 */}
<div className={style['good-table']}>
<Table
pagination={false}
rowKey="orderSkuId"
bordered
columns={goodColumns}
dataSource={goodData}
></Table>
</div>
{/* 券码 */}
<div className={style['coupon-table']}>
<Table pagination={false} bordered columns={couponColumns} dataSource={couponData}></Table>
</div>
</Modal>
);
};
export default forwardRef(DetailModal);
.order-info {
display: flex;
padding: 20px;
background: #fafafa;
&--item {
flex: 1;
&__title {
height: 40px;
font-weight: bold;
font-size: 16px;
}
&__ul {
margin: 0;
padding: 0;
font-size: 14px;
li {
line-height: 30px;
}
}
}
}
.good-table,
.coupon-table {
margin-top: 20px;
}
import React from 'react';
import { Form, Input, Select, DatePicker, Button, Space, notification } from 'antd';
import { sub } from 'date-fns';
import style from './index.less';
const { Option } = Select;
const { RangePicker } = DatePicker;
const SEARCH_TYPE = {
SELECT: 'select',
RANGE_PICKER: 'range_picker',
INPUT: 'input',
};
const FormSearch = props => {
const {
width = '100%',
form,
initialValues = {},
onFinish = () => {},
formConfig,
formOptions,
btnConfig,
} = props;
const FormItemBox = ({ bindKey, label, column, children, afterRender }) => {
let columnValue = column;
if (afterRender) {
columnValue = '';
}
return (
<Form.Item
className={style['custom-form-item']}
column={columnValue}
name={bindKey}
label={label}
>
{children}
</Form.Item>
);
};
// 下拉框类型
const FormItemSelect = config => {
const {
bindKey,
options: configOptions = [],
originOptions = {},
afterRender = null,
afterOptions = {},
} = config;
// 提取公共部分
const BaseSelectElement = () => (
<FormItemBox {...config}>
<Select name={bindKey} className={style['form-item-tag']} {...originOptions}>
{configOptions.map(option => (
<Option key={option.value} value={option.value}>
{option.name}
</Option>
))}
</Select>
</FormItemBox>
);
if (afterRender) {
return (
<div className={style['custom-form-item-group']}>
<BaseSelectElement />
<FormItemBox {...afterOptions}>{afterRender()}</FormItemBox>
</div>
);
}
return <BaseSelectElement />;
};
// 选择日期范围类型
const FormItemRangePicker = config => {
const { originOptions = {} } = config;
return (
<FormItemBox {...config}>
<RangePicker className={style['form-item-tag']} {...originOptions} />
</FormItemBox>
);
};
// 选择日期
// 多级联动
// 输入框类型
const FormItemInput = config => {
const { originOptions = {} } = config;
return (
<FormItemBox {...config}>
<Input className={style['form-item-tag']} {...originOptions} />
</FormItemBox>
);
};
// 表单内容元素
const FormItemElement = () =>
formConfig.map(config => {
const { type } = config;
switch (type) {
case SEARCH_TYPE.SELECT:
return <FormItemSelect key={config.bindKey} {...config} />;
case SEARCH_TYPE.RANGE_PICKER:
return <FormItemRangePicker key={config.bindKey} {...config} />;
case SEARCH_TYPE.INPUT:
return <FormItemInput key={config.bindKey} {...config} />;
default:
return <></>;
}
});
/**
* @module 按钮操作
*/
const FormItemButton = () => (
<Space size={10}>
{btnConfig.map(config => {
const { label, onClick = () => {}, type = 'primary', clickType = 'search' } = config;
const htmlType = clickType === 'reset' ? 'reset' : 'submit';
const callback = () => {
setTimeout(() => {
onClick({ type: clickType, params: form.getFieldValue() });
});
};
return (
<Button key={clickType} type={type} htmlType={htmlType} onClick={callback}>
{label}
</Button>
);
})}
</Space>
);
return (
<div className={style['form-search']}>
<Form
style={{ width }}
layout="inline"
form={form}
initialValues={initialValues}
onFinish={onFinish}
{...formOptions}
>
<FormItemElement></FormItemElement>
<Form.Item>
<FormItemButton></FormItemButton>
</Form.Item>
</Form>
</div>
);
};
export { FormSearch, SEARCH_TYPE };
.form-search {
padding: 15px;
background: #fff;
}
.custom-form-item {
min-width: 320px;
margin-bottom: 20px !important;
&[column='1'] {
width: 100%;
}
&[column='2'] {
width: calc(50% - 16px);
}
&[column='3'] {
width: calc(33.3333% - 32px);
}
&[column='4'] {
width: calc(25% - 48px);
}
&[column='5'] {
width: calc(20% - 54px);
}
}
.form-item-tag {
width: 100%;
}
.custom-form-item-group {
display: flex;
}
import LogisticsForm from '../../../pendingDeliveryOrder/components/LogisticsForm';
const LogisticsFormModal = props => (
// eslint-disable-next-line react/react-in-jsx-scope
<LogisticsForm {...props} />
);
export default LogisticsFormModal;
// 搜索类型
export const ORDER_SEARCH_TYPE = [
{
value: 'orderNo',
name: '订单编号',
},
{
value: 'skuId',
name: '商品id',
},
{
value: 'channelOrderNo',
name: '外部订单',
},
{
value: 'receiverName',
name: '收货人姓名',
},
{
value: 'userMobile',
name: '买家手机号',
},
{
value: 'userMobile4',
name: '买家手机号后四位',
},
{
value: 'receiverMobile4',
name: '收货人手机号后四位',
},
];
// 订单类型
export const ORDER_TYPE = [
{
value: '',
name: '全部',
},
{
value: 1,
name: '普通订单',
},
{
value: 4,
name: '服务订单',
},
];
// 订单状态
export const ORDER_STATUS = [
{
value: '',
name: '全部',
},
{
value: 1,
name: '未支付',
},
{
value: 2,
name: '待发货',
},
{
value: 3,
name: '已发货',
},
{
value: 4,
name: '已完成',
},
{
value: 5,
name: '已关闭',
},
];
This diff is collapsed.
.table-item-header {
display: flex;
padding: 0 20px;
line-height: 40px;
background-color: #f7f8f9;
&--info {
flex: 1;
span {
margin-right: 20px;
}
}
&--btn {
padding: 0 10px;
color: #61b0ff;
}
}
.white-box {
background-color: #fff;
}
.tab-box {
margin-top: 20px;
background-color: #fff;
}
.table-pagination {
margin-bottom: 30px;
padding: 20px 30px;
text-align: right;
}
.sku-info {
display: flex;
.sku-info__name {
margin-left: 10px;
}
}
import { stringify } from 'querystring';
import _ from 'lodash';
import request from '@/utils/request';
import { saveAs } from 'file-saver';
import { format } from 'date-fns';
import config from '../../../../config/env.config';
// 查询订单列表
export async function queryOrderList(params) {
return request.post('/api/kdsp/queryOrderList', {
prefix: config.kdspApi,
data: params,
});
}
// 待发货订单
export async function queryToSend(params) {
try {
const {
data: { current, records, total, size },
} = await request.post('/api/kdsp/op/mch-order/list-v2', {
prefix: config.kdspApi,
data: stringify(_.omitBy(params, v => !v)),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
return {
current,
data: records.map(v => ({ ...v, logisticsStatus: `_${v.logisticsStatus}` })),
total,
pageSize: size,
};
} catch (error) {
return {};
}
}
// 发货/更新物流
export async function updateExpress(params) {
return request.post('/api/kdsp/op/mch-order/update-express-info', {
prefix: config.kdspApi,
data: params,
});
}
// 快递公司
export async function queryExpress() {
try {
const { data } = await request.get('/api/kdsp/op/express/list', {
prefix: config.kdspApi,
});
return data;
} catch (error) {
return {};
}
}
export async function getGoods(orderId) {
const { data } = await request.get(`/api/kdsp/op/mch-order/skus?orderId=${orderId}`, {
prefix: config.kdspApi,
});
return data;
}
export async function getLogistics(orderId) {
const { data } = await request.get(`/api/kdsp/op/mch-order/logistics-skus?orderId=${orderId}`, {
prefix: config.kdspApi,
});
return data;
}
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