Commit 0ce641f8a794e6c6dbd0320fc56f053fa4de1877
Merge branch 'Shinner-sale-task' into 'master'
零售任务分配设置 See merge request !255
Showing
23 changed files
with
2382 additions
and
11 deletions
config/routers/order3.ts
... | ... | @@ -50,14 +50,14 @@ export default [ |
50 | 50 | component: "./order3/SpcManagement", |
51 | 51 | }, |
52 | 52 | { |
53 | - //零售任务手动调整 | |
54 | - path: "/order3/retailTask", | |
55 | - component: "./order3/RetailTask", | |
53 | + //零售任务分配 | |
54 | + path: "/order3/saleTask", | |
55 | + component: "./order3/SaleTask", | |
56 | 56 | }, |
57 | 57 | { |
58 | - //零售任务自动分配 | |
59 | - path: "/order3/RetailManualAdjust", | |
60 | - component: "./order3/RetailManualAdjust", | |
58 | + //零售任务编辑 | |
59 | + path: "/order3/saleTask/edit", | |
60 | + component: "./order3/SaleTask/subpages/TaskEdit", | |
61 | 61 | }, |
62 | 62 | { |
63 | 63 | //攻坚车型任务手动分配 | ... | ... |
src/pages/cas/GrossProfit/subpages/Add/components/AdviserTable.tsx
src/pages/cas/GrossProfit/subpages/Add/components/BizTypeTable.tsx
... | ... | @@ -29,7 +29,6 @@ const BizTypeTable = ({ params, showAdviserModal }: BizTypeTableProps) => { |
29 | 29 | pagination={paginationConfig} |
30 | 30 | rowKey="id" |
31 | 31 | loading={loading} |
32 | - childrenColumnName="other" | |
33 | 32 | > |
34 | 33 | <Column title="业务类型" dataIndex="name" /> |
35 | 34 | <Column title="毛利目标(万元)" dataIndex="doubleGrossProfitTarget" /> | ... | ... |
src/pages/cas/GrossProfit/subpages/Add/components/GPConfirm.tsx
... | ... | @@ -192,7 +192,6 @@ export default function GPConfirm({ settingId }: GPConfirmProps) { |
192 | 192 | pagination={paginationConfig} |
193 | 193 | rowKey="id" |
194 | 194 | loading={loading} |
195 | - childrenColumnName="other" | |
196 | 195 | > |
197 | 196 | <Column title={QueryTypeObj[queryType]} dataIndex="name" /> |
198 | 197 | <Column title="毛利目标(万元)" dataIndex="doubleGrossProfitTarget" /> | ... | ... |
src/pages/cas/GrossProfit/subpages/Add/components/GPSetting.tsx
... | ... | @@ -209,7 +209,6 @@ const GPSetting = ({ settingId, filterMonth, setCurrStep }: GPSettingProps) => { |
209 | 209 | pagination={false} |
210 | 210 | rowKey="id" |
211 | 211 | loading={loading} |
212 | - childrenColumnName="other" | |
213 | 212 | > |
214 | 213 | <Column title="门店" dataIndex="shopName" /> |
215 | 214 | <Column title="净利目标(万元)" dataIndex="doubleNetProfitTarget" /> | ... | ... |
src/pages/cas/GrossProfit/subpages/Add/components/ShopTable.tsx
... | ... | @@ -28,7 +28,6 @@ const ShopTable = ({ params, showAdviserModal }: ShopTableProps) => { |
28 | 28 | pagination={paginationConfig} |
29 | 29 | rowKey="id" |
30 | 30 | loading={loading} |
31 | - childrenColumnName="other" | |
32 | 31 | > |
33 | 32 | <Column title="门店" dataIndex="name" /> |
34 | 33 | <Column title="毛利目标(万元)" dataIndex="doubleGrossProfitTarget" /> | ... | ... |
src/pages/order3/SaleTask/api.ts
0 → 100644
1 | +import { http } from "@/typing/http"; | |
2 | +import request from "@/utils/request"; | |
3 | +import { FVM_HOST, ORDER3_HOST } from "@/utils/host"; | |
4 | + | |
5 | +type PromiseResp<T> = http.PromiseResp<T>; | |
6 | + | |
7 | +export interface GetSaleTaskApiReq { | |
8 | + taskDate?: number; | |
9 | + shopName?: string; | |
10 | +} | |
11 | + | |
12 | +export interface GetSaleTaskApiRes { | |
13 | + id: number; | |
14 | + totalTaskCount: number; | |
15 | + shopTaskList: ShopTaskItem[]; | |
16 | + year: number; | |
17 | + month: number; | |
18 | + canModified: boolean; // 是否可以编辑 | |
19 | + revoke: boolean; // 是否可以撤销 | |
20 | + newEnergyTaskCount: number; | |
21 | + fuelVehicleTaskCount: number; | |
22 | + clueDealTaskCount: number; | |
23 | + approvalNumber: string; | |
24 | + tackCarTaskCount: number; | |
25 | + testDriveTaskCount: number; | |
26 | + seriesTaskCount: number; | |
27 | + vehicleGrossProfitTask: number; | |
28 | +} | |
29 | + | |
30 | +export interface SeriesTaskItem { | |
31 | + brandId: number; | |
32 | + brandName: string; | |
33 | + seriesId: number; | |
34 | + seriesName: string; | |
35 | + newEnergy: boolean; | |
36 | + taskCount: number; | |
37 | + id?: number; | |
38 | +} | |
39 | + | |
40 | +export interface StaffTaskItem { | |
41 | + id?: number; | |
42 | + staffId: number; | |
43 | + staffName: string; | |
44 | + taskCount: number; | |
45 | + clueDealTaskCount: number; | |
46 | + regularMonth: number; | |
47 | + addedValueTask: number; | |
48 | + seriesTaskList: SeriesTaskItem[]; | |
49 | + newEnergyTaskCount: number; | |
50 | + fuelVehicleTaskCount: number; | |
51 | + tackCarTaskCount: number; | |
52 | + testDriveTaskCount: number; | |
53 | + seriesTaskCount: number; | |
54 | + vehicleGrossProfitTask: number; | |
55 | +} | |
56 | + | |
57 | +export interface ShopTaskItem { | |
58 | + id?: number; | |
59 | + shopId: number; | |
60 | + shopName: string; | |
61 | + taskCount: number; | |
62 | + clueDealTaskCount: number; | |
63 | + clueDealTaskRate: number; | |
64 | + staffTaskList: StaffTaskItem[]; | |
65 | + addedValueTask: number; | |
66 | + seriesTaskList: SeriesTaskItem[]; | |
67 | + newEnergyTaskCount: number; | |
68 | + fuelVehicleTaskCount: number; | |
69 | + tackCarTaskCount: number; | |
70 | + testDriveTaskCount: number; | |
71 | + seriesTaskCount: number; | |
72 | + vehicleGrossProfitTask: number; | |
73 | + taskId?: number; | |
74 | +} | |
75 | + | |
76 | +/** 月度零售任务列表 */ | |
77 | +export function getSaleTaskApi( | |
78 | + params: GetSaleTaskApiReq | |
79 | +): PromiseResp<GetSaleTaskApiRes> { | |
80 | + return request.get(`${ORDER3_HOST}/erp/sales/task/detail`, { params }); | |
81 | +} | |
82 | + | |
83 | +export interface GetShopSaleTaskReq { | |
84 | + shopId: number; | |
85 | + taskDate: number; | |
86 | +} | |
87 | + | |
88 | +/** 门店零售任务详情 */ | |
89 | +export function getShopSaleTask( | |
90 | + params: GetShopSaleTaskReq | |
91 | +): PromiseResp<ShopTaskItem> { | |
92 | + return request.get(`${ORDER3_HOST}/erp/sales/task/detail/one`, { params }); | |
93 | +} | |
94 | + | |
95 | +/** 保存门店零售任务 */ | |
96 | +export function saveShopSaleTask(params: ShopTaskItem): PromiseResp<boolean> { | |
97 | + return request.post(`${ORDER3_HOST}/erp/sales/task/manual/assign`, params); | |
98 | +} | |
99 | + | |
100 | +/** 撤销审批零售任务 */ | |
101 | +export function cancelSaleTask(params: { id: number }): PromiseResp<boolean> { | |
102 | + return request.post(`${ORDER3_HOST}/erp/sales/task/cancel/approve`, params, { | |
103 | + contentType: "form-urlencoded", | |
104 | + }); | |
105 | +} | |
106 | + | |
107 | +/** 提交审批零售任务 */ | |
108 | +export function submitSaleTask(params: { id: number }): PromiseResp<boolean> { | |
109 | + return request.post(`${ORDER3_HOST}/erp/sales/task/submit`, params); | |
110 | +} | |
111 | + | |
112 | +export interface BrandItem { | |
113 | + id: number; | |
114 | + initial: string; | |
115 | + name: string; | |
116 | + thumbnail: string; | |
117 | +} | |
118 | + | |
119 | +/** 门店下面品牌列表 */ | |
120 | +export function getBrands(params: { | |
121 | + shopId: number; | |
122 | +}): PromiseResp<BrandItem[]> { | |
123 | + return request.get(`${FVM_HOST}/erp/putaway/vehicle/support/brand`, { | |
124 | + params, | |
125 | + }); | |
126 | +} | |
127 | + | |
128 | +export interface SeriesItem { | |
129 | + id: number; | |
130 | + name: string; | |
131 | + newEnergy: boolean; | |
132 | + thumbnail: string; | |
133 | +} | |
134 | + | |
135 | +/** 门店某品牌下车系列表 */ | |
136 | +export function getSeries(params: { | |
137 | + shopId: number; | |
138 | + brandId: number; | |
139 | +}): PromiseResp<SeriesItem[]> { | |
140 | + return request.get(`${FVM_HOST}/erp/putaway/vehicle/support/series`, { | |
141 | + params, | |
142 | + }); | |
143 | +} | |
144 | + | |
145 | +export interface PreviewTaskReq { | |
146 | + shopId?: number; | |
147 | + firstManageId?: number; | |
148 | + secondManageId?: number; | |
149 | + thirdManageId?: number; | |
150 | + taskId?: number; | |
151 | + orderTaskApprovalType: number; | |
152 | + id: number; | |
153 | + token?: string; | |
154 | +} | |
155 | + | |
156 | +export interface TaskListItem { | |
157 | + id: number; | |
158 | + dataId: number; | |
159 | + dataName: string; | |
160 | + beforeTaskCount: number; | |
161 | + afterTaskCount: number; | |
162 | + beforeClueDealTaskCount: number; | |
163 | + afterClueDealTaskCount: number; | |
164 | + beforeNewEnergyTaskCount: number; | |
165 | + afterNewEnergyTaskCount: number; | |
166 | + beforeFuelVehicleTaskCount: number; | |
167 | + afterFuelVehicleTaskCount: number; | |
168 | + beforeTackCarTaskCount: number; | |
169 | + afterTackCarTaskCount: number; | |
170 | + beforeTestDriveTaskCount: number; | |
171 | + afterTestDriveTaskCount: number; | |
172 | + taskCountModified: boolean; | |
173 | + clueDealTaskCountModified: boolean; | |
174 | + newEnergyTaskCountModified: boolean; | |
175 | + fuelVehicleTaskCountModified: boolean; | |
176 | + tackCarTaskCountModified: boolean; | |
177 | + testDriveTaskCountModified: boolean; | |
178 | + seriesTaskCount: number; | |
179 | + newEnergy: boolean; | |
180 | +} | |
181 | + | |
182 | +export interface PreviewTaskRes { | |
183 | + year: number; | |
184 | + month: number; | |
185 | + remark: string; | |
186 | + attachmentList: string[]; | |
187 | + taskList: TaskListItem[]; | |
188 | +} | |
189 | + | |
190 | +/** 预览任务 */ | |
191 | +export function previewTask(params: PreviewTaskReq): PromiseResp<PreviewTaskRes> { | |
192 | + return request.get(`${ORDER3_HOST}/erp/sales/task/approve/info`, { | |
193 | + params, | |
194 | + }); | |
195 | +} | ... | ... |
src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx
0 → 100644
1 | +import React from "react"; | |
2 | +import { Table } from "antd"; | |
3 | +import { observer } from "mobx-react-lite"; | |
4 | +import * as API from "../api"; | |
5 | +import useInitial from "@/hooks/useInitail"; | |
6 | +import ModifiedTableCell from "./ModifiedTableCell"; | |
7 | + | |
8 | +const { Column } = Table; | |
9 | + | |
10 | +// 查看销顾任务弹框 | |
11 | +interface AdviserTaskPreviewProps { | |
12 | + params: any; // API.PreviewTaskReq | |
13 | + showSeriesModal: (record: API.TaskListItem) => void; | |
14 | +} | |
15 | + | |
16 | +const AdviserTaskPreview = ({ | |
17 | + params, | |
18 | + showSeriesModal, | |
19 | +}: AdviserTaskPreviewProps) => { | |
20 | + const { data, loading } = useInitial<API.PreviewTaskRes, API.PreviewTaskReq>( | |
21 | + API.previewTask, | |
22 | + {} as API.PreviewTaskRes, | |
23 | + params | |
24 | + ); | |
25 | + | |
26 | + // 查看车系任务 | |
27 | + const handlePreviewSeriesTask = (record: API.TaskListItem) => { | |
28 | + if (record.seriesTaskCount === 0) { | |
29 | + return; | |
30 | + } | |
31 | + showSeriesModal(record); | |
32 | + }; | |
33 | + | |
34 | + return ( | |
35 | + <Table | |
36 | + rowKey="dataId" | |
37 | + loading={loading} | |
38 | + dataSource={data.taskList} | |
39 | + pagination={false} | |
40 | + scroll={{ y: 450 }} | |
41 | + > | |
42 | + <Column title="姓名" dataIndex="dataName" /> | |
43 | + <Column | |
44 | + title="零售任务(台)" | |
45 | + dataIndex="taskCount" | |
46 | + render={(text: string, record: API.TaskListItem) => { | |
47 | + return ModifiedTableCell(record, "taskCount"); | |
48 | + }} | |
49 | + /> | |
50 | + <Column | |
51 | + title="新能源车任务(台)" | |
52 | + dataIndex="newEnergyTaskCount" | |
53 | + render={(text: string, record: API.TaskListItem) => { | |
54 | + return ModifiedTableCell(record, "newEnergyTaskCount"); | |
55 | + }} | |
56 | + /> | |
57 | + <Column | |
58 | + title="传统燃油车任务(台)" | |
59 | + dataIndex="fuelVehicleTaskCount" | |
60 | + render={(text: string, record: API.TaskListItem) => { | |
61 | + return ModifiedTableCell(record, "fuelVehicleTaskCount"); | |
62 | + }} | |
63 | + /> | |
64 | + <Column | |
65 | + title="车辆毛利任务(元)" | |
66 | + dataIndex="vehicleGrossProfitTask" | |
67 | + render={(text: string, record: API.TaskListItem) => { | |
68 | + return ModifiedTableCell(record, "vehicleGrossProfitTask"); | |
69 | + }} | |
70 | + /> | |
71 | + <Column | |
72 | + title="线索到店零售台数(台)" | |
73 | + dataIndex="clueDealTaskCount" | |
74 | + render={(text: string, record: API.TaskListItem) => { | |
75 | + return ModifiedTableCell(record, "clueDealTaskCount"); | |
76 | + }} | |
77 | + /> | |
78 | + <Column | |
79 | + title="首客试驾成交任务数(台)" | |
80 | + dataIndex="testDriveTaskCount" | |
81 | + render={(text: string, record: API.TaskListItem) => { | |
82 | + return ModifiedTableCell(record, "testDriveTaskCount"); | |
83 | + }} | |
84 | + /> | |
85 | + <Column | |
86 | + title="攻坚车任务数(台)" | |
87 | + dataIndex="tackCarTaskCount" | |
88 | + render={(text: string, record: API.TaskListItem) => { | |
89 | + return ModifiedTableCell(record, "tackCarTaskCount"); | |
90 | + }} | |
91 | + /> | |
92 | + <Column | |
93 | + title="车系任务数(台)" | |
94 | + dataIndex="seriesTaskCount" | |
95 | + render={(text: string, record: API.TaskListItem) => { | |
96 | + if (record.dataId === -999) return text; | |
97 | + return <a onClick={() => handlePreviewSeriesTask(record)}>{text}</a>; | |
98 | + }} | |
99 | + /> | |
100 | + </Table> | |
101 | + ); | |
102 | +}; | |
103 | + | |
104 | +export default observer(AdviserTaskPreview); | ... | ... |
src/pages/order3/SaleTask/components/ApprovalProgressModal.tsx
0 → 100644
1 | +import ApprovalProgress from "@/components/ApprovalProgress"; | |
2 | +import { Button, Modal } from "antd"; | |
3 | +import React from "react"; | |
4 | +import { useStore } from "../store"; | |
5 | + | |
6 | +export default function ApprovalProgressModal() { | |
7 | + const { approvalProgressModalInfo, setApprovalProgressModalInfo } = | |
8 | + useStore(); | |
9 | + | |
10 | + const onCancel = () => setApprovalProgressModalInfo({ visible: false }); | |
11 | + | |
12 | + return ( | |
13 | + <Modal | |
14 | + title={`${ | |
15 | + approvalProgressModalInfo.title | |
16 | + ? approvalProgressModalInfo.title + "-" | |
17 | + : "" | |
18 | + }审批进度`} | |
19 | + open={approvalProgressModalInfo.visible} | |
20 | + onCancel={onCancel} | |
21 | + maskClosable={false} | |
22 | + destroyOnClose | |
23 | + footer={[ | |
24 | + <Button key="cancel" onClick={onCancel}> | |
25 | + 关闭 | |
26 | + </Button>, | |
27 | + ]} | |
28 | + > | |
29 | + <ApprovalProgress orderNo={approvalProgressModalInfo.orderNo} /> | |
30 | + </Modal> | |
31 | + ); | |
32 | +} | ... | ... |
src/pages/order3/SaleTask/components/EntryTaskPreview.tsx
0 → 100644
1 | +import React, { useState } from "react"; | |
2 | +import { Card, Radio, RadioChangeEvent, Row, Table } from "antd"; | |
3 | +import { observer } from "mobx-react-lite"; | |
4 | +import * as API from "../api"; | |
5 | +import { OrderTaskApprovalType } from "../entity"; | |
6 | +import useInitial from "@/hooks/useInitail"; | |
7 | +import ModifiedTableCell from "./ModifiedTableCell"; | |
8 | + | |
9 | +const { Column } = Table; | |
10 | +const RadioButton = Radio.Button; | |
11 | +const RadioGroup = Radio.Group; | |
12 | + | |
13 | +// 预览任务入口弹框 | |
14 | +interface EntryTaskPreviewProps { | |
15 | + params: any; // API.PreviewTaskReq | |
16 | + showAdviserModal: ( | |
17 | + record: API.TaskListItem, | |
18 | + type: OrderTaskApprovalType | |
19 | + ) => void; | |
20 | + showSeriesModal: ( | |
21 | + record: API.TaskListItem, | |
22 | + type: OrderTaskApprovalType | |
23 | + ) => void; | |
24 | +} | |
25 | + | |
26 | +const EntryTaskPreview = ({ | |
27 | + params, | |
28 | + showAdviserModal, | |
29 | + showSeriesModal, | |
30 | +}: EntryTaskPreviewProps) => { | |
31 | + const [type, setType] = useState(OrderTaskApprovalType.门店维度); | |
32 | + | |
33 | + const { data, loading, setParams } = useInitial< | |
34 | + API.PreviewTaskRes, | |
35 | + API.PreviewTaskReq | |
36 | + >(API.previewTask, {} as API.PreviewTaskRes, params); | |
37 | + | |
38 | + const handleChangeType = (e: RadioChangeEvent) => { | |
39 | + const value = e.target.value; | |
40 | + if (value === 99) { | |
41 | + setType(OrderTaskApprovalType.新车一级管理维度); | |
42 | + setParams( | |
43 | + // @ts-ignore | |
44 | + { orderTaskApprovalType: OrderTaskApprovalType.新车一级管理维度 }, | |
45 | + true | |
46 | + ); | |
47 | + return; | |
48 | + } | |
49 | + setType(value); | |
50 | + // @ts-ignore | |
51 | + setParams({ orderTaskApprovalType: value }, true); | |
52 | + }; | |
53 | + | |
54 | + // 查看顾问任务 | |
55 | + const handlePreviewAdviserTask = (record: API.TaskListItem) => { | |
56 | + if (record.dataId === -999) { | |
57 | + return; | |
58 | + } | |
59 | + showAdviserModal(record, type); | |
60 | + }; | |
61 | + | |
62 | + // 查看车系任务 | |
63 | + const handlePreviewSeriesTask = (record: API.TaskListItem) => { | |
64 | + if (record.seriesTaskCount === 0) { | |
65 | + return; | |
66 | + } | |
67 | + showSeriesModal(record, type); | |
68 | + }; | |
69 | + | |
70 | + return ( | |
71 | + <Card | |
72 | + title={ | |
73 | + <Row align="middle" justify="start"> | |
74 | + <RadioGroup onChange={handleChangeType} value={type}> | |
75 | + <RadioButton value={OrderTaskApprovalType.门店维度}> | |
76 | + 门店 | |
77 | + </RadioButton> | |
78 | + <RadioButton value={OrderTaskApprovalType.销售顾问维度}> | |
79 | + 销顾 | |
80 | + </RadioButton> | |
81 | + <RadioButton value={99}>销售管理</RadioButton> | |
82 | + </RadioGroup> | |
83 | + {type !== OrderTaskApprovalType.门店维度 && | |
84 | + type !== OrderTaskApprovalType.销售顾问维度 && ( | |
85 | + <RadioGroup | |
86 | + onChange={handleChangeType} | |
87 | + value={type} | |
88 | + style={{ marginLeft: 20 }} | |
89 | + > | |
90 | + <RadioButton value={OrderTaskApprovalType.新车一级管理维度}> | |
91 | + 销售一级管理 | |
92 | + </RadioButton> | |
93 | + <RadioButton value={OrderTaskApprovalType.新车二级管理维度}> | |
94 | + 销售二级管理 | |
95 | + </RadioButton> | |
96 | + <RadioButton value={OrderTaskApprovalType.新车三级管理维度}> | |
97 | + 销售三级管理 | |
98 | + </RadioButton> | |
99 | + </RadioGroup> | |
100 | + )} | |
101 | + </Row> | |
102 | + } | |
103 | + > | |
104 | + <Table | |
105 | + rowKey="dataId" | |
106 | + loading={loading} | |
107 | + dataSource={data.taskList} | |
108 | + pagination={false} | |
109 | + scroll={{ y: 450 }} | |
110 | + > | |
111 | + <Column | |
112 | + title={type === OrderTaskApprovalType.门店维度 ? "门店" : "姓名"} | |
113 | + dataIndex="dataName" | |
114 | + filterSearch | |
115 | + onFilter={( | |
116 | + value: string | number | boolean, | |
117 | + record: API.TaskListItem | |
118 | + ) => { | |
119 | + return record.dataName.startsWith(value.toString()); | |
120 | + }} | |
121 | + /> | |
122 | + <Column | |
123 | + title="零售任务(台)" | |
124 | + dataIndex="taskCount" | |
125 | + render={(text: string, record: API.TaskListItem) => { | |
126 | + return ModifiedTableCell(record, "taskCount"); | |
127 | + }} | |
128 | + /> | |
129 | + <Column | |
130 | + title="新能源车任务(台)" | |
131 | + dataIndex="newEnergyTaskCount" | |
132 | + render={(text: string, record: API.TaskListItem) => { | |
133 | + return ModifiedTableCell(record, "newEnergyTaskCount"); | |
134 | + }} | |
135 | + /> | |
136 | + <Column | |
137 | + title="传统燃油车任务(台)" | |
138 | + dataIndex="fuelVehicleTaskCount" | |
139 | + render={(text: string, record: API.TaskListItem) => { | |
140 | + return ModifiedTableCell(record, "fuelVehicleTaskCount"); | |
141 | + }} | |
142 | + /> | |
143 | + <Column | |
144 | + title="车辆毛利任务(元)" | |
145 | + dataIndex="vehicleGrossProfitTask" | |
146 | + render={(text: string, record: API.TaskListItem) => { | |
147 | + return ModifiedTableCell(record, "vehicleGrossProfitTask"); | |
148 | + }} | |
149 | + /> | |
150 | + <Column | |
151 | + title="线索到店零售台数(台)" | |
152 | + dataIndex="clueDealTaskCount" | |
153 | + render={(text: string, record: API.TaskListItem) => { | |
154 | + return ModifiedTableCell(record, "clueDealTaskCount"); | |
155 | + }} | |
156 | + /> | |
157 | + <Column | |
158 | + title="首客试驾成交任务数(台)" | |
159 | + dataIndex="testDriveTaskCount" | |
160 | + render={(text: string, record: API.TaskListItem) => { | |
161 | + return ModifiedTableCell(record, "testDriveTaskCount"); | |
162 | + }} | |
163 | + /> | |
164 | + <Column | |
165 | + title="攻坚车任务数(台)" | |
166 | + dataIndex="tackCarTaskCount" | |
167 | + render={(text: string, record: API.TaskListItem) => { | |
168 | + return ModifiedTableCell(record, "tackCarTaskCount"); | |
169 | + }} | |
170 | + /> | |
171 | + <Column | |
172 | + title="车系任务数(台)" | |
173 | + dataIndex="seriesTaskCount" | |
174 | + render={(text: string, record: API.TaskListItem) => { | |
175 | + if (record.dataId === -999) return text; | |
176 | + return ( | |
177 | + <a onClick={() => handlePreviewSeriesTask(record)}>{text}</a> | |
178 | + ); | |
179 | + }} | |
180 | + /> | |
181 | + {type === OrderTaskApprovalType.门店维度 && ( | |
182 | + <Column | |
183 | + title="销顾" | |
184 | + render={(text: string, record: API.TaskListItem) => { | |
185 | + if (record.dataId === -999) return "-"; | |
186 | + return ( | |
187 | + <a onClick={() => handlePreviewAdviserTask(record)}>查看</a> | |
188 | + ); | |
189 | + }} | |
190 | + /> | |
191 | + )} | |
192 | + </Table> | |
193 | + </Card> | |
194 | + ); | |
195 | +}; | |
196 | + | |
197 | +export default observer(EntryTaskPreview); | ... | ... |
src/pages/order3/SaleTask/components/ModifiedTableCell.tsx
0 → 100644
1 | +import React from "react"; | |
2 | +import { Row } from "antd"; | |
3 | +import * as API from "../api"; | |
4 | + | |
5 | +export default function ModifiedTableCell( | |
6 | + record: API.TaskListItem, | |
7 | + field: string | |
8 | +) { | |
9 | + const capitalized = field.charAt(0).toUpperCase() + field.slice(1); | |
10 | + const beforeName = `before${capitalized}`; | |
11 | + const afterName = `after${capitalized}`; | |
12 | + const isModifiedName = `${field}Modified`; | |
13 | + const isModified = record[isModifiedName]; | |
14 | + return ( | |
15 | + <Row align="middle" justify="center"> | |
16 | + {!isModified ? ( | |
17 | + <span>{record[beforeName]}</span> | |
18 | + ) : ( | |
19 | + <> | |
20 | + <span style={{ color: "#CCCCCC" }}>{record[beforeName]}</span> | |
21 | + <span style={{ color: "#F4333C " }}>→</span> | |
22 | + <span style={{ color: "#858CA0" }}>{record[afterName]}</span> | |
23 | + </> | |
24 | + )} | |
25 | + </Row> | |
26 | + ); | |
27 | +} | ... | ... |
src/pages/order3/SaleTask/components/SeriesTaskPreview.tsx
0 → 100644
1 | +import React from "react"; | |
2 | +import { Row, Table, Tag } from "antd"; | |
3 | +import { observer } from "mobx-react-lite"; | |
4 | +import * as API from "../api"; | |
5 | +import useInitial from "@/hooks/useInitail"; | |
6 | + | |
7 | +const { Column } = Table; | |
8 | + | |
9 | +// 查看车系任务弹框 | |
10 | +interface SeriesTaskPreviewProps { | |
11 | + params: any; // API.PreviewTaskReq | |
12 | +} | |
13 | + | |
14 | +const SeriesTaskPreview = ({ params }: SeriesTaskPreviewProps) => { | |
15 | + const { data, loading } = useInitial<API.PreviewTaskRes, API.PreviewTaskReq>( | |
16 | + API.previewTask, | |
17 | + {} as API.PreviewTaskRes, | |
18 | + params | |
19 | + ); | |
20 | + | |
21 | + return ( | |
22 | + <Table | |
23 | + rowKey="dataId" | |
24 | + loading={loading} | |
25 | + dataSource={data.taskList} | |
26 | + pagination={false} | |
27 | + scroll={{ y: 450 }} | |
28 | + > | |
29 | + <Column | |
30 | + title="车系名称" | |
31 | + dataIndex="dataName" | |
32 | + render={(text: string, record: API.TaskListItem) => { | |
33 | + return ( | |
34 | + <Row align="middle" justify="start"> | |
35 | + <span>{text}</span> | |
36 | + {record.newEnergy && ( | |
37 | + <Tag style={{ marginBottom: 5, marginLeft: 7 }} color="green"> | |
38 | + 新能源 | |
39 | + </Tag> | |
40 | + )} | |
41 | + </Row> | |
42 | + ); | |
43 | + }} | |
44 | + /> | |
45 | + <Column title="零售任务(台)" dataIndex="seriesTaskCount" /> | |
46 | + </Table> | |
47 | + ); | |
48 | +}; | |
49 | + | |
50 | +export default observer(SeriesTaskPreview); | ... | ... |
src/pages/order3/SaleTask/entity.ts
0 → 100644
src/pages/order3/SaleTask/index.tsx
0 → 100644
1 | +import React, { useEffect, useState } from "react"; | |
2 | +import { | |
3 | + Card, | |
4 | + Table, | |
5 | + DatePicker, | |
6 | + Row, | |
7 | + message, | |
8 | + Input, | |
9 | + Button, | |
10 | + Modal, | |
11 | +} from "antd"; | |
12 | +import * as API from "./api"; | |
13 | +import { PageHeaderWrapper } from "@ant-design/pro-layout"; | |
14 | +import { history } from "umi"; | |
15 | +import moment, { Moment } from "moment"; | |
16 | +import useInitial from "@/hooks/useInitail"; | |
17 | +import { Provider, useStore } from "./store"; | |
18 | +import ApprovalProgressModal from "./components/ApprovalProgressModal"; | |
19 | +import EntryTaskPreview from "./components/EntryTaskPreview"; | |
20 | +import { OrderTaskApprovalType } from "./entity"; | |
21 | +import AdviserTaskPreview from "./components/AdviserTaskPreview"; | |
22 | +import SeriesTaskPreview from "./components/SeriesTaskPreview"; | |
23 | +import ApproveModal from "@/pages/order3/Common/ApproveModal"; | |
24 | + | |
25 | +const { Column } = Table; | |
26 | + | |
27 | +export default () => ( | |
28 | + <Provider> | |
29 | + <SaleTaskList /> | |
30 | + </Provider> | |
31 | +); | |
32 | + | |
33 | +function SaleTaskList() { | |
34 | + const { | |
35 | + isReadOnly, | |
36 | + setShopTaskItem, | |
37 | + setIsReadOnly, | |
38 | + setApprovalProgressModalInfo, | |
39 | + } = useStore(); | |
40 | + const [approveOpen, setApproveOpen] = useState(false); | |
41 | + const [targetMonth, setTargetMonth] = useState(moment()); | |
42 | + const [etpVisible, setEtpVisible] = useState(false); | |
43 | + const [previewTaskParams, setPreviewTaskParams] = useState({}); | |
44 | + const [atpVisible, setAtpVisible] = useState(false); | |
45 | + const [adviserTaskParams, setAdviserTaskParams] = useState({}); | |
46 | + const [stpVisible, setStpVisible] = useState(false); | |
47 | + const [seriesTaskParams, setSeriesTaskParams] = useState({}); | |
48 | + | |
49 | + const { data, loading, setParams } = useInitial( | |
50 | + API.getSaleTaskApi, | |
51 | + {} as API.GetSaleTaskApiRes, | |
52 | + { | |
53 | + taskDate: targetMonth.valueOf(), | |
54 | + } | |
55 | + ); | |
56 | + | |
57 | + useEffect(() => { | |
58 | + setIsReadOnly(!data.canModified); | |
59 | + }, [data]); | |
60 | + | |
61 | + const handleChangeMonth = (date: Moment | null) => { | |
62 | + setTargetMonth(moment(date)); | |
63 | + setParams({ taskDate: moment(date).valueOf() }, true); | |
64 | + }; | |
65 | + | |
66 | + const goToEditPage = (record: API.ShopTaskItem) => { | |
67 | + setShopTaskItem(record); | |
68 | + history.push({ | |
69 | + pathname: "/order3/saleTask/edit", | |
70 | + query: { | |
71 | + readOnly: isReadOnly ? "1" : "0", | |
72 | + shopId: String(record.shopId), | |
73 | + taskDate: String(targetMonth.valueOf()), | |
74 | + }, | |
75 | + }); | |
76 | + }; | |
77 | + | |
78 | + // 查看流程进度 | |
79 | + const viewProcess = () => { | |
80 | + setApprovalProgressModalInfo({ | |
81 | + visible: true, | |
82 | + title: `【${moment(targetMonth).format("YYYY-MM")}】月度零售任务审批进度`, | |
83 | + orderNo: data.approvalNumber, | |
84 | + }); | |
85 | + }; | |
86 | + | |
87 | + const cancelSaleTask = () => { | |
88 | + Modal.confirm({ | |
89 | + title: "提示", | |
90 | + content: `确认撤销【${targetMonth.format("YYYY-MM")}】零售任务审批吗?`, | |
91 | + zIndex: 1002, | |
92 | + onOk: () => { | |
93 | + API.cancelSaleTask({ | |
94 | + id: data.id, | |
95 | + }) | |
96 | + .then((res) => { | |
97 | + message.success("撤销成功"); | |
98 | + }) | |
99 | + .catch((error: any) => { | |
100 | + message.error(error.message ?? "请求失败"); | |
101 | + }) | |
102 | + .finally(() => { | |
103 | + setParams({}, true); | |
104 | + }); | |
105 | + }, | |
106 | + }); | |
107 | + }; | |
108 | + | |
109 | + const submitSaleTask = (parmas: any) => { | |
110 | + const hide = message.loading("提交中", 0); | |
111 | + API.submitSaleTask({ | |
112 | + id: data.id, | |
113 | + ...parmas, | |
114 | + }) | |
115 | + .then((res) => { | |
116 | + message.success("提交成功"); | |
117 | + }) | |
118 | + .catch((error: any) => { | |
119 | + message.error(error.message ?? "请求失败"); | |
120 | + }) | |
121 | + .finally(() => { | |
122 | + hide(); | |
123 | + setParams({}, true); | |
124 | + }); | |
125 | + }; | |
126 | + | |
127 | + const handlePreviewTask = () => { | |
128 | + const params: any = { | |
129 | + id: data.id, | |
130 | + orderTaskApprovalType: OrderTaskApprovalType.门店维度, | |
131 | + }; | |
132 | + setPreviewTaskParams(params); | |
133 | + setEtpVisible(true); | |
134 | + }; | |
135 | + | |
136 | + return ( | |
137 | + <PageHeaderWrapper title="零售任务分配"> | |
138 | + <Card> | |
139 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
140 | + <DatePicker | |
141 | + placeholder="月度" | |
142 | + style={{ width: 260 }} | |
143 | + picker="month" | |
144 | + value={targetMonth} | |
145 | + onChange={handleChangeMonth} | |
146 | + allowClear={false} | |
147 | + /> | |
148 | + <Input.Search | |
149 | + allowClear | |
150 | + placeholder="门店名称" | |
151 | + style={{ width: 260, marginLeft: 15 }} | |
152 | + onSearch={(v) => { | |
153 | + setParams({ shopName: v }, true); | |
154 | + }} | |
155 | + /> | |
156 | + </Row> | |
157 | + <Table | |
158 | + rowKey="id" | |
159 | + loading={loading} | |
160 | + dataSource={data.shopTaskList} | |
161 | + pagination={false} | |
162 | + scroll={{ y: 450 }} | |
163 | + summary={() => { | |
164 | + if (!data || !data.shopTaskList || data.shopTaskList.length <= 0) { | |
165 | + return null; | |
166 | + } | |
167 | + return ( | |
168 | + <Table.Summary fixed="bottom"> | |
169 | + <Table.Summary.Row | |
170 | + style={{ | |
171 | + background: "#FAFAFA", | |
172 | + fontSize: 18, | |
173 | + fontWeight: 500, | |
174 | + }} | |
175 | + > | |
176 | + <Table.Summary.Cell align="left" index={1}> | |
177 | + 合计 | |
178 | + </Table.Summary.Cell> | |
179 | + <Table.Summary.Cell align="left" index={2}> | |
180 | + {data.totalTaskCount} | |
181 | + </Table.Summary.Cell> | |
182 | + <Table.Summary.Cell align="left" index={3}> | |
183 | + {data.newEnergyTaskCount} | |
184 | + </Table.Summary.Cell> | |
185 | + <Table.Summary.Cell align="left" index={4}> | |
186 | + {data.fuelVehicleTaskCount} | |
187 | + </Table.Summary.Cell> | |
188 | + <Table.Summary.Cell align="left" index={5}> | |
189 | + {data.vehicleGrossProfitTask} | |
190 | + </Table.Summary.Cell> | |
191 | + <Table.Summary.Cell index={6}> | |
192 | + {data.clueDealTaskCount} | |
193 | + </Table.Summary.Cell> | |
194 | + <Table.Summary.Cell index={7}> | |
195 | + {data.testDriveTaskCount} | |
196 | + </Table.Summary.Cell> | |
197 | + <Table.Summary.Cell index={8}> | |
198 | + {data.tackCarTaskCount} | |
199 | + </Table.Summary.Cell> | |
200 | + <Table.Summary.Cell index={9}> | |
201 | + {data.seriesTaskCount} | |
202 | + </Table.Summary.Cell> | |
203 | + <Table.Summary.Cell index={10} /> | |
204 | + </Table.Summary.Row> | |
205 | + </Table.Summary> | |
206 | + ); | |
207 | + }} | |
208 | + > | |
209 | + <Column title="门店" dataIndex="shopName" /> | |
210 | + <Column title="零售任务(台)" dataIndex="taskCount" /> | |
211 | + <Column title="新能源车任务(台)" dataIndex="newEnergyTaskCount" /> | |
212 | + <Column title="传统燃油车任务(台)" dataIndex="fuelVehicleTaskCount" /> | |
213 | + <Column title="车辆毛利任务(元)" dataIndex="vehicleGrossProfitTask" /> | |
214 | + <Column title="线索到店零售台数(台)" dataIndex="clueDealTaskCount" /> | |
215 | + <Column | |
216 | + title="首客试驾成交任务数(台)" | |
217 | + dataIndex="testDriveTaskCount" | |
218 | + /> | |
219 | + <Column title="攻坚车任务数(台)" dataIndex="tackCarTaskCount" /> | |
220 | + <Column title="车系任务数(台)" dataIndex="seriesTaskCount" /> | |
221 | + <Column | |
222 | + title="操作" | |
223 | + render={(text: string, record: API.ShopTaskItem) => { | |
224 | + return ( | |
225 | + <a | |
226 | + onClick={() => { | |
227 | + goToEditPage(record); | |
228 | + }} | |
229 | + > | |
230 | + {isReadOnly ? "查看" : "编辑"} | |
231 | + </a> | |
232 | + ); | |
233 | + }} | |
234 | + /> | |
235 | + </Table> | |
236 | + {data.revoke ? ( | |
237 | + <Row align="middle" justify="center" style={{ marginTop: 50 }}> | |
238 | + <Button onClick={cancelSaleTask}>撤销</Button> | |
239 | + <Button | |
240 | + type="primary" | |
241 | + style={{ marginLeft: 10 }} | |
242 | + onClick={viewProcess} | |
243 | + > | |
244 | + 审批进度 | |
245 | + </Button> | |
246 | + <Button | |
247 | + type="primary" | |
248 | + style={{ marginLeft: 10 }} | |
249 | + onClick={handlePreviewTask} | |
250 | + > | |
251 | + 预览任务 | |
252 | + </Button> | |
253 | + </Row> | |
254 | + ) : ( | |
255 | + !isReadOnly && ( | |
256 | + <Row align="middle" justify="center" style={{ marginTop: 50 }}> | |
257 | + <Button type="primary" onClick={() => setApproveOpen(true)}> | |
258 | + 提交审批 | |
259 | + </Button> | |
260 | + </Row> | |
261 | + ) | |
262 | + )} | |
263 | + </Card> | |
264 | + <Modal | |
265 | + width={1200} | |
266 | + title="预览任务" | |
267 | + open={etpVisible} | |
268 | + onCancel={() => setEtpVisible(false)} | |
269 | + destroyOnClose | |
270 | + footer={null} | |
271 | + > | |
272 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
273 | + <DatePicker | |
274 | + placeholder="月度" | |
275 | + style={{ width: 230 }} | |
276 | + picker="month" | |
277 | + value={targetMonth} | |
278 | + allowClear={false} | |
279 | + disabled | |
280 | + /> | |
281 | + {/* <Input.Search | |
282 | + allowClear | |
283 | + placeholder="门店名称" | |
284 | + style={{ width: 263, marginLeft: 20 }} | |
285 | + onSearch={(v) => { | |
286 | + // todo setParams({ shopName: v }, true); | |
287 | + }} | |
288 | + /> */} | |
289 | + </Row> | |
290 | + <EntryTaskPreview | |
291 | + params={previewTaskParams} | |
292 | + showAdviserModal={(record, type) => { | |
293 | + const params: any = { | |
294 | + id: data.id, | |
295 | + taskId: record.id, | |
296 | + orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 | |
297 | + }; | |
298 | + switch (type) { | |
299 | + case OrderTaskApprovalType.门店维度: | |
300 | + params.shopId = record.dataId; | |
301 | + break; | |
302 | + case OrderTaskApprovalType.销售顾问维度: | |
303 | + params.staffId = record.dataId; | |
304 | + break; | |
305 | + case OrderTaskApprovalType.新车一级管理维度: | |
306 | + params.firstManageId = record.dataId; | |
307 | + break; | |
308 | + case OrderTaskApprovalType.新车二级管理维度: | |
309 | + params.secondManageId = record.dataId; | |
310 | + break; | |
311 | + case OrderTaskApprovalType.新车三级管理维度: | |
312 | + params.thirdManageId = record.dataId; | |
313 | + break; | |
314 | + default: | |
315 | + break; | |
316 | + } | |
317 | + setAdviserTaskParams(params); | |
318 | + setAtpVisible(true); | |
319 | + }} | |
320 | + showSeriesModal={(record, type) => { | |
321 | + const params: any = { | |
322 | + id: data.id, | |
323 | + taskId: record.id, | |
324 | + orderTaskApprovalType: OrderTaskApprovalType.车系, | |
325 | + }; | |
326 | + switch (type) { | |
327 | + case OrderTaskApprovalType.门店维度: | |
328 | + params.shopId = record.dataId; | |
329 | + break; | |
330 | + case OrderTaskApprovalType.销售顾问维度: | |
331 | + params.staffId = record.dataId; | |
332 | + break; | |
333 | + case OrderTaskApprovalType.新车一级管理维度: | |
334 | + params.firstManageId = record.dataId; | |
335 | + break; | |
336 | + case OrderTaskApprovalType.新车二级管理维度: | |
337 | + params.secondManageId = record.dataId; | |
338 | + break; | |
339 | + case OrderTaskApprovalType.新车三级管理维度: | |
340 | + params.thirdManageId = record.dataId; | |
341 | + break; | |
342 | + default: | |
343 | + break; | |
344 | + } | |
345 | + setSeriesTaskParams(params); | |
346 | + setStpVisible(true); | |
347 | + }} | |
348 | + /> | |
349 | + </Modal> | |
350 | + <Modal | |
351 | + width={1200} | |
352 | + title="销顾任务" | |
353 | + open={atpVisible} | |
354 | + onCancel={() => setAtpVisible(false)} | |
355 | + destroyOnClose | |
356 | + footer={null} | |
357 | + > | |
358 | + <AdviserTaskPreview | |
359 | + params={adviserTaskParams} | |
360 | + showSeriesModal={(record) => { | |
361 | + const params = { ...adviserTaskParams } as any; | |
362 | + params.taskId = record.id; | |
363 | + params.orderTaskApprovalType = OrderTaskApprovalType.车系; | |
364 | + params.staffId = record.dataId; | |
365 | + setSeriesTaskParams(params); | |
366 | + setStpVisible(true); | |
367 | + }} | |
368 | + /> | |
369 | + </Modal> | |
370 | + <Modal | |
371 | + width={600} | |
372 | + title="车系任务" | |
373 | + open={stpVisible} | |
374 | + onCancel={() => setStpVisible(false)} | |
375 | + destroyOnClose | |
376 | + footer={null} | |
377 | + > | |
378 | + <SeriesTaskPreview params={seriesTaskParams} /> | |
379 | + </Modal> | |
380 | + <ApprovalProgressModal /> | |
381 | + <ApproveModal | |
382 | + callback={submitSaleTask} | |
383 | + open={approveOpen} | |
384 | + setOpen={setApproveOpen} | |
385 | + /> | |
386 | + </PageHeaderWrapper> | |
387 | + ); | |
388 | +} | ... | ... |
src/pages/order3/SaleTask/store.ts
0 → 100644
1 | +import { useState } from "react"; | |
2 | +import * as API from "./api"; | |
3 | +import { createStore } from "@/hooks/moz"; | |
4 | + | |
5 | +function store() { | |
6 | + const [approvalProgressModalInfo, setApprovalProgressModalInfo] = | |
7 | + useState<PublicNotice.ApprovalProgressModalInfo>({ visible: false }); | |
8 | + const [shopTaskItem, setShopTaskItem] = useState<API.ShopTaskItem | null>( | |
9 | + null | |
10 | + ); | |
11 | + const [isReadOnly, setIsReadOnly] = useState(false); | |
12 | + const [editAdviser, setEditAdviser] = useState<API.StaffTaskItem>(); | |
13 | + | |
14 | + /** -----------门店零售任务分配----------- */ | |
15 | + // 更新车系任务 | |
16 | + const setShopSeriesRow = (seriesId: number, row: API.SeriesTaskItem) => { | |
17 | + const newTask = { ...shopTaskItem! }; | |
18 | + const editIndex = newTask.seriesTaskList.findIndex( | |
19 | + (item) => seriesId === item.seriesId | |
20 | + ); | |
21 | + const item = newTask.seriesTaskList[editIndex]; | |
22 | + newTask.seriesTaskList.splice(editIndex, 1, { | |
23 | + ...item, | |
24 | + ...row, | |
25 | + }); | |
26 | + setShopTaskItem(newTask); | |
27 | + }; | |
28 | + | |
29 | + // 删除车系任务 | |
30 | + const deleteShopSeriesRow = (seriesId: number) => { | |
31 | + const newTask = { ...shopTaskItem! }; | |
32 | + const deleteIndex = newTask.seriesTaskList.findIndex( | |
33 | + (item) => seriesId === item.seriesId | |
34 | + ); | |
35 | + newTask.seriesTaskList.splice(deleteIndex, 1); | |
36 | + setShopTaskItem(newTask); | |
37 | + }; | |
38 | + | |
39 | + // 添加车系任务 | |
40 | + const addShopSeriesRow = (values: any) => { | |
41 | + const newTask = { ...shopTaskItem! }; | |
42 | + const selectedSeries = values.series.map((item: any) => ({ | |
43 | + brandId: values.brand.value, | |
44 | + brandName: values.brand.label, | |
45 | + seriesId: item.value, | |
46 | + seriesName: item.label, | |
47 | + newEnergy: item.newEnergy, | |
48 | + taskCount: 0, | |
49 | + })); | |
50 | + newTask.seriesTaskList = selectedSeries.concat(newTask.seriesTaskList); | |
51 | + setShopTaskItem(newTask); | |
52 | + }; | |
53 | + | |
54 | + /** -----------销顾零售任务分配----------- */ | |
55 | + // 更新车系任务 | |
56 | + const setAdviserSeriesRow = ( | |
57 | + seriesId: number, | |
58 | + row: API.SeriesTaskItem | |
59 | + ) => { | |
60 | + const newAdviser = { ...editAdviser! }; | |
61 | + const editIndex = newAdviser.seriesTaskList.findIndex( | |
62 | + (item) => item.seriesId === seriesId | |
63 | + ); | |
64 | + const item = newAdviser.seriesTaskList[editIndex]; | |
65 | + newAdviser.seriesTaskList.splice(editIndex, 1, { | |
66 | + ...item, | |
67 | + ...row, | |
68 | + }); | |
69 | + setEditAdviser(newAdviser); | |
70 | + }; | |
71 | + | |
72 | + // 删除车系任务 | |
73 | + const deleteAdviserSeriesRow = (seriesId: number) => { | |
74 | + const newAdviser = { ...editAdviser! }; | |
75 | + const deleteIndex = newAdviser.seriesTaskList.findIndex( | |
76 | + (item) => item.seriesId === seriesId | |
77 | + ); | |
78 | + newAdviser.seriesTaskList.splice(deleteIndex, 1); | |
79 | + setEditAdviser(newAdviser); | |
80 | + }; | |
81 | + | |
82 | + // 添加车系任务 | |
83 | + const addAdviserSeriesRow = (values: any) => { | |
84 | + const newAdviser = { ...editAdviser! }; | |
85 | + const selectedSeries = values.series.map((item: any) => ({ | |
86 | + brandId: values.brand.value, | |
87 | + brandName: values.brand.label, | |
88 | + seriesId: item.value, | |
89 | + seriesName: item.label, | |
90 | + newEnergy: item.newEnergy, | |
91 | + taskCount: 0, | |
92 | + })); | |
93 | + newAdviser.seriesTaskList = selectedSeries.concat( | |
94 | + newAdviser.seriesTaskList | |
95 | + ); | |
96 | + setEditAdviser(newAdviser); | |
97 | + }; | |
98 | + | |
99 | + return { | |
100 | + approvalProgressModalInfo, | |
101 | + shopTaskItem, | |
102 | + isReadOnly, | |
103 | + editAdviser, | |
104 | + setEditAdviser, | |
105 | + setApprovalProgressModalInfo, | |
106 | + setShopTaskItem, | |
107 | + setIsReadOnly, | |
108 | + // 业务函数 | |
109 | + setShopSeriesRow, | |
110 | + deleteShopSeriesRow, | |
111 | + addShopSeriesRow, | |
112 | + setAdviserSeriesRow, | |
113 | + deleteAdviserSeriesRow, | |
114 | + addAdviserSeriesRow, | |
115 | + }; | |
116 | +} | |
117 | + | |
118 | +export const { Provider, useStore } = createStore(store); | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTask.tsx
0 → 100644
1 | +import React, { useEffect, useRef, useState } from "react"; | |
2 | +import { Table, Row, Input, Modal, Button, message } from "antd"; | |
3 | +import { useStore } from "../../../store"; | |
4 | +import * as API from "../../../api"; | |
5 | +import AdviserTaskEdit from "./AdviserTaskEdit"; | |
6 | +import { history, useRequest } from "umi"; | |
7 | +import { cloneDeep } from "lodash"; | |
8 | + | |
9 | +const { Column } = Table; | |
10 | + | |
11 | +interface AdviserTaskEditForm { | |
12 | + submit: (callback: (data: any) => void) => void; | |
13 | +} | |
14 | + | |
15 | +interface AdviserTaskProps { | |
16 | + form: any; | |
17 | +} | |
18 | + | |
19 | +export default function AdviserTask({ form }: AdviserTaskProps) { | |
20 | + const adviserTaskEditRef = useRef<AdviserTaskEditForm>(null); | |
21 | + const { | |
22 | + shopTaskItem, | |
23 | + isReadOnly, | |
24 | + setShopTaskItem, | |
25 | + editAdviser, | |
26 | + setEditAdviser, | |
27 | + } = useStore(); | |
28 | + const [advisersFiltered, setAdvisersFiltered] = useState<API.StaffTaskItem[]>( | |
29 | + [] | |
30 | + ); | |
31 | + const [visible, setVisible] = useState(false); | |
32 | + const saveShopSaleTaskHook = useRequest(API.saveShopSaleTask, { | |
33 | + manual: true, | |
34 | + throwOnError: true, | |
35 | + }); | |
36 | + | |
37 | + // 用于顾问前端搜索 | |
38 | + useEffect(() => { | |
39 | + setAdvisersFiltered(shopTaskItem?.staffTaskList ?? []); | |
40 | + }, [shopTaskItem]); | |
41 | + | |
42 | + const filterByAdviserName = (value: string) => { | |
43 | + const list = (shopTaskItem?.staffTaskList ?? []).filter( | |
44 | + (item: any) => item.staffName.indexOf(value) !== -1 | |
45 | + ); | |
46 | + setAdvisersFiltered(list); | |
47 | + }; | |
48 | + | |
49 | + // 前端更新编辑后的顾问分配任务 | |
50 | + const onOk = () => { | |
51 | + adviserTaskEditRef.current?.submit((newTask) => { | |
52 | + setShopTaskItem(newTask); | |
53 | + setAdvisersFiltered(newTask?.staffTaskList); // 刷新列表 | |
54 | + setVisible(false); | |
55 | + }); | |
56 | + }; | |
57 | + | |
58 | + const handleGoBack = () => { | |
59 | + history.goBack(); // todo 提示是否有未保存的修改 | |
60 | + }; | |
61 | + | |
62 | + const handleSaveTask = async () => { | |
63 | + const shopFormValue = form.getFieldsValue(); | |
64 | + const { taskId, ...other } = shopTaskItem!; // 因为门店零售任务分配表格数据填写时未修改 store | |
65 | + saveShopSaleTaskHook | |
66 | + .run({ ...other, id: taskId, ...shopFormValue }) | |
67 | + .then(() => { | |
68 | + message.success("保存草稿成功"); | |
69 | + }) | |
70 | + .catch((error: any) => { | |
71 | + message.error(error.message ?? "请求失败"); | |
72 | + }); | |
73 | + }; | |
74 | + | |
75 | + return ( | |
76 | + <> | |
77 | + <Row | |
78 | + align="middle" | |
79 | + justify="start" | |
80 | + style={{ marginBottom: 20, marginTop: 14 }} | |
81 | + > | |
82 | + <Input.Search | |
83 | + allowClear | |
84 | + placeholder="顾问名称" | |
85 | + style={{ width: 260, marginLeft: 15 }} | |
86 | + onSearch={filterByAdviserName} | |
87 | + /> | |
88 | + </Row> | |
89 | + <Table | |
90 | + dataSource={[...advisersFiltered]} | |
91 | + pagination={false} | |
92 | + rowKey="id" | |
93 | + loading={false} | |
94 | + > | |
95 | + <Column title="销售顾问" dataIndex="staffName" width={100} /> | |
96 | + <Column title="零售任务(台)" dataIndex="taskCount" /> | |
97 | + <Column title="新能源车任务(台)" dataIndex="newEnergyTaskCount" /> | |
98 | + <Column title="传统燃油车任务(台)" dataIndex="fuelVehicleTaskCount" /> | |
99 | + <Column title="车辆毛利任务(元)" dataIndex="vehicleGrossProfitTask" /> | |
100 | + <Column title="线索到店零售台数(台)" dataIndex="clueDealTaskCount" /> | |
101 | + <Column title="攻坚车任务数(台)" dataIndex="tackCarTaskCount" /> | |
102 | + <Column title="首客试驾成交任务数(台)" dataIndex="testDriveTaskCount" /> | |
103 | + <Column title="车系任务数(台)" dataIndex="seriesTaskCount" /> | |
104 | + {!isReadOnly && ( | |
105 | + <Column | |
106 | + title="操作" | |
107 | + width={100} | |
108 | + render={(text: string, record: API.StaffTaskItem) => { | |
109 | + return ( | |
110 | + <a | |
111 | + onClick={() => { | |
112 | + setEditAdviser(cloneDeep(record)); // 注意对象引用 | |
113 | + setVisible(true); | |
114 | + }} | |
115 | + > | |
116 | + 编辑 | |
117 | + </a> | |
118 | + ); | |
119 | + }} | |
120 | + /> | |
121 | + )} | |
122 | + </Table> | |
123 | + <Row align="middle" justify="center" style={{ marginTop: 50 }}> | |
124 | + <Button onClick={handleGoBack}>返回列表</Button> | |
125 | + {!isReadOnly && ( | |
126 | + <Button | |
127 | + type="primary" | |
128 | + style={{ marginLeft: 10 }} | |
129 | + loading={saveShopSaleTaskHook.loading} | |
130 | + onClick={handleSaveTask} | |
131 | + > | |
132 | + 保存草稿 | |
133 | + </Button> | |
134 | + )} | |
135 | + </Row> | |
136 | + <Modal | |
137 | + width={1200} | |
138 | + title={`当前选择顾问:${editAdviser?.staffName}`} | |
139 | + open={visible} | |
140 | + onOk={onOk} | |
141 | + okText="确认" | |
142 | + onCancel={() => setVisible(false)} | |
143 | + maskClosable={false} | |
144 | + keyboard={false} | |
145 | + destroyOnClose | |
146 | + > | |
147 | + {/* @ts-ignore */} | |
148 | + <AdviserTaskEdit ref={adviserTaskEditRef} /> | |
149 | + </Modal> | |
150 | + </> | |
151 | + ); | |
152 | +} | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/components/AdviserTaskEdit.tsx
0 → 100644
1 | +import React, { | |
2 | + useEffect, | |
3 | + useImperativeHandle, | |
4 | + useMemo, | |
5 | + useState, | |
6 | +} from "react"; | |
7 | +import { | |
8 | + InputNumber, | |
9 | + Divider, | |
10 | + Form, | |
11 | + message, | |
12 | + Table, | |
13 | + Button, | |
14 | + Row, | |
15 | + Tag, | |
16 | +} from "antd"; | |
17 | +import { PlusOutlined } from "@ant-design/icons"; | |
18 | +import * as API from "../../../api"; | |
19 | +import styles from "../index.less"; | |
20 | +import { useStore } from "../../../store"; | |
21 | +import _ from "lodash"; | |
22 | +import EditableCell from "./EditableCell"; | |
23 | +import SeriesModal from "./SeriesModal"; | |
24 | +import { observer } from "mobx-react-lite"; | |
25 | +import { MAX_NUM } from "../../../entity"; | |
26 | + | |
27 | +let _callback: (data: any) => void; | |
28 | +const { Column } = Table; | |
29 | + | |
30 | +export interface AdviserTaskEditProps {} | |
31 | + | |
32 | +function AdviserTaskEdit(props: AdviserTaskEditProps, ref: any) { | |
33 | + const { | |
34 | + shopTaskItem, | |
35 | + isReadOnly, | |
36 | + setAdviserSeriesRow, | |
37 | + deleteAdviserSeriesRow, | |
38 | + addAdviserSeriesRow, | |
39 | + editAdviser, | |
40 | + } = useStore(); | |
41 | + const [form] = Form.useForm(); | |
42 | + const [seriesForm] = Form.useForm(); | |
43 | + const [editingKey, setEditingKey] = useState(-1); // 编辑表格 key | |
44 | + const [seriesVisible, setSeriesVisible] = useState(false); | |
45 | + | |
46 | + // 车系去重 | |
47 | + const selectedIds = useMemo(() => { | |
48 | + if (!editAdviser) return []; | |
49 | + return editAdviser?.seriesTaskList.map((item) => String(item.seriesId)); | |
50 | + }, [editAdviser]); | |
51 | + | |
52 | + // 计算车系任务总数自动填写 | |
53 | + useEffect(() => { | |
54 | + const total = editAdviser?.seriesTaskList.reduce( | |
55 | + (total, currItem) => total + currItem.taskCount, | |
56 | + 0 | |
57 | + ); | |
58 | + form.setFieldValue("seriesTaskCount", total); | |
59 | + }, [editAdviser]); | |
60 | + | |
61 | + useImperativeHandle(ref, () => { | |
62 | + return { | |
63 | + submit: (callback: (data: any) => void) => { | |
64 | + _callback = callback; | |
65 | + form.submit(); | |
66 | + }, | |
67 | + }; | |
68 | + }); | |
69 | + | |
70 | + const isEditing = (record: API.SeriesTaskItem) => { | |
71 | + return record.seriesId === Number(editingKey); | |
72 | + }; | |
73 | + | |
74 | + const setSeriesRow = (seriesId: number) => { | |
75 | + seriesForm | |
76 | + .validateFields() | |
77 | + .then((row: any) => { | |
78 | + setEditingKey(-1); | |
79 | + setAdviserSeriesRow(seriesId, row); | |
80 | + }) | |
81 | + .catch((error: any) => { | |
82 | + message.error(error.message ?? "表单校验失败"); | |
83 | + }); | |
84 | + }; | |
85 | + | |
86 | + const editSeriesRow = (record: API.SeriesTaskItem) => { | |
87 | + seriesForm.setFieldsValue({ ...record }); | |
88 | + setEditingKey(record.seriesId); | |
89 | + }; | |
90 | + | |
91 | + // 添加车系 | |
92 | + const handleSelectSeries = (values: any) => { | |
93 | + addAdviserSeriesRow(values); | |
94 | + setSeriesVisible(false); | |
95 | + }; | |
96 | + | |
97 | + const onFinish = async (values: any) => { | |
98 | + await form.validateFields(); | |
99 | + const newTask = { ...shopTaskItem! }; | |
100 | + const currAdviserIndex = newTask.staffTaskList.findIndex( | |
101 | + (item) => item.staffId === editAdviser?.staffId | |
102 | + ); | |
103 | + newTask.staffTaskList[currAdviserIndex] = { ...editAdviser, ...values }; | |
104 | + _callback(newTask); | |
105 | + }; | |
106 | + | |
107 | + const layout = { | |
108 | + labelCol: { span: 5 }, | |
109 | + wrapperCol: { span: 19 }, | |
110 | + }; | |
111 | + | |
112 | + const components = { | |
113 | + body: { | |
114 | + cell: EditableCell, | |
115 | + }, | |
116 | + }; | |
117 | + | |
118 | + return ( | |
119 | + <div style={{ width: 1050, margin: "0 auto" }}> | |
120 | + <div style={{ width: 950, margin: "0 auto", paddingTop: 40 }}> | |
121 | + <Form | |
122 | + {...layout} | |
123 | + labelAlign="left" | |
124 | + form={form} | |
125 | + initialValues={editAdviser} | |
126 | + onFinish={onFinish} | |
127 | + > | |
128 | + <Form.Item name="taskCount" label="零售任务:" required> | |
129 | + <InputNumber | |
130 | + formatter={(value) => `${value}台`} | |
131 | + parser={(value: any) => value.replace("台", "")} | |
132 | + min={0} | |
133 | + max={MAX_NUM} | |
134 | + style={{ width: "100%" }} | |
135 | + disabled={isReadOnly} | |
136 | + precision={0} | |
137 | + /> | |
138 | + </Form.Item> | |
139 | + <Form.Item | |
140 | + noStyle | |
141 | + shouldUpdate={(prevValues, currentValues) => { | |
142 | + if (prevValues.taskCount !== currentValues.taskCount) { | |
143 | + form.setFieldValue( | |
144 | + "clueDealTaskCount", | |
145 | + ( | |
146 | + currentValues.taskCount * | |
147 | + ((shopTaskItem?.clueDealTaskRate ?? 100) / 100) | |
148 | + ).toFixed(2) | |
149 | + ); | |
150 | + } | |
151 | + return prevValues.taskCount !== currentValues.taskCount; | |
152 | + }} | |
153 | + > | |
154 | + {() => { | |
155 | + return ( | |
156 | + <Form.Item | |
157 | + name="clueDealTaskCount" | |
158 | + label="线索到店零售台数:" | |
159 | + required | |
160 | + > | |
161 | + <InputNumber | |
162 | + formatter={(value) => `${value}台`} | |
163 | + parser={(value: any) => value.replace("台", "")} | |
164 | + min={0} | |
165 | + max={MAX_NUM} | |
166 | + style={{ width: "100%" }} | |
167 | + disabled={isReadOnly} | |
168 | + precision={2} | |
169 | + /> | |
170 | + </Form.Item> | |
171 | + ); | |
172 | + }} | |
173 | + </Form.Item> | |
174 | + <Form.Item | |
175 | + name="newEnergyTaskCount" | |
176 | + label="新能源车任务台数:" | |
177 | + dependencies={["taskCount", "fuelVehicleTaskCount"]} | |
178 | + required | |
179 | + rules={[ | |
180 | + ({ getFieldValue }) => ({ | |
181 | + validator(_, value) { | |
182 | + const taskCount = getFieldValue("taskCount"); | |
183 | + const fuelVehicleTaskCount = getFieldValue( | |
184 | + "fuelVehicleTaskCount" | |
185 | + ); | |
186 | + if ( | |
187 | + value >= 0 && | |
188 | + fuelVehicleTaskCount + value === taskCount | |
189 | + ) { | |
190 | + return Promise.resolve(); | |
191 | + } | |
192 | + return Promise.reject( | |
193 | + new Error( | |
194 | + "规则:新能源车任务台数 + 传统燃油车任务台数 = 零售任务台数" | |
195 | + ) | |
196 | + ); | |
197 | + }, | |
198 | + }), | |
199 | + ]} | |
200 | + > | |
201 | + <InputNumber | |
202 | + formatter={(value) => `${value}台`} | |
203 | + parser={(value: any) => value.replace("台", "")} | |
204 | + min={0} | |
205 | + max={MAX_NUM} | |
206 | + style={{ width: "100%" }} | |
207 | + disabled={isReadOnly} | |
208 | + precision={0} | |
209 | + /> | |
210 | + </Form.Item> | |
211 | + <Form.Item | |
212 | + name="fuelVehicleTaskCount" | |
213 | + label="传统燃油车任务台数:" | |
214 | + required | |
215 | + dependencies={["taskCount", "newEnergyTaskCount"]} | |
216 | + rules={[ | |
217 | + ({ getFieldValue }) => ({ | |
218 | + validator(_, value) { | |
219 | + const taskCount = getFieldValue("taskCount"); | |
220 | + const newEnergyTaskCount = | |
221 | + getFieldValue("newEnergyTaskCount"); | |
222 | + if (value >= 0 && newEnergyTaskCount + value === taskCount) { | |
223 | + return Promise.resolve(); | |
224 | + } | |
225 | + return Promise.reject( | |
226 | + new Error( | |
227 | + "规则:新能源车任务台数 + 传统燃油车任务台数 = 零售任务台数" | |
228 | + ) | |
229 | + ); | |
230 | + }, | |
231 | + }), | |
232 | + ]} | |
233 | + > | |
234 | + <InputNumber | |
235 | + formatter={(value) => `${value}台`} | |
236 | + parser={(value: any) => value.replace("台", "")} | |
237 | + min={0} | |
238 | + max={MAX_NUM} | |
239 | + style={{ width: "100%" }} | |
240 | + disabled={isReadOnly} | |
241 | + precision={0} | |
242 | + /> | |
243 | + </Form.Item> | |
244 | + <Form.Item | |
245 | + name="vehicleGrossProfitTask" | |
246 | + label="车辆毛利任务:" | |
247 | + required | |
248 | + > | |
249 | + <InputNumber | |
250 | + formatter={(value) => `${value}元`} | |
251 | + parser={(value: any) => value.replace("元", "")} | |
252 | + min={0} | |
253 | + max={MAX_NUM} | |
254 | + style={{ width: "100%" }} | |
255 | + disabled={isReadOnly} | |
256 | + precision={2} | |
257 | + /> | |
258 | + </Form.Item> | |
259 | + <Form.Item | |
260 | + name="testDriveTaskCount" | |
261 | + label="首客试驾成交任务台数:" | |
262 | + required | |
263 | + > | |
264 | + <InputNumber | |
265 | + formatter={(value) => `${value}台`} | |
266 | + parser={(value: any) => value.replace("台", "")} | |
267 | + min={0} | |
268 | + max={MAX_NUM} | |
269 | + style={{ width: "100%" }} | |
270 | + disabled={isReadOnly} | |
271 | + precision={0} | |
272 | + /> | |
273 | + </Form.Item> | |
274 | + <Form.Item name="tackCarTaskCount" label="攻坚车任务台数:" required> | |
275 | + <InputNumber | |
276 | + formatter={(value) => `${value}台`} | |
277 | + parser={(value: any) => value.replace("台", "")} | |
278 | + min={0} | |
279 | + max={MAX_NUM} | |
280 | + style={{ width: "100%" }} | |
281 | + disabled={isReadOnly} | |
282 | + precision={0} | |
283 | + /> | |
284 | + </Form.Item> | |
285 | + <Form.Item name="seriesTaskCount" label="车系任务台数:" required> | |
286 | + <InputNumber | |
287 | + formatter={(value) => `${value}台`} | |
288 | + parser={(value: any) => value.replace("台", "")} | |
289 | + min={0} | |
290 | + max={MAX_NUM} | |
291 | + style={{ width: "100%" }} | |
292 | + precision={0} | |
293 | + disabled | |
294 | + /> | |
295 | + </Form.Item> | |
296 | + </Form> | |
297 | + </div> | |
298 | + <Divider /> | |
299 | + <div style={{ width: 950, margin: "0 auto" }}> | |
300 | + <Row | |
301 | + align="middle" | |
302 | + justify="space-between" | |
303 | + style={{ marginBottom: 20 }} | |
304 | + > | |
305 | + <h2 className={styles.carTask}>车系任务</h2> | |
306 | + {!isReadOnly && ( | |
307 | + <Button | |
308 | + icon={<PlusOutlined />} | |
309 | + type="primary" | |
310 | + onClick={() => { | |
311 | + setSeriesVisible(true); | |
312 | + }} | |
313 | + > | |
314 | + 添加车系 | |
315 | + </Button> | |
316 | + )} | |
317 | + </Row> | |
318 | + <Form form={seriesForm} component={false}> | |
319 | + <Table | |
320 | + scroll={{ y: 400 }} | |
321 | + rowKey="seriesId" | |
322 | + rowClassName={() => "editable-cell"} | |
323 | + pagination={false} | |
324 | + components={components} | |
325 | + dataSource={[...(editAdviser?.seriesTaskList ?? [])]} | |
326 | + > | |
327 | + <Column | |
328 | + title="车系" | |
329 | + dataIndex="seriesName" | |
330 | + render={(value, record: API.SeriesTaskItem) => { | |
331 | + return ( | |
332 | + <Row align="middle" justify="start"> | |
333 | + <span>{value}</span> | |
334 | + {record.newEnergy && ( | |
335 | + <Tag | |
336 | + style={{ marginBottom: 5, marginLeft: 7 }} | |
337 | + color="green" | |
338 | + > | |
339 | + 新能源 | |
340 | + </Tag> | |
341 | + )} | |
342 | + </Row> | |
343 | + ); | |
344 | + }} | |
345 | + /> | |
346 | + <Column | |
347 | + title="零售任务(台)" | |
348 | + dataIndex="taskCount" | |
349 | + onCell={(record: API.SeriesTaskItem) => ({ | |
350 | + record, | |
351 | + inputType: "number", | |
352 | + dataIndex: "taskCount", | |
353 | + title: "零售任务(台)", | |
354 | + editing: isEditing(record), | |
355 | + })} | |
356 | + /> | |
357 | + {!isReadOnly && ( | |
358 | + <Column | |
359 | + width={120} | |
360 | + title="操作" | |
361 | + render={(text, record: API.SeriesTaskItem) => { | |
362 | + const editable = isEditing(record); | |
363 | + return ( | |
364 | + <div> | |
365 | + {editable ? ( | |
366 | + <span> | |
367 | + <a onClick={() => setSeriesRow(record.seriesId)}> | |
368 | + 确认 | |
369 | + </a> | |
370 | + <Divider type="vertical" /> | |
371 | + <a | |
372 | + style={{ color: "#999" }} | |
373 | + onClick={() => setEditingKey(-1)} | |
374 | + > | |
375 | + 取消 | |
376 | + </a> | |
377 | + </span> | |
378 | + ) : ( | |
379 | + <span> | |
380 | + <a onClick={() => editSeriesRow(record)}>编辑</a> | |
381 | + <Divider type="vertical" /> | |
382 | + <a | |
383 | + style={{ color: "#999" }} | |
384 | + onClick={() => { | |
385 | + deleteAdviserSeriesRow(record.seriesId); | |
386 | + }} | |
387 | + > | |
388 | + 删除 | |
389 | + </a> | |
390 | + </span> | |
391 | + )} | |
392 | + </div> | |
393 | + ); | |
394 | + }} | |
395 | + /> | |
396 | + )} | |
397 | + </Table> | |
398 | + </Form> | |
399 | + </div> | |
400 | + <SeriesModal | |
401 | + visible={seriesVisible} | |
402 | + selectedIds={selectedIds} | |
403 | + onOk={handleSelectSeries} | |
404 | + onCancel={() => { | |
405 | + setSeriesVisible(false); | |
406 | + }} | |
407 | + /> | |
408 | + </div> | |
409 | + ); | |
410 | +} | |
411 | + | |
412 | +export default observer(AdviserTaskEdit, { forwardRef: true }); | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/components/EditableCell.tsx
0 → 100644
1 | +import React, { HTMLAttributes } from "react"; | |
2 | +import { Input, InputNumber, Form } from "antd"; | |
3 | +import _ from "lodash"; | |
4 | +import { MAX_NUM } from "../../../entity"; | |
5 | + | |
6 | +interface EditableCellProps extends HTMLAttributes<HTMLElement> { | |
7 | + editing: boolean; | |
8 | + dataIndex: string; | |
9 | + title: string; | |
10 | + inputType: "number" | "text"; | |
11 | + record: any; | |
12 | + index: number; | |
13 | + children: React.ReactNode; | |
14 | +} | |
15 | + | |
16 | +function EditableCell(props: EditableCellProps) { | |
17 | + const { editing, dataIndex, title, inputType, record, index, ...restProps } = | |
18 | + props; | |
19 | + let timer: any = null; | |
20 | + | |
21 | + const check = async (rule: any, value: any): Promise<any> => { | |
22 | + timer && clearTimeout(timer); | |
23 | + return new Promise((resolve, reject) => { | |
24 | + timer = setTimeout(async () => { | |
25 | + switch (dataIndex) { | |
26 | + default: | |
27 | + resolve(true); | |
28 | + break; | |
29 | + } | |
30 | + }, 500); | |
31 | + }); | |
32 | + }; | |
33 | + | |
34 | + return ( | |
35 | + <td {...restProps}> | |
36 | + {editing ? ( | |
37 | + <Form.Item | |
38 | + name={dataIndex} | |
39 | + style={{ margin: 0 }} | |
40 | + rules={[ | |
41 | + { | |
42 | + required: true, | |
43 | + message: `请输入正确的${title}值`, | |
44 | + }, | |
45 | + { validator: check }, | |
46 | + ]} | |
47 | + > | |
48 | + {inputType === "number" ? ( | |
49 | + <InputNumber min={0} max={MAX_NUM} /> | |
50 | + ) : ( | |
51 | + <Input /> | |
52 | + )} | |
53 | + </Form.Item> | |
54 | + ) : ( | |
55 | + restProps.children | |
56 | + )} | |
57 | + </td> | |
58 | + ); | |
59 | +} | |
60 | + | |
61 | +export default EditableCell; | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/components/SeriesModal.tsx
0 → 100644
1 | +import React, { useEffect } from "react"; | |
2 | +import { Modal, Form, Select } from "antd"; | |
3 | +import { useRequest } from "umi"; | |
4 | +import * as API from "../../../api"; | |
5 | +import { useStore } from "../../../store"; | |
6 | + | |
7 | +export interface SeriesModalProps { | |
8 | + visible: boolean; | |
9 | + selectedIds: string[]; | |
10 | + onOk: (values: any) => void; | |
11 | + onCancel: () => void; | |
12 | +} | |
13 | + | |
14 | +export default function SeriesModal({ | |
15 | + visible, | |
16 | + selectedIds, | |
17 | + onOk, | |
18 | + onCancel, | |
19 | +}: SeriesModalProps) { | |
20 | + const { shopTaskItem } = useStore(); | |
21 | + const [form] = Form.useForm(); | |
22 | + | |
23 | + const getBrandsHook = useRequest(API.getBrands, { | |
24 | + manual: true, | |
25 | + }); | |
26 | + | |
27 | + useEffect(() => { | |
28 | + getBrandsHook.run({ shopId: shopTaskItem?.shopId! }); | |
29 | + }, []); | |
30 | + | |
31 | + const getSeriesHook = useRequest(API.getSeries, { | |
32 | + manual: true, | |
33 | + // 车系去重 | |
34 | + formatResult: ({ data }) => { | |
35 | + const formatData: API.SeriesItem[] = []; | |
36 | + data?.forEach((item) => { | |
37 | + if (selectedIds.includes(String(item.id))) return; | |
38 | + formatData.push(item); | |
39 | + }); | |
40 | + return formatData; | |
41 | + }, | |
42 | + }); | |
43 | + | |
44 | + const onFinish = (values: any) => { | |
45 | + const newSeries = (values.series ?? []).map((item: any) => { | |
46 | + const currSeries = (getSeriesHook.data ?? []).filter( | |
47 | + (i) => i.id === item.value | |
48 | + ); | |
49 | + return { | |
50 | + ...item, | |
51 | + newEnergy: currSeries[0].newEnergy, | |
52 | + }; | |
53 | + }); | |
54 | + onOk({ ...values, series: newSeries }); | |
55 | + }; | |
56 | + | |
57 | + return ( | |
58 | + <Modal | |
59 | + title="选择车系" | |
60 | + open={visible} | |
61 | + onCancel={onCancel} | |
62 | + onOk={() => form.submit()} | |
63 | + width={600} | |
64 | + destroyOnClose | |
65 | + afterClose={() => form.resetFields()} | |
66 | + > | |
67 | + <Form | |
68 | + form={form} | |
69 | + labelCol={{ span: 6 }} | |
70 | + wrapperCol={{ span: 15 }} | |
71 | + onFinish={onFinish} | |
72 | + > | |
73 | + <Form.Item | |
74 | + label="品牌" | |
75 | + name="brand" | |
76 | + rules={[{ required: true, message: "请先选择品牌" }]} | |
77 | + > | |
78 | + <Select | |
79 | + showSearch | |
80 | + allowClear | |
81 | + labelInValue | |
82 | + loading={getBrandsHook.loading} | |
83 | + placeholder="请先选择品牌" | |
84 | + style={{ width: "100%" }} | |
85 | + fieldNames={{ value: "id", label: "name" }} | |
86 | + options={getBrandsHook.data} | |
87 | + /> | |
88 | + </Form.Item> | |
89 | + <Form.Item | |
90 | + noStyle | |
91 | + shouldUpdate={(prevValues, currentValues) => { | |
92 | + if (prevValues.brand !== currentValues.brand) { | |
93 | + form.setFieldValue("series", undefined); | |
94 | + getSeriesHook.run({ | |
95 | + shopId: shopTaskItem?.shopId!, | |
96 | + brandId: currentValues.brand.value, | |
97 | + }); | |
98 | + } | |
99 | + return prevValues.brand !== currentValues.brand; | |
100 | + }} | |
101 | + > | |
102 | + {({ getFieldValue }) => { | |
103 | + if (!getFieldValue("brand")) return null; | |
104 | + return ( | |
105 | + <Form.Item | |
106 | + label="车系" | |
107 | + name="series" | |
108 | + rules={[{ required: true, message: "请选择车系" }]} | |
109 | + > | |
110 | + <Select | |
111 | + mode="multiple" | |
112 | + showSearch | |
113 | + allowClear | |
114 | + labelInValue | |
115 | + loading={getSeriesHook.loading} | |
116 | + placeholder="请选择车系" | |
117 | + style={{ width: "100%" }} | |
118 | + fieldNames={{ value: "id", label: "name" }} | |
119 | + options={getSeriesHook.data} | |
120 | + /> | |
121 | + </Form.Item> | |
122 | + ); | |
123 | + }} | |
124 | + </Form.Item> | |
125 | + </Form> | |
126 | + </Modal> | |
127 | + ); | |
128 | +} | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/components/ShopTask.tsx
0 → 100644
1 | +import React, { useEffect, useMemo, useState } from "react"; | |
2 | +import { | |
3 | + InputNumber, | |
4 | + Divider, | |
5 | + Form, | |
6 | + message, | |
7 | + Table, | |
8 | + Button, | |
9 | + Row, | |
10 | + Tag, | |
11 | +} from "antd"; | |
12 | +import { PlusOutlined } from "@ant-design/icons"; | |
13 | +import * as API from "../../../api"; | |
14 | +import styles from "../index.less"; | |
15 | +import { useStore } from "../../../store"; | |
16 | +import _ from "lodash"; | |
17 | +import EditableCell from "./EditableCell"; | |
18 | +import SeriesModal from "./SeriesModal"; | |
19 | +import { history, useRequest } from "umi"; | |
20 | +import { MAX_NUM } from "../../../entity"; | |
21 | + | |
22 | +const { Column } = Table; | |
23 | + | |
24 | +interface ShopTaskProps { | |
25 | + form: any; | |
26 | +} | |
27 | + | |
28 | +export default function ShopTask({ form }: ShopTaskProps) { | |
29 | + const { | |
30 | + shopTaskItem, | |
31 | + isReadOnly, | |
32 | + setShopSeriesRow, | |
33 | + deleteShopSeriesRow, | |
34 | + addShopSeriesRow, | |
35 | + } = useStore(); | |
36 | + const [seriesForm] = Form.useForm(); | |
37 | + const [editingKey, setEditingKey] = useState(-1); // 编辑表格 key | |
38 | + const [seriesVisible, setSeriesVisible] = useState(false); | |
39 | + | |
40 | + const saveShopSaleTaskHook = useRequest(API.saveShopSaleTask, { | |
41 | + manual: true, | |
42 | + throwOnError: true, | |
43 | + }); | |
44 | + | |
45 | + // 车系去重 | |
46 | + const selectedIds = useMemo(() => { | |
47 | + if (!shopTaskItem) return []; | |
48 | + return shopTaskItem.seriesTaskList.map((item) => String(item.seriesId)); | |
49 | + }, [shopTaskItem]); | |
50 | + | |
51 | + // 计算车系任务总数自动填写 | |
52 | + useEffect(() => { | |
53 | + const total = shopTaskItem?.seriesTaskList.reduce( | |
54 | + (total, currItem) => total + currItem.taskCount, | |
55 | + 0 | |
56 | + ); | |
57 | + form.setFieldValue("seriesTaskCount", total); | |
58 | + }, [shopTaskItem]); | |
59 | + | |
60 | + const isEditing = (record: API.SeriesTaskItem) => { | |
61 | + return record.seriesId === Number(editingKey); | |
62 | + }; | |
63 | + | |
64 | + const setSeriesRow = (id: number) => { | |
65 | + seriesForm | |
66 | + .validateFields() | |
67 | + .then((row: any) => { | |
68 | + setEditingKey(-1); | |
69 | + setShopSeriesRow(id, row); | |
70 | + }) | |
71 | + .catch((error: any) => { | |
72 | + message.error(error.message ?? "表单校验失败"); | |
73 | + }); | |
74 | + }; | |
75 | + | |
76 | + const editSeriesRow = (record: API.SeriesTaskItem) => { | |
77 | + seriesForm.setFieldsValue({ ...record }); | |
78 | + setEditingKey(record.seriesId); | |
79 | + }; | |
80 | + | |
81 | + // 添加车系 | |
82 | + const handleSelectSeries = (values: any) => { | |
83 | + addShopSeriesRow(values); | |
84 | + setSeriesVisible(false); | |
85 | + }; | |
86 | + | |
87 | + const handleGoBack = () => { | |
88 | + history.goBack(); // todo 提示是否有未保存的修改 | |
89 | + }; | |
90 | + | |
91 | + const handleSaveTask = async () => { | |
92 | + await form.validateFields(); | |
93 | + const values = form.getFieldsValue(); | |
94 | + const { taskId, ...other } = shopTaskItem!; | |
95 | + saveShopSaleTaskHook | |
96 | + .run({ ...other, ...values, id: taskId }) | |
97 | + .then(() => { | |
98 | + message.success("保存草稿成功"); | |
99 | + }) | |
100 | + .catch((error: any) => { | |
101 | + message.error(error.message ?? "请求失败"); | |
102 | + }); | |
103 | + }; | |
104 | + | |
105 | + const layout = { | |
106 | + labelCol: { span: 5 }, | |
107 | + wrapperCol: { span: 19 }, | |
108 | + }; | |
109 | + | |
110 | + const components = { | |
111 | + body: { | |
112 | + cell: EditableCell, | |
113 | + }, | |
114 | + }; | |
115 | + | |
116 | + return ( | |
117 | + <div style={{ width: 1200, margin: "0 auto" }}> | |
118 | + <div style={{ width: 880, margin: "0 auto", paddingTop: 24 }}> | |
119 | + <Form | |
120 | + {...layout} | |
121 | + labelAlign="left" | |
122 | + form={form} | |
123 | + initialValues={shopTaskItem!} | |
124 | + > | |
125 | + <Form.Item name="taskCount" label="零售任务:" required> | |
126 | + <InputNumber | |
127 | + formatter={(value) => `${value}台`} | |
128 | + parser={(value: any) => value.replace("台", "")} | |
129 | + min={0} | |
130 | + max={MAX_NUM} | |
131 | + style={{ width: "100%" }} | |
132 | + disabled={isReadOnly} | |
133 | + precision={0} | |
134 | + /> | |
135 | + </Form.Item> | |
136 | + <Form.Item | |
137 | + noStyle | |
138 | + shouldUpdate={(prevValues, currentValues) => { | |
139 | + if (prevValues.taskCount !== currentValues.taskCount) { | |
140 | + form.setFieldValue( | |
141 | + "clueDealTaskCount", | |
142 | + ( | |
143 | + currentValues.taskCount * | |
144 | + ((shopTaskItem?.clueDealTaskRate ?? 100) / 100) | |
145 | + ).toFixed(2) | |
146 | + ); | |
147 | + } | |
148 | + return prevValues.taskCount !== currentValues.taskCount; | |
149 | + }} | |
150 | + > | |
151 | + {() => { | |
152 | + return ( | |
153 | + <Form.Item | |
154 | + name="clueDealTaskCount" | |
155 | + label="线索到店零售台数:" | |
156 | + required | |
157 | + > | |
158 | + <InputNumber | |
159 | + formatter={(value) => `${value}台`} | |
160 | + parser={(value: any) => value.replace("台", "")} | |
161 | + min={0} | |
162 | + max={MAX_NUM} | |
163 | + style={{ width: "100%" }} | |
164 | + disabled={isReadOnly} | |
165 | + precision={2} | |
166 | + /> | |
167 | + </Form.Item> | |
168 | + ); | |
169 | + }} | |
170 | + </Form.Item> | |
171 | + <Form.Item | |
172 | + name="newEnergyTaskCount" | |
173 | + label="新能源车任务台数:" | |
174 | + required | |
175 | + dependencies={["taskCount", "fuelVehicleTaskCount"]} | |
176 | + rules={[ | |
177 | + ({ getFieldValue }) => ({ | |
178 | + validator(_, value) { | |
179 | + const taskCount = getFieldValue("taskCount"); | |
180 | + const fuelVehicleTaskCount = getFieldValue( | |
181 | + "fuelVehicleTaskCount" | |
182 | + ); | |
183 | + if ( | |
184 | + value >= 0 && | |
185 | + fuelVehicleTaskCount + value === taskCount | |
186 | + ) { | |
187 | + return Promise.resolve(); | |
188 | + } | |
189 | + return Promise.reject( | |
190 | + new Error( | |
191 | + "规则:新能源车任务台数 + 传统燃油车任务台数 = 零售任务台数" | |
192 | + ) | |
193 | + ); | |
194 | + }, | |
195 | + }), | |
196 | + ]} | |
197 | + > | |
198 | + <InputNumber | |
199 | + formatter={(value) => `${value}台`} | |
200 | + parser={(value: any) => value.replace("台", "")} | |
201 | + min={0} | |
202 | + max={MAX_NUM} | |
203 | + style={{ width: "100%" }} | |
204 | + disabled={isReadOnly} | |
205 | + precision={0} | |
206 | + /> | |
207 | + </Form.Item> | |
208 | + | |
209 | + <Form.Item | |
210 | + name="fuelVehicleTaskCount" | |
211 | + label="传统燃油车任务台数:" | |
212 | + required | |
213 | + dependencies={["taskCount", "newEnergyTaskCount"]} | |
214 | + rules={[ | |
215 | + ({ getFieldValue }) => ({ | |
216 | + validator(_, value) { | |
217 | + const taskCount = getFieldValue("taskCount"); | |
218 | + const newEnergyTaskCount = | |
219 | + getFieldValue("newEnergyTaskCount"); | |
220 | + if (value >= 0 && newEnergyTaskCount + value === taskCount) { | |
221 | + return Promise.resolve(); | |
222 | + } | |
223 | + return Promise.reject( | |
224 | + new Error( | |
225 | + "规则:新能源车任务台数 + 传统燃油车任务台数 = 零售任务台数" | |
226 | + ) | |
227 | + ); | |
228 | + }, | |
229 | + }), | |
230 | + ]} | |
231 | + > | |
232 | + <InputNumber | |
233 | + formatter={(value) => `${value}台`} | |
234 | + parser={(value: any) => value.replace("台", "")} | |
235 | + min={0} | |
236 | + max={MAX_NUM} | |
237 | + style={{ width: "100%" }} | |
238 | + disabled={isReadOnly} | |
239 | + precision={0} | |
240 | + /> | |
241 | + </Form.Item> | |
242 | + <Form.Item | |
243 | + name="vehicleGrossProfitTask" | |
244 | + label="车辆毛利任务:" | |
245 | + required | |
246 | + > | |
247 | + <InputNumber | |
248 | + formatter={(value) => `${value}元`} | |
249 | + parser={(value: any) => value.replace("元", "")} | |
250 | + min={0} | |
251 | + max={MAX_NUM} | |
252 | + style={{ width: "100%" }} | |
253 | + disabled={isReadOnly} | |
254 | + precision={2} | |
255 | + /> | |
256 | + </Form.Item> | |
257 | + <Form.Item | |
258 | + name="testDriveTaskCount" | |
259 | + label="首客试驾成交任务台数:" | |
260 | + required | |
261 | + > | |
262 | + <InputNumber | |
263 | + formatter={(value) => `${value}台`} | |
264 | + parser={(value: any) => value.replace("台", "")} | |
265 | + min={0} | |
266 | + max={MAX_NUM} | |
267 | + style={{ width: "100%" }} | |
268 | + disabled={isReadOnly} | |
269 | + precision={0} | |
270 | + /> | |
271 | + </Form.Item> | |
272 | + <Form.Item name="tackCarTaskCount" label="攻坚车任务台数:" required> | |
273 | + <InputNumber | |
274 | + formatter={(value) => `${value}台`} | |
275 | + parser={(value: any) => value.replace("台", "")} | |
276 | + min={0} | |
277 | + max={MAX_NUM} | |
278 | + style={{ width: "100%" }} | |
279 | + disabled={isReadOnly} | |
280 | + precision={0} | |
281 | + /> | |
282 | + </Form.Item> | |
283 | + <Form.Item name="seriesTaskCount" label="车系任务台数:" required> | |
284 | + <InputNumber | |
285 | + formatter={(value) => `${value}台`} | |
286 | + parser={(value: any) => value.replace("台", "")} | |
287 | + min={0} | |
288 | + max={MAX_NUM} | |
289 | + style={{ width: "100%" }} | |
290 | + precision={0} | |
291 | + disabled | |
292 | + /> | |
293 | + </Form.Item> | |
294 | + </Form> | |
295 | + </div> | |
296 | + <Divider /> | |
297 | + <div style={{ width: 880, margin: "0 auto" }}> | |
298 | + <Row | |
299 | + align="middle" | |
300 | + justify="space-between" | |
301 | + style={{ marginBottom: 20 }} | |
302 | + > | |
303 | + <h2 className={styles.carTask}>车系任务</h2> | |
304 | + {!isReadOnly && ( | |
305 | + <Button | |
306 | + icon={<PlusOutlined />} | |
307 | + type="primary" | |
308 | + onClick={() => { | |
309 | + setSeriesVisible(true); | |
310 | + }} | |
311 | + > | |
312 | + 添加车系 | |
313 | + </Button> | |
314 | + )} | |
315 | + </Row> | |
316 | + <Form form={seriesForm} component={false}> | |
317 | + <Table | |
318 | + scroll={{ y: 400 }} | |
319 | + rowKey="seriesId" | |
320 | + rowClassName={() => "editable-cell"} | |
321 | + pagination={false} | |
322 | + components={components} | |
323 | + dataSource={[...shopTaskItem?.seriesTaskList!]} | |
324 | + > | |
325 | + <Column | |
326 | + title="车系" | |
327 | + dataIndex="seriesName" | |
328 | + render={(value, record: API.SeriesTaskItem) => { | |
329 | + return ( | |
330 | + <Row align="middle" justify="start"> | |
331 | + <span>{value}</span> | |
332 | + {record.newEnergy && ( | |
333 | + <Tag | |
334 | + style={{ marginBottom: 5, marginLeft: 7 }} | |
335 | + color="green" | |
336 | + > | |
337 | + 新能源 | |
338 | + </Tag> | |
339 | + )} | |
340 | + </Row> | |
341 | + ); | |
342 | + }} | |
343 | + /> | |
344 | + <Column | |
345 | + title="零售任务(台)" | |
346 | + dataIndex="taskCount" | |
347 | + onCell={(record: API.SeriesTaskItem) => ({ | |
348 | + record, | |
349 | + inputType: "number", | |
350 | + dataIndex: "taskCount", | |
351 | + title: "零售任务(台)", | |
352 | + editing: isEditing(record), | |
353 | + })} | |
354 | + /> | |
355 | + {!isReadOnly && ( | |
356 | + <Column | |
357 | + width={120} | |
358 | + title="操作" | |
359 | + render={(text, record: API.SeriesTaskItem) => { | |
360 | + const editable = isEditing(record); | |
361 | + return ( | |
362 | + <div> | |
363 | + {editable ? ( | |
364 | + <span> | |
365 | + <a onClick={() => setSeriesRow(record.seriesId)}> | |
366 | + 确认 | |
367 | + </a> | |
368 | + <Divider type="vertical" /> | |
369 | + <a | |
370 | + style={{ color: "#999" }} | |
371 | + onClick={() => setEditingKey(-1)} | |
372 | + > | |
373 | + 取消 | |
374 | + </a> | |
375 | + </span> | |
376 | + ) : ( | |
377 | + <span> | |
378 | + <a onClick={() => editSeriesRow(record)}>编辑</a> | |
379 | + <Divider type="vertical" /> | |
380 | + <a | |
381 | + style={{ color: "#999" }} | |
382 | + onClick={() => deleteShopSeriesRow(record.seriesId)} | |
383 | + > | |
384 | + 删除 | |
385 | + </a> | |
386 | + </span> | |
387 | + )} | |
388 | + </div> | |
389 | + ); | |
390 | + }} | |
391 | + /> | |
392 | + )} | |
393 | + </Table> | |
394 | + </Form> | |
395 | + </div> | |
396 | + <Row align="middle" justify="center" style={{ marginTop: 50 }}> | |
397 | + <Button onClick={handleGoBack}>返回列表</Button> | |
398 | + {!isReadOnly && ( | |
399 | + <Button | |
400 | + type="primary" | |
401 | + style={{ marginLeft: 10 }} | |
402 | + loading={saveShopSaleTaskHook.loading} | |
403 | + onClick={handleSaveTask} | |
404 | + > | |
405 | + 保存草稿 | |
406 | + </Button> | |
407 | + )} | |
408 | + </Row> | |
409 | + <SeriesModal | |
410 | + visible={seriesVisible} | |
411 | + selectedIds={selectedIds} | |
412 | + onOk={handleSelectSeries} | |
413 | + onCancel={() => { | |
414 | + setSeriesVisible(false); | |
415 | + }} | |
416 | + /> | |
417 | + </div> | |
418 | + ); | |
419 | +} | ... | ... |
src/pages/order3/SaleTask/subpages/TaskEdit/index.less
0 → 100644
src/pages/order3/SaleTask/subpages/TaskEdit/index.tsx
0 → 100644
1 | +import React, { useEffect, useState } from "react"; | |
2 | +import { Button, Card, Form, Row, Tabs } from "antd"; | |
3 | +import { PageHeaderWrapper } from "@ant-design/pro-layout"; | |
4 | +import { history } from "umi"; | |
5 | +import { Provider, useStore } from "../../store"; | |
6 | +import ShopTask from "./components/ShopTask"; | |
7 | +import AdviserTask from "./components/AdviserTask"; | |
8 | +import useInitial from "@/hooks/useInitail"; | |
9 | +import * as API from "../../api"; | |
10 | +import { isEmpty } from "lodash"; | |
11 | + | |
12 | +export default () => ( | |
13 | + <Provider> | |
14 | + <TaskEdit /> | |
15 | + </Provider> | |
16 | +); | |
17 | + | |
18 | +function TaskEdit() { | |
19 | + const querys: any = history.location?.query; | |
20 | + const readOnly = querys?.readOnly === "1"; | |
21 | + const shopId = querys?.shopId; | |
22 | + const taskDate = querys?.taskDate; | |
23 | + const { shopTaskItem, setShopTaskItem, setIsReadOnly } = useStore(); | |
24 | + const [currStep, setCurrStep] = useState("1"); | |
25 | + const [shopTaskForm] = Form.useForm(); | |
26 | + | |
27 | + // 获取门店零售任务详情 | |
28 | + const { data } = useInitial<API.ShopTaskItem, API.GetShopSaleTaskReq>( | |
29 | + API.getShopSaleTask, | |
30 | + {} as API.ShopTaskItem, | |
31 | + { | |
32 | + shopId, | |
33 | + taskDate, | |
34 | + } | |
35 | + ); | |
36 | + | |
37 | + useEffect(() => { | |
38 | + setShopTaskItem(data); | |
39 | + setIsReadOnly(readOnly); | |
40 | + }, [data, readOnly]); | |
41 | + | |
42 | + return ( | |
43 | + <PageHeaderWrapper title={false}> | |
44 | + <Card | |
45 | + bodyStyle={{ paddingTop: 0 }} | |
46 | + title={<span>当前选择门店:{shopTaskItem?.shopName}</span>} | |
47 | + > | |
48 | + <Tabs | |
49 | + defaultActiveKey={currStep} | |
50 | + onChange={(activeKey) => setCurrStep(activeKey)} | |
51 | + items={[ | |
52 | + { | |
53 | + label: `门店任务分配${readOnly ? "详情" : ""}`, | |
54 | + key: "1", | |
55 | + children: !isEmpty(shopTaskItem) && ( | |
56 | + <ShopTask form={shopTaskForm} /> | |
57 | + ), | |
58 | + }, | |
59 | + { | |
60 | + label: `销售顾问任务分配${readOnly ? "详情" : ""}`, | |
61 | + key: "2", | |
62 | + children: !isEmpty(shopTaskItem) && ( | |
63 | + <AdviserTask form={shopTaskForm} /> | |
64 | + ), | |
65 | + }, | |
66 | + ]} | |
67 | + /> | |
68 | + {isEmpty(shopTaskItem) && ( | |
69 | + <Row align="middle" justify="center" style={{ marginTop: 50 }}> | |
70 | + <Button onClick={() => history.goBack()}>返回列表</Button> | |
71 | + </Row> | |
72 | + )} | |
73 | + </Card> | |
74 | + </PageHeaderWrapper> | |
75 | + ); | |
76 | +} | ... | ... |
src/utils/host.ts
... | ... | @@ -39,6 +39,7 @@ export const PROENGINE_HOST = '/proengine'; |
39 | 39 | export const MCAR_HOST = '/mcar'; |
40 | 40 | /** 订单系统 */ |
41 | 41 | export const ORDER_HOST = '/order'; |
42 | +export const ORDER3_HOST = '/order3'; | |
42 | 43 | /** 卡系统 */ |
43 | 44 | export const CARD_HOST = '/card'; |
44 | 45 | /** 潘多拉系统 */ | ... | ... |