feat: new page
This commit is contained in:
@@ -16,7 +16,7 @@ export default {
|
||||
'/admin-api/': {
|
||||
// http://192.168.1.231:48080 伟强
|
||||
// https://petshy.tashowz.com/
|
||||
target: 'https://petshy.tashowz.com',
|
||||
target: 'https://petshy.tashowz.com/ ',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -46,17 +46,18 @@ export default [
|
||||
// ],
|
||||
|
||||
// },
|
||||
{
|
||||
path: '/ai1',
|
||||
name: 'AI1',
|
||||
routes: [
|
||||
{
|
||||
name: 'ai样本',
|
||||
path: '/ai1/tag',
|
||||
component: './ai/sample-tag',
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// path: "/ai1",
|
||||
// name: "AI1",
|
||||
// // redirect: "tag",
|
||||
// routes: [
|
||||
// {
|
||||
// name: "ai样本",
|
||||
// path: "tag",
|
||||
// component: "./prod/list/components/service-rule/index",
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// path: "/system",
|
||||
// name: "系统管理",
|
||||
|
||||
60
src/components/EditableProTable/index.tsx
Normal file
60
src/components/EditableProTable/index.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
ActionType,
|
||||
EditableFormInstance,
|
||||
EditableProTable,
|
||||
type EditableProTableProps,
|
||||
type ParamsType,
|
||||
ProColumns,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Form, Table } from 'antd';
|
||||
import React, { forwardRef, useEffect, useRef, useState } from 'react';
|
||||
|
||||
interface EditableProTableComProps<
|
||||
T extends Record<string, any>,
|
||||
U extends ParamsType = ParamsType,
|
||||
> extends Omit<
|
||||
EditableProTableProps<T, U>,
|
||||
'editableFormRef' | 'recordCreatorProps'
|
||||
> {
|
||||
editableFormRef?: React.Ref<any>;
|
||||
recordCreatorProps?: any;
|
||||
}
|
||||
|
||||
const EditableProTableCom = forwardRef(
|
||||
<T extends Record<string, any>>(
|
||||
props: EditableProTableComProps<T>,
|
||||
ref: React.Ref<any>,
|
||||
) => {
|
||||
// 提取不应该直接传递给 EditableProTable 的属性
|
||||
const { value, columns, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<EditableProTable
|
||||
value={value}
|
||||
columns={columns}
|
||||
actionRef={ref}
|
||||
recordCreatorProps={false}
|
||||
{...restProps}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// const EditableProTableCom = <T extends Record<string, any>>(
|
||||
// props: EditableProTableComProps<T>
|
||||
// ) => {
|
||||
// // 提取不应该直接传递给 EditableProTable 的属性
|
||||
// const { value, columns, ...restProps } = props;
|
||||
|
||||
// return (
|
||||
// <EditableProTable
|
||||
// value={value}
|
||||
// columns={columns}
|
||||
// recordCreatorProps={false}
|
||||
// {...restProps}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
export default EditableProTableCom as <T extends Record<string, any>>(
|
||||
props: EditableProTableComProps<T> & { ref?: React.Ref<any> },
|
||||
) => React.ReactElement;
|
||||
36
src/components/ModalCom/CommonModal.tsx
Normal file
36
src/components/ModalCom/CommonModal.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Modal, type ModalProps } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const CommonModal: React.FC<ModalProps> = (props) => {
|
||||
const { children } = props;
|
||||
return (
|
||||
<Modal
|
||||
{...props}
|
||||
width={'70vw'}
|
||||
styles={{
|
||||
body: {
|
||||
height: '65vh',
|
||||
overflowY: 'auto',
|
||||
padding: '0 16px',
|
||||
minWidth: '100%',
|
||||
},
|
||||
content: {
|
||||
background: '#f5f5f5',
|
||||
padding: 0,
|
||||
},
|
||||
header: {
|
||||
padding: '16px 24px',
|
||||
marginBottom: 16,
|
||||
},
|
||||
footer: {
|
||||
padding: '16px 24px',
|
||||
background: '#ffff',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(CommonModal);
|
||||
@@ -120,41 +120,45 @@ const UploadImages: React.FC<{
|
||||
|
||||
message.success('上传成功');
|
||||
} else {
|
||||
setUploading(false);
|
||||
throw new Error('上传失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Upload error:', error);
|
||||
onError?.(error as Error);
|
||||
setUploading(false);
|
||||
message.error(`上传失败: ${(error as Error).message}`);
|
||||
} finally {
|
||||
console.log('finally');
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Spin spinning={uploading}>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
fileList={fileList}
|
||||
onPreview={handlePreview}
|
||||
onRemove={handleRemove}
|
||||
beforeUpload={beforeUpload}
|
||||
customRequest={handleLoadImage}
|
||||
multiple={multiple}
|
||||
accept={accept}
|
||||
>
|
||||
{fileList.length >= maxCount ? null : uploadButton}
|
||||
</Upload>
|
||||
{previewImage && (
|
||||
<Image
|
||||
wrapperStyle={{ display: 'none' }}
|
||||
preview={{
|
||||
visible: previewOpen,
|
||||
onVisibleChange: (visible) => setPreviewOpen(visible),
|
||||
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
||||
}}
|
||||
src={previewImage}
|
||||
/>
|
||||
)}
|
||||
{uploading}
|
||||
<>
|
||||
<Upload
|
||||
listType="picture-card"
|
||||
fileList={fileList}
|
||||
onPreview={handlePreview}
|
||||
onRemove={handleRemove}
|
||||
beforeUpload={beforeUpload}
|
||||
customRequest={handleLoadImage}
|
||||
multiple={multiple}
|
||||
accept={accept}
|
||||
>
|
||||
{fileList.length >= maxCount ? null : uploadButton}
|
||||
</Upload>
|
||||
{previewImage && (
|
||||
<Image
|
||||
wrapperStyle={{ display: 'none' }}
|
||||
preview={{
|
||||
visible: previewOpen,
|
||||
onVisibleChange: (visible) => setPreviewOpen(visible),
|
||||
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
||||
}}
|
||||
src={previewImage}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</Spin>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { BadgeProps } from 'antd';
|
||||
|
||||
export enum OrderStatus {
|
||||
All = 1,
|
||||
All = 0,
|
||||
PendingPayment = 10,
|
||||
PendingConfirmation = 20,
|
||||
PendingService = 30,
|
||||
@@ -53,3 +53,21 @@ export const mapOrderStatusToBadgeStatus = (
|
||||
|
||||
return statusMap[statusColor] || 'default';
|
||||
};
|
||||
|
||||
export enum SalesStatus {
|
||||
All = 0,
|
||||
Apply = 10,
|
||||
SellerAgree = 20,
|
||||
BuyerDelivery = 30,
|
||||
WaitRefund = 40,
|
||||
Completed = 50,
|
||||
}
|
||||
|
||||
export const SalesStatusLableMap: Record<SalesStatus, string> = {
|
||||
[SalesStatus.All]: '所有售后单',
|
||||
[SalesStatus.Apply]: '等待审核',
|
||||
[SalesStatus.SellerAgree]: '审核通过',
|
||||
[SalesStatus.BuyerDelivery]: '已完成',
|
||||
[SalesStatus.WaitRefund]: '已取消',
|
||||
[SalesStatus.Completed]: '已拒绝',
|
||||
};
|
||||
|
||||
@@ -75,7 +75,9 @@ ul,
|
||||
ol {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.ant-statistic {
|
||||
text-align: center;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.ant-table {
|
||||
width: 100%;
|
||||
@@ -93,6 +95,11 @@ ol {
|
||||
}
|
||||
}
|
||||
|
||||
.ant-table-row {
|
||||
.ant-form-item {
|
||||
margin-block: -16px !important;
|
||||
}
|
||||
}
|
||||
.page-container {
|
||||
background: #fff;
|
||||
width: 100%;
|
||||
|
||||
@@ -131,7 +131,7 @@ const Welcome: React.FC = () => {
|
||||
百业到家 是一个整合了 umi,Ant Design 和 ProComponents
|
||||
的脚手架方案。致力于在设计规范和基础组件的基础上,继续向上构建,提炼出典型模板/业务组件/配套设计资源,进一步提升企业级中后台产品设计研发过程中的『用户』和『设计者』的体验。
|
||||
</p>
|
||||
<div
|
||||
{/* <div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
@@ -156,7 +156,7 @@ const Welcome: React.FC = () => {
|
||||
href="https://procomponents.ant.design"
|
||||
desc="ProComponents 是一个基于 Ant Design 做了更高抽象的模板组件,以 一个组件就是一个页面为开发理念,为中后台开发带来更好的体验。"
|
||||
/>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</Card>
|
||||
</PageContainer>
|
||||
|
||||
@@ -27,20 +27,60 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
name="prodName"
|
||||
label="商品名字"
|
||||
rules={[{ required: true }]}
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="abbreviation"
|
||||
label="商品简称"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="brief"
|
||||
label="商品概述"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="prodNumber"
|
||||
label="商品编码"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="brand"
|
||||
label="品牌"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="shopId"
|
||||
label="商品所有权"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText
|
||||
name="categoryName"
|
||||
label="关联类目"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText name="abbreviation" label="商品简称" width="xl" />
|
||||
<ProFormText name="brief" label="商品概述" width="xl" />
|
||||
<ProFormText name="prodNumber" label="商品编码" width="xl" />
|
||||
<ProFormText name="brand" label="品牌" width="xl" />
|
||||
<ProFormText name="shopId" label="商品所有权" width="xl" />
|
||||
<ProFormText name="categoryName" label="关联类目" width="xl" />
|
||||
<ProForm.Item
|
||||
name="tag"
|
||||
label="商品标签"
|
||||
tooltip="设置卖点标签,单标签限xx字符,最多x个标签"
|
||||
layout="horizontal"
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
required={false}
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
>
|
||||
@@ -58,7 +98,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
style={{ width: '100%' }}
|
||||
name="pic"
|
||||
label="主图"
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
||||
// rules={[{ required: true }]}
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
@@ -70,7 +112,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
name="imgs"
|
||||
label="轮播图"
|
||||
layout="horizontal"
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限7张"
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
>
|
||||
@@ -80,16 +124,21 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
style={{ width: '100%' }}
|
||||
name="whiteImg"
|
||||
label="白底图"
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
extra="仅支持.jpg .png 格式,建议图片比例1:1,限1张"
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
>
|
||||
<UploadImages />
|
||||
</ProForm.Item>
|
||||
<ProForm.Item
|
||||
style={{ width: '100%' }}
|
||||
name="video"
|
||||
label="主视频"
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
extra="仅支持.MP4 .MOV 格式,建议比例1:1、16:9,限1个"
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
>
|
||||
@@ -99,7 +148,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
name="content"
|
||||
label="图文介绍"
|
||||
rules={[{ required: true }]}
|
||||
width="xl"
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
// getValueFromEvent={(e) => e.fileList}
|
||||
>
|
||||
<TinymceEditor />
|
||||
@@ -110,7 +161,7 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
name="seoShortName"
|
||||
label="短标题"
|
||||
colProps={{
|
||||
span: 20,
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
<ProFormText name="seoSearch" label="SEO标题" width={'xl'} />
|
||||
@@ -118,7 +169,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
<Divider />
|
||||
|
||||
<ProForm.Item
|
||||
style={{ width: '100%' }}
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
name="shareImage"
|
||||
label="分享图"
|
||||
width="xl"
|
||||
@@ -130,7 +183,9 @@ const ProdInfo = <T extends Record<string, any>>(props: ProdInfoProps<T>) => {
|
||||
name="shareContent"
|
||||
label="分享话术"
|
||||
placeholder={'请输入分享话术'}
|
||||
width={'xl'}
|
||||
colProps={{
|
||||
span: 24,
|
||||
}}
|
||||
/>
|
||||
</ProFormGroup>
|
||||
<ProFormGroup>
|
||||
|
||||
@@ -1,67 +1,85 @@
|
||||
import { ProForm, ProFormText } from '@ant-design/pro-components';
|
||||
import { Anchor, Col, Form, Row } from 'antd';
|
||||
import ProdReservationConfig from './prodReservationConfig';
|
||||
import ProdServiceAreasInfo from './prodServiceAreasInfo';
|
||||
import ProdWeightConfig from './prodWeightConfig';
|
||||
|
||||
const ServiceRule: React.FC = () => {
|
||||
return (
|
||||
<ProForm<{
|
||||
name: string;
|
||||
company: string;
|
||||
}>
|
||||
grid
|
||||
onFinish={async (values) => {
|
||||
// await waitTime(2000);
|
||||
console.log(values);
|
||||
// message.success('提交成功');
|
||||
}}
|
||||
initialValues={{
|
||||
name: '蚂蚁设计有限公司',
|
||||
useMode: 'chapter',
|
||||
}}
|
||||
<ProForm
|
||||
onFinish={async (value) => console.log(value)}
|
||||
layout="horizontal"
|
||||
style={{ width: '100%' }}
|
||||
labelCol={{ span: '100px' }}
|
||||
>
|
||||
<ProForm.Group>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="name"
|
||||
label="签约客户名称"
|
||||
tooltip="最长为 24 位"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
<ProFormText
|
||||
width="md"
|
||||
name="company"
|
||||
label="我方公司名称"
|
||||
placeholder="请输入名称"
|
||||
/>
|
||||
</ProForm.Group>
|
||||
<ProFormText width="sm" name="id" label="主合同编号" />
|
||||
<ProForm.Item
|
||||
label="数组数据"
|
||||
name="dataSource"
|
||||
// initialValue={defaultData}
|
||||
trigger="onValuesChange"
|
||||
>
|
||||
{/* <EditableProTable<DataSourceType>
|
||||
rowKey="id"
|
||||
toolBarRender={false}
|
||||
columns={columns}
|
||||
recordCreatorProps={{
|
||||
newRecordType: 'dataSource',
|
||||
position: 'top',
|
||||
record: () => ({
|
||||
id: Date.now(),
|
||||
addonBefore: 'ccccccc',
|
||||
decs: 'testdesc',
|
||||
}),
|
||||
}}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys,
|
||||
onChange: setEditableRowKeys,
|
||||
actionRender: (row, _, dom) => {
|
||||
return [dom.delete];
|
||||
},
|
||||
}} */}
|
||||
{/* /> */}
|
||||
</ProForm.Item>
|
||||
<Row gutter={24}>
|
||||
<Col span={18}>
|
||||
<div id="prodServiceAreasInfo">
|
||||
<ProdServiceAreasInfo />
|
||||
</div>
|
||||
<div id="prodReservationConfig">
|
||||
<ProForm.Group>
|
||||
<ProdReservationConfig />
|
||||
</ProForm.Group>
|
||||
</div>
|
||||
<div id="prodEmergencyInfoVO">
|
||||
<ProForm.Group></ProForm.Group>
|
||||
</div>
|
||||
<div id="productOrderLimitVO">
|
||||
<ProForm.Group title="周期接单上限"></ProForm.Group>
|
||||
</div>
|
||||
<div id="prodAdditionalFeeDatesList">
|
||||
<ProForm.Group title="特殊时段规则"></ProForm.Group>
|
||||
</div>
|
||||
<div id="prodAdditionalFeePeriodsList">
|
||||
<ProForm.Group title="特殊日期规则"></ProForm.Group>
|
||||
</div>
|
||||
<div id="prodWeightConfig">
|
||||
<ProdWeightConfig />
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="400px">
|
||||
<Anchor
|
||||
items={[
|
||||
{
|
||||
key: 'prodServiceAreasInfo',
|
||||
href: '#prodServiceAreasInfo',
|
||||
title: '可服务区域',
|
||||
},
|
||||
{
|
||||
key: 'prodReservationConfig',
|
||||
href: '#prodReservationConfig',
|
||||
title: '可预约时段',
|
||||
},
|
||||
{
|
||||
key: 'prodEmergencyInfoVO',
|
||||
href: '#prodEmergencyInfoVO',
|
||||
title: '紧急响应服务',
|
||||
},
|
||||
{
|
||||
key: 'productOrderLimitVO',
|
||||
href: '#productOrderLimitVO',
|
||||
title: '周期接单上限',
|
||||
},
|
||||
{
|
||||
key: 'prodAdditionalFeeDatesList',
|
||||
href: '#prodAdditionalFeeDatesList',
|
||||
title: '特殊时段规则',
|
||||
},
|
||||
{
|
||||
key: 'prodAdditionalFeePeriodsList',
|
||||
href: '#prodAdditionalFeePeriodsList',
|
||||
title: '特殊日期规则',
|
||||
},
|
||||
{
|
||||
key: 'prodWeightConfig',
|
||||
href: '#prodWeightConfig',
|
||||
title: '体重/体型选项',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</ProForm>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
import {
|
||||
ProFormCheckbox,
|
||||
ProFormDigit,
|
||||
ProFormRadio,
|
||||
ProFormSelect,
|
||||
ProFormSwitch,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Divider, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import type { ProdReservationInfoVO } from '@/services/prod/prod-manager/rule';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
type ValueConfig = {
|
||||
reservationSwitch?: number;
|
||||
prodReservationConfig?: ProdReservationInfoVO;
|
||||
};
|
||||
interface ProdReservationConfigProps {
|
||||
value?: ValueConfig;
|
||||
onChange?: (value?: ValueConfig) => void;
|
||||
}
|
||||
const ProdReservationConfig: React.FC<ProdReservationConfigProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Title level={4}>服务区域配置</Title>
|
||||
<Text type="secondary">
|
||||
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<ProFormSwitch
|
||||
name="reservationSwitch"
|
||||
label="应用配置"
|
||||
labelCol={{ span: 13 }}
|
||||
fieldProps={{
|
||||
onChange: (checked) => {
|
||||
props.onChange?.({
|
||||
reservationSwitch: checked ? 1 : 0,
|
||||
});
|
||||
},
|
||||
checkedChildren: '开启',
|
||||
unCheckedChildren: '关闭',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Divider />
|
||||
<ProFormDigit
|
||||
name="advanceHours"
|
||||
label="预约时间约定"
|
||||
wrapperCol={{ span: 11 }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入提前预约时间',
|
||||
},
|
||||
]}
|
||||
fieldProps={{
|
||||
placeholder: '请输入提前预约时间',
|
||||
min: 0,
|
||||
max: 24,
|
||||
addonBefore: '提前',
|
||||
addonAfter: '小时',
|
||||
}}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name="reservationDateRange"
|
||||
label="预约日期范围"
|
||||
options={[
|
||||
{ label: '7天', value: 7 },
|
||||
{ label: '10天', value: 10 },
|
||||
{ label: '15天', value: 15 },
|
||||
{ label: '30天', value: 30 },
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择预约日期范围',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name="allowChange"
|
||||
label="更改预约时间"
|
||||
options={[
|
||||
{ label: '允许更改预约时间', value: 1 },
|
||||
{ label: '禁止更改预约时间', value: 0 },
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择更改预约时间',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name="changeTimeRule"
|
||||
label="时间更改规则"
|
||||
options={[
|
||||
{ label: '服务开始前 1小时 可更改预约时间', value: 1 },
|
||||
{ label: '服务开始前 2小时 可更改预约时间', value: 2 },
|
||||
{ label: '服务开始前 3小时 可更改预约时间', value: 3 },
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择时间更改规则',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name="maxChangeTimes"
|
||||
label="更改次数规则"
|
||||
options={[
|
||||
{ label: '允许修改 1 次', value: 1 },
|
||||
{ label: '允许修改 2 次', value: 2 },
|
||||
{ label: '允许修改 3 次', value: 3 },
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择时间更改规则',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ProdReservationConfig);
|
||||
@@ -0,0 +1,115 @@
|
||||
import {
|
||||
ProFormCheckbox,
|
||||
ProFormDigit,
|
||||
ProFormRadio,
|
||||
ProFormSelect,
|
||||
ProFormSwitch,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Divider, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import type { ProdServiceAreasInfoVO } from '@/services/prod/prod-manager/rule';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
type ValueConfig = {
|
||||
regionSwitch?: number;
|
||||
prodServiceAreasInfo?: ProdServiceAreasInfoVO;
|
||||
};
|
||||
interface ProdServiceAreasInfoProps {
|
||||
value?: ValueConfig;
|
||||
onChange?: (value?: ValueConfig) => void;
|
||||
}
|
||||
const ProdServiceAreasInfo: React.FC<ProdServiceAreasInfoProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Title level={4}>服务区域配置</Title>
|
||||
<Text type="secondary">
|
||||
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||
</Text>
|
||||
</div>
|
||||
<ProFormSwitch
|
||||
name="regionSwitch"
|
||||
label="应用配置"
|
||||
labelCol={{ span: 13 }}
|
||||
fieldProps={{
|
||||
onChange: (checked) => {
|
||||
props.onChange?.({
|
||||
regionSwitch: checked ? 1 : 0,
|
||||
});
|
||||
},
|
||||
checkedChildren: '开启',
|
||||
unCheckedChildren: '关闭',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
<ProFormSelect
|
||||
name="areaNameList"
|
||||
label="服务区域"
|
||||
options={[
|
||||
'鼓楼区',
|
||||
'台江区',
|
||||
'晋安区',
|
||||
'马尾区',
|
||||
'闽侯县',
|
||||
'福清市(县级市)',
|
||||
'长乐区',
|
||||
'罗源县',
|
||||
'永泰县',
|
||||
'平潭县',
|
||||
]}
|
||||
fieldProps={{
|
||||
mode: 'multiple',
|
||||
}}
|
||||
placeholder="请选择服务区域"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择服务区域',
|
||||
type: 'array',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormRadio.Group
|
||||
name="ruleType"
|
||||
label="超区规则"
|
||||
options={[
|
||||
{ label: '拒单', value: 0 },
|
||||
{ label: '接单-收超区费', value: 2 },
|
||||
{ label: '接单-收超区费-收超区费', value: 3 },
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择服务区域',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<ProFormDigit
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请设置费用',
|
||||
},
|
||||
]}
|
||||
label="设置费用"
|
||||
name="fee"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ProdServiceAreasInfo);
|
||||
278
src/pages/prod/list/components/service-rule/prodWeightConfig.tsx
Normal file
278
src/pages/prod/list/components/service-rule/prodWeightConfig.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
import {
|
||||
DragSortTable,
|
||||
EditableProTable,
|
||||
type ProColumns,
|
||||
ProFormCheckbox,
|
||||
ProFormDigit,
|
||||
ProFormRadio,
|
||||
ProFormSelect,
|
||||
ProFormSwitch,
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { Divider, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import type {
|
||||
ProdWeightRangePricesDO,
|
||||
ProdWeightRangePricesSaveInfoVO,
|
||||
} from '@/services/prod/prod-manager/rule';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
type ValueConfig = {
|
||||
weightSwitch?: number;
|
||||
prodWeightConfig?: ProdWeightRangePricesSaveInfoVO;
|
||||
};
|
||||
interface ProdWeightConfigProps {
|
||||
value?: ValueConfig;
|
||||
onChange?: (value?: ValueConfig) => void;
|
||||
}
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '体型&体重',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
width: '20%',
|
||||
render: (text: string, record: any) => {
|
||||
return <ProFormText name={record.key} label={text} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '价格',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
dataIndex: 'price',
|
||||
key: 'price',
|
||||
width: '20%',
|
||||
},
|
||||
];
|
||||
const prodWeightConfig: React.FC<ProdWeightConfigProps> = (props) => {
|
||||
const { value } = props;
|
||||
const columns: ProColumns<ProdWeightRangePricesDO>[] = [
|
||||
{
|
||||
title: '体型&体重 *',
|
||||
dataIndex: 'weightRange',
|
||||
width: '30%',
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
message: '此项是必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '价格 *',
|
||||
dataIndex: 'price',
|
||||
width: '30%',
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
whitespace: true,
|
||||
message: '此项是必填项',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 250,
|
||||
render: () => {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
];
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
gap: 12,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<Title level={4}>服务区域配置</Title>
|
||||
<Text type="secondary">
|
||||
默认服务区域为全城,开启此配置,可自定义可服务区域以及超区时的限制规则
|
||||
</Text>
|
||||
</div>
|
||||
<ProFormSwitch
|
||||
name="weightSwitch"
|
||||
label="应用配置"
|
||||
labelCol={{ span: 13 }}
|
||||
fieldProps={{
|
||||
onChange: (checked) => {
|
||||
props.onChange?.({
|
||||
weightSwitch: checked ? 1 : 0,
|
||||
});
|
||||
},
|
||||
checkedChildren: '开启',
|
||||
unCheckedChildren: '关闭',
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
<ProFormRadio.Group
|
||||
name="isWeightCharge"
|
||||
label="是否收费"
|
||||
options={[
|
||||
{
|
||||
label: '是',
|
||||
value: 0,
|
||||
},
|
||||
{
|
||||
label: '否',
|
||||
value: 1,
|
||||
},
|
||||
]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择是否收费',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{/* <EditableProTable<ProdWeightRangePricesDO>
|
||||
rowKey="id"
|
||||
headerTitle="可拖拽编辑表格"
|
||||
maxLength={10}
|
||||
recordCreatorProps={{
|
||||
position: 'bottom',
|
||||
record: () => ({ id: Date.now().toString(), weightRange: '', price: 0, }),
|
||||
}}
|
||||
columns={columns}
|
||||
value={dataSource}
|
||||
onChange={setDataSource}
|
||||
editable={{
|
||||
type: 'multiple',
|
||||
editableKeys,
|
||||
onSave: async (rowKey, data) => {
|
||||
console.log('保存数据:', data);
|
||||
},
|
||||
onChange: setEditableRowKeys,
|
||||
}}
|
||||
// 拖拽排序配置
|
||||
dragSortKey="sort"
|
||||
onDragSortEnd={(newDataSource) => {
|
||||
console.log('排序后:', newDataSource);
|
||||
setDataSource(newDataSource as DataType[]);
|
||||
}}
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(prodWeightConfig);
|
||||
|
||||
// import { EditableProTable } from '@ant-design/pro-components';
|
||||
// import type { ProColumns } from '@ant-design/pro-components';
|
||||
// import { useState } from 'react';
|
||||
// import { MenuOutlined } from '@ant-design/icons';
|
||||
|
||||
// interface DataType {
|
||||
// id: string;
|
||||
// name: string;
|
||||
// age: number;
|
||||
// email: string;
|
||||
// }
|
||||
|
||||
// const DragEditableProTable: React.FC = () => {
|
||||
// const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||
// const [dataSource, setDataSource] = useState<DataType[]>([
|
||||
// { id: '1', name: '张三', age: 25, email: 'zhangsan@example.com' },
|
||||
// { id: '2', name: '李四', age: 30, email: 'lisi@example.com' },
|
||||
// { id: '3', name: '王五', age: 28, email: 'wangwu@example.com' },
|
||||
// ]);
|
||||
|
||||
// const columns: ProColumns<DataType>[] = [
|
||||
// {
|
||||
// title: '排序',
|
||||
// dataIndex: 'sort',
|
||||
// width: 60,
|
||||
// render: () => <MenuOutlined style={{ cursor: 'grab', color: '#999' }} />,
|
||||
// },
|
||||
// {
|
||||
// title: '姓名',
|
||||
// dataIndex: 'name',
|
||||
// formItemProps: {
|
||||
// rules: [{ required: true, message: '请输入姓名' }],
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// title: '年龄',
|
||||
// dataIndex: 'age',
|
||||
// valueType: 'digit',
|
||||
// },
|
||||
// {
|
||||
// title: '邮箱',
|
||||
// dataIndex: 'email',
|
||||
// valueType: 'text',
|
||||
// },
|
||||
// {
|
||||
// title: '操作',
|
||||
// valueType: 'option',
|
||||
// width: 200,
|
||||
// render: (text, record, _, action) => [
|
||||
// <a
|
||||
// key="editable"
|
||||
// onClick={() => {
|
||||
// action?.startEditable?.(record.id);
|
||||
// }}
|
||||
// >
|
||||
// 编辑
|
||||
// </a>,
|
||||
// <a
|
||||
// key="delete"
|
||||
// onClick={() => {
|
||||
// setDataSource(dataSource.filter((item) => item.id !== record.id));
|
||||
// }}
|
||||
// >
|
||||
// 删除
|
||||
// </a>,
|
||||
// ],
|
||||
// },
|
||||
// ];
|
||||
|
||||
// return (
|
||||
// <EditableProTable<DataType>
|
||||
// rowKey="id"
|
||||
// headerTitle="可拖拽编辑表格"
|
||||
// maxLength={10}
|
||||
// recordCreatorProps={{
|
||||
// position: 'bottom',
|
||||
// record: () => ({ id: Date.now().toString(), name: '', age: 0, email: '' }),
|
||||
// }}
|
||||
// columns={columns}
|
||||
// value={dataSource}
|
||||
// onChange={setDataSource}
|
||||
// editable={{
|
||||
// type: 'multiple',
|
||||
// editableKeys,
|
||||
// onSave: async (rowKey, data) => {
|
||||
// console.log('保存数据:', data);
|
||||
// },
|
||||
// onChange: setEditableRowKeys,
|
||||
// }}
|
||||
// // 拖拽排序配置
|
||||
// dragSortKey="sort"
|
||||
// onDragSortEnd={(newDataSource) => {
|
||||
// console.log('排序后:', newDataSource);
|
||||
// setDataSource(newDataSource as DataType[]);
|
||||
// }}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
|
||||
// export default DragEditableProTable;
|
||||
@@ -6,16 +6,9 @@ import ProdInfo from '@/pages/prod/list/components/prod-info';
|
||||
import Sku from '@/pages/prod/list/components/sku';
|
||||
import type { Prod, ProdDetail, SkuConfig } from '@/services/prod/prod-manager';
|
||||
|
||||
const waitTime = (time: number = 100) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
|
||||
const ProdDetailPage: React.FC<{
|
||||
data?: Prod;
|
||||
type?: string;
|
||||
formRef?: React.RefObject<FormInstance<any>>;
|
||||
}> = (props) => {
|
||||
const { data, formRef } = props;
|
||||
@@ -42,8 +35,8 @@ const ProdDetailPage: React.FC<{
|
||||
formRef={formRef}
|
||||
submitter={false}
|
||||
onFinish={async () => {
|
||||
await waitTime(1000);
|
||||
message.success('提交成功');
|
||||
// await waitTime(1000);
|
||||
// message.success('提交成功');
|
||||
}}
|
||||
formProps={{
|
||||
validateMessages: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { EllipsisOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import type { ActionType, ProColumns } from '@ant-design/pro-components';
|
||||
import { useNavigate } from '@umijs/max';
|
||||
import type { MenuProps, TabsProps } from 'antd';
|
||||
import { Button, Dropdown, Space, Tabs } from 'antd';
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
@@ -25,6 +26,7 @@ const ProdList = () => {
|
||||
const [type, setType] = useState<'create' | 'update' | 'test'>('create');
|
||||
const [status, setStatus] = useState<number>();
|
||||
const detailRef = useRef<ConfigurableDrawerFormRef>(null);
|
||||
const navigator = useNavigate();
|
||||
// const editRef = useRef<ConfigurableDrawerFormRef>(null);
|
||||
const [id, setId] = useState<number>(0);
|
||||
const onChange = useCallback(
|
||||
@@ -89,6 +91,12 @@ const ProdList = () => {
|
||||
[id, type],
|
||||
);
|
||||
|
||||
const handleLink = (key: string, row: Prod) => {
|
||||
if (key === 'sku') {
|
||||
// navigator(`/prod/list/sku/${row.prodId}`);
|
||||
}
|
||||
console.log(key, row);
|
||||
};
|
||||
// const renderDetailFooter = () => {
|
||||
// if (type === "update") {
|
||||
// return (
|
||||
@@ -118,7 +126,11 @@ const ProdList = () => {
|
||||
},
|
||||
{
|
||||
key: 'sku',
|
||||
label: <Button type="link">SKU管理</Button>,
|
||||
label: (
|
||||
<Button type="link" onClick={() => handleLink('sku', row)}>
|
||||
SKU管理
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'rules-service',
|
||||
@@ -155,7 +167,9 @@ const ProdList = () => {
|
||||
<Button type="link" onClick={() => handleEdit(record)}>
|
||||
商品编辑
|
||||
</Button>
|
||||
<Button type="link">SKU管理</Button>
|
||||
<Button type="link" onClick={() => handleLink('sku', record)}>
|
||||
SKU管理
|
||||
</Button>
|
||||
</Space.Compact>
|
||||
<Space.Compact direction="vertical" block key="2">
|
||||
<Button type="link">服务规则</Button>
|
||||
|
||||
20
src/pages/trade/order/components/createSales.module.less
Normal file
20
src/pages/trade/order/components/createSales.module.less
Normal file
@@ -0,0 +1,20 @@
|
||||
.sales {
|
||||
:global {
|
||||
.ant-pro-card-body {
|
||||
padding: 16px !important;
|
||||
}
|
||||
.ant-pro-card-size-small {
|
||||
.ant-pro-card-header {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
min-height: 41px;
|
||||
}
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.ant-pro-card-col {
|
||||
flex: 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
762
src/pages/trade/order/components/createSalesModal copy.tsx
Normal file
762
src/pages/trade/order/components/createSalesModal copy.tsx
Normal file
@@ -0,0 +1,762 @@
|
||||
//新建售后
|
||||
|
||||
import {
|
||||
BetaSchemaForm,
|
||||
type EditableFormInstance,
|
||||
EditableProTable,
|
||||
ProCard,
|
||||
type ProColumns,
|
||||
ProForm,
|
||||
type ProFormColumnsType,
|
||||
ProFormDependency,
|
||||
ProFormDigit,
|
||||
ProFormRadio,
|
||||
ProFormTextArea,
|
||||
} from '@ant-design/pro-components';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Divider,
|
||||
Empty,
|
||||
Form,
|
||||
type FormInstance,
|
||||
Input,
|
||||
InputNumber,
|
||||
Space,
|
||||
Spin,
|
||||
Statistic,
|
||||
Table,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import EditableProTableCom from '@/components/EditableProTable';
|
||||
import CommonModal from '@/components/ModalCom/CommonModal';
|
||||
import UploadImages from '@/components/Upload/UploadImages';
|
||||
import styles from './createSales.module.less';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
interface DataType {
|
||||
key: string;
|
||||
name: string;
|
||||
age: number;
|
||||
address: string;
|
||||
}
|
||||
const originData = Array.from({ length: 100 }).map<DataType>((_, i) => ({
|
||||
key: i.toString(),
|
||||
name: `Edward ${i}`,
|
||||
age: 32,
|
||||
address: `London Park no. ${i}`,
|
||||
}));
|
||||
|
||||
const waitTime = (time: number = 100) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(true);
|
||||
}, time);
|
||||
});
|
||||
};
|
||||
const CreateSalesModal = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const formRefSales = useRef<FormInstance>(null);
|
||||
const formRef = useRef<FormInstance>(null);
|
||||
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const handleOk = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const values1 = formRef.current?.getFieldsValue();
|
||||
const values2 = formRefSales.current?.getFieldsValue();
|
||||
|
||||
console.log({ ...values1, values2 });
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const onOpen = useCallback(
|
||||
(flag: boolean) => {
|
||||
setOpen(flag);
|
||||
},
|
||||
[open],
|
||||
);
|
||||
const salesColumns: ProFormColumnsType[] = [
|
||||
{
|
||||
title: '售后类型',
|
||||
dataIndex: 'cancelReason',
|
||||
valueType: 'radioButton',
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldProps: {
|
||||
options: [{ value: '仅退款', label: '仅退款' }],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '退款类型',
|
||||
dataIndex: 'cancelRemark',
|
||||
valueType: 'radioButton',
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ value: '整单退款(通用)', label: '整单退款' },
|
||||
{ value: '选项二', label: '商品退款' },
|
||||
{ value: '选项三', label: '扩展服务退款' },
|
||||
{ value: '服务附加费退款', label: '服务附加费退款' },
|
||||
{ value: '选项五', label: '退运费' },
|
||||
{ value: '其他', label: '其他' },
|
||||
],
|
||||
},
|
||||
},
|
||||
// {
|
||||
// title: "",
|
||||
// dependencies: ["cancelRemark"],
|
||||
// dataIndex: "cancelRemark1",
|
||||
// valueType: "textarea",
|
||||
// formItemProps: {
|
||||
// rules: [
|
||||
// {
|
||||
// required: true,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// fieldProps: {
|
||||
// placeholder: "请输入",
|
||||
// },
|
||||
// renderFormItem: (schema, config, form) => {
|
||||
// const grade = form.getFieldValue("cancelRemark");
|
||||
// if (grade === "其他") {
|
||||
// return <Input.TextArea placeholder="请输入" rows={4} />;
|
||||
// }
|
||||
// if (grade === "服务附加费退款") {
|
||||
// return (
|
||||
// <Form.Item>
|
||||
// <ExtendRefunds />
|
||||
// </Form.Item>
|
||||
// );
|
||||
// }
|
||||
// return <></>;
|
||||
// },
|
||||
// },
|
||||
];
|
||||
const applyColumns: ProFormColumnsType[] = [
|
||||
{
|
||||
title: '售后类型',
|
||||
dataIndex: 'cancelReason1',
|
||||
valueType: 'radio',
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ value: '用户主动取消', label: '用户主动取消' },
|
||||
{ value: '选项二', label: '选项二' },
|
||||
{ value: '选项三', label: '选项三' },
|
||||
{ value: '选项四', label: '选项四' },
|
||||
{ value: '选项五', label: '选项五' },
|
||||
{ value: '选项六', label: '选项六' },
|
||||
{ value: '选项七', label: '选项七' },
|
||||
],
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
marginTop: '6px',
|
||||
background: '#f0f0f0',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '问题描述',
|
||||
dataIndex: 'cancelRemark22',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '问题凭证',
|
||||
dataIndex: 'cancelRemark23',
|
||||
formItemProps: {
|
||||
extra: '请上传问题凭证,支持.jpg .png 格式,最多 4 张。',
|
||||
},
|
||||
renderFormItem: () => {
|
||||
return <UploadImages multiple={true} maxCount={4} />;
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: '商家备注',
|
||||
dataIndex: 'cancelRemark2311',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => onOpen(true)}>新建售后</Button>
|
||||
<CommonModal
|
||||
title="新建售后单"
|
||||
open={true}
|
||||
onOk={handleOk}
|
||||
onCancel={() => onOpen(false)}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{ width: '100%' }}
|
||||
size={16}
|
||||
className={styles.sales}
|
||||
>
|
||||
<ProCard.Group direction="row">
|
||||
<ProCard layout="center">
|
||||
<Statistic title="实付金额" value={1000} precision={2} />
|
||||
</ProCard>
|
||||
<ProCard layout="center">
|
||||
<Statistic title="累计退款金额" value={200} precision={2} />
|
||||
</ProCard>
|
||||
<ProCard layout="center">
|
||||
<Statistic title="可退款金额" value={800} precision={2} />
|
||||
</ProCard>
|
||||
</ProCard.Group>
|
||||
<ProCard title="售后选项">
|
||||
<ProForm
|
||||
formRef={formRefSales}
|
||||
layout="horizontal"
|
||||
submitter={false}
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
labelAlign="right"
|
||||
>
|
||||
<ProFormRadio.Group
|
||||
name="cancelReason"
|
||||
label="售后类型"
|
||||
rules={[{ required: true, message: '请选择售后类型' }]}
|
||||
options={[{ value: '仅退款', label: '仅退款' }]}
|
||||
radioType="button"
|
||||
/>
|
||||
|
||||
<ProFormRadio.Group
|
||||
name="cancelRemark"
|
||||
label="退款类型"
|
||||
rules={[{ required: true, message: '请选择退款类型' }]}
|
||||
options={[
|
||||
{ value: '整单退款(通用)', label: '整单退款' },
|
||||
{ value: '选项二', label: '商品退款' },
|
||||
{ value: '选项三', label: '扩展服务退款' },
|
||||
{ value: '服务附加费退款', label: '服务附加费退款' },
|
||||
{ value: '选项五', label: '退运费' },
|
||||
{ value: '其他', label: '其他' },
|
||||
]}
|
||||
radioType="button"
|
||||
/>
|
||||
|
||||
<ConditionalFields />
|
||||
</ProForm>
|
||||
</ProCard>
|
||||
|
||||
<ProCard title="申请信息(通用表单样式,根据当前订单状态调用不同的申请原因表单)">
|
||||
<BetaSchemaForm
|
||||
layoutType="Form"
|
||||
formRef={formRef}
|
||||
columns={applyColumns || []}
|
||||
layout="horizontal"
|
||||
submitter={false}
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
labelAlign="right"
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard split="vertical" headerBordered title="预估退款明细">
|
||||
<ProCard
|
||||
colSpan={'50%'}
|
||||
headerBordered
|
||||
title="预估退款明细"
|
||||
size="small"
|
||||
>
|
||||
<Space align="center" style={{ marginBottom: 16 }}>
|
||||
<div>
|
||||
<Text type="danger">最高退款金额</Text>
|
||||
<Text type="secondary">(不含虚拟资产)</Text>
|
||||
</div>
|
||||
<ProFormDigit
|
||||
name="input-number"
|
||||
min={1}
|
||||
max={10}
|
||||
fieldProps={{
|
||||
precision: 0,
|
||||
prefix: '¥',
|
||||
style: { width: '100px', margin: 0 },
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
退款金额上限 <Text type="danger">¥700</Text>
|
||||
,金额不支持修改,优惠券、积分、余额等虚拟资产不支持修改,虚拟资产将随退款而原路退回,具体金额以实际情况为准。
|
||||
</Text>
|
||||
</div>
|
||||
<Divider />
|
||||
<Title level={5}>预估退款项</Title>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>商品/服务</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>增值/扩展服务</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>运费</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</ProCard>
|
||||
<ProCard
|
||||
size="small"
|
||||
headerBordered
|
||||
title="预估退款至"
|
||||
colSpan={'50%'}
|
||||
>
|
||||
<Title level={5}>
|
||||
预估退款项
|
||||
<Text type="secondary">(退款方式:原支付方式返还)</Text>
|
||||
</Title>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
微信支付
|
||||
<Text type="secondary">(建设银行 尾号1626)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
钱包余额
|
||||
<Text type="secondary">(个人钱包 编号1090)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>宠豆豆(积分)</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>红包</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
<Divider />
|
||||
<Title level={5}>预估扣除项</Title>
|
||||
<Text type="secondary">
|
||||
如果后续有设计退款时扣除比例的设定才会存在此字段。目前暂时不做这个业务逻辑,仅为布局占位。
|
||||
</Text>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
违约金
|
||||
<Text type="secondary">(服务开始前60分钟内退款)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</ProCard>
|
||||
</ProCard>
|
||||
</Space>
|
||||
</CommonModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
type DataSourceType = {
|
||||
id: React.Key;
|
||||
prod?: string;
|
||||
num?: number;
|
||||
state?: string;
|
||||
created_at?: number;
|
||||
children?: DataSourceType[];
|
||||
};
|
||||
|
||||
const columnsSale: ProColumns<DataSourceType>[] = [
|
||||
{
|
||||
title: '商品',
|
||||
dataIndex: 'prod',
|
||||
editable: () => false,
|
||||
},
|
||||
{
|
||||
title: '退款信息',
|
||||
key: 'refund',
|
||||
editable: () => false,
|
||||
dataIndex: 'state',
|
||||
},
|
||||
{
|
||||
title: '退款数量',
|
||||
dataIndex: 'num',
|
||||
valueType: 'digit',
|
||||
editable: () => true,
|
||||
formItemProps: {
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入退款数量',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const defaultData: DataSourceType[] = new Array(20).fill(1).map((_, index) => {
|
||||
return {
|
||||
id: (Date.now() + index).toString(),
|
||||
prod: `活动名称${index}`,
|
||||
num: index,
|
||||
state: 'open',
|
||||
created_at: 1590486176000,
|
||||
};
|
||||
});
|
||||
|
||||
const ExtendRefunds = (props: {
|
||||
onChange?: (value: readonly DataSourceType[]) => void;
|
||||
}) => {
|
||||
const [data, setData] = useState<readonly DataSourceType[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const editorFormRef = useRef<EditableFormInstance<DataSourceType>>(null);
|
||||
const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||
const [formformSales] = Form.useForm();
|
||||
const [formExtend] = Form.useForm();
|
||||
const [formServeExtFee] = Form.useForm();
|
||||
const [formShippingFee] = Form.useForm();
|
||||
const fetchData = async () => {
|
||||
await waitTime();
|
||||
setData(defaultData);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
props.onChange?.(data);
|
||||
}, [data, props.onChange]);
|
||||
|
||||
useEffect(() => {
|
||||
setEditableRowKeys(data!.map((item) => item.id));
|
||||
}, [data]);
|
||||
console.log(editableKeys);
|
||||
return (
|
||||
<EditableProTable
|
||||
value={defaultData}
|
||||
columns={columnsSale}
|
||||
rowKey="id"
|
||||
recordCreatorProps={false}
|
||||
editable={{
|
||||
// form: formformSales,
|
||||
editableKeys: editableKeys,
|
||||
onChange: setEditableRowKeys,
|
||||
onValuesChange: (record, recordList) => {
|
||||
setData(recordList);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
// <EditableProTableCom<DataSourceType>
|
||||
// columns={columnsSale}
|
||||
// value={defaultData}
|
||||
// rowKey="id"
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// editable={{
|
||||
// form: formformSales,
|
||||
// editableKeys: editableKeys,
|
||||
// onChange: setEditableRowKeys,
|
||||
// onValuesChange: (record, recordList) => {
|
||||
// setData(recordList);
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
);
|
||||
};
|
||||
// const ExtendRefunds = (props: {
|
||||
// onChange?: (value: readonly DataSourceType[]) => void;
|
||||
// }) => {
|
||||
// const [data, setData] = useState<readonly DataSourceType[]>([]);
|
||||
// const [loading, setLoading] = useState(true);
|
||||
// const editorFormRef = useRef<EditableFormInstance<DataSourceType>>(null);
|
||||
// const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
|
||||
// const [formformSales] = Form.useForm();
|
||||
// const [formExtend] = Form.useForm();
|
||||
// const [formServeExtFee] = Form.useForm();
|
||||
// const [formShippingFee] = Form.useForm();
|
||||
|
||||
// const fetchData = async () => {
|
||||
// await waitTime();
|
||||
// setData(defaultData);
|
||||
// setLoading(false);
|
||||
// };
|
||||
|
||||
// useEffect(() => {
|
||||
// fetchData();
|
||||
// }, []);
|
||||
// useEffect(() => {
|
||||
// props.onChange?.(data);
|
||||
// }, [data]);
|
||||
|
||||
// useEffect(() => {
|
||||
// setEditableRowKeys(data!.map((item) => item.id));
|
||||
// console.log(data);
|
||||
// }, [data]);
|
||||
|
||||
// const renderContent = useCallback(() => {
|
||||
// // if (data.length === 0 && !loading) {
|
||||
// // return (
|
||||
// // <div>
|
||||
// // <Empty
|
||||
// // description="订单无此退款项/类型"
|
||||
// // style={{
|
||||
// // background: "#fff",
|
||||
// // padding: "16px",
|
||||
// // margin: 0,
|
||||
// // borderRadius: "4px",
|
||||
// // }}
|
||||
// // />
|
||||
// // </div>);
|
||||
// // }
|
||||
// return (
|
||||
// <Spin spinning={loading} >
|
||||
// <EditableProTable<DataSourceType>
|
||||
// columns={columnsSale}
|
||||
// rowKey="id"
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// value={data}
|
||||
// editable={{
|
||||
// form: formformSales,
|
||||
// editableKeys: data.map((item) => item.id),
|
||||
// onChange: setEditableRowKeys,
|
||||
// onValuesChange: (record, recordList) => {
|
||||
// setData(recordList);
|
||||
// },
|
||||
// }}
|
||||
// />
|
||||
// {/* <EditableProTableCom<DataSourceType>
|
||||
// columns={columnsSale}
|
||||
// rowKey="id"
|
||||
// value={data}
|
||||
// key={"sale"}
|
||||
// // onChange={onTableChange}
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// editable={{
|
||||
// form: formformSales,
|
||||
// editableKeys: data.map((item) => item.id),
|
||||
// onChange: setEditableRowKeys,
|
||||
// // onValuesChange: (record, recordList) => {
|
||||
// // setData(recordList);
|
||||
// // },
|
||||
// }}
|
||||
// />
|
||||
// </ProCard>
|
||||
// <ProCard title="退款项">
|
||||
// <EditableProTableCom<DataSourceType>
|
||||
// columns={columnsSale}
|
||||
// rowKey="id"
|
||||
// value={data}
|
||||
// // onChange={onTableChange}
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// key={"sale1"}
|
||||
// editable={{
|
||||
// form: formExtend,
|
||||
// editableKeys,
|
||||
// onChange: setEditableRowKeys,
|
||||
// // onValuesChange: (record, recordList) => {
|
||||
// // setData(recordList);
|
||||
// // },
|
||||
// }}
|
||||
// />
|
||||
// </ProCard>
|
||||
// <ProCard>
|
||||
// <EditableProTableCom<DataSourceType>
|
||||
// columns={columns}
|
||||
// rowKey="id"
|
||||
// value={data}
|
||||
// // onChange={onTableChange}
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// key={"sale2"}
|
||||
// editable={{
|
||||
// form: formServeExtFee,
|
||||
// editableKeys,
|
||||
// onChange: setEditableRowKeys,
|
||||
// // onValuesChange: (record, recordList) => {
|
||||
// // setData(recordList);
|
||||
// // },
|
||||
// }}
|
||||
// />
|
||||
// </ProCard>
|
||||
// <ProCard>
|
||||
// <EditableProTableCom<DataSourceType>
|
||||
// columns={columns}
|
||||
// rowKey="id"
|
||||
// value={data}
|
||||
// key={"sale4"}
|
||||
// onChange={onTableChange}
|
||||
// rowSelection={{
|
||||
// selections: [Table.SELECTION_ALL, Table.SELECTION_INVERT],
|
||||
// }}
|
||||
// editable={{
|
||||
// form: formShippingFee,
|
||||
// editableKeys,
|
||||
// onChange: setEditableRowKeys,
|
||||
// // onValuesChange: (record, recordList) => {
|
||||
// // setData(recordList);
|
||||
// // },
|
||||
// }}
|
||||
// />
|
||||
// </ProCard> */}
|
||||
// </Spin>
|
||||
// );
|
||||
// }, [loading, data]);
|
||||
// return <div key="salcessw"> {renderContent()}</div>;
|
||||
// };
|
||||
|
||||
export default CreateSalesModal;
|
||||
|
||||
const ConditionalFields: React.FC = () => {
|
||||
const cancelRemark = Form.useWatch('cancelRemark', { preserve: true });
|
||||
|
||||
if (cancelRemark === '其他') {
|
||||
return (
|
||||
<ProFormTextArea
|
||||
name="cancelRemarkText"
|
||||
label=""
|
||||
placeholder="请输入"
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
fieldProps={{
|
||||
rows: 4,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (cancelRemark === '服务附加费退款') {
|
||||
return (
|
||||
<ProForm.Item name="extendRefunds" label="">
|
||||
<ExtendRefunds />
|
||||
</ProForm.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
609
src/pages/trade/order/components/createSalesModal.tsx
Normal file
609
src/pages/trade/order/components/createSalesModal.tsx
Normal file
@@ -0,0 +1,609 @@
|
||||
import {
|
||||
BetaSchemaForm,
|
||||
ProCard,
|
||||
type ProFormColumnsType,
|
||||
} from '@ant-design/pro-components';
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
Radio,
|
||||
Space,
|
||||
Statistic,
|
||||
Table,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import CommonModal from '@/components/ModalCom/CommonModal';
|
||||
import styles from './createSales.module.less';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
type DataSourceType = {
|
||||
id: React.Key;
|
||||
prod: string;
|
||||
num: number;
|
||||
state: string;
|
||||
availableNum: number; // 新增:可退款数量
|
||||
};
|
||||
|
||||
const CreateSalesModal = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [formSales] = Form.useForm();
|
||||
const [formApply] = Form.useForm();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [cancelRemarkType, setCancelRemarkType] = useState<string>('');
|
||||
|
||||
const handleOk = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const values1 = await formApply.validateFields();
|
||||
const values2 = await formSales.validateFields();
|
||||
|
||||
console.log('申请信息:', values1);
|
||||
console.log('售后信息:', values2);
|
||||
console.log('表格数据:', values2.extendRefunds);
|
||||
|
||||
message.success('提交成功');
|
||||
onOpen(false);
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
message.error('请完善表单信息');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [formApply, formSales]);
|
||||
|
||||
const onOpen = useCallback(
|
||||
(flag: boolean) => {
|
||||
setOpen(flag);
|
||||
if (!flag) {
|
||||
setCancelRemarkType('');
|
||||
formSales.resetFields();
|
||||
formApply.resetFields();
|
||||
}
|
||||
},
|
||||
[formSales, formApply],
|
||||
);
|
||||
|
||||
const applyColumns: ProFormColumnsType[] = [
|
||||
{
|
||||
title: '售后类型',
|
||||
dataIndex: 'cancelReason1',
|
||||
valueType: 'radio',
|
||||
formItemProps: {
|
||||
rules: [{ required: true }],
|
||||
},
|
||||
fieldProps: {
|
||||
options: [
|
||||
{ value: '用户主动取消', label: '用户主动取消' },
|
||||
{ value: '选项二', label: '选项二' },
|
||||
{ value: '选项三', label: '选项三' },
|
||||
{ value: '选项四', label: '选项四' },
|
||||
{ value: '选项五', label: '选项五' },
|
||||
{ value: '选项六', label: '选项六' },
|
||||
{ value: '选项七', label: '选项七' },
|
||||
],
|
||||
style: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '8px',
|
||||
marginTop: '6px',
|
||||
background: '#f0f0f0',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '问题描述',
|
||||
dataIndex: 'cancelRemark22',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
{
|
||||
title: '商家备注',
|
||||
dataIndex: 'cancelRemark2311',
|
||||
valueType: 'textarea',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={() => onOpen(true)}>新建售后</Button>
|
||||
<CommonModal
|
||||
title="新建售后单"
|
||||
open={open}
|
||||
onOk={handleOk}
|
||||
onCancel={() => onOpen(false)}
|
||||
confirmLoading={loading}
|
||||
width={1000}
|
||||
>
|
||||
<Space
|
||||
direction="vertical"
|
||||
style={{ width: '100%' }}
|
||||
size={16}
|
||||
className={styles.sales}
|
||||
>
|
||||
{/* 统计卡片 */}
|
||||
<ProCard.Group direction="row">
|
||||
<ProCard layout="center">
|
||||
<Statistic title="实付金额" value={1000} precision={2} />
|
||||
</ProCard>
|
||||
<ProCard layout="center">
|
||||
<Statistic title="累计退款金额" value={200} precision={2} />
|
||||
</ProCard>
|
||||
<ProCard layout="center">
|
||||
<Statistic title="可退款金额" value={800} precision={2} />
|
||||
</ProCard>
|
||||
</ProCard.Group>
|
||||
|
||||
{/* 售后选项 */}
|
||||
<ProCard title="售后选项">
|
||||
<Form
|
||||
form={formSales}
|
||||
layout="horizontal"
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
labelAlign="right"
|
||||
>
|
||||
<Form.Item
|
||||
name="cancelReason"
|
||||
label="售后类型"
|
||||
rules={[{ required: true, message: '请选择售后类型' }]}
|
||||
>
|
||||
<Radio.Group buttonStyle="solid">
|
||||
<Radio.Button value="仅退款">仅退款</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="cancelRemark"
|
||||
label="退款类型"
|
||||
rules={[{ required: true, message: '请选择退款类型' }]}
|
||||
>
|
||||
<Radio.Group
|
||||
buttonStyle="solid"
|
||||
onChange={(e) => {
|
||||
setCancelRemarkType(e.target.value);
|
||||
// 切换类型时清空相关字段
|
||||
formSales.setFieldsValue({
|
||||
cancelRemarkText: undefined,
|
||||
extendRefunds: undefined,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Radio.Button value="整单退款(通用)">整单退款</Radio.Button>
|
||||
<Radio.Button value="选项二">商品退款</Radio.Button>
|
||||
<Radio.Button value="选项三">扩展服务退款</Radio.Button>
|
||||
<Radio.Button value="服务附加费退款">
|
||||
服务附加费退款
|
||||
</Radio.Button>
|
||||
<Radio.Button value="选项五">退运费</Radio.Button>
|
||||
<Radio.Button value="其他">其他</Radio.Button>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
|
||||
{cancelRemarkType === '其他' && (
|
||||
<Form.Item
|
||||
name="cancelRemarkText"
|
||||
label=""
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
>
|
||||
<Input.TextArea placeholder="请输入" rows={4} />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{cancelRemarkType === '服务附加费退款' && (
|
||||
<Form.Item
|
||||
name="extendRefunds"
|
||||
label=""
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (!value || value.length === 0) {
|
||||
return Promise.reject(
|
||||
new Error('请至少选择一项服务附加费'),
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ExtendRefundsTable />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</ProCard>
|
||||
|
||||
{/* 申请信息 */}
|
||||
<ProCard title="申请信息(通用表单样式,根据当前订单状态调用不同的申请原因表单)">
|
||||
<BetaSchemaForm
|
||||
layoutType="Form"
|
||||
form={formApply}
|
||||
columns={applyColumns}
|
||||
layout="horizontal"
|
||||
submitter={false}
|
||||
labelCol={{ style: { width: '80px' } }}
|
||||
labelAlign="right"
|
||||
/>
|
||||
</ProCard>
|
||||
|
||||
{/* 预估退款明细 */}
|
||||
<ProCard split="vertical" headerBordered title="预估退款明细">
|
||||
<ProCard
|
||||
colSpan={'50%'}
|
||||
headerBordered
|
||||
title="预估退款明细"
|
||||
size="small"
|
||||
>
|
||||
<Space align="center" style={{ marginBottom: 16 }}>
|
||||
<div>
|
||||
<Text type="danger">最高退款金额</Text>
|
||||
<Text type="secondary">(不含虚拟资产)</Text>
|
||||
</div>
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={10000}
|
||||
precision={0}
|
||||
prefix="¥"
|
||||
style={{ width: '120px' }}
|
||||
defaultValue={700}
|
||||
/>
|
||||
</Space>
|
||||
<div>
|
||||
<Text type="secondary">
|
||||
退款金额上限 <Text type="danger">¥700</Text>
|
||||
,金额不支持修改,优惠券、积分、余额等虚拟资产不支持修改,虚拟资产将随退款而原路退回,具体金额以实际情况为准。
|
||||
</Text>
|
||||
</div>
|
||||
<Divider />
|
||||
<Title level={5}>预估退款项</Title>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>商品/服务</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>增值/扩展服务</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>运费</Text>
|
||||
<Text>合计¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</ProCard>
|
||||
<ProCard
|
||||
size="small"
|
||||
headerBordered
|
||||
title="预估退款至"
|
||||
colSpan={'50%'}
|
||||
>
|
||||
<Title level={5}>
|
||||
预估退款项
|
||||
<Text type="secondary">(退款方式:原支付方式返还)</Text>
|
||||
</Title>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
微信支付
|
||||
<Text type="secondary">(建设银行 尾号1626)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
钱包余额
|
||||
<Text type="secondary">(个人钱包 编号1090)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>宠豆豆(积分)</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>红包</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
<Divider />
|
||||
<Title level={5}>预估扣除项</Title>
|
||||
<Text type="secondary">
|
||||
如果后续有设计退款时扣除比例的设定才会存在此字段。目前暂时不做这个业务逻辑,仅为布局占位。
|
||||
</Text>
|
||||
<Space direction="vertical" size={8} style={{ width: '100%' }}>
|
||||
<Badge
|
||||
status="processing"
|
||||
style={{ width: '100%' }}
|
||||
text={
|
||||
<div
|
||||
style={{
|
||||
display: 'inline-flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
}}
|
||||
>
|
||||
<Text>
|
||||
违约金
|
||||
<Text type="secondary">(服务开始前60分钟内退款)</Text>
|
||||
</Text>
|
||||
<Text>¥500</Text>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</Space>
|
||||
</ProCard>
|
||||
</ProCard>
|
||||
</Space>
|
||||
</CommonModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ExtendRefundsTable: React.FC<{
|
||||
value?: DataSourceType[];
|
||||
onChange?: (value: DataSourceType[]) => void;
|
||||
}> = ({ value, onChange }) => {
|
||||
const [dataSource, setDataSource] = useState<DataSourceType[]>([]);
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
|
||||
// 初始化数据 - 默认值为 1,并设置可退款数量
|
||||
useEffect(() => {
|
||||
const initData: DataSourceType[] = Array.from({ length: 10 }).map(
|
||||
(_, index) => ({
|
||||
id: `service_${index}`,
|
||||
prod: `服务附加费项目${index + 1}`,
|
||||
num: 1,
|
||||
state: '待退款',
|
||||
availableNum: index % 3 === 0 ? 0 : Math.floor(Math.random() * 10) + 1, // 模拟数据,每3个有一个为0
|
||||
}),
|
||||
);
|
||||
|
||||
if (!value) {
|
||||
setDataSource(initData);
|
||||
// 初始化时通知父组件,只选中可退款数量 > 0 的项
|
||||
const validItems = initData.filter((item) => item.availableNum > 0);
|
||||
const validKeys = validItems.map((item) => item.id);
|
||||
notifyChange(initData, validKeys);
|
||||
setSelectedRowKeys(validKeys);
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 当外部 value 变化时更新
|
||||
useEffect(() => {
|
||||
if (value && value.length > 0) {
|
||||
// 更新选中状态,只保留可退款数量 > 0 的项
|
||||
setSelectedRowKeys((prev) =>
|
||||
prev.filter((key) => {
|
||||
const item = value.find((v) => v.id === key);
|
||||
return item && item.availableNum > 0;
|
||||
}),
|
||||
);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
// 通知父组件数据变化(包含选中信息)
|
||||
const notifyChange = (data: DataSourceType[], selected: React.Key[]) => {
|
||||
// 只返回选中的项
|
||||
const selectedData = data.filter((item) => selected.includes(item.id));
|
||||
onChange?.(selectedData);
|
||||
};
|
||||
|
||||
const handleNumChange = (id: React.Key, num: number | null) => {
|
||||
const record = dataSource.find((item) => item.id === id);
|
||||
if (!record) return;
|
||||
|
||||
// 如果可退款数量为0,不允许修改
|
||||
if (record.availableNum <= 0) return;
|
||||
|
||||
const finalNum = num ?? 1;
|
||||
// 不能超过可退款数量
|
||||
const validNum = Math.min(finalNum, record.availableNum);
|
||||
|
||||
const newData = dataSource.map((item) =>
|
||||
item.id === id ? { ...item, num: validNum } : item,
|
||||
);
|
||||
setDataSource(newData);
|
||||
|
||||
// 如果数量变为 0 或负数,取消选中
|
||||
let newSelectedKeys = selectedRowKeys;
|
||||
if (validNum <= 0) {
|
||||
newSelectedKeys = selectedRowKeys.filter((key) => key !== id);
|
||||
setSelectedRowKeys(newSelectedKeys);
|
||||
}
|
||||
|
||||
notifyChange(newData, newSelectedKeys);
|
||||
};
|
||||
|
||||
// 判断是否有错误
|
||||
const isInvalid = (num: number | undefined | null): boolean => {
|
||||
if (num === undefined || num === null) return true;
|
||||
if (typeof num !== 'number') return true;
|
||||
if (num <= 0) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '服务项目',
|
||||
dataIndex: 'prod',
|
||||
key: 'prod',
|
||||
width: '30%',
|
||||
ellipsis: true,
|
||||
},
|
||||
{
|
||||
title: '退款信息',
|
||||
dataIndex: 'state',
|
||||
key: 'state',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: '可退款数量',
|
||||
dataIndex: 'availableNum',
|
||||
key: 'availableNum',
|
||||
width: '20%',
|
||||
},
|
||||
{
|
||||
title: <span style={{ color: 'red' }}>* 退款数量</span>,
|
||||
dataIndex: 'num',
|
||||
key: 'num',
|
||||
width: '30%',
|
||||
render: (value: number, record: DataSourceType) => {
|
||||
const hasError = isInvalid(value);
|
||||
const isDisabled = record.availableNum <= 0;
|
||||
|
||||
return (
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={record.availableNum} // 最大值为可退款数量
|
||||
value={value}
|
||||
onChange={(val) => handleNumChange(record.id, val)}
|
||||
disabled={isDisabled}
|
||||
style={{
|
||||
width: '100%',
|
||||
}}
|
||||
placeholder={isDisabled ? '不可退款' : '请输入退款数量'}
|
||||
precision={0}
|
||||
/>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// 行选择配置
|
||||
const rowSelection = {
|
||||
selectedRowKeys,
|
||||
onChange: (newSelectedRowKeys: React.Key[]) => {
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
notifyChange(dataSource, newSelectedRowKeys);
|
||||
},
|
||||
getCheckboxProps: (record: DataSourceType) => ({
|
||||
disabled: record.availableNum <= 0, // 可退款数量为 0 时禁用选中
|
||||
name: record.prod,
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 8,
|
||||
fontWeight: 500,
|
||||
fontSize: 14,
|
||||
color: '#000',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<span>服务附加费退款明细</span>
|
||||
<span style={{ fontSize: 12, fontWeight: 'normal', color: '#666' }}>
|
||||
已选择 {selectedRowKeys.length} 项
|
||||
</span>
|
||||
</div>
|
||||
<Table
|
||||
rowKey="id"
|
||||
dataSource={dataSource}
|
||||
columns={columns}
|
||||
rowSelection={rowSelection}
|
||||
size="small"
|
||||
bordered
|
||||
scroll={{ y: 300 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default CreateSalesModal;
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Tabs, type TabsProps } from 'antd';
|
||||
import React from 'react';
|
||||
import type { TradeOrderPageRespVO } from '@/services/trade/order';
|
||||
import { SalesTable } from '../../sales/list';
|
||||
import OrderInfo from './order-info';
|
||||
import SalesRefunds from './src/sales-refunds';
|
||||
|
||||
const DetailCom: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
||||
const items: TabsProps['items'] = [
|
||||
@@ -20,6 +22,11 @@ const DetailCom: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
||||
label: '商品配送',
|
||||
children: '商品配送',
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
label: '售后与退款',
|
||||
children: <SalesRefunds orderStatus={props.data?.orderStatus} />,
|
||||
},
|
||||
];
|
||||
return <Tabs defaultActiveKey="1" items={items} destroyOnHidden />;
|
||||
};
|
||||
|
||||
@@ -39,12 +39,12 @@ const OrderDetail: React.FC<{ data?: TradeOrderPageRespVO }> = (props) => {
|
||||
return (
|
||||
<Space direction="vertical" size={24} style={{ width: '100%' }}>
|
||||
<BasicInfo data={detais} loading={loading} />
|
||||
<ProdInfo data={detais?.items} />
|
||||
<ProdInfo data={detais?.items} loading={loading} />
|
||||
{detais?.tradeServeInfo && (
|
||||
<ServiceInfo
|
||||
data={detais.tradeServeInfo}
|
||||
orderCategoryId={detais?.orderCategoryId}
|
||||
id={detais?.id}
|
||||
loading={loading}
|
||||
/>
|
||||
)}
|
||||
{detais?.tradeExtendServeInfo && (
|
||||
|
||||
@@ -23,7 +23,7 @@ const sharedOnCell = (_: DeptVO, index?: number) => {
|
||||
export const surchargeInfoColumns: ProColumns<DeptVO>[] = [
|
||||
{
|
||||
title: '服务附加费',
|
||||
dataIndex: 'name',
|
||||
dataIndex: 'serveExtFee',
|
||||
width: '33.33%',
|
||||
render: (_, record, index) => {
|
||||
if (index === 3) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ProCard } from '@ant-design/pro-components';
|
||||
import { Button, Card, Image, Modal, Space, Tag, Typography } from 'antd';
|
||||
import { Button, Card, Image, Modal, Space, Spin, Tag, Typography } from 'antd';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import AdvancedImageGallery from '@/components/AdvancedImageGallery';
|
||||
import DangerouslySetInnerHTML from '@/components/DangerouslySetInnerHTML';
|
||||
@@ -42,7 +42,7 @@ const ProdInfo: React.FC<ItemConfig<Item[]>> = (props) => {
|
||||
|
||||
return (
|
||||
<div className={styles['order-info']}>
|
||||
<Card title="商品信息">
|
||||
<Card title="商品信息" loading={props.loading}>
|
||||
{data?.map((item) => (
|
||||
<ProCard
|
||||
key={item.id}
|
||||
@@ -175,14 +175,20 @@ const PhotoModal: React.FC<{
|
||||
item?: Item;
|
||||
}> = (props) => {
|
||||
const [photo, setPhoto] = useState<TradeOrderFastPhotoRespVo>();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const { item } = props;
|
||||
console.log(item, 'item');
|
||||
const onPhoto = useCallback(async () => {
|
||||
const res = await getfastPhoto({
|
||||
itemId: item?.id as number,
|
||||
spuId: item?.spuId as number,
|
||||
});
|
||||
setPhoto(res);
|
||||
try {
|
||||
setLoading(true);
|
||||
const res = await getfastPhoto({
|
||||
itemId: item?.id as number,
|
||||
spuId: item?.spuId as number,
|
||||
});
|
||||
setPhoto(res);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [item]);
|
||||
useEffect(() => {
|
||||
if (props.open && item) {
|
||||
@@ -198,53 +204,55 @@ const PhotoModal: React.FC<{
|
||||
footer={null}
|
||||
width={820}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '480px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
<div className="left" style={{ width: '386px', height: '100%' }}>
|
||||
<AdvancedImageGallery images={photo?.imgs?.split(',') || []} />
|
||||
</div>
|
||||
<Spin spinning={loading}>
|
||||
<div
|
||||
className="right"
|
||||
style={{ height: '100%', overflow: 'auto', width: '386px' }}
|
||||
style={{
|
||||
height: '480px',
|
||||
width: '100%',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
gap: '16px',
|
||||
}}
|
||||
>
|
||||
<Title level={4}>{photo?.spuName}</Title>
|
||||
<Paragraph>
|
||||
<Text type="secondary">{photo?.brief}</Text>
|
||||
</Paragraph>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
background: '#00000005',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
当前页面为订单快照,包含订单创建时的商品描述和下单信息,买卖双方和平台在发生交易争议时,将作为判断依据。
|
||||
</span>
|
||||
<div className="left" style={{ width: '386px', height: '100%' }}>
|
||||
<AdvancedImageGallery images={photo?.imgs?.split(',') || []} />
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
background: '#00000005',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
className="right"
|
||||
style={{ height: '100%', overflow: 'auto', width: '386px' }}
|
||||
>
|
||||
<strong>已选:{photo?.skuName}(已选的SKU规格)</strong>
|
||||
<Title level={4}>{photo?.spuName}</Title>
|
||||
<Paragraph>
|
||||
<Text type="secondary">{photo?.brief}</Text>
|
||||
</Paragraph>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
background: '#00000005',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
当前页面为订单快照,包含订单创建时的商品描述和下单信息,买卖双方和平台在发生交易争议时,将作为判断依据。
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: '16px',
|
||||
background: '#00000005',
|
||||
padding: '8px',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<strong>已选:{photo?.skuName}(已选的SKU规格)</strong>
|
||||
</div>
|
||||
<DangerouslySetInnerHTML content={photo?.content} />
|
||||
</div>
|
||||
<DangerouslySetInnerHTML content={photo?.content} />
|
||||
</div>
|
||||
</div>
|
||||
</Spin>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,13 @@ import ServicePetUI from './uis/pets/service';
|
||||
|
||||
const ServiceInfo: React.FC<ItemConfig<TradeServeInfo>> = (props) => {
|
||||
const { data = {}, orderCategoryId, id } = props;
|
||||
return <>{orderCategoryId === 1 && <ServicePetUI data={data} id={id} />}</>; //宠物服务ui
|
||||
return (
|
||||
<>
|
||||
{orderCategoryId === 1 && (
|
||||
<ServicePetUI data={data} id={id} loading={props.loading} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(ServiceInfo);
|
||||
|
||||
22
src/pages/trade/order/detail/src/sales-refunds.tsx
Normal file
22
src/pages/trade/order/detail/src/sales-refunds.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Button, Space, Typography } from 'antd';
|
||||
import React from 'react';
|
||||
import { SalesTable } from '@/pages/trade/sales/list';
|
||||
import CreateSalesModal from '../../components/createSalesModal';
|
||||
|
||||
const { Title } = Typography;
|
||||
const SalesRefunds: React.FC<{ orderStatus?: number }> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Space align="center" style={{ marginBottom: 16 }} size={16}>
|
||||
<Title level={3} style={{ margin: 0 }}>
|
||||
售后与退款
|
||||
</Title>
|
||||
<CreateSalesModal />
|
||||
</Space>
|
||||
|
||||
<SalesTable orderStatus={props?.orderStatus} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(SalesRefunds);
|
||||
@@ -67,7 +67,7 @@ const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
|
||||
) => {
|
||||
const data = await getTradeOrderPage({
|
||||
...params,
|
||||
orderStatus,
|
||||
orderStatus: props?.orderStatus ? props?.orderStatus : undefined,
|
||||
pageNo: params.current,
|
||||
pageSize: params.pageSize,
|
||||
});
|
||||
|
||||
157
src/pages/trade/sales/config.tsx
Normal file
157
src/pages/trade/sales/config.tsx
Normal file
@@ -0,0 +1,157 @@
|
||||
import type { ProColumns } from '@ant-design/pro-components';
|
||||
import { Badge, Button, Image, Space, Tag, Typography } from 'antd';
|
||||
import type { TradeOrderPageRespVO } from '@/services/trade/order';
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
export const baseOrderColumns: ProColumns<TradeOrderPageRespVO>[] = [
|
||||
{
|
||||
title: '售后服务单',
|
||||
dataIndex: 'items',
|
||||
hideInSearch: true,
|
||||
ellipsis: true,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical">
|
||||
<Badge status="error" text="等待退款" />
|
||||
<div>
|
||||
<Text type="secondary">售后类型:</Text>
|
||||
<Text>{record?.payPrice || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">退款类型:</Text>
|
||||
<Text>{record.payType || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">申请原因:</Text>
|
||||
<Text>{record.financeStatus || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">申请人员:</Text>
|
||||
<Text>{record.financeStatus || '-'}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: '售后商品',
|
||||
dataIndex: 'serveAddress',
|
||||
hideInSearch: true,
|
||||
ellipsis: true,
|
||||
render: (_, record) => {
|
||||
if (!record.items) {
|
||||
return _;
|
||||
}
|
||||
return record.items.map((item, index) => (
|
||||
<>
|
||||
<div
|
||||
style={{ width: '100%', display: 'flex', gap: '8px' }}
|
||||
key={`${index}${Math.random()}`}
|
||||
>
|
||||
<Image src={item.picUrl} width={64} height={64} />
|
||||
<div style={{ flex: '1', overflow: 'hidden' }}>
|
||||
<Paragraph ellipsis style={{ width: '100%', marginBottom: 0 }}>
|
||||
{item.spuName}测试商品名称测试商品名称测试商品名称测试商品名称
|
||||
</Paragraph>
|
||||
<div>{item.skuName}</div>
|
||||
<div>
|
||||
<Text type="secondary">申请数量:</Text>
|
||||
<Text>{item.count || 0}</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button size="small" style={{ marginTop: 10, textAlign: 'right' }}>
|
||||
售后详情
|
||||
</Button>
|
||||
</>
|
||||
));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '申请信息',
|
||||
dataIndex: 'price',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical">
|
||||
<div>
|
||||
<Text type="secondary">售后类型:</Text>
|
||||
<Text>{record?.payPrice || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">退款类型:</Text>
|
||||
<Text>{record.payType || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">申请原因:</Text>
|
||||
<Text>{record.financeStatus || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">申请人员:</Text>
|
||||
<Text>{record.financeStatus || '-'}</Text>
|
||||
</div>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '退款信息',
|
||||
dataIndex: 'merchantName',
|
||||
hideInSearch: true,
|
||||
render: (_, record) => (
|
||||
<Space direction="vertical">
|
||||
<Badge status="default" text="无退款" />
|
||||
<div>
|
||||
<Text type="secondary">退款金额:</Text>
|
||||
<Text>{record?.payPrice || '-'}</Text>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">退款方式:</Text>
|
||||
<Text>{record.payType || '-'}</Text>
|
||||
</div>
|
||||
<Space>
|
||||
<Button size="small" type="primary">
|
||||
重试退款
|
||||
</Button>
|
||||
<Button size="small" type="primary">
|
||||
审核处理
|
||||
</Button>
|
||||
<Button size="small">转人工退款</Button>
|
||||
<Button size="small">取消申请</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '申请人姓名、手机或ID',
|
||||
dataIndex: 'userSearch',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '售后类型',
|
||||
dataIndex: 'orderCategoryId',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '退款类型',
|
||||
dataIndex: 'orderTerminal',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '退款状态',
|
||||
dataIndex: 'financeStatus',
|
||||
valueType: 'select',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '申请时间',
|
||||
dataIndex: 'createTime',
|
||||
valueType: 'dateRange',
|
||||
hideInTable: true,
|
||||
},
|
||||
{
|
||||
title: '退款时间',
|
||||
dataIndex: 'subTime',
|
||||
valueType: 'dateRange',
|
||||
hideInTable: true,
|
||||
},
|
||||
];
|
||||
13
src/pages/trade/sales/index.module.less
Normal file
13
src/pages/trade/sales/index.module.less
Normal file
@@ -0,0 +1,13 @@
|
||||
.sales {
|
||||
:global {
|
||||
.ant-pro-card-header {
|
||||
background: rgba(0, 0, 0, 0.02);
|
||||
min-height: 41px;
|
||||
}
|
||||
.ant-pro-card-col {
|
||||
flex: 1 auto;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,24 @@
|
||||
// 售后列表
|
||||
const TradeSales: React.FC = () => {
|
||||
return 111;
|
||||
import type { TabsProps } from 'antd';
|
||||
import { Tabs } from 'antd';
|
||||
import React from 'react';
|
||||
import { SalesStatusLableMap } from '@/constants/trade';
|
||||
import SalesListItem from './list';
|
||||
|
||||
const TradeSalesList: React.FC = () => {
|
||||
const items: TabsProps['items'] = Object.entries(SalesStatusLableMap).map(
|
||||
([value, label]) => ({
|
||||
key: value,
|
||||
label: label,
|
||||
children: <SalesListItem orderStatus={Number(value)} />,
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="page-container">
|
||||
<Tabs defaultActiveKey="1" items={items} destroyOnHidden />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TradeSales;
|
||||
export default TradeSalesList;
|
||||
|
||||
133
src/pages/trade/sales/list.tsx
Normal file
133
src/pages/trade/sales/list.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
import { type ActionType, ProCard } from '@ant-design/pro-components';
|
||||
import { Button, Input, Space, Statistic } from 'antd';
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import EnhancedProTable from '@/components/EnhancedProTable';
|
||||
import { baseOrderColumns } from './config';
|
||||
|
||||
const { Search } = Input;
|
||||
|
||||
import { DownOutlined, UpOutlined, UserOutlined } from '@ant-design/icons';
|
||||
|
||||
import {
|
||||
getTradeOrderPage,
|
||||
getTradeSummary,
|
||||
type TradeOrderPageRespVO,
|
||||
type TradeReq,
|
||||
type TradeSummaryRespVO,
|
||||
} from '@/services/trade/order';
|
||||
|
||||
const OrderListItem: React.FC<{ orderStatus: number }> = (props) => {
|
||||
const { orderStatus } = props;
|
||||
const tableRef = useRef<ActionType>(null);
|
||||
const [isShowTotal, setIsShowTotal] = useState<boolean>(false);
|
||||
const [summary, setSummary] = useState<TradeSummaryRespVO>();
|
||||
const fetchSummary = async () => {
|
||||
const res = await getTradeSummary();
|
||||
setSummary(res);
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchSummary();
|
||||
}, []);
|
||||
|
||||
const handleIsTotal = useCallback(() => {
|
||||
setIsShowTotal(!isShowTotal);
|
||||
}, [isShowTotal]);
|
||||
|
||||
const handleSearch = useCallback((value: string) => {
|
||||
console.log('搜索', value);
|
||||
tableRef.current?.reload();
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
<Space
|
||||
style={{
|
||||
padding: '18px',
|
||||
background: '#fff',
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Search
|
||||
placeholder="输入售后单号/关联订单号搜索"
|
||||
enterButton
|
||||
onSearch={handleSearch}
|
||||
style={{ width: 300 }}
|
||||
/>
|
||||
<Button
|
||||
icon={isShowTotal ? <DownOutlined /> : <UpOutlined />}
|
||||
onClick={handleIsTotal}
|
||||
>
|
||||
统计
|
||||
</Button>
|
||||
</Space>
|
||||
{isShowTotal && (
|
||||
<ProCard.Group direction="row" style={{ marginBottom: 18 }}>
|
||||
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||
<Statistic
|
||||
title="售后单数量"
|
||||
value={summary?.orderCount}
|
||||
precision={2}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||
<Statistic
|
||||
title="待退款金额"
|
||||
value={summary?.payPrice}
|
||||
precision={2}
|
||||
/>
|
||||
</ProCard>
|
||||
<ProCard layout="center" style={{ background: '#f5f5f5' }}>
|
||||
<Statistic
|
||||
title="累计退款金额"
|
||||
value={summary?.livePrice}
|
||||
precision={2}
|
||||
/>
|
||||
</ProCard>
|
||||
</ProCard.Group>
|
||||
)}
|
||||
<SalesTable orderStatus={orderStatus} search={true} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SalesTable = (props: {
|
||||
orderStatus?: number;
|
||||
search?: boolean;
|
||||
}) => {
|
||||
const tableRef = useRef<ActionType>(null);
|
||||
const onFetch = async (
|
||||
params: TradeReq & {
|
||||
pageSize: number;
|
||||
current: number;
|
||||
},
|
||||
) => {
|
||||
const data = await getTradeOrderPage({
|
||||
...params,
|
||||
orderStatus: props?.orderStatus ? props?.orderStatus : undefined,
|
||||
pageNo: params.current,
|
||||
pageSize: params.pageSize,
|
||||
});
|
||||
|
||||
return {
|
||||
data: data.list,
|
||||
success: true,
|
||||
total: data.total,
|
||||
};
|
||||
};
|
||||
|
||||
const tabelConfig = {};
|
||||
|
||||
return (
|
||||
<EnhancedProTable<TradeOrderPageRespVO>
|
||||
ref={tableRef}
|
||||
columns={baseOrderColumns}
|
||||
request={onFetch}
|
||||
headerTitle="售后列表"
|
||||
showIndex={false}
|
||||
showSelection={false}
|
||||
search={props.search ? { defaultCollapsed: true } : false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(OrderListItem);
|
||||
@@ -100,9 +100,6 @@ export const errorConfig: RequestConfig = {
|
||||
},
|
||||
// 错误接收及处理
|
||||
errorHandler: async (error: any, opts: any) => {
|
||||
if (opts?.skipErrorHandler) throw error;
|
||||
// 我们的 errorThrower 抛出的错误。
|
||||
console.log('errorHandler', error);
|
||||
const errorInfo: ResponseStructure | undefined = error.info;
|
||||
if (error.name === 'BizError') {
|
||||
if (errorInfo) {
|
||||
@@ -118,6 +115,7 @@ export const errorConfig: RequestConfig = {
|
||||
} else {
|
||||
message.error(`发送请求时出了点问题:${error.msg}`);
|
||||
}
|
||||
throw error;
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
@@ -116,3 +116,56 @@ export const loopMenuItem = (menus: MenuVO[], pId: number | string): any[] => {
|
||||
// return currentPath;
|
||||
// }
|
||||
// }
|
||||
// src/utils/route.ts
|
||||
import { lazy } from 'react';
|
||||
|
||||
export interface RouteItem {
|
||||
id: number;
|
||||
parentId: number;
|
||||
name: string;
|
||||
path: string;
|
||||
component: string | null;
|
||||
componentName: string;
|
||||
icon: string;
|
||||
visible: boolean;
|
||||
keepAlive: boolean;
|
||||
alwaysShow: boolean;
|
||||
children: RouteItem[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换后端路由数据为 Ant Design Pro 路由格式
|
||||
*/
|
||||
export function transformRoutes(routes: MenuVO[]): any[] {
|
||||
return routes
|
||||
.filter((route) => route.visible) // 只显示可见路由
|
||||
.map((route) => {
|
||||
const routeConfig: any = {
|
||||
path: route.path,
|
||||
name: route.name,
|
||||
icon: route.icon || undefined,
|
||||
};
|
||||
|
||||
// 处理组件
|
||||
if (route.component) {
|
||||
routeConfig.component = lazy(
|
||||
() =>
|
||||
import(`@/pages/${route.component}`).catch(
|
||||
() => import('@/pages/404'),
|
||||
), // 组件不存在时显示404
|
||||
);
|
||||
}
|
||||
|
||||
// 处理子路由
|
||||
if (route.children && route.children.length > 0) {
|
||||
routeConfig.routes = transformRoutes(route.children);
|
||||
}
|
||||
|
||||
// 隐藏菜单但保留路由
|
||||
if (!route.visible) {
|
||||
routeConfig.hideInMenu = true;
|
||||
}
|
||||
|
||||
return routeConfig;
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user