Commit 764fb26a1827cbf7adf8ec698c5d3fc369814094
Merge branch 'order_direct_car' into 'master'
Order direct car See merge request !698
Showing
22 changed files
with
2153 additions
and
1 deletions
config/routers/order3.ts
... | ... | @@ -296,6 +296,11 @@ export default [ |
296 | 296 | component: './order3/InstallmentCommissionAllocation', |
297 | 297 | }, |
298 | 298 | { |
299 | + // 车辆订单确认配件配置 | |
300 | + path: '/order3/carChargePile', | |
301 | + component: './order3/CarChargePile', | |
302 | + }, | |
303 | + { | |
299 | 304 | // 上户和抵押时效配置 |
300 | 305 | path: '/order3/registerTiming', |
301 | 306 | component: './order3/RegisterTiming', |
... | ... | @@ -310,4 +315,9 @@ export default [ |
310 | 315 | path: '/order3/loanPurchaseCost/addConfig', |
311 | 316 | component: './order3/BuyCarLoanCost/subpages/AddConfig', |
312 | 317 | }, |
318 | + { | |
319 | + // 直营策车厂家促销设置 | |
320 | + path: '/order3/directCarPromotion', | |
321 | + component: './order3/DirectCarPromotion', | |
322 | + }, | |
313 | 323 | ]; | ... | ... |
src/pages/order3/CarChargePile/api.ts
0 → 100644
1 | +import { http } from '@/typing/http'; | |
2 | +import request from '@/utils/request'; | |
3 | +import { ORDER3, DECORATION } from '@/utils/host'; | |
4 | +import { PageParams } from '@/typing/common'; | |
5 | + | |
6 | +type P<T> = http.PromiseResp<T>; | |
7 | +type Page<T> = http.PromisePageResp<T>; | |
8 | + | |
9 | +interface Params extends PageParams { | |
10 | + brandId?: number; // 品牌id | |
11 | + seriesId?: number; // 车系id | |
12 | + specId?: number; // 车型id | |
13 | + partName?: string; // 配件名称 | |
14 | +} | |
15 | + | |
16 | +export interface ListResult { | |
17 | + id?: number; // id | |
18 | + partName?: string; // 配件名称 | |
19 | + partCode?: string; // 配置编码 | |
20 | + clientAmount?: number; // 收客户金额 | |
21 | + clientAmountFlow?: number; // 收客户款流向 | |
22 | + clientFlowAmount?: number; // 收客户款流向金额 | |
23 | + partCost?: number; // 配件采购成本 | |
24 | + partCostFlow?: number; // 配件款款项流向 | |
25 | + partFlowCost?: number; // 配件款款项流向金额 | |
26 | + beginTime?: number; // 开始时间 | |
27 | + endTime?: number; // 结束时间 | |
28 | + carVOList?: CarVOList[]; // 车辆详情 | |
29 | +} | |
30 | + | |
31 | +interface CarVOList { | |
32 | + applyCarType?: number; // 适用车辆类型(1:全部车系,2:全部车型,3,部分车型) | |
33 | + brandId?: number; // 品牌id | |
34 | + brandName?: string; // 品牌名称 | |
35 | + seriesList?: SeriesVo[]; // 车系列表 | |
36 | +} | |
37 | + | |
38 | +interface SeriesVo { | |
39 | + seriesId?: number; // 车系id | |
40 | + seriesName?: string; // 车系名称 | |
41 | + specList?: SpecVo[]; // 车型列表 | |
42 | +} | |
43 | + | |
44 | +interface SpecVo { | |
45 | + specId?: number; // 车型id | |
46 | + specName?: string; // 车型名称 | |
47 | +} | |
48 | + | |
49 | +interface CarDtoList { | |
50 | + applyCarType?: number; // 适用车辆类型(1:全部车系,2:全部车型,3,部分车型) | |
51 | + brandId?: number; // 品牌id | |
52 | + brandName?: string; // 品牌名称 | |
53 | + seriesId?: number; // 车系id | |
54 | + seriesName?: string; // 车系名称 | |
55 | + specId?: number; // 车型id | |
56 | + specName?: string; // 车型名称 | |
57 | +} | |
58 | + | |
59 | +export interface SaveParams { | |
60 | + id?: number; // id | |
61 | + partName?: string; // 配件名称 | |
62 | + partCode?: string; // 配置编码 | |
63 | + clientAmount?: number; // 收客户金额 | |
64 | + clientAmountFlow?: number; // 收客户款流向 | |
65 | + clientFlowAmount?: number; // 收客户款流向金额 | |
66 | + partCost?: number; // 配件采购成本 | |
67 | + partCostFlow?: number; // 配件款款项流向 | |
68 | + partFlowCost?: number; // 配件款款项流向金额 | |
69 | + beginTime?: number; // 开始时间 | |
70 | + endTime?: number; // 结束时间 | |
71 | + carDtoList?: CarDtoList[]; // 车辆详情 | |
72 | +} | |
73 | + | |
74 | +/** | |
75 | + * 获取车辆订单确认配件配置 | |
76 | + * @param params | |
77 | + * @returns | |
78 | + */ | |
79 | +export function getListApi(params: Params): Page<ListResult> { | |
80 | + return request.get(`${ORDER3}/erp/vehicle/part/config/page`, { params }); | |
81 | +} | |
82 | + | |
83 | +/** | |
84 | + * 保存车辆订单确认配置配置 | |
85 | + * @param params | |
86 | + * @returns | |
87 | + */ | |
88 | +export function saveConfigApi(params: SaveParams): P<string> { | |
89 | + return request.post(`${ORDER3}/erp/vehicle/part/config/saveOrUpdate`, params); | |
90 | +} | |
91 | + | |
92 | +/** | |
93 | + * 删除车辆订单确认配置配置 | |
94 | + * @param params | |
95 | + * @returns | |
96 | + */ | |
97 | +export function fetchDeleteApi(params: {id?: number}): P<string> { | |
98 | + return request.post(`${ORDER3}/erp/vehicle/part/config/delete`, params, { contentType: 'form-urlencoded' }); | |
99 | +} | |
100 | + | |
101 | +export interface DecoParams { | |
102 | + cars?: Cars[]; | |
103 | + bizType?: number; // 0汽车装潢1配件零售 | |
104 | +} | |
105 | + | |
106 | +interface Cars { | |
107 | + brandId?: number; // 品牌id | |
108 | + seriesId?: number; // 车系id | |
109 | + specId?: number; // 车型id | |
110 | +} | |
111 | + | |
112 | +export interface DecoResult { | |
113 | + goodId?: number; // 装潢商品id | |
114 | + partId?: number; // 配件id | |
115 | + partCode?: string; // 配件编码 | |
116 | + partName?: string; // 配件名称 | |
117 | + mainImgFids?: string; // 装潢图片 | |
118 | + value?: string; // 配件编码值 | |
119 | + label?: string; // 配件名称值 | |
120 | +} | |
121 | + | |
122 | +export enum CostFlowEnum { | |
123 | + '销售折让收回' = 1, | |
124 | +} | |
125 | + | |
126 | +export enum AmountFlow { | |
127 | + '扣减销售折让' = 1, | |
128 | +} | |
129 | + | |
130 | +/** | |
131 | + * 查询充电桩列表 | |
132 | + * @param params | |
133 | + * @returns | |
134 | + */ | |
135 | +export function getDecoListApi(params: DecoParams): P<DecoResult[]> { | |
136 | + return request.post(`${DECORATION}/deco/goods/get/spec/category/goods`, params); | |
137 | +} | ... | ... |
src/pages/order3/CarChargePile/components/CarModal.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Modal } from 'antd'; | |
3 | +import _ from 'lodash'; | |
4 | +import { useStore } from '../index'; | |
5 | +import CarTableTreeAuth from '@/components/CarTableTreeAuth'; | |
6 | + | |
7 | +const CarModal = () => { | |
8 | + const { carModal, setCarModal } = useStore(); | |
9 | + const [carData, setCarData] = useState<any>([]); | |
10 | + | |
11 | + const handleCancel = () => { | |
12 | + setCarModal({ data: {}, visible: false }); | |
13 | + }; | |
14 | + useEffect(() => { | |
15 | + if (carModal.visible) { | |
16 | + const data = JSON.parse(JSON.stringify(carModal.data?.carVOList || [])); | |
17 | + if (data.length) { | |
18 | + data.forEach((v: any) => { | |
19 | + if (v.seriesList?.length) { | |
20 | + v.children = v.seriesList; | |
21 | + v.seriesList.forEach((k: any) => { | |
22 | + if (k.specList?.length) { | |
23 | + k.children = k.specList; | |
24 | + } | |
25 | + }); | |
26 | + } | |
27 | + }); | |
28 | + setCarData(data); | |
29 | + } else { | |
30 | + setCarData([]); | |
31 | + } | |
32 | + } | |
33 | + }, [carModal.visible]); | |
34 | + return ( | |
35 | + <Modal destroyOnClose forceRender open={carModal.visible} title="适用车辆" maskClosable={false} onCancel={handleCancel} onOk={handleCancel}> | |
36 | + <CarTableTreeAuth value={carData} disabled brandMultiple={false} /> | |
37 | + </Modal> | |
38 | + ); | |
39 | +}; | |
40 | +export default CarModal; | ... | ... |
src/pages/order3/CarChargePile/components/ChargePileModal.tsx
0 → 100644
1 | +import { Modal, Table, Row, message, Col } from 'antd'; | |
2 | +import React, { useState } from 'react'; | |
3 | +import { getDecoListApi, DecoResult, DecoParams } from '../api'; | |
4 | +import Filters from '@/pages/order3/Common/Filter'; | |
5 | + | |
6 | +interface Props { | |
7 | + value?: DecoResult[]; | |
8 | + visible: boolean; | |
9 | + setVisible: React.Dispatch<React.SetStateAction<boolean>>; | |
10 | + selected: DecoResult[]; | |
11 | + setSelected: React.Dispatch<React.SetStateAction<DecoResult[]>>; | |
12 | + onChange?: Function; | |
13 | + tableType: '列表' | '选择'; | |
14 | + setTableType: React.Dispatch<React.SetStateAction<'列表' | '选择'>>; | |
15 | +} | |
16 | + | |
17 | +export default function ChargePileModal({ value, visible, setVisible, selected, setSelected, onChange, tableType, setTableType }: Props) { | |
18 | + const [list, setList] = useState<DecoResult[]>([]); | |
19 | + const [loading, setLoading] = useState<boolean>(false); | |
20 | + | |
21 | + /** | |
22 | + * @description: 当一个配件被点击时,触发该回调函数。 | |
23 | + * @param {DecoResult} record 当前门店数据 | |
24 | + * @param {boolean} _selected 是否选中 | |
25 | + */ | |
26 | + function onSelect(record: DecoResult, _selected: boolean) { | |
27 | + if (_selected) { | |
28 | + setSelected([{ ...record, value: record.partCode, label: record.partName }]); | |
29 | + } else { | |
30 | + setSelected([{ ...record, value: record.partCode, label: record.partName }]); | |
31 | + } | |
32 | + } | |
33 | + | |
34 | + function rowClick(record: DecoResult) { | |
35 | + if (selected.length && selected[0].partCode == record.partCode) { | |
36 | + return; | |
37 | + } | |
38 | + setSelected([{ ...record, value: record.partCode, label: record.partName }]); | |
39 | + } | |
40 | + | |
41 | + function onOk() { | |
42 | + onChange && onChange(selected); | |
43 | + setVisible(false); | |
44 | + } | |
45 | + | |
46 | + function onChangBrand(value?: any) { | |
47 | + if (!value.specId) { | |
48 | + return; | |
49 | + } | |
50 | + const params = { | |
51 | + brandId: value.brandId, | |
52 | + seriesId: value.seriesId, | |
53 | + specId: value.specId, | |
54 | + }; | |
55 | + getDeco(params); | |
56 | + } | |
57 | + | |
58 | + function getDeco(params: DecoParams) { | |
59 | + setLoading(true); | |
60 | + setTableType('选择'); | |
61 | + getDecoListApi(params) | |
62 | + .then((res) => { | |
63 | + setList(res.data || []); | |
64 | + setSelected([]); | |
65 | + setLoading(false); | |
66 | + }) | |
67 | + .catch((e) => { | |
68 | + setList([]); | |
69 | + setLoading(false); | |
70 | + setSelected([]); | |
71 | + message.error(e.message); | |
72 | + }); | |
73 | + } | |
74 | + | |
75 | + return ( | |
76 | + <Modal destroyOnClose title="选择配件" width="50%" zIndex={1001} maskClosable={false} open={visible} onCancel={() => setVisible(false)} onOk={() => onOk()}> | |
77 | + {visible ? ( | |
78 | + <Row style={{ marginBottom: '15px' }}> | |
79 | + <Col style={{ minWidth: '300px' }}> | |
80 | + <Filters onFilter={(value: any) => onChangBrand(value)} /> | |
81 | + </Col> | |
82 | + </Row> | |
83 | + ) : null} | |
84 | + <Table | |
85 | + dataSource={tableType === '列表' ? value : list} | |
86 | + loading={loading} | |
87 | + pagination={false} | |
88 | + rowKey="partCode" | |
89 | + size="small" | |
90 | + onRow={(record) => ({ | |
91 | + onClick: () => rowClick(record), | |
92 | + })} | |
93 | + rowSelection={{ | |
94 | + type: 'radio', | |
95 | + selectedRowKeys: selected.map((item) => item.partCode || -1), | |
96 | + onSelect, | |
97 | + getCheckboxProps: (record) => ({}), | |
98 | + }} | |
99 | + scroll={{ y: '60vh', scrollToFirstRowOnChange: true }} | |
100 | + > | |
101 | + <Table.Column title="配件编码" dataIndex="partCode" align="left" /> | |
102 | + <Table.Column title="配件名称" dataIndex="partName" align="left" /> | |
103 | + </Table> | |
104 | + </Modal> | |
105 | + ); | |
106 | +} | ... | ... |
src/pages/order3/CarChargePile/components/ChargePileSelect.tsx
0 → 100644
1 | +import React, { forwardRef, Ref, useCallback, useEffect, useImperativeHandle, useMemo, useState } from 'react'; | |
2 | +import { DecoResult } from '../api'; | |
3 | +import Close from './Close'; | |
4 | +import ChargePileModal from './ChargePileModal'; | |
5 | +import './style.less'; | |
6 | + | |
7 | +interface Props { | |
8 | + value?: DecoResult[]; | |
9 | + onChange?: (value?: DecoResult[]) => void; | |
10 | +} | |
11 | + | |
12 | +export default forwardRef(StaffSelectNew); | |
13 | + | |
14 | +function StaffSelectNew( | |
15 | + { | |
16 | + value=[], | |
17 | + onChange, | |
18 | + }: Props, | |
19 | + ref: Ref<any>, | |
20 | +) { | |
21 | + const [visible, setVisible] = useState(false); | |
22 | + const [selected, setSelected] = useState<DecoResult[]>([]); | |
23 | + const [tableType, setTableType] = useState<'列表' | '选择'>('列表'); | |
24 | + | |
25 | + useEffect(() => { | |
26 | + if (!visible) { | |
27 | + setTableType('列表'); | |
28 | + } | |
29 | + }, [visible]) | |
30 | + | |
31 | + useEffect(() => { | |
32 | + if (value.length) { | |
33 | + setSelected(value); | |
34 | + } | |
35 | + }, [value]) | |
36 | + | |
37 | + useImperativeHandle(ref, () => ({ | |
38 | + reset: () => { | |
39 | + setSelected([]); | |
40 | + }, | |
41 | + })); | |
42 | + | |
43 | + const deleteAll = () => { | |
44 | + setSelected([]); | |
45 | + onChange && onChange([]); | |
46 | + }; | |
47 | + | |
48 | + const openModal = useCallback(() => { | |
49 | + setVisible(true); | |
50 | + if (value.length) { | |
51 | + setSelected([{...value[0], label: value[0].partName, value: value[0].partCode}]) | |
52 | + } else { | |
53 | + setSelected([]); | |
54 | + } | |
55 | + }, [value]); | |
56 | + | |
57 | + const showList = useMemo(() => (value?.every(item => item.partName) ? value : selected), [value, selected]); | |
58 | + | |
59 | + return ( | |
60 | + <div style={{ width: '100%' }}> | |
61 | + <span className={`ant-input-affix-wrapper`} onClick={openModal}> | |
62 | + <div className={`StaffSelectNew_Container`}> | |
63 | + {showList?.length ? ( | |
64 | + // @ts-ignore | |
65 | + <span className="text">{value?.[0]?.label || '-'}</span> | |
66 | + ) : ( | |
67 | + <span className="placeholder">请选择配件</span> | |
68 | + )} | |
69 | + </div> | |
70 | + {value?.length ? <Close deleteAll={deleteAll} /> : null} | |
71 | + </span> | |
72 | + <ChargePileModal | |
73 | + onChange={onChange} | |
74 | + selected={selected} | |
75 | + setSelected={setSelected} | |
76 | + visible={visible} | |
77 | + setVisible={setVisible} | |
78 | + tableType={tableType} | |
79 | + setTableType={setTableType} | |
80 | + value={value} | |
81 | + /> | |
82 | + </div> | |
83 | + ); | |
84 | +} | ... | ... |
src/pages/order3/CarChargePile/components/Close.tsx
0 → 100644
1 | +import React from 'react'; | |
2 | + | |
3 | +interface Props { | |
4 | + deleteAll: () => void; | |
5 | +} | |
6 | + | |
7 | +export default function Close({ deleteAll }: Props) { | |
8 | + return ( | |
9 | + <span | |
10 | + className="ant-input-suffix" | |
11 | + onClick={(e) => { | |
12 | + e.stopPropagation(); | |
13 | + deleteAll(); | |
14 | + }} | |
15 | + > | |
16 | + <span className="ant-input-clear-icon" role="button" tabIndex={-1}> | |
17 | + <span role="img" aria-label="close-circle" className="anticon anticon-close-circle"> | |
18 | + <svg viewBox="64 64 896 896" focusable="false" data-icon="close-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true"> | |
19 | + <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 | |
20 | + 64zm165.4 618.2l-66-.3L512 563.4l-99.3 118.4-66.1.3c-4.4 0-8-3.5-8-8 0-1.9.7-3.7 1.9-5.2l130.1-155L340.5 | |
21 | + 359a8.32 8.32 0 01-1.9-5.2c0-4.4 3.6-8 8-8l66.1.3L512 464.6l99.3-118.4 66-.3c4.4 0 8 3.5 8 8 0 1.9-.7 3.7-1.9 5.2L553.5 514l130 | |
22 | + 155c1.2 1.5 1.9 3.3 1.9 5.2 0 4.4-3.6 8-8 8z" /> | |
23 | + </svg> | |
24 | + </span> | |
25 | + </span> | |
26 | + </span> | |
27 | + ); | |
28 | +} | ... | ... |
src/pages/order3/CarChargePile/components/EditModal.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Modal, Button, message, Form, InputNumber, DatePicker, Select, Tag } from 'antd'; | |
3 | +import { useStore } from '../index'; | |
4 | +import { saveConfigApi, SaveParams, CostFlowEnum, AmountFlow, getDecoListApi, DecoResult } from '../api'; | |
5 | +import { debounce } from 'lodash'; | |
6 | +import CarTableTreeAuth from '@/components/CarTableTreeAuth'; | |
7 | +import moment from 'moment'; | |
8 | +import { PlusSquareOutlined } from '@ant-design/icons'; | |
9 | +import { CarAuthList } from '@/components/CarTableTreeAuth/entity'; | |
10 | +// import ChargePileSelect from '@/pages/order3/CarChargePile/components/ChargePileSelect'; | |
11 | + | |
12 | +const { RangePicker } = DatePicker; | |
13 | +const Option = Select.Option; | |
14 | + | |
15 | +// const _partList = [{partCode: 'asdasd', partName: '配件1213'}] | |
16 | + | |
17 | +export default function DetailModal() { | |
18 | + const { setModalData, setLoading, modalData } = useStore(); | |
19 | + const [confirm, setConfirm] = useState<boolean>(false); | |
20 | + const [clientFlowAmount, setClientFlowAmount] = useState<number>(); | |
21 | + const [partFlowCost, setPartFlowCost] = useState<number>(); | |
22 | + const [form] = Form.useForm(); | |
23 | + const [partList, setPartList] = useState<DecoResult[]>([]); | |
24 | + const [selectPart, setSelectPart] = useState<any>(); | |
25 | + | |
26 | + useEffect(() => { | |
27 | + if (modalData.visible && modalData.data?.id) { | |
28 | + const applyCarList: any = JSON.parse(JSON.stringify(modalData.data?.carVOList || [])); | |
29 | + applyCarList.forEach((v: any) => { | |
30 | + if (v.seriesList?.length) { | |
31 | + v.children = v.seriesList; | |
32 | + v.seriesList.forEach((k: any) => { | |
33 | + if (k.specList?.length) { | |
34 | + k.children = k.specList; | |
35 | + } | |
36 | + }); | |
37 | + } | |
38 | + }); | |
39 | + setClientFlowAmount(modalData.data?.clientAmount); | |
40 | + setPartFlowCost(modalData.data?.partFlowCost); | |
41 | + const result: any = []; | |
42 | + modalData.data?.carVOList?.forEach((v) => { | |
43 | + if (v.seriesList?.length) { | |
44 | + v.seriesList.forEach((k) => { | |
45 | + if (k.specList?.length) { | |
46 | + k.specList.forEach((r) => { | |
47 | + const item = { | |
48 | + brandId: v.brandId, | |
49 | + seriesId: k.seriesId, | |
50 | + specId: r.specId, | |
51 | + }; | |
52 | + result.push(item); | |
53 | + }); | |
54 | + } else { | |
55 | + const item = { brandId: v.brandId, seriesId: k.seriesId }; | |
56 | + result.push(item); | |
57 | + } | |
58 | + }); | |
59 | + } else { | |
60 | + const item = { brandId: v.brandId }; | |
61 | + result.push(item); | |
62 | + } | |
63 | + }); | |
64 | + getDecoListApi({cars: result, bizType: 0}) | |
65 | + .then(res => { | |
66 | + setPartList(res.data || []); | |
67 | + }) | |
68 | + .catch(e => { | |
69 | + message.error(e.message); | |
70 | + setPartList([]); | |
71 | + }) | |
72 | + setSelectPart({partName: modalData.data?.partName, partCode: modalData.data?.partCode}); | |
73 | + form.setFieldsValue({ | |
74 | + part: modalData.data?.partCode, | |
75 | + clientAmount: modalData.data?.clientAmount, | |
76 | + partCost: modalData.data?.partCost, | |
77 | + time: [moment(modalData.data.beginTime), moment(modalData.data.endTime)], | |
78 | + optionalAuth: applyCarList, | |
79 | + }); | |
80 | + } | |
81 | + }, [modalData.visible]); | |
82 | + | |
83 | + function onCancle() { | |
84 | + setClientFlowAmount(undefined); | |
85 | + setPartFlowCost(undefined); | |
86 | + setModalData({ visible: false, data: undefined }); | |
87 | + setPartList([]); | |
88 | + setSelectPart(undefined); | |
89 | + } | |
90 | + | |
91 | + function onCarChange(value?: any) { | |
92 | + if (value && value.length) { | |
93 | + const params = handlePartParams(value); | |
94 | + getDecoListApi({cars: params, bizType: 0}) | |
95 | + .then(res => { | |
96 | + setPartList(res.data || []); | |
97 | + }) | |
98 | + .catch(e => { | |
99 | + setPartList([]); | |
100 | + }) | |
101 | + } | |
102 | + } | |
103 | + | |
104 | + function onPartChange(option: any) { | |
105 | + if(option?.value) { | |
106 | + setSelectPart({ partCode: option.value, partName: option.label}) | |
107 | + } | |
108 | + } | |
109 | + | |
110 | + /** | |
111 | + * 将树形车辆结构转换成扁平结构 | |
112 | + * @param value | |
113 | + * @returns | |
114 | + */ | |
115 | + function handlePartParams(value: CarAuthList[]) { | |
116 | + const result: any = []; | |
117 | + value.forEach((v) => { | |
118 | + if (v.children?.length) { | |
119 | + v.children.forEach((k) => { | |
120 | + if (k.children?.length) { | |
121 | + k.children.forEach((r) => { | |
122 | + const item = { | |
123 | + brandId: v.brandId, | |
124 | + seriesId: k.seriesId, | |
125 | + specId: r.specId, | |
126 | + }; | |
127 | + result.push(item); | |
128 | + }); | |
129 | + } else { | |
130 | + const item = { brandId: v.brandId, seriesId: k.seriesId }; | |
131 | + result.push(item); | |
132 | + } | |
133 | + }); | |
134 | + } else { | |
135 | + const item = { brandId: v.brandId }; | |
136 | + result.push(item); | |
137 | + } | |
138 | + }); | |
139 | + return result; | |
140 | + } | |
141 | + | |
142 | + /** | |
143 | + * 将树形车辆结构转换成扁平结构 | |
144 | + * applyCarType为最细化层级的类型 | |
145 | + * @param value | |
146 | + * @returns | |
147 | + */ | |
148 | + function handleCarData(value: CarAuthList[]) { | |
149 | + const result: any = []; | |
150 | + value.forEach((v) => { | |
151 | + if (v.children?.length) { | |
152 | + v.children.forEach((k) => { | |
153 | + if (k.children?.length) { | |
154 | + k.children.forEach((r) => { | |
155 | + const item = { | |
156 | + brandId: v.brandId, | |
157 | + brandName: v.brandName, | |
158 | + seriesId: k.seriesId, | |
159 | + seriesName: k.seriesName, | |
160 | + specId: r.specId, | |
161 | + specName: r.specName, | |
162 | + applyCarType: 3, | |
163 | + }; | |
164 | + result.push(item); | |
165 | + }); | |
166 | + } else { | |
167 | + const item = { brandId: v.brandId, brandName: v.brandName, seriesId: k.seriesId, seriesName: k.seriesName, applyCarType: 2 }; | |
168 | + result.push(item); | |
169 | + } | |
170 | + }); | |
171 | + } else { | |
172 | + const item = { brandId: v.brandId, brandName: v.brandName, applyCarType: 1 }; | |
173 | + result.push(item); | |
174 | + } | |
175 | + }); | |
176 | + return result; | |
177 | + } | |
178 | + | |
179 | + async function onFinish() { | |
180 | + const params = await form.validateFields(); | |
181 | + const _params: SaveParams = { | |
182 | + partName: selectPart?.partName, | |
183 | + partCode: selectPart?.partCode, | |
184 | + beginTime: moment(params.time[0]).startOf('day').valueOf(), | |
185 | + endTime: moment(params.time[1]).endOf('day').valueOf(), | |
186 | + clientAmount: params.clientAmount, | |
187 | + clientFlowAmount: params.clientAmount, | |
188 | + clientAmountFlow: 1, | |
189 | + partCost: params.partCost, | |
190 | + partCostFlow: 1, | |
191 | + partFlowCost: params.partCost, | |
192 | + carDtoList: handleCarData(params.optionalAuth), | |
193 | + id: modalData.data?.id, | |
194 | + }; | |
195 | + setConfirm(true); | |
196 | + saveConfigApi(_params) | |
197 | + .then((res) => { | |
198 | + message.success(res.result); | |
199 | + setConfirm(false); | |
200 | + setLoading(true); | |
201 | + onCancle(); | |
202 | + }) | |
203 | + .catch((e) => { | |
204 | + message.error(e.message); | |
205 | + setConfirm(false); | |
206 | + }); | |
207 | + } | |
208 | + | |
209 | + return ( | |
210 | + <Modal | |
211 | + destroyOnClose | |
212 | + forceRender | |
213 | + title={modalData.data?.id ? '编辑随车配件零售配置' : '新增随车配件零售配置'} | |
214 | + open={modalData.visible} | |
215 | + maskClosable={false} | |
216 | + onCancel={onCancle} | |
217 | + afterClose={form.resetFields} | |
218 | + width="60%" | |
219 | + footer={[ | |
220 | + <Button key="cancel" loading={confirm} disabled={confirm} onClick={onCancle} style={{ marginLeft: 10 }}> | |
221 | + 取消 | |
222 | + </Button>, | |
223 | + <Button key="submit" disabled={confirm} onClick={debounce(onFinish, 380)} type="primary" htmlType="submit" loading={confirm}> | |
224 | + 确认 | |
225 | + </Button>, | |
226 | + ]} | |
227 | + > | |
228 | + <Form form={form}> | |
229 | + <Form.Item | |
230 | + name="optionalAuth" | |
231 | + label="适用车辆" | |
232 | + rules={[{ required: true, message: '选择车辆' }]} | |
233 | + tooltip={ | |
234 | + <span style={{ color: '#ccc', fontSize: 12 }}> | |
235 | + *点击图标 | |
236 | + <PlusSquareOutlined /> | |
237 | + 展开授权详情 | |
238 | + </span> | |
239 | + } | |
240 | + > | |
241 | + <CarTableTreeAuth onChange={(value) => onCarChange(value)} brandMultiple={false} /> | |
242 | + </Form.Item> | |
243 | + <Form.Item label="配件名称" name="part" rules={[{ required: true, message: '请选择' }]}> | |
244 | + <Select | |
245 | + onChange={(value, option) => onPartChange(option)} | |
246 | + placeholder="请选择" | |
247 | + showSearch | |
248 | + allowClear | |
249 | + optionFilterProp="children" | |
250 | + > | |
251 | + {partList.map((v) => ( | |
252 | + <Option key={v.partCode} value={v.partCode} label={v.partName}> | |
253 | + {v.partName} | |
254 | + ({v.partCode}) | |
255 | + </Option> | |
256 | + ))} | |
257 | + </Select> | |
258 | + </Form.Item> | |
259 | + {/* <Form.Item label="配件名称" name="part" rules={[{ required: true, message: '请选择' }]}> | |
260 | + <ChargePileSelect /> | |
261 | + </Form.Item> */} | |
262 | + <Form.Item label="收客户款款项流向" required style={{ marginBottom: 0 }}> | |
263 | + <Form.Item | |
264 | + name="clientAmount" | |
265 | + rules={[{ required: true, message: '请输入' }]} | |
266 | + style={{ display: 'inline-block', width: 'calc(50% - 8px)' }} | |
267 | + > | |
268 | + <InputNumber | |
269 | + style={{ width: '100%' }} | |
270 | + controls={false} | |
271 | + min={0} | |
272 | + formatter={(value: any) => `${value}元`} | |
273 | + precision={2} | |
274 | + parser={(value?: string) => value?.replace('元', '')} | |
275 | + onChange={(value) => setClientFlowAmount(value)} | |
276 | + /> | |
277 | + </Form.Item> | |
278 | + {clientFlowAmount ? ( | |
279 | + <Form.Item name="clientAmountFlow" style={{ display: 'inline-block' }}> | |
280 | + ({AmountFlow[1]} | |
281 | + <span style={{ color: '#ff921c' }}>{clientFlowAmount}</span>元) | |
282 | + </Form.Item> | |
283 | + ) : null} | |
284 | + </Form.Item> | |
285 | + <Form.Item label="配件款款项流向" required style={{ marginBottom: 0 }}> | |
286 | + <Form.Item name="partCost" rules={[{ required: true, message: '请输入' }]} style={{ display: 'inline-block', width: 'calc(50% - 8px)' }}> | |
287 | + <InputNumber | |
288 | + style={{ width: '100%' }} | |
289 | + controls={false} | |
290 | + min={0} | |
291 | + formatter={(value: any) => `${value}元`} | |
292 | + precision={2} | |
293 | + parser={(value?: string) => value?.replace('元', '')} | |
294 | + onChange={(value) => setPartFlowCost(value)} | |
295 | + /> | |
296 | + </Form.Item> | |
297 | + {partFlowCost ? ( | |
298 | + <Form.Item name="costFlowAmount" style={{ display: 'inline-block' }}> | |
299 | + <span> | |
300 | + ({CostFlowEnum[1]} | |
301 | + <span style={{ color: '#ff921c' }}>{partFlowCost}</span>元) | |
302 | + </span> | |
303 | + </Form.Item> | |
304 | + ) : null} | |
305 | + </Form.Item> | |
306 | + <Form.Item label="生效时间" name="time" rules={[{ required: true, message: '请输入' }]}> | |
307 | + <RangePicker | |
308 | + disabledDate={(current) => current && current < moment().startOf('day')} | |
309 | + format="YYYY.MM.DD HH:mm:ss" | |
310 | + style={{ width: '100%' }} | |
311 | + showTime={{ defaultValue: [moment('00:00:00', 'HH:mm:ss'), moment('23:59:59', 'HH:mm:ss')] }} | |
312 | + /> | |
313 | + </Form.Item> | |
314 | + </Form> | |
315 | + </Modal> | |
316 | + ); | |
317 | +} | ... | ... |
src/pages/order3/CarChargePile/components/List.tsx
0 → 100644
1 | +import React from 'react'; | |
2 | +import { message, Popconfirm, Space, Table, Tooltip, Typography } from 'antd'; | |
3 | +import moment from 'moment'; | |
4 | +import { useStore } from '../index'; | |
5 | +import { fetchDeleteApi, ListResult, CostFlowEnum, AmountFlow } from '../api'; | |
6 | + | |
7 | +const Column = Table.Column; | |
8 | + | |
9 | +export default function List() { | |
10 | + const { list, paginationConfig, loading, setLoading, setModalData, setCarModal } = useStore(); | |
11 | + | |
12 | + function onConfirm(id?: number) { | |
13 | + fetchDeleteApi({ id }) | |
14 | + .then((res) => { | |
15 | + message.success(res.result); | |
16 | + setLoading(true); | |
17 | + }) | |
18 | + .catch((e) => { | |
19 | + message.error(e.message); | |
20 | + }); | |
21 | + } | |
22 | + | |
23 | + function onEdit(value: ListResult) { | |
24 | + setModalData({ visible: true, data: value }); | |
25 | + } | |
26 | + | |
27 | + return ( | |
28 | + <div> | |
29 | + <Table scroll={{ x: 1300 }} bordered dataSource={list} pagination={paginationConfig} loading={loading} rowKey="id"> | |
30 | + <Column fixed="left" title="配件名称" align="left" dataIndex="partName" /> | |
31 | + <Column fixed="left" title="配件编码" align="left" dataIndex="partCode" /> | |
32 | + <Table.ColumnGroup title="收客户款款项流向"> | |
33 | + <Column | |
34 | + title="收客户金额" | |
35 | + dataIndex="clientAmount" | |
36 | + align="left" | |
37 | + render={(_text) => ( | |
38 | + <span> | |
39 | + <span style={{ color: '#ff921c' }}>{_text ?? '--'}</span>元 | |
40 | + </span> | |
41 | + )} | |
42 | + /> | |
43 | + <Column | |
44 | + title="款项流向" | |
45 | + dataIndex="clientAmountFlow" | |
46 | + align="left" | |
47 | + render={(_text, record: ListResult) => ( | |
48 | + <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}> | |
49 | + <span>{_text && AmountFlow[_text]}</span> | |
50 | + <span> | |
51 | + <span style={{ color: '#ff921c' }}>{record.clientFlowAmount ?? '--'}</span>元 | |
52 | + </span> | |
53 | + </div> | |
54 | + )} | |
55 | + /> | |
56 | + </Table.ColumnGroup> | |
57 | + | |
58 | + {/* <Table.ColumnGroup title="配件款款项流向"> */} | |
59 | + {/* <Column | |
60 | + title="配件款款项流向" | |
61 | + dataIndex="partCost" | |
62 | + align="left" | |
63 | + render={(_text) => ( | |
64 | + <span> | |
65 | + <span style={{ color: '#ff921c' }}>{_text ?? '--'}</span>元 | |
66 | + </span> | |
67 | + )} | |
68 | + /> */} | |
69 | + <Column | |
70 | + title="配件款款项流向" | |
71 | + dataIndex="partCostFlow" | |
72 | + align="left" | |
73 | + render={(_text, record: ListResult) => ( | |
74 | + <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}> | |
75 | + <span>{_text && CostFlowEnum[_text]}</span> | |
76 | + <span> | |
77 | + <span style={{ color: '#ff921c' }}>{record.partCost ?? '--'}</span>元 | |
78 | + </span> | |
79 | + </div> | |
80 | + )} | |
81 | + /> | |
82 | + {/* </Table.ColumnGroup> */} | |
83 | + | |
84 | + <Column | |
85 | + title="适用车辆" | |
86 | + align="left" | |
87 | + dataIndex="cars" | |
88 | + render={(_text, record: ListResult) => ( | |
89 | + <span onClick={() => setCarModal({ visible: true, data: record })} style={{ color: '#4189FD' }}> | |
90 | + 查看 | |
91 | + </span> | |
92 | + )} | |
93 | + /> | |
94 | + <Column | |
95 | + title="生效时段" | |
96 | + align="left" | |
97 | + dataIndex="beginTime" | |
98 | + width={200} | |
99 | + render={(_text, record: any) => ( | |
100 | + <> | |
101 | + <Tooltip | |
102 | + placement="topLeft" | |
103 | + color="#FFF" | |
104 | + title={ | |
105 | + <span style={{ color: '#666' }}> | |
106 | + {record.beginTime && moment(record.beginTime).format('YYYY-MM-DD HH:mm:ss')}至 | |
107 | + {record.endTime && moment(record.endTime).format('YYYY-MM-DD HH:mm:ss')} | |
108 | + </span> | |
109 | + } | |
110 | + > | |
111 | + <Space direction="vertical"> | |
112 | + <span> {record.beginTime && moment(record.beginTime).format('YYYY-MM-DD HH:mm:ss')}至</span> | |
113 | + <span> {record.endTime && moment(record.endTime).format('YYYY-MM-DD HH:mm:ss')}</span> | |
114 | + </Space> | |
115 | + </Tooltip> | |
116 | + </> | |
117 | + )} | |
118 | + /> | |
119 | + <Column | |
120 | + title="操作" | |
121 | + align="left" | |
122 | + dataIndex="cars" | |
123 | + render={(_text, record: ListResult) => ( | |
124 | + <Space wrap> | |
125 | + <Popconfirm title="是否删除?" onConfirm={() => onConfirm(record.id)}> | |
126 | + <Typography.Link style={{ color: '#999' }}>删除</Typography.Link> | |
127 | + </Popconfirm> | |
128 | + | |
129 | + <Typography.Link onClick={() => onEdit(record)} style={{ color: '#4189FD' }}> | |
130 | + 编辑 | |
131 | + </Typography.Link> | |
132 | + </Space> | |
133 | + )} | |
134 | + /> | |
135 | + </Table> | |
136 | + </div> | |
137 | + ); | |
138 | +} | ... | ... |
src/pages/order3/CarChargePile/components/style.less
0 → 100644
1 | +.span_limit_1 { | |
2 | + display: -webkit-box; | |
3 | + overflow: hidden; | |
4 | + text-align: left; | |
5 | + text-overflow: ellipsis; | |
6 | + /*! autoprefixer: off */ | |
7 | + -webkit-box-orient: vertical; | |
8 | + -webkit-line-clamp: 1; | |
9 | +} | |
10 | + | |
11 | +.ant-input-affix-wrapper { | |
12 | + box-sizing: border-box; | |
13 | + min-height: 32px; | |
14 | + padding: 1px 11px 1px 4px; | |
15 | + | |
16 | + .StaffSelectNew_Container { | |
17 | + display: flex; | |
18 | + flex-wrap: wrap; | |
19 | + align-items: center; | |
20 | + justify-content: flex-start; | |
21 | + box-sizing: border-box; | |
22 | + width: 100%; | |
23 | + padding-top: 2px; | |
24 | + cursor: text; | |
25 | + | |
26 | + .tag { | |
27 | + position: relative; | |
28 | + display: flex; | |
29 | + align-items: center; | |
30 | + flex: none; | |
31 | + box-sizing: border-box; | |
32 | + max-width: 100%; | |
33 | + height: 24px; | |
34 | + margin: 2px 4px 2px 0; | |
35 | + margin-top: 2px; | |
36 | + margin-bottom: 2px; | |
37 | + padding: 0 4px 0 8px; | |
38 | + line-height: 22px; | |
39 | + background: #f5f5f5; | |
40 | + border: 1px solid #f0f0f0; | |
41 | + border-radius: 2px; | |
42 | + cursor: default; | |
43 | + transition: font-size 0.3s, line-height 0.3s, height 0.3s; | |
44 | + user-select: none; | |
45 | + padding-inline-end: 4px; | |
46 | + font-size: 14px; | |
47 | + } | |
48 | + | |
49 | + .text { | |
50 | + padding: 0 7px; | |
51 | + } | |
52 | + | |
53 | + .placeholder { | |
54 | + color: #ccc; | |
55 | + padding: 0 7px; | |
56 | + } | |
57 | + } | |
58 | + | |
59 | + .StaffSelectNew_Container_disabled { | |
60 | + cursor: not-allowed; | |
61 | + } | |
62 | +} | |
63 | + | |
64 | +.ant-input-affix-wrapper_disabled { | |
65 | + background-color: rgb(245, 245, 245); | |
66 | + cursor: not-allowed; | |
67 | + | |
68 | + &:hover { | |
69 | + border-color: #d9d9d9 !important; | |
70 | + cursor: not-allowed !important; | |
71 | + } | |
72 | +} | |
73 | + | |
74 | +.StaffSelectNew_checkbox { | |
75 | + align-items: center; | |
76 | + | |
77 | + .ant-checkbox { | |
78 | + top: 0 !important; | |
79 | + } | |
80 | +} | |
81 | + | |
82 | +.StaffSelectNew_Filter_Container>.ant-select.ant-select-in-form-item { | |
83 | + width: auto !important; | |
84 | +} | |
0 | 85 | \ No newline at end of file | ... | ... |
src/pages/order3/CarChargePile/index.tsx
0 → 100644
1 | +import React from 'react'; | |
2 | +import { Card, Button, Row, Col, Input } from 'antd'; | |
3 | +import { PageHeaderWrapper } from '@ant-design/pro-layout'; | |
4 | +import { createStore } from '@/hooks/moz'; | |
5 | +import store from './store'; | |
6 | +import List from './components/List'; | |
7 | +import EditMoadal from './components/EditModal'; | |
8 | +import CarModal from './components/CarModal'; | |
9 | +import { debounce } from 'lodash'; | |
10 | +import Filters from '@/pages/order3/Common/Filter'; | |
11 | + | |
12 | + | |
13 | +export const { Provider, useStore } = createStore(store); | |
14 | + | |
15 | +function AddValueTaskConfig() { | |
16 | + const { setModalData, setParams, innerParams } = useStore(); | |
17 | + | |
18 | + | |
19 | + function handleAdd() { | |
20 | + setModalData({ visible: true, data: {} }); | |
21 | + } | |
22 | + | |
23 | + const handleSearch = debounce((text?: string) => { | |
24 | + setParams({ ...innerParams, partName: text, current: 1, pageSize: 10 }, true); | |
25 | + }, 380); | |
26 | + | |
27 | + return ( | |
28 | + <PageHeaderWrapper title="随车配件零售配置"> | |
29 | + <Card> | |
30 | + <Row justify="space-between" style={{ marginBottom: 20 }}> | |
31 | + <Row> | |
32 | + <Col> | |
33 | + <Filters | |
34 | + onFilter={(value: any) => setParams({ ...innerParams, brandId: value.brandId, seriesId: value.seriesId, specId: value.specId }, true)} | |
35 | + /> | |
36 | + </Col> | |
37 | + <Col style={{marginLeft: '20px'}}> | |
38 | + <Input.Search placeholder="请输入配件名称" allowClear onSearch={(value) => handleSearch(value)} /> | |
39 | + </Col> | |
40 | + </Row> | |
41 | + <Col> | |
42 | + <Button type="primary" onClick={handleAdd}> | |
43 | + 新增 | |
44 | + </Button> | |
45 | + </Col> | |
46 | + </Row> | |
47 | + <List /> | |
48 | + </Card> | |
49 | + <EditMoadal /> | |
50 | + <CarModal /> | |
51 | + </PageHeaderWrapper> | |
52 | + ); | |
53 | +} | |
54 | + | |
55 | +export default () => ( | |
56 | + <Provider> | |
57 | + <AddValueTaskConfig /> | |
58 | + </Provider> | |
59 | +); | ... | ... |
src/pages/order3/CarChargePile/store.ts
0 → 100644
1 | +import React, { useState } from 'react'; | |
2 | +import * as api from './api'; | |
3 | +import usePagination from '@/hooks/usePagination'; | |
4 | + | |
5 | +interface ModalData { | |
6 | + visible: boolean; | |
7 | + data?: api.ListResult; | |
8 | +} | |
9 | + | |
10 | +export default function useStore() { | |
11 | + const { list, loading, setLoading, paginationConfig, setParams, innerParams } = usePagination(api.getListApi, {}); | |
12 | + const [modalData, setModalData] = useState<ModalData>({visible: false}); | |
13 | + const [carModal, setCarModal] = useState<ModalData>({visible: false}); | |
14 | + | |
15 | + return { | |
16 | + setParams, | |
17 | + paginationConfig, | |
18 | + loading, | |
19 | + setLoading, | |
20 | + list, | |
21 | + innerParams, | |
22 | + modalData, | |
23 | + setModalData, | |
24 | + carModal, | |
25 | + setCarModal | |
26 | + }; | |
27 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/api.ts
0 → 100644
1 | +import request from '@/utils/request'; | |
2 | +import { FINANCE2_HOST, FVM_HOST, ORDER3 } from '@/utils/host'; | |
3 | +import { http } from '@/typing/http'; | |
4 | + | |
5 | +type Page<T> = http.PromisePageResp<T>; | |
6 | + | |
7 | +export enum DeductionMode { | |
8 | + '固定金额' = 1, | |
9 | + '固定比例', | |
10 | +} | |
11 | + | |
12 | +export interface SaveParams { | |
13 | + id?: number; // 主键id | |
14 | + brandId?: number; // 品牌id | |
15 | + brandName?: string; // 品牌名称 | |
16 | + carConfigDtoList: ApplySeriesList[]; // 适用车系列表 | |
17 | + startTime?: number; // 开始时间 | |
18 | + endTime?: number; // 结束时间 | |
19 | + sellName?: string; // 促销名称 | |
20 | + sellAmount?: number; // 促销金额 | |
21 | + dealerAdvancePayment?: boolean; // 经销商垫付 | |
22 | + receivableDeduction?: number; // 挂厂家应收款扣除比例或金额 | |
23 | + fundFlow?: number; // 款项流向(1:折让确认,3:资金账户) | |
24 | + sellShopConfigDtoList?: AccountShopList[]; // 资金账户列表 | |
25 | + deductionMode?: number; // 扣除方式1.按固定金额2.按比例 | |
26 | +} | |
27 | + | |
28 | +export interface ListResult { | |
29 | + id?: number; // id | |
30 | + brandId?: number; // 品牌id | |
31 | + brandName?: string; // 品牌名称 | |
32 | + carSeriesVoList?: ApplySeriesList[]; // 适用车系列表 | |
33 | + startTime?: number; // 开始时间 | |
34 | + endTime?: number; // 结束时间 | |
35 | + sellName?: string; // 促销名称 | |
36 | + sellAmount?: number; // 促销金额 | |
37 | + dealerAdvancePayment?: boolean; // 经销商垫付(经销商垫付(0:否,1:是)) | |
38 | + receivableDeduction?: number; // 挂厂家应收扣除比例 | |
39 | + fundFlow?: number; // 款项流向 | |
40 | + shopAccountVoList?: AccountShopList[]; // 资金账户列表 | |
41 | + deductionMode?: number; // 扣除方式 | |
42 | +} | |
43 | + | |
44 | +interface SeriesList { | |
45 | + seriesId?: number; // 车系id | |
46 | + seriesName?: string; // 车系名称 | |
47 | + specList: SpecList[]; // 车型列表 | |
48 | +} | |
49 | + | |
50 | +interface SpecList { | |
51 | + specId?: number; // 车型id | |
52 | + specName?: string; // 车型名称 | |
53 | +} | |
54 | + | |
55 | +interface AccountShopList { | |
56 | + accountId?: number; // 账户id | |
57 | + accountName?: string; // 账户名称 | |
58 | + shopList: ShopVo[]; // 账户对应门店列表 | |
59 | +} | |
60 | + | |
61 | +interface ApplySeriesList { | |
62 | + brandId?: number; // 品牌id | |
63 | + brandName?: string; // 品牌名称 | |
64 | + seriesId?: number; // 车系id | |
65 | + seriesName?: string; // 车系名称 | |
66 | + applyCarType?: number; // 适用车辆类型 | |
67 | + specId?: number; // 车系id | |
68 | + specName?: string; // 车系名称 | |
69 | + seriesList?: SeriesList[]; // 车系列表(列表返回值) | |
70 | +} | |
71 | + | |
72 | +/** | |
73 | + * 查询集团在售上架的品牌列表 | |
74 | + * @returns | |
75 | + */ | |
76 | +export function getOnsaleBrandApi(): http.PromiseResp<EnclosureSpace.OptionVo[]> { | |
77 | + return request.get(`${FVM_HOST}/erp/putaway/vehicle/support/brand`); | |
78 | +} | |
79 | + | |
80 | +/** | |
81 | + * 查询集团在售上架的车系列表 | |
82 | + * @param brandId | |
83 | + * @returns | |
84 | + */ | |
85 | +export function getOnsaleSeriesApi(brandId: number): http.PromiseResp<EnclosureSpace.OptionVo[]> { | |
86 | + return request.get(`${FVM_HOST}/erp/putaway/vehicle/support/series`, { | |
87 | + params: { brandId }, | |
88 | + }); | |
89 | +} | |
90 | + | |
91 | +/** | |
92 | + * 查询直营车厂家促销列表 | |
93 | + * @param params | |
94 | + * @returns | |
95 | + */ | |
96 | +export function getListApi(params: any): Page<ListResult> { | |
97 | + return request.get(`${ORDER3}/erp/factory/direct/sell/config/page`, { params }); | |
98 | +} | |
99 | + | |
100 | +/** | |
101 | + * 新增直营车厂家促销配置 | |
102 | + * @param params | |
103 | + * @returns | |
104 | + */ | |
105 | +export function saveConfigApi(params: SaveParams) { | |
106 | + return request.post(`${ORDER3}/erp/factory/direct/sell/config/save`, params); | |
107 | +} | |
108 | + | |
109 | +/** | |
110 | + * 编辑直营车厂家促销配置 | |
111 | + * @param params | |
112 | + * @returns | |
113 | + */ | |
114 | +export function updateConfigApi(params: SaveParams) { | |
115 | + return request.post(`${ORDER3}/erp/factory/direct/sell/config/update`, params); | |
116 | +} | |
117 | + | |
118 | +/** | |
119 | + * 删除直营车厂家促销设置 | |
120 | + * @param id | |
121 | + * @returns | |
122 | + */ | |
123 | +export function fetchDeleteConfigApi(id: number) { | |
124 | + return request.post(`${ORDER3}/erp/factory/direct/sell/config/delete`, { id }, { contentType: 'form-urlencoded' }); | |
125 | +} | |
126 | + | |
127 | +export interface GetShopListByAccountReq { | |
128 | + accountId: number; | |
129 | +} | |
130 | +export interface ShopVo { | |
131 | + shopId: number; | |
132 | + shopName: string; | |
133 | +} | |
134 | + | |
135 | +export interface AccountShopItem { | |
136 | + accountId?: number; | |
137 | + accountName?: string; | |
138 | + shopList: ShopVo[]; | |
139 | +} | |
140 | + | |
141 | +/** | |
142 | + * 获取授权账户下门店列表 | |
143 | + * @param params | |
144 | + * @returns | |
145 | + */ | |
146 | +export function getShopListByAccount(params: GetShopListByAccountReq): http.PromiseResp<ShopVo[]> { | |
147 | + return request.get(`${FINANCE2_HOST}/common/account/authed/shop/list`, { params }); | |
148 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/components/AccountModal.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Modal, Button, message, Form, Select } from 'antd'; | |
3 | +import { useStore } from '../index'; | |
4 | +import { getShopListByAccount } from '../api'; | |
5 | + | |
6 | +const FormItem = Form.Item; | |
7 | +export default function AccountModal() { | |
8 | + const { accountModal, setAccountModal, accountList, setAccountShopList, accountShopList } = useStore(); | |
9 | + const [form] = Form.useForm(); | |
10 | + const [shopData, setShopData] = useState<any>([]); | |
11 | + | |
12 | + useEffect(() => { | |
13 | + if (accountModal.visible && accountModal.data?.accountId) { | |
14 | + form.setFieldsValue({ | |
15 | + account: {label: accountModal?.data.accountName, value: accountModal?.data.accountId}, | |
16 | + shopList: accountModal.data?.shopList?.map(v => ({label: v.shopName, value: v.shopId})), | |
17 | + }); | |
18 | + getShopData(accountModal.data.accountId) | |
19 | + } | |
20 | + }, [accountModal.visible]) | |
21 | + | |
22 | + function onAccountChange(accountId?: number) { | |
23 | + if (!accountId) { | |
24 | + form.setFieldValue('shopList', undefined); | |
25 | + return | |
26 | + } | |
27 | + getShopListByAccount({ accountId }) | |
28 | + .then((res) => { | |
29 | + setShopData(res.data || []); | |
30 | + form.setFieldsValue({ | |
31 | + shopList: res.data?.map((v) => ({ label: v.shopName, value: v.shopId })), | |
32 | + }); | |
33 | + }) | |
34 | + .catch((e) => { | |
35 | + message.error(e.message); | |
36 | + setShopData([]); | |
37 | + }); | |
38 | + } | |
39 | + | |
40 | + function getShopData(accountId: number) { | |
41 | + getShopListByAccount({ accountId }) | |
42 | + .then((res) => { | |
43 | + setShopData(res.data || []); | |
44 | + }) | |
45 | + .catch((e) => { | |
46 | + message.error(e.message); | |
47 | + setShopData([]); | |
48 | + }); | |
49 | + } | |
50 | + | |
51 | + function onCancle() { | |
52 | + form.resetFields(); | |
53 | + setAccountModal({title: '', visible: false, data: undefined}); | |
54 | + } | |
55 | + | |
56 | + async function onOK() { | |
57 | + const params = await form.validateFields(); | |
58 | + const value = { | |
59 | + accountId: params.account?.value, | |
60 | + accountName: params.account?.label, | |
61 | + shopList: params.shopList.map((v: any) => ({shopId: v.value, shopName: v.label})), | |
62 | + }; | |
63 | + if (accountModal.data?.accountId) { | |
64 | + const data = JSON.parse(JSON.stringify(accountShopList || [])); | |
65 | + const list = data?.map((v: any) => { | |
66 | + const item = { | |
67 | + accountId: v.accountId, | |
68 | + accountName: v.accountName, | |
69 | + shopList: v.shopList, | |
70 | + }; | |
71 | + if (accountModal.data?.accountId === params.account.value) { | |
72 | + item.shopList = value.shopList; | |
73 | + } | |
74 | + return item | |
75 | + }) | |
76 | + setAccountShopList(list); | |
77 | + } else { | |
78 | + setAccountShopList([...accountShopList, value]); | |
79 | + } | |
80 | + setAccountModal({visible: false, title: '', data: undefined}); | |
81 | + } | |
82 | + | |
83 | + return ( | |
84 | + <Modal | |
85 | + destroyOnClose | |
86 | + forceRender | |
87 | + title={accountModal.title} | |
88 | + open={accountModal.visible} | |
89 | + maskClosable={false} | |
90 | + onCancel={onCancle} | |
91 | + afterClose={form.resetFields} | |
92 | + footer={[ | |
93 | + <Button key="cancel" onClick={onCancle} style={{ marginRight: 10 }}> | |
94 | + 取消 | |
95 | + </Button>, | |
96 | + <Button key="submit" onClick={onOK} type="primary" htmlType="submit"> | |
97 | + 确认 | |
98 | + </Button>, | |
99 | + ]} | |
100 | + > | |
101 | + <Form form={form}> | |
102 | + <FormItem label="资金账户" name="account" rules={[{ required: true, message: '请选择资金账户' }]}> | |
103 | + <Select | |
104 | + showSearch | |
105 | + allowClear | |
106 | + labelInValue | |
107 | + optionFilterProp="label" | |
108 | + placeholder="请选择资金账户" | |
109 | + style={{ width: '100%' }} | |
110 | + disabled={!!accountModal.data?.accountId} | |
111 | + onChange={value => onAccountChange(value?.value)} | |
112 | + options={accountList.map((v) => ({ label: v.name, value: v.id }))} | |
113 | + /> | |
114 | + </FormItem> | |
115 | + <FormItem label="适用门店" name="shopList" rules={[{ required: true, message: '请选择适用门店' }]}> | |
116 | + <Select | |
117 | + showSearch | |
118 | + allowClear | |
119 | + labelInValue | |
120 | + mode="multiple" | |
121 | + optionFilterProp="label" | |
122 | + placeholder="请选择适用门店" | |
123 | + style={{ width: '100%' }} | |
124 | + options={shopData.map((v: any) => ({ label: v.shopName, value: v.shopId }))} | |
125 | + /> | |
126 | + </FormItem> | |
127 | + </Form> | |
128 | + </Modal> | |
129 | + ); | |
130 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/components/CarShopModal.tsx
0 → 100644
1 | +import React, { useMemo } from 'react'; | |
2 | +import { Modal } from 'antd'; | |
3 | +import _ from 'lodash'; | |
4 | +import { useStore } from '../index'; | |
5 | +import CarTableTreeAuth from '@/components/CarTableTreeAuth'; | |
6 | + | |
7 | +const ShowModal = () => { | |
8 | + const { showModal, setShowModal } = useStore(); | |
9 | + | |
10 | + const handleCancel = () => { | |
11 | + setShowModal({ ...showModal, visible: false }); | |
12 | + }; | |
13 | + | |
14 | + const carData = useMemo(() => { | |
15 | + const data: any[] = JSON.parse(JSON.stringify(showModal.data || [])); | |
16 | + if (data.length) { | |
17 | + data.forEach((v) => { | |
18 | + if (v.seriesList?.length) { | |
19 | + v.children = v.seriesList; | |
20 | + v.seriesList.forEach((k: any) => { | |
21 | + if (k.specList?.length) { | |
22 | + k.children = k.specList; | |
23 | + } | |
24 | + }); | |
25 | + } | |
26 | + }); | |
27 | + } | |
28 | + return data; | |
29 | + }, [showModal.visible]); | |
30 | + | |
31 | + return ( | |
32 | + <Modal | |
33 | + destroyOnClose | |
34 | + forceRender | |
35 | + open={showModal.visible} | |
36 | + title={showModal.title || ''} | |
37 | + maskClosable={false} | |
38 | + onCancel={handleCancel} | |
39 | + onOk={handleCancel} | |
40 | + > | |
41 | + <CarTableTreeAuth value={carData} disabled brandMultiple={false} /> | |
42 | + </Modal> | |
43 | + ); | |
44 | +}; | |
45 | +export default ShowModal; | ... | ... |
src/pages/order3/DirectCarPromotion/components/EditModal.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Modal, Form, DatePicker, Button, message, Input, Select, Typography, Card, Row, Radio, Table, Space, Popconfirm, Tooltip } from 'antd'; | |
3 | +import * as api from '../api'; | |
4 | +import { useStore } from '../index'; | |
5 | +import moment from 'moment'; | |
6 | +import { PlusSquareOutlined, ExclamationCircleFilled } from '@ant-design/icons'; | |
7 | +import CarTableTreeAuth from '@/components/CarTableTreeAuth'; | |
8 | +import { CarAuthList } from '@/components/CarTableTreeAuth/entity'; | |
9 | +import { CapitalFlowEnum } from '../entity'; | |
10 | + | |
11 | +const FormItem = Form.Item; | |
12 | +const { Option } = Select; | |
13 | + | |
14 | +export default function EditModal() { | |
15 | + const [form] = Form.useForm(); | |
16 | + const [confirmLoading, setConfirmLoading] = useState<boolean>(false); | |
17 | + const { editModal, setEditModal, setLoading, accountShopList, setAccountModal, setAccountShopList, edit, setEdit } = useStore(); | |
18 | + | |
19 | + useEffect(() => { | |
20 | + if (editModal.visible && editModal.data?.id) { | |
21 | + const applyCarList = editModal.data?.carSeriesVoList?.map((v) => { | |
22 | + const item: any = { | |
23 | + brandId: v.brandId, | |
24 | + brandName: v.brandName, | |
25 | + }; | |
26 | + if (v.seriesList?.length) { | |
27 | + const series = v.seriesList.map((k) => { | |
28 | + const seriesItem: any = { seriesId: k.seriesId, seriesName: k.seriesName }; | |
29 | + if (k.specList.length) { | |
30 | + const spec = k.specList.map((l) => ({ specId: l.specId, specName: l.specName })); | |
31 | + seriesItem.children = spec; | |
32 | + } | |
33 | + return seriesItem; | |
34 | + }); | |
35 | + item.children = series; | |
36 | + } | |
37 | + return item; | |
38 | + }); | |
39 | + | |
40 | + const current = editModal.data || {}; | |
41 | + form.setFieldsValue({ | |
42 | + time: [moment(current.startTime), moment(current.endTime)], | |
43 | + sellName: current.sellName, | |
44 | + sellAmount: current.sellAmount, | |
45 | + dealerAdvancePayment: current.dealerAdvancePayment, | |
46 | + deductionMode: current.deductionMode, | |
47 | + receivableDeduction: current.receivableDeduction, | |
48 | + fundFlow: current.fundFlow, | |
49 | + optionalAuth: applyCarList, | |
50 | + // accountShopList, | |
51 | + }); | |
52 | + if (current.fundFlow === 3) { | |
53 | + setAccountShopList(current.shopAccountVoList || []); | |
54 | + } | |
55 | + } | |
56 | + }, [editModal.visible]); | |
57 | + | |
58 | + const handleSave = async () => { | |
59 | + const value = await form.validateFields(); | |
60 | + const _accountShopList: any = []; | |
61 | + accountShopList?.map((v: any) => { | |
62 | + v.shopList?.map((k: any) => { | |
63 | + const item = { shopId: k.shopId, shopName: k.shopName, accountId: v.accountId, accountName: v.accountName }; | |
64 | + _accountShopList.push(item); | |
65 | + return {...k}; | |
66 | + }) | |
67 | + return {...v}; | |
68 | + }) | |
69 | + setConfirmLoading(true); | |
70 | + const params: any = { | |
71 | + brandId: value.optionalAuth[0].brandId, // 品牌id | |
72 | + brandName: value.optionalAuth[0].brandName, // 品牌名称 | |
73 | + startTime: moment(value.time[0]).valueOf(), | |
74 | + endTime: moment(value.time[1]).valueOf(), | |
75 | + carConfigDtoList: handleCarData(value.optionalAuth), | |
76 | + sellName: value.sellName, | |
77 | + sellAmount: value.sellAmount, | |
78 | + dealerAdvancePayment: value.dealerAdvancePayment, | |
79 | + receivableDeduction: value.receivableDeduction, | |
80 | + deductionMode: value.deductionMode, | |
81 | + fundFlow: value.fundFlow, | |
82 | + }; | |
83 | + if (value.fundFlow === 3) { | |
84 | + params.sellShopConfigDtoList = _accountShopList; | |
85 | + } | |
86 | + if (edit) { | |
87 | + params.id = editModal.data?.id; | |
88 | + } | |
89 | + const requestApi = edit ? api.updateConfigApi : api.saveConfigApi; | |
90 | + requestApi({ ...params }) | |
91 | + .then((res) => { | |
92 | + message.success(res.result); | |
93 | + setConfirmLoading(false); | |
94 | + setLoading(true); | |
95 | + setEdit(false); | |
96 | + setEditModal({ visible: false, data: undefined }); | |
97 | + }) | |
98 | + .catch((e) => { | |
99 | + setConfirmLoading(false); | |
100 | + message.error(e.message); | |
101 | + setEdit(false) | |
102 | + }); | |
103 | + }; | |
104 | + | |
105 | + /** | |
106 | + * 将树形车辆结构转换成扁平结构 | |
107 | + * applyCarType为最细化层级的类型 | |
108 | + * @param value | |
109 | + * @returns | |
110 | + */ | |
111 | + function handleCarData(value: CarAuthList[]) { | |
112 | + const result: any = []; | |
113 | + value.forEach((v) => { | |
114 | + if (v.children?.length) { | |
115 | + v.children.forEach((k) => { | |
116 | + if (k.children?.length) { | |
117 | + k.children.forEach((r) => { | |
118 | + const item = { | |
119 | + brandId: v.brandId, | |
120 | + brandName: v.brandName, | |
121 | + seriesId: k.seriesId, | |
122 | + seriesName: k.seriesName, | |
123 | + specId: r.specId, | |
124 | + specName: r.specName, | |
125 | + applyCarType: 3, | |
126 | + }; | |
127 | + result.push(item); | |
128 | + }); | |
129 | + } else { | |
130 | + const item = { | |
131 | + brandId: v.brandId, | |
132 | + brandName: v.brandName, | |
133 | + seriesId: k.seriesId, | |
134 | + seriesName: k.seriesName, | |
135 | + applyCarType: 2, | |
136 | + }; | |
137 | + result.push(item); | |
138 | + } | |
139 | + }); | |
140 | + } else { | |
141 | + const item = { | |
142 | + brandId: v.brandId, | |
143 | + brandName: v.brandName, | |
144 | + applyCarType: 1, | |
145 | + }; | |
146 | + result.push(item); | |
147 | + } | |
148 | + }); | |
149 | + return result; | |
150 | + } | |
151 | + | |
152 | + function close() { | |
153 | + setEditModal({visible: false, data: undefined}) | |
154 | + setAccountShopList([]); | |
155 | + setEdit(false); | |
156 | + }; | |
157 | + | |
158 | + function accountShopListRules(getFieldValue: any) { | |
159 | + const capitalFlow = getFieldValue('fundFlow'); | |
160 | + // const accountShopList = getFieldValue('accountShopList'); | |
161 | + if (capitalFlow === 3 && accountShopList.length <= 0) { | |
162 | + return Promise.reject(new Error('请添加至少一个门店资金账户')); | |
163 | + } | |
164 | + return Promise.resolve(); | |
165 | + }; | |
166 | + | |
167 | + function onChangeMode() { | |
168 | + form.setFieldsValue({ deductionRatio: undefined }); | |
169 | + } | |
170 | + | |
171 | + function deleteAccount(id?: number) { | |
172 | + const list = accountShopList.filter(v => v.accountId !== id); | |
173 | + setAccountShopList(list); | |
174 | + } | |
175 | + | |
176 | + function renderShop(value?: api.ShopVo[]) { | |
177 | + if (!value?.length) { | |
178 | + return '--' | |
179 | + } | |
180 | + if (value.length <= 3) { | |
181 | + const text = value.map(v => v.shopName).join(","); | |
182 | + return text; | |
183 | + } | |
184 | + const len = value.length; | |
185 | + const str = value.slice(0, 3).map(v => v.shopName).join(","); | |
186 | + const text = value.map(v => v.shopName).join(","); | |
187 | + return ( | |
188 | + <Tooltip | |
189 | + placement="topLeft" | |
190 | + color="#FFF" | |
191 | + title={ | |
192 | + <span style={{ color: '#666' }}> | |
193 | + {text} | |
194 | + </span> | |
195 | + } | |
196 | + > | |
197 | + <span>{str}等{len}个门店</span> | |
198 | + </Tooltip> | |
199 | + ); | |
200 | + } | |
201 | + | |
202 | + return ( | |
203 | + <Modal | |
204 | + title={`${edit ? '编辑' : '新增'}直营车厂家促销`} | |
205 | + open={editModal.visible} | |
206 | + onCancel={close} | |
207 | + style={{ minWidth: 1000 }} | |
208 | + maskClosable={false} | |
209 | + destroyOnClose | |
210 | + forceRender | |
211 | + afterClose={() => { | |
212 | + setEditModal({ visible: false, data: undefined }); | |
213 | + form.resetFields(); | |
214 | + }} | |
215 | + footer={null} | |
216 | + > | |
217 | + <div id="modal"> | |
218 | + <Form form={form} labelAlign="left" onFinish={handleSave} preserve={false}> | |
219 | + <Form.Item | |
220 | + name="optionalAuth" | |
221 | + label="促销车辆" | |
222 | + rules={[{ required: true, message: '选择车辆' }]} | |
223 | + tooltip={ | |
224 | + <span style={{ color: '#ccc', fontSize: 12 }}> | |
225 | + *点击图标 | |
226 | + <PlusSquareOutlined /> | |
227 | + 展开授权详情 | |
228 | + </span> | |
229 | + } | |
230 | + > | |
231 | + <CarTableTreeAuth brandMultiple={false} /> | |
232 | + </Form.Item> | |
233 | + <Form.Item name="time" label="生效时间段" rules={[{ type: 'array' as const, required: true, message: '请选择生效时间段!' }]}> | |
234 | + <DatePicker.RangePicker | |
235 | + style={{ width: '100%' }} | |
236 | + disabledDate={(current) => !!current && current < moment().startOf('day')} | |
237 | + picker="date" | |
238 | + /> | |
239 | + </Form.Item> | |
240 | + <FormItem name="sellName" label="促销名称" rules={[{ required: true, message: '请输入促销名称' }]}> | |
241 | + <Input placeholder="请输入促销名称" style={{ width: '100%' }} /> | |
242 | + </FormItem> | |
243 | + <FormItem | |
244 | + name="sellAmount" | |
245 | + label="促销金额" | |
246 | + rules={[{ required: true, pattern: /^(\d+|\d+\.\d{1,2})$/, message: '请输入促销金额' }]} | |
247 | + > | |
248 | + <Input placeholder="请输入促销金额" style={{ width: '100%' }} suffix="元" /> | |
249 | + </FormItem> | |
250 | + <FormItem | |
251 | + extra={ | |
252 | + <div style={{ marginTop: '5px' }}> | |
253 | + <ExclamationCircleFilled style={{ fontSize: '11px', color: '#ff921c' }} /> | |
254 | + <span style={{ color: '#999', fontSize: '12px', paddingLeft: '5px' }}> | |
255 | + 示例:双十二前下定锁车额外享6000元现金(经销商先兑换给客户,然后厂家折让方式返回;挂应收,折让收回后冲抵应收) | |
256 | + </span> | |
257 | + </div> | |
258 | + } | |
259 | + name="dealerAdvancePayment" | |
260 | + label="经销商是否垫付给客户" | |
261 | + rules={[{ required: true, message: '请选择经销商是否垫付给客户' }]} | |
262 | + > | |
263 | + <Select placeholder="清选择"> | |
264 | + <Select.Option key={1} value={true}> | |
265 | + 是 | |
266 | + </Select.Option> | |
267 | + <Select.Option key={2} value={false}> | |
268 | + 否 | |
269 | + </Select.Option> | |
270 | + </Select> | |
271 | + </FormItem> | |
272 | + | |
273 | + <Form.Item | |
274 | + noStyle | |
275 | + shouldUpdate={(prevValues, currValues) => { | |
276 | + return prevValues.dealerAdvancePayment !== currValues.dealerAdvancePayment; | |
277 | + }} | |
278 | + > | |
279 | + {({getFieldValue}) => { | |
280 | + const value = getFieldValue('dealerAdvancePayment'); | |
281 | + if (!value) { | |
282 | + return; | |
283 | + } | |
284 | + return ( | |
285 | + <> | |
286 | + <FormItem name="deductionMode" label="扣除方式" rules={[{ required: true, message: '请选择扣除方式' }]}> | |
287 | + <Radio.Group onChange={() => onChangeMode()}> | |
288 | + <Radio value={1}>固定金额</Radio> | |
289 | + <Radio value={2}>固定比例</Radio> | |
290 | + </Radio.Group> | |
291 | + </FormItem> | |
292 | + <Form.Item | |
293 | + noStyle | |
294 | + shouldUpdate={(prevValues, currValues) => { | |
295 | + return prevValues.deductionMode !== currValues.deductionMode; | |
296 | + }} | |
297 | + > | |
298 | + {({ getFieldValue }) => { | |
299 | + const value = getFieldValue('deductionMode'); | |
300 | + if (!value) { | |
301 | + return null; | |
302 | + } | |
303 | + return value === 1 ? ( | |
304 | + <FormItem | |
305 | + name="receivableDeduction" | |
306 | + label="挂厂家应收扣除金额" | |
307 | + dependencies={['sellAmount']} | |
308 | + rules={[ | |
309 | + { | |
310 | + required: true, | |
311 | + pattern: /^(\d+|\d+\.\d{1,2})$/, | |
312 | + message: '请输入挂厂家应收扣除金额', | |
313 | + }, | |
314 | + ({ getFieldValue }) => ({ | |
315 | + validator(_, value) { | |
316 | + if (!value || +(getFieldValue('sellAmount') || 0) >= +value) { | |
317 | + return Promise.resolve(); | |
318 | + } | |
319 | + return Promise.reject(new Error('挂厂家应收扣除金额不能大于代收金额!')); | |
320 | + }, | |
321 | + }), | |
322 | + ]} | |
323 | + > | |
324 | + <Input placeholder="请输入扣除金额" style={{ width: '100%' }} suffix="元" /> | |
325 | + </FormItem> | |
326 | + ) : ( | |
327 | + <FormItem | |
328 | + name="receivableDeduction" | |
329 | + extra="比例值为0~100" | |
330 | + label="挂厂家应收扣除比例" | |
331 | + rules={[ | |
332 | + { | |
333 | + required: true, | |
334 | + pattern: /^(\d+|\d+\.\d{1,2})$/, | |
335 | + message: '请输入挂厂家应收扣除比例', | |
336 | + }, | |
337 | + ({ getFieldValue }) => ({ | |
338 | + validator(_, value) { | |
339 | + if (value && +value <= 100 && +value >= 0) { | |
340 | + return Promise.resolve(); | |
341 | + } | |
342 | + return Promise.reject(new Error('比例范围应在0~100!')); | |
343 | + }, | |
344 | + }), | |
345 | + ]} | |
346 | + > | |
347 | + <Input placeholder="请输入扣除比例" style={{ width: '100%' }} suffix="%" /> | |
348 | + </FormItem> | |
349 | + ); | |
350 | + }} | |
351 | + </Form.Item> | |
352 | + <FormItem noStyle> | |
353 | + <FormItem label="款项流向" name="fundFlow" rules={[{ required: true, message: '请选择款项流向' }]}> | |
354 | + <Select placeholder="请选择款项流向"> | |
355 | + {[1, 2, 3].map((item) => ( | |
356 | + <Option key={item} value={item}> | |
357 | + {CapitalFlowEnum[item]} | |
358 | + </Option> | |
359 | + ))} | |
360 | + </Select> | |
361 | + </FormItem> | |
362 | + <Form.Item | |
363 | + noStyle | |
364 | + shouldUpdate={(prevValues, currValues) => { | |
365 | + return prevValues.fundFlow !== currValues.fundFlow; | |
366 | + }} | |
367 | + > | |
368 | + {({ getFieldValue }) => { | |
369 | + if (getFieldValue('fundFlow') !== 3) { | |
370 | + return null; | |
371 | + } | |
372 | + return ( | |
373 | + <FormItem | |
374 | + label="     " | |
375 | + name="accountShopList" | |
376 | + colon={false} | |
377 | + rules={[ | |
378 | + ({ getFieldValue }) => ({ | |
379 | + validator(_, value) { | |
380 | + return accountShopListRules(getFieldValue); | |
381 | + }, | |
382 | + }), | |
383 | + ]} | |
384 | + > | |
385 | + <Card> | |
386 | + <Row justify="end" style={{ marginBottom: 10 }}> | |
387 | + <Typography.Link | |
388 | + onClick={() => setAccountModal({ title: '新增', visible: true, data: undefined })} | |
389 | + style={{ color: '#4189FD' }} | |
390 | + > | |
391 | + 新增 | |
392 | + </Typography.Link> | |
393 | + </Row> | |
394 | + <Table dataSource={accountShopList} pagination={false} rowKey="accountId"> | |
395 | + <Table.Column title="资金账户" dataIndex="accountName" align="left" /> | |
396 | + <Table.Column title="适用门店" dataIndex="shopList" align="left" render={(_text) => renderShop(_text)} /> | |
397 | + <Table.Column | |
398 | + title="操作" | |
399 | + dataIndex="option" | |
400 | + align="left" | |
401 | + render={(_text, record: any) => ( | |
402 | + <Space wrap> | |
403 | + <Popconfirm onConfirm={() => deleteAccount(record.accountId)} title="是否删除?"> | |
404 | + <Typography.Link style={{ color: '#999' }}>删除</Typography.Link> | |
405 | + </Popconfirm> | |
406 | + <Typography.Link | |
407 | + onClick={() => setAccountModal({ title: '编辑', data: record, visible: true })} | |
408 | + style={{ color: '#4189FD' }} | |
409 | + > | |
410 | + 编辑 | |
411 | + </Typography.Link> | |
412 | + </Space> | |
413 | + )} | |
414 | + /> | |
415 | + </Table> | |
416 | + </Card> | |
417 | + </FormItem> | |
418 | + ); | |
419 | + }} | |
420 | + </Form.Item> | |
421 | + </FormItem> | |
422 | + </> | |
423 | + ); | |
424 | + }} | |
425 | + </Form.Item> | |
426 | + </Form> | |
427 | + <Row justify="center"> | |
428 | + <Space> | |
429 | + <Button key="cancel" onClick={close} style={{ marginLeft: 10 }}> | |
430 | + 取消 | |
431 | + </Button> | |
432 | + <Button key="submit" onClick={handleSave} type="primary" htmlType="submit" loading={confirmLoading}> | |
433 | + 确认 | |
434 | + </Button> | |
435 | + </Space> | |
436 | + </Row> | |
437 | + </div> | |
438 | + </Modal> | |
439 | + ); | |
440 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/components/Filter.tsx
0 → 100644
1 | +import React, { useCallback } from 'react'; | |
2 | +import { Button, Row } from 'antd'; | |
3 | +import { useStore } from '../index'; | |
4 | +import Filters from '@/pages/order3/Common/Filter/index'; | |
5 | +import { debounce } from 'lodash'; | |
6 | + | |
7 | +export default function Filter() { | |
8 | + const { setEditModal, setParams, innerParams } = useStore(); | |
9 | + | |
10 | + const _onChange = useCallback( | |
11 | + debounce((params: any) => { | |
12 | + setParams({ ...innerParams, ...params, current: 1 }, true); | |
13 | + }, 500), | |
14 | + [innerParams], | |
15 | + ); | |
16 | + | |
17 | + return ( | |
18 | + <Row justify="space-between" style={{ marginBottom: 20 }}> | |
19 | + <div style={{ width: 200 }}> | |
20 | + <Filters | |
21 | + onFilter={(value: any) => { | |
22 | + _onChange({ | |
23 | + brandId: value?.brandId, | |
24 | + seriesId: value?.seriesId, | |
25 | + specId: value?.specId, | |
26 | + }); | |
27 | + }} | |
28 | + /> | |
29 | + </div> | |
30 | + <Button | |
31 | + type="primary" | |
32 | + onClick={() => { | |
33 | + setEditModal({visible: true, data: undefined}) | |
34 | + }} | |
35 | + > | |
36 | + 新增 | |
37 | + </Button> | |
38 | + </Row> | |
39 | + ); | |
40 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/components/List.tsx
0 → 100644
1 | +import React, { useState } from 'react'; | |
2 | +import { message, Modal, Popconfirm, Space, Table, Typography, Divider } from 'antd'; | |
3 | +import moment from 'moment'; | |
4 | +import { useStore } from '../index'; | |
5 | +import * as api from '../api'; | |
6 | +import { CapitalFlowEnum } from '../entity'; | |
7 | +import ShopAccountTable from './ShopAccountTable'; | |
8 | +import { isNil } from 'lodash'; | |
9 | + | |
10 | +const Column = Table.Column; | |
11 | + | |
12 | +export default function List() { | |
13 | + const { list, paginationConfig, loading, setLoading, setShowModal, setEdit, setEditModal } = useStore(); | |
14 | + const [visible, setVisible] = useState(false); | |
15 | + const [accounts, setAccounts] = useState<api.AccountShopItem[]>([] as api.AccountShopItem[]); | |
16 | + | |
17 | + function handleDelete(id: number) { | |
18 | + api | |
19 | + .fetchDeleteConfigApi(id) | |
20 | + .then((res) => { | |
21 | + message.success(res.result); | |
22 | + setLoading(true); | |
23 | + }) | |
24 | + .catch((e) => { | |
25 | + message.error(e.message); | |
26 | + }); | |
27 | + } | |
28 | + | |
29 | + function onShow(value: any, title: string) { | |
30 | + setShowModal({ | |
31 | + visible: true, | |
32 | + title, | |
33 | + data: value, | |
34 | + }); | |
35 | + } | |
36 | + return ( | |
37 | + <div> | |
38 | + <Table scroll={{ x: 1400 }} dataSource={list} pagination={paginationConfig} rowKey="id" loading={loading}> | |
39 | + <Column fixed="left" title="品牌" dataIndex="brandName" align="left" /> | |
40 | + <Column | |
41 | + title="适用车辆" | |
42 | + align="left" | |
43 | + dataIndex="factorySellCarSeriesVoList" | |
44 | + render={(_text, record: api.ListResult) => ( | |
45 | + <span style={{ display: 'block' }}> | |
46 | + <a onClick={() => onShow(record.carSeriesVoList, '适用车辆')}>查看</a> | |
47 | + </span> | |
48 | + )} | |
49 | + /> | |
50 | + <Column | |
51 | + title="生效时间段" | |
52 | + dataIndex="startTime" | |
53 | + align="left" | |
54 | + render={(text, record: api.ListResult) => { | |
55 | + return `${moment(record.startTime).format('YYYY.MM.DD')}~${moment(record.endTime).format('YYYY.MM.DD')}`; | |
56 | + }} | |
57 | + /> | |
58 | + <Column title="促销名称" dataIndex="sellName" align="left" /> | |
59 | + <Column title="促销金额" dataIndex="sellAmount" align="left" render={(text: any) => <span>{text + '元'}</span>} /> | |
60 | + <Column | |
61 | + title="经销商垫付给客户" | |
62 | + dataIndex="dealerAdvancePayment" | |
63 | + align="left" | |
64 | + render={(_text) => (isNil(_text) ? null : _text ? '是' : '否')} | |
65 | + /> | |
66 | + | |
67 | + <Column | |
68 | + title="挂厂家应收款" | |
69 | + dataIndex="deductionRatio" | |
70 | + align="left" | |
71 | + render={(text: any, record: api.ListResult) => | |
72 | + !!record.dealerAdvancePayment ? ( | |
73 | + <div style={{ display: 'flex', flexDirection: 'column' }}> | |
74 | + <span>扣除方式:{(record.deductionMode && api.DeductionMode[record.deductionMode]) || '--'}</span> | |
75 | + {record.deductionMode ? ( | |
76 | + <span> | |
77 | + {record.deductionMode === 1 | |
78 | + ? `扣除金额:${record.receivableDeduction ?? '--'}元` | |
79 | + : `扣除比例:${record.receivableDeduction ?? '--'}%`} | |
80 | + </span> | |
81 | + ) : null} | |
82 | + </div> | |
83 | + ) : null | |
84 | + } | |
85 | + /> | |
86 | + <Column | |
87 | + title="款项流向" | |
88 | + dataIndex="fundFlow" | |
89 | + align="left" | |
90 | + render={(text: any, record: api.ListResult) => { | |
91 | + if (!record.dealerAdvancePayment) { | |
92 | + return '--'; | |
93 | + } | |
94 | + if (record.fundFlow !== 3) { | |
95 | + return text ? CapitalFlowEnum[text] : '--'; | |
96 | + } | |
97 | + return ( | |
98 | + <Typography.Link | |
99 | + onClick={() => { | |
100 | + setVisible(true); | |
101 | + setAccounts(record.shopAccountVoList ?? []); | |
102 | + }} | |
103 | + style={{ color: '#4189FD' }} | |
104 | + > | |
105 | + {CapitalFlowEnum[text]} | |
106 | + </Typography.Link> | |
107 | + ); | |
108 | + }} | |
109 | + /> | |
110 | + <Column | |
111 | + title="操作" | |
112 | + align="left" | |
113 | + // width={200} | |
114 | + render={(text, record: api.ListResult) => ( | |
115 | + <Space size={0} wrap split={<Divider type="vertical" />}> | |
116 | + <Popconfirm title="是否删除此项?" onConfirm={() => handleDelete(record.id || 0)} okText="确定" cancelText="取消"> | |
117 | + <Typography.Link style={{ color: '#999' }}>删除</Typography.Link> | |
118 | + </Popconfirm> | |
119 | + <Typography.Link | |
120 | + onClick={() => { | |
121 | + setEditModal({ visible: true, data: record }); | |
122 | + setEdit(false); | |
123 | + }} | |
124 | + style={{ color: '#4189FD' }} | |
125 | + > | |
126 | + 复制 | |
127 | + </Typography.Link> | |
128 | + <Typography.Link | |
129 | + onClick={() => { | |
130 | + setEditModal({ visible: true, data: record }); | |
131 | + setEdit(true); | |
132 | + }} | |
133 | + style={{ color: '#4189FD' }} | |
134 | + > | |
135 | + 编辑 | |
136 | + </Typography.Link> | |
137 | + </Space> | |
138 | + )} | |
139 | + /> | |
140 | + </Table> | |
141 | + <Modal width="60%" title="资金账户列表" open={visible} onCancel={() => setVisible(false)} destroyOnClose footer={null}> | |
142 | + <ShopAccountTable data={accounts} /> | |
143 | + </Modal> | |
144 | + </div> | |
145 | + ); | |
146 | +} | ... | ... |
src/pages/order3/DirectCarPromotion/components/ShopAccountTable.tsx
0 → 100644
1 | +import React, { useState } from 'react'; | |
2 | +import { Button, Modal, List, Table } from 'antd'; | |
3 | +import { AccountShopItem, ShopVo } from '../api'; | |
4 | + | |
5 | +const { Column } = Table; | |
6 | + | |
7 | +// 门店资金账户弹框 | |
8 | +interface ShopAccountTableProps { | |
9 | + data: AccountShopItem[]; | |
10 | +} | |
11 | + | |
12 | +const ShopAccountTable = ({ data }: ShopAccountTableProps) => { | |
13 | + const [shopData, setShopData] = useState<ShopVo[]>([]); | |
14 | + const [visible, setVisible] = useState(false); | |
15 | + | |
16 | + return ( | |
17 | + <> | |
18 | + <Table dataSource={data} pagination={false} rowKey="accountId" loading={false}> | |
19 | + <Column title="资金账户" dataIndex="accountName" /> | |
20 | + <Column | |
21 | + title="授权门店" | |
22 | + dataIndex="shopList" | |
23 | + render={(val: ShopVo[], record: AccountShopItem) => { | |
24 | + let displayName = ''; | |
25 | + if (val.length > 2) { | |
26 | + displayName = val | |
27 | + .slice(0, 2) | |
28 | + .map((item) => item.shopName) | |
29 | + .join(','); | |
30 | + displayName = `${displayName}等${val.length}个门店`; | |
31 | + } else { | |
32 | + displayName = val.map((item) => item.shopName).join(','); | |
33 | + } | |
34 | + return ( | |
35 | + <span | |
36 | + style={{ color: '#4189FD', cursor: 'pointer' }} | |
37 | + onClick={() => { | |
38 | + setShopData(record.shopList || []); | |
39 | + setVisible(true); | |
40 | + }} | |
41 | + > | |
42 | + {displayName} | |
43 | + </span> | |
44 | + ); | |
45 | + }} | |
46 | + /> | |
47 | + </Table> | |
48 | + <Modal destroyOnClose title="适用门店" open={visible} maskClosable={false} onCancel={() => setVisible(false)} footer={[]}> | |
49 | + <List | |
50 | + size="large" | |
51 | + style={{ maxHeight: '300px', overflow: 'scroll' }} | |
52 | + dataSource={shopData} | |
53 | + renderItem={(item: any) => <List.Item style={{ justifyContent: 'center' }}>{item.shopName}</List.Item>} | |
54 | + /> | |
55 | + <div | |
56 | + style={{ | |
57 | + textAlign: 'center', | |
58 | + marginTop: 12, | |
59 | + height: 32, | |
60 | + lineHeight: '32px', | |
61 | + }} | |
62 | + > | |
63 | + <Button type="primary" onClick={() => setVisible(false)}> | |
64 | + 返回 | |
65 | + </Button> | |
66 | + </div> | |
67 | + </Modal> | |
68 | + </> | |
69 | + ); | |
70 | +}; | |
71 | + | |
72 | +export default ShopAccountTable; | ... | ... |
src/pages/order3/DirectCarPromotion/entity.ts
0 → 100644
src/pages/order3/DirectCarPromotion/index.tsx
0 → 100644
1 | +import React from 'react'; | |
2 | +import { Card } from 'antd'; | |
3 | +import { PageHeaderWrapper } from '@ant-design/pro-layout'; | |
4 | +import Filter from './components/Filter'; | |
5 | +import List from './components/List'; | |
6 | +import { createStore } from '@/hooks/moz'; | |
7 | +import store from './store'; | |
8 | +import EditModal from './components/EditModal'; | |
9 | +import CarShopModal from './components/CarShopModal'; | |
10 | +import AccountModal from '@/pages/order3/DirectCarPromotion/components/AccountModal'; | |
11 | + | |
12 | +export const { Provider, useStore } = createStore(store); | |
13 | + | |
14 | +function ManufactureCollection() { | |
15 | + return ( | |
16 | + <PageHeaderWrapper title="直营车厂家促销设置"> | |
17 | + <Card> | |
18 | + <Filter /> | |
19 | + <List /> | |
20 | + </Card> | |
21 | + <EditModal /> | |
22 | + <CarShopModal /> | |
23 | + <AccountModal /> | |
24 | + </PageHeaderWrapper> | |
25 | + ); | |
26 | +} | |
27 | + | |
28 | +export default () => ( | |
29 | + <Provider> | |
30 | + <ManufactureCollection /> | |
31 | + </Provider> | |
32 | +); | ... | ... |
src/pages/order3/DirectCarPromotion/store.ts
0 → 100644
1 | +import { useState } from 'react'; | |
2 | +import usePagination from '@/hooks/usePagination'; | |
3 | +import useInitial from '@/hooks/useInitail'; | |
4 | +import * as API from './api'; | |
5 | +import { getAccountApi } from '@/pages/finance/SpecialAccount/DeductAccount/api'; | |
6 | +import { CapitalAccountTypeEnum } from '@/pages/finance/entitys'; | |
7 | + | |
8 | + | |
9 | +interface Show { | |
10 | + title?: string; | |
11 | + visible?: boolean; | |
12 | + data?: any; | |
13 | +} | |
14 | + | |
15 | +interface AccountModal { | |
16 | + title?: string; | |
17 | + data?: API.AccountShopItem; | |
18 | + visible?: boolean; | |
19 | +} | |
20 | + | |
21 | +interface EditModal { | |
22 | + visible: boolean; | |
23 | + data?: API.ListResult; | |
24 | +} | |
25 | + | |
26 | +export default function useStore() { | |
27 | + const { list, paginationConfig, setParams, innerParams, setLoading, loading } = usePagination(API.getListApi, {}); | |
28 | + const [seriesVisiable, setSeriesVisiable] = useState<boolean>(false); | |
29 | + const { data: brandData } = useInitial(API.getOnsaleBrandApi, [], {}); | |
30 | + const [timer, setTimer] = useState<any>(); | |
31 | + const [result, setResult] = useState<[][]>([]); | |
32 | + const [showModal, setShowModal] = useState<Show>({ title: '', visible: false, data: [] }); | |
33 | + const [accountModal, setAccountModal] = useState<AccountModal>({title: '新增', visible: false}); | |
34 | + const [accountShopList, setAccountShopList] = useState<API.AccountShopItem[]>([]); | |
35 | + const { list: accountList } = usePagination(getAccountApi, { current: 1, pageSize: 1000, type: CapitalAccountTypeEnum['银行账户'] }, {}); | |
36 | + const [edit, setEdit] = useState<boolean>(false); | |
37 | + const [editModal, setEditModal] = useState<EditModal>({visible: false, data: undefined}); | |
38 | + | |
39 | + return { | |
40 | + list, | |
41 | + paginationConfig, | |
42 | + seriesVisiable, | |
43 | + setSeriesVisiable, | |
44 | + setParams, | |
45 | + innerParams, | |
46 | + data: brandData, | |
47 | + timer, | |
48 | + loading, | |
49 | + setLoading, | |
50 | + result, | |
51 | + setResult, | |
52 | + showModal, | |
53 | + setShowModal, | |
54 | + accountModal, | |
55 | + setAccountModal, | |
56 | + accountShopList, | |
57 | + setAccountShopList, | |
58 | + accountList, | |
59 | + edit, | |
60 | + setEdit, | |
61 | + editModal, | |
62 | + setEditModal | |
63 | + }; | |
64 | +} | ... | ... |
src/pages/order3/ManufactureCollection/components/EditModal.tsx
... | ... | @@ -209,7 +209,7 @@ export default function EditModal() { |
209 | 209 | ]} |
210 | 210 | > |
211 | 211 | <div id="modal"> |
212 | - <Form form={form} labelAlign="left" labelCol={{ span: 5 }} wrapperCol={{ span: 19 }} onFinish={handleSave} preserve={false}> | |
212 | + <Form form={form} labelAlign="left" onFinish={handleSave} preserve={false}> | |
213 | 213 | <Form.Item |
214 | 214 | name="optionalAuth" |
215 | 215 | label="授权车辆范围" | ... | ... |