-
mentioned in commit 56c332ee
Showing
11 changed files
src/pages/order3/SaleTask/api.ts
... | ... | @@ -25,6 +25,7 @@ export interface GetSaleTaskApiRes { |
25 | 25 | testDriveTaskCount: number; |
26 | 26 | seriesTaskCount: number; |
27 | 27 | vehicleGrossProfitTask: number; |
28 | + totalGrossProfitTask: number; | |
28 | 29 | } |
29 | 30 | |
30 | 31 | export interface SeriesTaskItem { |
... | ... | @@ -71,6 +72,8 @@ export interface ShopTaskItem { |
71 | 72 | seriesTaskCount: number; |
72 | 73 | vehicleGrossProfitTask: number; |
73 | 74 | taskId?: number; |
75 | + orderTaskApplyId?: number; | |
76 | + orderShopTaskId?: number; | |
74 | 77 | } |
75 | 78 | |
76 | 79 | /** 月度零售任务列表 */ |
... | ... | @@ -81,8 +84,8 @@ export function getSaleTaskApi( |
81 | 84 | } |
82 | 85 | |
83 | 86 | export interface GetShopSaleTaskReq { |
84 | - shopId: number; | |
85 | - taskDate: number; | |
87 | + shopId?: number; | |
88 | + taskDate?: number; | |
86 | 89 | } |
87 | 90 | |
88 | 91 | /** 门店零售任务详情 */ |
... | ... | @@ -109,6 +112,54 @@ export function submitSaleTask(params: { id: number }): PromiseResp<boolean> { |
109 | 112 | return request.post(`${ORDER3_HOST}/erp/sales/task/submit`, params); |
110 | 113 | } |
111 | 114 | |
115 | +export interface AutoAssignItem { | |
116 | + shopId: number; | |
117 | + taskCount: number; | |
118 | + newEnergyTaskCount: number; | |
119 | + fuelVehicleTaskCount: number; | |
120 | + tackCarTaskCount: number; | |
121 | +} | |
122 | + | |
123 | +export interface AutoAssignSaleTaskReq { | |
124 | + id: number; | |
125 | + assignTask: boolean; | |
126 | + shopTaskList: AutoAssignItem[]; | |
127 | +} | |
128 | + | |
129 | +/** 自动分配零售任务 */ | |
130 | +export function autoAssignSaleTask( | |
131 | + params: AutoAssignSaleTaskReq | |
132 | +): PromiseResp<boolean> { | |
133 | + return request.post(`${ORDER3_HOST}/erp/sales/task/auto/assign`, params); | |
134 | +} | |
135 | + | |
136 | +interface BatchSetSaleTaskItem { | |
137 | + shopIdList: number[]; | |
138 | + taskAims: number; | |
139 | +} | |
140 | + | |
141 | +export interface BatchSetSaleTaskReq { | |
142 | + grossProfitTaskList?: BatchSetSaleTaskItem[]; | |
143 | + tackCarTaskList?: BatchSetSaleTaskItem[]; | |
144 | + testDriveTaskList?: BatchSetSaleTaskItem[]; | |
145 | + assignTask: boolean; | |
146 | + orderTaskApplyId: number; | |
147 | +} | |
148 | + | |
149 | +/** 批量设置零售任务 */ | |
150 | +export function batchSetSaleTask( | |
151 | + params: BatchSetSaleTaskReq | |
152 | +): PromiseResp<boolean> { | |
153 | + return request.post(`${ORDER3_HOST}/erp/sales/task/batch/shop/set`, params); | |
154 | +} | |
155 | + | |
156 | +/** 自动分配单个门店的零售任务 */ | |
157 | +export function autoAssignOneShop( | |
158 | + params: ShopTaskItem | |
159 | +): PromiseResp<boolean> { | |
160 | + return request.post(`${ORDER3_HOST}/erp/sales/task/auto/assign/shop`, params); | |
161 | +} | |
162 | + | |
112 | 163 | export interface BrandItem { |
113 | 164 | id: number; |
114 | 165 | initial: string; |
... | ... | @@ -148,9 +199,10 @@ export interface PreviewTaskReq { |
148 | 199 | secondManageId?: number; |
149 | 200 | thirdManageId?: number; |
150 | 201 | taskId?: number; |
151 | - orderTaskApprovalType: number; | |
152 | - id: number; | |
202 | + orderTaskApprovalType?: number; | |
203 | + id?: number; | |
153 | 204 | token?: string; |
205 | + keywords?: string; | |
154 | 206 | } |
155 | 207 | |
156 | 208 | export interface TaskListItem { |
... | ... | @@ -188,7 +240,9 @@ export interface PreviewTaskRes { |
188 | 240 | } |
189 | 241 | |
190 | 242 | /** 预览任务 */ |
191 | -export function previewTask(params: PreviewTaskReq): PromiseResp<PreviewTaskRes> { | |
243 | +export function previewTask( | |
244 | + params: PreviewTaskReq | |
245 | +): PromiseResp<PreviewTaskRes> { | |
192 | 246 | return request.get(`${ORDER3_HOST}/erp/sales/task/approve/info`, { |
193 | 247 | params, |
194 | 248 | }); | ... | ... |
src/pages/order3/SaleTask/components/AdviserTaskPreview.tsx
... | ... | @@ -4,8 +4,7 @@ import { observer } from "mobx-react-lite"; |
4 | 4 | import * as API from "../api"; |
5 | 5 | import useInitial from "@/hooks/useInitail"; |
6 | 6 | import ModifiedTableCell from "./ModifiedTableCell"; |
7 | - | |
8 | -const { Column } = Table; | |
7 | +import { ColumnsType } from "antd/es/table"; | |
9 | 8 | |
10 | 9 | // 查看销顾任务弹框 |
11 | 10 | interface AdviserTaskPreviewProps { |
... | ... | @@ -31,73 +30,100 @@ const AdviserTaskPreview = ({ |
31 | 30 | showSeriesModal(record); |
32 | 31 | }; |
33 | 32 | |
33 | + const columns: ColumnsType<API.TaskListItem> = [ | |
34 | + { | |
35 | + title: "姓名", | |
36 | + width: 100, | |
37 | + dataIndex: "dataName", | |
38 | + filterSearch: true, | |
39 | + onFilter: ( | |
40 | + value: string | number | boolean, | |
41 | + record: API.TaskListItem | |
42 | + ) => { | |
43 | + return record.dataName.startsWith(value.toString()); | |
44 | + }, | |
45 | + }, | |
46 | + { | |
47 | + title: "零售任务(台)", | |
48 | + children: [ | |
49 | + { | |
50 | + title: "合计", | |
51 | + dataIndex: "taskCount", | |
52 | + key: "taskCount", | |
53 | + render: (text: string, record: API.TaskListItem) => { | |
54 | + return ModifiedTableCell(record, "taskCount"); | |
55 | + }, | |
56 | + }, | |
57 | + { | |
58 | + title: "新能源车", | |
59 | + dataIndex: "newEnergyTaskCount", | |
60 | + key: "newEnergyTaskCount", | |
61 | + render: (text: string, record: API.TaskListItem) => { | |
62 | + return ModifiedTableCell(record, "newEnergyTaskCount"); | |
63 | + }, | |
64 | + }, | |
65 | + { | |
66 | + title: "传统燃油车", | |
67 | + dataIndex: "fuelVehicleTaskCount", | |
68 | + key: "fuelVehicleTaskCount", | |
69 | + render: (text: string, record: API.TaskListItem) => { | |
70 | + return ModifiedTableCell(record, "fuelVehicleTaskCount"); | |
71 | + }, | |
72 | + }, | |
73 | + ], | |
74 | + }, | |
75 | + { | |
76 | + title: "单车毛利任务(元)", | |
77 | + dataIndex: "vehicleGrossProfitTask", | |
78 | + render: (text: string, record: API.TaskListItem) => { | |
79 | + return ModifiedTableCell(record, "vehicleGrossProfitTask"); | |
80 | + }, | |
81 | + }, | |
82 | + { | |
83 | + title: "线索到店成交(台)", | |
84 | + width: 100, | |
85 | + dataIndex: "clueDealTaskCount", | |
86 | + render: (text: string, record: API.TaskListItem) => { | |
87 | + return ModifiedTableCell(record, "clueDealTaskCount"); | |
88 | + }, | |
89 | + }, | |
90 | + { | |
91 | + title: "首客试驾成交(台)", | |
92 | + width: 100, | |
93 | + dataIndex: "testDriveTaskCount", | |
94 | + render: (text: string, record: API.TaskListItem) => { | |
95 | + return ModifiedTableCell(record, "testDriveTaskCount"); | |
96 | + }, | |
97 | + }, | |
98 | + { | |
99 | + title: "攻坚车任务(台)", | |
100 | + width: 100, | |
101 | + dataIndex: "tackCarTaskCount", | |
102 | + render: (text: string, record: API.TaskListItem) => { | |
103 | + return ModifiedTableCell(record, "tackCarTaskCount"); | |
104 | + }, | |
105 | + }, | |
106 | + { | |
107 | + title: "车系任务(台)", | |
108 | + width: 100, | |
109 | + dataIndex: "seriesTaskCount", | |
110 | + render: (text: string, record: API.TaskListItem) => { | |
111 | + if (record.dataId === -999) return text; | |
112 | + return <a onClick={() => handlePreviewSeriesTask(record)}>{text}</a>; | |
113 | + }, | |
114 | + }, | |
115 | + ]; | |
116 | + | |
34 | 117 | return ( |
35 | 118 | <Table |
119 | + bordered | |
36 | 120 | rowKey="dataId" |
37 | 121 | loading={loading} |
122 | + columns={columns} | |
38 | 123 | dataSource={data.taskList} |
39 | 124 | pagination={false} |
40 | 125 | 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> | |
126 | + /> | |
101 | 127 | ); |
102 | 128 | }; |
103 | 129 | ... | ... |
src/pages/order3/SaleTask/components/EntryTaskPreview.tsx
1 | 1 | import React, { useState } from "react"; |
2 | -import { Card, Radio, RadioChangeEvent, Row, Table } from "antd"; | |
2 | +import { | |
3 | + Card, | |
4 | + DatePicker, | |
5 | + Input, | |
6 | + Radio, | |
7 | + RadioChangeEvent, | |
8 | + Row, | |
9 | + Table, | |
10 | +} from "antd"; | |
3 | 11 | import { observer } from "mobx-react-lite"; |
4 | 12 | import * as API from "../api"; |
5 | 13 | import { OrderTaskApprovalType } from "../entity"; |
6 | 14 | import useInitial from "@/hooks/useInitail"; |
7 | 15 | import ModifiedTableCell from "./ModifiedTableCell"; |
16 | +import { ColumnsType } from "antd/es/table"; | |
17 | +import { Moment } from "moment"; | |
8 | 18 | |
9 | -const { Column } = Table; | |
10 | 19 | const RadioButton = Radio.Button; |
11 | 20 | const RadioGroup = Radio.Group; |
12 | 21 | |
13 | 22 | // 预览任务入口弹框 |
14 | 23 | interface EntryTaskPreviewProps { |
24 | + month: Moment; | |
15 | 25 | params: any; // API.PreviewTaskReq |
16 | 26 | showAdviserModal: ( |
17 | 27 | record: API.TaskListItem, |
... | ... | @@ -24,11 +34,13 @@ interface EntryTaskPreviewProps { |
24 | 34 | } |
25 | 35 | |
26 | 36 | const EntryTaskPreview = ({ |
37 | + month, | |
27 | 38 | params, |
28 | 39 | showAdviserModal, |
29 | 40 | showSeriesModal, |
30 | 41 | }: EntryTaskPreviewProps) => { |
31 | 42 | const [type, setType] = useState(OrderTaskApprovalType.门店维度); |
43 | + const [keywords, setKeywords] = useState(""); | |
32 | 44 | |
33 | 45 | const { data, loading, setParams } = useInitial< |
34 | 46 | API.PreviewTaskRes, |
... | ... | @@ -40,15 +52,13 @@ const EntryTaskPreview = ({ |
40 | 52 | if (value === 99) { |
41 | 53 | setType(OrderTaskApprovalType.新车一级管理维度); |
42 | 54 | setParams( |
43 | - // @ts-ignore | |
44 | 55 | { orderTaskApprovalType: OrderTaskApprovalType.新车一级管理维度 }, |
45 | 56 | true |
46 | 57 | ); |
47 | 58 | return; |
48 | 59 | } |
49 | 60 | setType(value); |
50 | - // @ts-ignore | |
51 | - setParams({ orderTaskApprovalType: value }, true); | |
61 | + setParams({ orderTaskApprovalType: value, keywords }, true); | |
52 | 62 | }; |
53 | 63 | |
54 | 64 | // 查看顾问任务 |
... | ... | @@ -67,130 +77,171 @@ const EntryTaskPreview = ({ |
67 | 77 | showSeriesModal(record, type); |
68 | 78 | }; |
69 | 79 | |
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) => { | |
80 | + const columns: ColumnsType<API.TaskListItem> = [ | |
81 | + { | |
82 | + title: type === OrderTaskApprovalType.门店维度 ? "门店" : "姓名", | |
83 | + width: type === OrderTaskApprovalType.门店维度 ? 150 : 100, | |
84 | + dataIndex: "dataName", | |
85 | + filterSearch: true, | |
86 | + onFilter: ( | |
87 | + value: string | number | boolean, | |
88 | + record: API.TaskListItem | |
89 | + ) => { | |
90 | + return record.dataName.startsWith(value.toString()); | |
91 | + }, | |
92 | + }, | |
93 | + { | |
94 | + title: "零售任务(台)", | |
95 | + children: [ | |
96 | + { | |
97 | + title: "合计", | |
98 | + dataIndex: "taskCount", | |
99 | + key: "taskCount", | |
100 | + render: (text: string, record: API.TaskListItem) => { | |
126 | 101 | return ModifiedTableCell(record, "taskCount"); |
127 | - }} | |
128 | - /> | |
129 | - <Column | |
130 | - title="新能源车任务(台)" | |
131 | - dataIndex="newEnergyTaskCount" | |
132 | - render={(text: string, record: API.TaskListItem) => { | |
102 | + }, | |
103 | + }, | |
104 | + { | |
105 | + title: "新能源车", | |
106 | + dataIndex: "newEnergyTaskCount", | |
107 | + key: "newEnergyTaskCount", | |
108 | + render: (text: string, record: API.TaskListItem) => { | |
133 | 109 | return ModifiedTableCell(record, "newEnergyTaskCount"); |
134 | - }} | |
135 | - /> | |
136 | - <Column | |
137 | - title="传统燃油车任务(台)" | |
138 | - dataIndex="fuelVehicleTaskCount" | |
139 | - render={(text: string, record: API.TaskListItem) => { | |
110 | + }, | |
111 | + }, | |
112 | + { | |
113 | + title: "传统燃油车", | |
114 | + dataIndex: "fuelVehicleTaskCount", | |
115 | + key: "fuelVehicleTaskCount", | |
116 | + render: (text: string, record: API.TaskListItem) => { | |
140 | 117 | 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 | - }} | |
118 | + }, | |
119 | + }, | |
120 | + ], | |
121 | + }, | |
122 | + { | |
123 | + title: "单车毛利任务(元)", | |
124 | + dataIndex: "vehicleGrossProfitTask", | |
125 | + render: (text: string, record: API.TaskListItem) => { | |
126 | + return ModifiedTableCell(record, "vehicleGrossProfitTask"); | |
127 | + }, | |
128 | + }, | |
129 | + { | |
130 | + title: "线索到店成交(台)", | |
131 | + width: 100, | |
132 | + dataIndex: "clueDealTaskCount", | |
133 | + render: (text: string, record: API.TaskListItem) => { | |
134 | + return ModifiedTableCell(record, "clueDealTaskCount"); | |
135 | + }, | |
136 | + }, | |
137 | + { | |
138 | + title: "首客试驾成交(台)", | |
139 | + width: 100, | |
140 | + dataIndex: "testDriveTaskCount", | |
141 | + render: (text: string, record: API.TaskListItem) => { | |
142 | + return ModifiedTableCell(record, "testDriveTaskCount"); | |
143 | + }, | |
144 | + }, | |
145 | + { | |
146 | + title: "攻坚车任务(台)", | |
147 | + width: 100, | |
148 | + dataIndex: "tackCarTaskCount", | |
149 | + render: (text: string, record: API.TaskListItem) => { | |
150 | + return ModifiedTableCell(record, "tackCarTaskCount"); | |
151 | + }, | |
152 | + }, | |
153 | + { | |
154 | + title: "车系任务(台)", | |
155 | + width: 100, | |
156 | + dataIndex: "seriesTaskCount", | |
157 | + render: (text: string, record: API.TaskListItem) => { | |
158 | + if (record.dataId === -999) return text; | |
159 | + return <a onClick={() => handlePreviewSeriesTask(record)}>{text}</a>; | |
160 | + }, | |
161 | + }, | |
162 | + ]; | |
163 | + | |
164 | + const extraColumns: ColumnsType<API.TaskListItem> = [ | |
165 | + { | |
166 | + title: "销顾任务", | |
167 | + render: (text: string, record: API.TaskListItem) => { | |
168 | + if (record.dataId === -999) { | |
169 | + return "-"; | |
170 | + } | |
171 | + return <a onClick={() => handlePreviewAdviserTask(record)}>查看</a>; | |
172 | + }, | |
173 | + }, | |
174 | + ]; | |
175 | + | |
176 | + return ( | |
177 | + <> | |
178 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
179 | + <DatePicker | |
180 | + style={{ width: 245 }} | |
181 | + picker="month" | |
182 | + value={month} | |
183 | + allowClear={false} | |
184 | + disabled | |
163 | 185 | /> |
164 | - <Column | |
165 | - title="攻坚车任务数(台)" | |
166 | - dataIndex="tackCarTaskCount" | |
167 | - render={(text: string, record: API.TaskListItem) => { | |
168 | - return ModifiedTableCell(record, "tackCarTaskCount"); | |
186 | + <Input.Search | |
187 | + allowClear | |
188 | + placeholder="搜索门店或顾问" | |
189 | + style={{ width: 263, marginLeft: 20 }} | |
190 | + value={keywords} | |
191 | + onChange={(e) => setKeywords(e.target.value)} | |
192 | + onSearch={(v) => { | |
193 | + setParams({ keywords: v }, true); | |
169 | 194 | }} |
170 | 195 | /> |
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 | - }} | |
196 | + </Row> | |
197 | + <Card | |
198 | + title={ | |
199 | + <Row align="middle" justify="start"> | |
200 | + <RadioGroup onChange={handleChangeType} value={type}> | |
201 | + <RadioButton value={OrderTaskApprovalType.门店维度}> | |
202 | + 门店 | |
203 | + </RadioButton> | |
204 | + <RadioButton value={OrderTaskApprovalType.销售顾问维度}> | |
205 | + 销顾 | |
206 | + </RadioButton> | |
207 | + <RadioButton value={99}>销售管理层</RadioButton> | |
208 | + </RadioGroup> | |
209 | + {type !== OrderTaskApprovalType.门店维度 && | |
210 | + type !== OrderTaskApprovalType.销售顾问维度 && ( | |
211 | + <RadioGroup | |
212 | + onChange={handleChangeType} | |
213 | + value={type} | |
214 | + style={{ marginLeft: 20 }} | |
215 | + > | |
216 | + <RadioButton value={OrderTaskApprovalType.新车一级管理维度}> | |
217 | + 销售一级管理 | |
218 | + </RadioButton> | |
219 | + <RadioButton value={OrderTaskApprovalType.新车二级管理维度}> | |
220 | + 销售二级管理 | |
221 | + </RadioButton> | |
222 | + <RadioButton value={OrderTaskApprovalType.新车三级管理维度}> | |
223 | + 销售三级管理 | |
224 | + </RadioButton> | |
225 | + </RadioGroup> | |
226 | + )} | |
227 | + </Row> | |
228 | + } | |
229 | + > | |
230 | + <Table | |
231 | + bordered | |
232 | + rowKey="dataId" | |
233 | + columns={ | |
234 | + type === OrderTaskApprovalType.门店维度 | |
235 | + ? columns.concat(extraColumns) | |
236 | + : columns | |
237 | + } | |
238 | + loading={loading} | |
239 | + dataSource={data.taskList} | |
240 | + pagination={false} | |
241 | + scroll={{ y: 450 }} | |
180 | 242 | /> |
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> | |
243 | + </Card> | |
244 | + </> | |
194 | 245 | ); |
195 | 246 | }; |
196 | 247 | ... | ... |
src/pages/order3/SaleTask/components/SaleTaskAutoAssign.tsx
0 → 100644
1 | +import React, { useContext, useEffect, useRef, useState } from "react"; | |
2 | +import { | |
3 | + Table, | |
4 | + Form, | |
5 | + InputRef, | |
6 | + Row, | |
7 | + Button, | |
8 | + message, | |
9 | + Modal, | |
10 | + InputNumber, | |
11 | +} from "antd"; | |
12 | +import type { FormInstance } from "antd/es/form"; | |
13 | +import * as API from "../api"; | |
14 | +import "./index.less"; | |
15 | +import { MAX_NUM } from "../entity"; | |
16 | + | |
17 | +type EditableTableProps = Parameters<typeof Table>[0]; | |
18 | +type ColumnTypes = Exclude<EditableTableProps["columns"], undefined>; | |
19 | +interface Item { | |
20 | + id: string; | |
21 | + shopName: string; | |
22 | + taskCount: number; | |
23 | + newEnergyTaskCount: number; | |
24 | + fuelVehicleTaskCount: number; | |
25 | + tackCarTaskCount: number; | |
26 | +} | |
27 | +interface EditableRowProps { | |
28 | + index: number; | |
29 | +} | |
30 | +interface EditableCellProps { | |
31 | + title: React.ReactNode; | |
32 | + editable: boolean; | |
33 | + children: React.ReactNode; | |
34 | + dataIndex: keyof Item; | |
35 | + record: Item; | |
36 | + handleSave: (record: Item) => void; | |
37 | +} | |
38 | + | |
39 | +const defaultColumns: (ColumnTypes[number] & { | |
40 | + editable?: boolean; | |
41 | + dataIndex: string; | |
42 | +})[] = [ | |
43 | + { | |
44 | + title: "门店", | |
45 | + dataIndex: "shopName", | |
46 | + editable: false, | |
47 | + }, | |
48 | + { | |
49 | + title: "零售任务(台)", | |
50 | + dataIndex: "taskCount", | |
51 | + editable: true, | |
52 | + }, | |
53 | + { | |
54 | + title: "新能源车任务(台)", | |
55 | + dataIndex: "newEnergyTaskCount", | |
56 | + editable: true, | |
57 | + }, | |
58 | + { | |
59 | + title: "传统燃油车任务(台)", | |
60 | + dataIndex: "fuelVehicleTaskCount", | |
61 | + editable: false, | |
62 | + }, | |
63 | + { | |
64 | + title: "攻坚车任务(台)", | |
65 | + dataIndex: "tackCarTaskCount", | |
66 | + editable: true, | |
67 | + }, | |
68 | +]; | |
69 | + | |
70 | +interface SaleTaskAutoAssignProps { | |
71 | + id: number; | |
72 | + value?: API.ShopTaskItem[]; | |
73 | + onCancel: () => void; | |
74 | + onRefresh: () => void; | |
75 | +} | |
76 | + | |
77 | +export default function SaleTaskAutoAssign(props: SaleTaskAutoAssignProps) { | |
78 | + const EditableContext = React.createContext<FormInstance<any> | null>(null); | |
79 | + const [dataSource, setDataSource] = useState<API.ShopTaskItem[]>([]); | |
80 | + | |
81 | + useEffect(() => { | |
82 | + setDataSource(props.value ? [...props.value] : []); | |
83 | + }, [props.value]); | |
84 | + | |
85 | + const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => { | |
86 | + const [form] = Form.useForm(); | |
87 | + return ( | |
88 | + <Form form={form} component={false}> | |
89 | + <EditableContext.Provider value={form}> | |
90 | + <tr {...props} /> | |
91 | + </EditableContext.Provider> | |
92 | + </Form> | |
93 | + ); | |
94 | + }; | |
95 | + | |
96 | + const EditableCell: React.FC<EditableCellProps> = ({ | |
97 | + title, | |
98 | + editable, | |
99 | + children, | |
100 | + dataIndex, | |
101 | + record, | |
102 | + handleSave, | |
103 | + ...restProps | |
104 | + }) => { | |
105 | + const [editing, setEditing] = useState(false); | |
106 | + const inputRef = useRef<InputRef>(null); | |
107 | + const form = useContext(EditableContext)!; | |
108 | + | |
109 | + useEffect(() => { | |
110 | + if (editing) { | |
111 | + inputRef.current!.focus(); | |
112 | + } | |
113 | + }, [editing]); | |
114 | + | |
115 | + const toggleEdit = () => { | |
116 | + setEditing(!editing); | |
117 | + form.setFieldsValue({ [dataIndex]: record[dataIndex] }); | |
118 | + }; | |
119 | + | |
120 | + const save = async () => { | |
121 | + try { | |
122 | + const values = await form.validateFields(); | |
123 | + toggleEdit(); | |
124 | + handleSave({ ...record, ...values }); | |
125 | + } catch (errInfo) { | |
126 | + console.log("Save failed:", errInfo); | |
127 | + } | |
128 | + }; | |
129 | + | |
130 | + let childNode = children; | |
131 | + | |
132 | + if (editable) { | |
133 | + childNode = editing ? ( | |
134 | + <Form.Item | |
135 | + noStyle | |
136 | + name={dataIndex} | |
137 | + rules={[ | |
138 | + { | |
139 | + required: true, | |
140 | + message: `请输入${title}`, | |
141 | + }, | |
142 | + ]} | |
143 | + > | |
144 | + <InputNumber | |
145 | + ref={inputRef} | |
146 | + min={0} | |
147 | + max={MAX_NUM} | |
148 | + style={{ width: "80px" }} | |
149 | + onPressEnter={save} | |
150 | + onBlur={save} | |
151 | + /> | |
152 | + </Form.Item> | |
153 | + ) : ( | |
154 | + <div className="editable-cell-value-wrap" onClick={toggleEdit}> | |
155 | + {children} | |
156 | + </div> | |
157 | + ); | |
158 | + } | |
159 | + | |
160 | + return <td {...restProps}>{childNode}</td>; | |
161 | + }; | |
162 | + | |
163 | + const handleSave = (row: API.ShopTaskItem) => { | |
164 | + const newData = [...dataSource]; | |
165 | + const index = newData.findIndex((item) => row.id === item.id); | |
166 | + const item = newData[index]; | |
167 | + if (row.taskCount !== 0 && row.newEnergyTaskCount > row.taskCount) { | |
168 | + message.warn("新能源车任务台数不得超过零售任务台数"); | |
169 | + return; | |
170 | + } | |
171 | + const newRow = { | |
172 | + ...item, | |
173 | + ...row, | |
174 | + fuelVehicleTaskCount: row.taskCount - row.newEnergyTaskCount, | |
175 | + }; | |
176 | + if (row.taskCount === 0) { | |
177 | + newRow.taskCount = 0; | |
178 | + newRow.newEnergyTaskCount = 0; | |
179 | + newRow.fuelVehicleTaskCount = 0; | |
180 | + } | |
181 | + newData.splice(index, 1, newRow); | |
182 | + setDataSource(newData); | |
183 | + }; | |
184 | + | |
185 | + const autoAssignSaleTask = (isAssignToAdviser: boolean) => { | |
186 | + Modal.confirm({ | |
187 | + title: isAssignToAdviser ? ( | |
188 | + <span> | |
189 | + 确认分配到 | |
190 | + <span className="tip">全部门店和顾问</span> | |
191 | + 吗? | |
192 | + </span> | |
193 | + ) : ( | |
194 | + <span> | |
195 | + 确认分配到 | |
196 | + <span className="tip">全部门店</span> | |
197 | + 吗? | |
198 | + </span> | |
199 | + ), | |
200 | + zIndex: 1002, | |
201 | + onOk: async () => { | |
202 | + const hide = message.loading("分配中,请稍候", 0); | |
203 | + API.autoAssignSaleTask({ | |
204 | + id: props.id, | |
205 | + shopTaskList: dataSource.map((item) => ({ | |
206 | + shopId: item.shopId, | |
207 | + taskCount: item.taskCount, | |
208 | + newEnergyTaskCount: item.newEnergyTaskCount, | |
209 | + fuelVehicleTaskCount: item.fuelVehicleTaskCount, | |
210 | + tackCarTaskCount: item.tackCarTaskCount, | |
211 | + })), | |
212 | + assignTask: isAssignToAdviser, | |
213 | + }) | |
214 | + .then((res) => { | |
215 | + message.success("分配成功"); | |
216 | + props.onRefresh(); | |
217 | + }) | |
218 | + .catch((error: any) => { | |
219 | + message.error(error.message ?? "请求失败"); | |
220 | + }) | |
221 | + .finally(() => { | |
222 | + hide(); | |
223 | + }); | |
224 | + }, | |
225 | + }); | |
226 | + }; | |
227 | + | |
228 | + const components = { | |
229 | + body: { | |
230 | + row: EditableRow, | |
231 | + cell: EditableCell, | |
232 | + }, | |
233 | + }; | |
234 | + | |
235 | + const columns = defaultColumns.map((col) => { | |
236 | + if (!col.editable) { | |
237 | + return col; | |
238 | + } | |
239 | + return { | |
240 | + ...col, | |
241 | + onCell: (record: API.ShopTaskItem) => ({ | |
242 | + record, | |
243 | + editable: col.editable, | |
244 | + dataIndex: col.dataIndex, | |
245 | + title: col.title, | |
246 | + handleSave, | |
247 | + }), | |
248 | + }; | |
249 | + }); | |
250 | + | |
251 | + return ( | |
252 | + <> | |
253 | + <Table | |
254 | + components={components} | |
255 | + rowClassName={() => "editable-row"} | |
256 | + bordered | |
257 | + rowKey="id" | |
258 | + dataSource={dataSource} | |
259 | + columns={columns as ColumnTypes} | |
260 | + /> | |
261 | + <Row align="middle" justify="center" style={{ marginTop: 20 }}> | |
262 | + <Button onClick={props.onCancel}>取消</Button> | |
263 | + <Button | |
264 | + type="primary" | |
265 | + style={{ marginLeft: 10 }} | |
266 | + onClick={() => autoAssignSaleTask(false)} | |
267 | + > | |
268 | + 分配到门店 | |
269 | + </Button> | |
270 | + <Button | |
271 | + type="primary" | |
272 | + style={{ marginLeft: 10 }} | |
273 | + onClick={() => autoAssignSaleTask(true)} | |
274 | + > | |
275 | + 分配到门店和顾问 | |
276 | + </Button> | |
277 | + </Row> | |
278 | + </> | |
279 | + ); | |
280 | +} | ... | ... |
src/pages/order3/SaleTask/components/SaleTaskBatchSet.tsx
0 → 100644
1 | +import { PlusOutlined } from "@ant-design/icons"; | |
2 | +import { | |
3 | + Button, | |
4 | + Card, | |
5 | + Col, | |
6 | + Form, | |
7 | + InputNumber, | |
8 | + Modal, | |
9 | + Row, | |
10 | + message, | |
11 | +} from "antd"; | |
12 | +import "./index.less"; | |
13 | +import React, { useState } from "react"; | |
14 | +import { MAX_NUM } from "../entity"; | |
15 | +import * as API from "../api"; | |
16 | +import ShopSelectNew from "@/components/ShopSelectNew"; | |
17 | +import { isArray } from "lodash"; | |
18 | + | |
19 | +interface SaleTaskBatchSetProps { | |
20 | + id: number; | |
21 | + onCancel: () => void; | |
22 | + onRefresh: () => void; | |
23 | +} | |
24 | + | |
25 | +export default function SaleTaskBatchSet(props: SaleTaskBatchSetProps) { | |
26 | + const [form] = Form.useForm(); | |
27 | + // 过滤各项已经选择的门店 | |
28 | + // const [selectedShopIds, setSelectedShopIds] = useState({ | |
29 | + // grossProfit: [], | |
30 | + // tackCar: [], | |
31 | + // testDrive: [], | |
32 | + // }); | |
33 | + | |
34 | + const batchSetSaleTask = async (isAssignToAdviser: boolean) => { | |
35 | + await form.validateFields(); | |
36 | + const values = form.getFieldsValue(); | |
37 | + if ( | |
38 | + Object.values(values).every( | |
39 | + (item) => !item || (isArray(item) && item.length === 0) | |
40 | + ) | |
41 | + ) { | |
42 | + message.warn("请设置任务后再进行分配"); | |
43 | + return; | |
44 | + } | |
45 | + const newValues = {}; | |
46 | + Array.from(Object.keys(values)).forEach((valueKey: any) => { | |
47 | + if (values[valueKey]) { | |
48 | + newValues[valueKey] = values[valueKey].map((valueItem: any) => ({ | |
49 | + taskAims: valueItem.taskAims, | |
50 | + shopIdList: valueItem.shopIdList.map( | |
51 | + (shopItem: any) => shopItem.shopId | |
52 | + ), | |
53 | + })); | |
54 | + } | |
55 | + }); | |
56 | + Modal.confirm({ | |
57 | + title: isAssignToAdviser ? ( | |
58 | + <span> | |
59 | + 确认分配到 | |
60 | + <span className="tip">全部门店和顾问</span> | |
61 | + 吗? | |
62 | + </span> | |
63 | + ) : ( | |
64 | + <span> | |
65 | + 确认分配到 | |
66 | + <span className="tip">全部门店</span> | |
67 | + 吗? | |
68 | + </span> | |
69 | + ), | |
70 | + zIndex: 1002, | |
71 | + onOk: async () => { | |
72 | + const hide = message.loading("分配中,请稍候", 0); | |
73 | + API.batchSetSaleTask({ | |
74 | + assignTask: isAssignToAdviser, | |
75 | + orderTaskApplyId: props.id, | |
76 | + ...newValues, | |
77 | + }) | |
78 | + .then((res) => { | |
79 | + message.success("分配成功"); | |
80 | + props.onRefresh(); | |
81 | + }) | |
82 | + .catch((error: any) => { | |
83 | + message.error(error.message ?? "请求失败"); | |
84 | + }) | |
85 | + .finally(() => { | |
86 | + hide(); | |
87 | + }); | |
88 | + }, | |
89 | + }); | |
90 | + }; | |
91 | + | |
92 | + // const handleFormChange = (changedValues: any) => { | |
93 | + // const labelKey: any = Object.keys(changedValues)[0]; | |
94 | + // const list: any = Object.values(changedValues)[0]; | |
95 | + // console.log(list); | |
96 | + // if (!list[0]) return; | |
97 | + // if (Object.keys(list[0])[0] === "shopIdList") { | |
98 | + // const newSelectedIds = { ...selectedShopIds }; | |
99 | + // newSelectedIds[labelKey] = Object.values(list[0])[0]; | |
100 | + // setSelectedShopIds(newSelectedIds); | |
101 | + // } | |
102 | + // }; | |
103 | + | |
104 | + return ( | |
105 | + <Form | |
106 | + form={form} | |
107 | + name="sale-task-batch-set-form" | |
108 | + autoComplete="off" | |
109 | + // onValuesChange={handleFormChange} | |
110 | + > | |
111 | + <Form.List name="grossProfitTaskList"> | |
112 | + {(fields, { add, remove }) => ( | |
113 | + <Card> | |
114 | + <Row | |
115 | + align="middle" | |
116 | + justify="space-between" | |
117 | + style={{ marginBottom: 15 }} | |
118 | + > | |
119 | + <h4 className="title">单车毛利任务</h4> | |
120 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | |
121 | + 新增 | |
122 | + </Button> | |
123 | + </Row> | |
124 | + {fields.map(({ key, name, ...restField }) => ( | |
125 | + <Row gutter={16} key={key}> | |
126 | + <Col className="gutter-row" span={6}> | |
127 | + <Form.Item | |
128 | + {...restField} | |
129 | + name={[name, "taskAims"]} | |
130 | + rules={[{ required: true, message: "请填写单车毛利任务" }]} | |
131 | + > | |
132 | + <InputNumber | |
133 | + formatter={(value) => `${value}元`} | |
134 | + parser={(value: any) => value.replace("元", "")} | |
135 | + min={0} | |
136 | + max={MAX_NUM} | |
137 | + style={{ width: "100%" }} | |
138 | + precision={2} | |
139 | + placeholder="请填写单车毛利任务" | |
140 | + /> | |
141 | + </Form.Item> | |
142 | + </Col> | |
143 | + <Col className="gutter-row" span={15}> | |
144 | + <Form.Item | |
145 | + {...restField} | |
146 | + name={[name, "shopIdList"]} | |
147 | + rules={[{ required: true, message: "请选择适用门店" }]} | |
148 | + > | |
149 | + <ShopSelectNew | |
150 | + multiple | |
151 | + defaultOptions={{ bizTypes: "1" }} | |
152 | + placeholder="请选择适用门店" | |
153 | + // disabledShopIds={selectedShopIds.grossProfit} | |
154 | + /> | |
155 | + </Form.Item> | |
156 | + </Col> | |
157 | + <Col className="gutter-row" span={3}> | |
158 | + <Button | |
159 | + type="link" | |
160 | + style={{ color: "#999" }} | |
161 | + onClick={() => remove(name)} | |
162 | + > | |
163 | + 删除 | |
164 | + </Button> | |
165 | + </Col> | |
166 | + </Row> | |
167 | + ))} | |
168 | + </Card> | |
169 | + )} | |
170 | + </Form.List> | |
171 | + <Form.List name="testDriveTaskList"> | |
172 | + {(fields, { add, remove }) => ( | |
173 | + <Card style={{ marginTop: 20 }}> | |
174 | + <Row | |
175 | + align="middle" | |
176 | + justify="space-between" | |
177 | + style={{ marginBottom: 15 }} | |
178 | + > | |
179 | + <h4 className="title">首客试驾成交</h4> | |
180 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | |
181 | + 新增 | |
182 | + </Button> | |
183 | + </Row> | |
184 | + {fields.map(({ key, name, ...restField }) => ( | |
185 | + <Row gutter={16} key={key}> | |
186 | + <Col className="gutter-row" span={6}> | |
187 | + <Form.Item | |
188 | + {...restField} | |
189 | + name={[name, "taskAims"]} | |
190 | + rules={[{ required: true, message: "请填写首客试驾成交" }]} | |
191 | + > | |
192 | + <InputNumber | |
193 | + formatter={(value) => `${value}台`} | |
194 | + parser={(value: any) => value.replace("台", "")} | |
195 | + min={0} | |
196 | + max={MAX_NUM} | |
197 | + style={{ width: "100%" }} | |
198 | + precision={0} | |
199 | + placeholder="请填写首客试驾成交" | |
200 | + /> | |
201 | + </Form.Item> | |
202 | + </Col> | |
203 | + <Col className="gutter-row" span={15}> | |
204 | + <Form.Item | |
205 | + {...restField} | |
206 | + name={[name, "shopIdList"]} | |
207 | + rules={[{ required: true, message: "请选择适用门店" }]} | |
208 | + > | |
209 | + <ShopSelectNew | |
210 | + multiple | |
211 | + defaultOptions={{ bizTypes: "1" }} | |
212 | + placeholder="请选择适用门店" | |
213 | + // disabledShopIds={selectedShopIds.testDrive} | |
214 | + /> | |
215 | + </Form.Item> | |
216 | + </Col> | |
217 | + <Col className="gutter-row" span={3}> | |
218 | + <Button | |
219 | + type="link" | |
220 | + style={{ color: "#999" }} | |
221 | + onClick={() => remove(name)} | |
222 | + > | |
223 | + 删除 | |
224 | + </Button> | |
225 | + </Col> | |
226 | + </Row> | |
227 | + ))} | |
228 | + </Card> | |
229 | + )} | |
230 | + </Form.List> | |
231 | + <Form.List name="tackCarTaskList"> | |
232 | + {(fields, { add, remove }) => ( | |
233 | + <Card style={{ marginTop: 20 }}> | |
234 | + <Row | |
235 | + align="middle" | |
236 | + justify="space-between" | |
237 | + style={{ marginBottom: 15 }} | |
238 | + > | |
239 | + <h4 className="title">攻坚车任务</h4> | |
240 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | |
241 | + 新增 | |
242 | + </Button> | |
243 | + </Row> | |
244 | + {fields.map(({ key, name, ...restField }) => ( | |
245 | + <Row gutter={16} key={key}> | |
246 | + <Col className="gutter-row" span={6}> | |
247 | + <Form.Item | |
248 | + {...restField} | |
249 | + name={[name, "taskAims"]} | |
250 | + rules={[{ required: true, message: "请填写攻坚车任务" }]} | |
251 | + > | |
252 | + <InputNumber | |
253 | + formatter={(value) => `${value}台`} | |
254 | + parser={(value: any) => value.replace("台", "")} | |
255 | + min={0} | |
256 | + max={MAX_NUM} | |
257 | + style={{ width: "100%" }} | |
258 | + precision={0} | |
259 | + placeholder="请填写攻坚车任务" | |
260 | + /> | |
261 | + </Form.Item> | |
262 | + </Col> | |
263 | + <Col className="gutter-row" span={15}> | |
264 | + <Form.Item | |
265 | + {...restField} | |
266 | + name={[name, "shopIdList"]} | |
267 | + rules={[{ required: true, message: "请选择适用门店" }]} | |
268 | + > | |
269 | + <ShopSelectNew | |
270 | + multiple | |
271 | + defaultOptions={{ bizTypes: "1" }} | |
272 | + placeholder="请选择适用门店" | |
273 | + // disabledShopIds={selectedShopIds.tackCar} | |
274 | + /> | |
275 | + </Form.Item> | |
276 | + </Col> | |
277 | + <Col className="gutter-row" span={3}> | |
278 | + <Button | |
279 | + type="link" | |
280 | + style={{ color: "#999" }} | |
281 | + onClick={() => remove(name)} | |
282 | + > | |
283 | + 删除 | |
284 | + </Button> | |
285 | + </Col> | |
286 | + </Row> | |
287 | + ))} | |
288 | + </Card> | |
289 | + )} | |
290 | + </Form.List> | |
291 | + <Row align="middle" justify="center" style={{ marginTop: 20 }}> | |
292 | + <Button onClick={props.onCancel}>取消</Button> | |
293 | + <Button | |
294 | + type="primary" | |
295 | + style={{ marginLeft: 10 }} | |
296 | + onClick={() => batchSetSaleTask(false)} | |
297 | + > | |
298 | + 分配到门店 | |
299 | + </Button> | |
300 | + <Button | |
301 | + type="primary" | |
302 | + style={{ marginLeft: 10 }} | |
303 | + onClick={() => batchSetSaleTask(true)} | |
304 | + > | |
305 | + 分配到门店和顾问 | |
306 | + </Button> | |
307 | + </Row> | |
308 | + </Form> | |
309 | + ); | |
310 | +} | ... | ... |
src/pages/order3/SaleTask/components/index.less
0 → 100644
src/pages/order3/SaleTask/index.tsx
... | ... | @@ -15,14 +15,15 @@ import { history } from "umi"; |
15 | 15 | import moment, { Moment } from "moment"; |
16 | 16 | import useInitial from "@/hooks/useInitail"; |
17 | 17 | import { Provider, useStore } from "./store"; |
18 | -import { default as ApprovalProgressModal } from "@/pages/stock/AdvanceProgress/components/ApproveModal"; | |
18 | +import ApprovalProgressModal from "@/pages/stock/AdvanceProgress/components/ApproveModal"; | |
19 | 19 | import EntryTaskPreview from "./components/EntryTaskPreview"; |
20 | 20 | import { OrderTaskApprovalType } from "./entity"; |
21 | 21 | import AdviserTaskPreview from "./components/AdviserTaskPreview"; |
22 | 22 | import SeriesTaskPreview from "./components/SeriesTaskPreview"; |
23 | 23 | import ApproveModal from "@/pages/order3/Common/ApproveModal"; |
24 | - | |
25 | -const { Column } = Table; | |
24 | +import SaleTaskAutoAssign from "./components/SaleTaskAutoAssign"; | |
25 | +import SaleTaskBatchSet from "./components/SaleTaskBatchSet"; | |
26 | +import { ColumnsType } from "antd/es/table"; | |
26 | 27 | |
27 | 28 | export default () => ( |
28 | 29 | <Provider> |
... | ... | @@ -42,6 +43,9 @@ function SaleTaskList() { |
42 | 43 | const [stpVisible, setStpVisible] = useState(false); |
43 | 44 | const [seriesTaskParams, setSeriesTaskParams] = useState({}); |
44 | 45 | |
46 | + const [autoVisible, setAutoVisible] = useState(false); | |
47 | + const [batchVisible, setBatchVisible] = useState(false); | |
48 | + | |
45 | 49 | const { data, loading, setParams } = useInitial( |
46 | 50 | API.getSaleTaskApi, |
47 | 51 | {} as API.GetSaleTaskApiRes, |
... | ... | @@ -71,6 +75,19 @@ function SaleTaskList() { |
71 | 75 | }); |
72 | 76 | }; |
73 | 77 | |
78 | + // 查看销顾任务 | |
79 | + const goToAdviserPage = (record: API.ShopTaskItem) => { | |
80 | + history.push({ | |
81 | + pathname: "/order3/saleTask/edit", | |
82 | + query: { | |
83 | + readOnly: isReadOnly ? "1" : "0", | |
84 | + shopId: String(record.shopId), | |
85 | + taskDate: String(targetMonth.valueOf()), | |
86 | + currTab: "2", | |
87 | + }, | |
88 | + }); | |
89 | + }; | |
90 | + | |
74 | 91 | // 查看流程进度 |
75 | 92 | const viewProcess = () => { |
76 | 93 | setApprove({ |
... | ... | @@ -128,30 +145,242 @@ function SaleTaskList() { |
128 | 145 | setEtpVisible(true); |
129 | 146 | }; |
130 | 147 | |
148 | + // 销顾任务 | |
149 | + const showAdviserModal = ( | |
150 | + record: API.TaskListItem, | |
151 | + type: OrderTaskApprovalType | |
152 | + ) => { | |
153 | + const params: any = { | |
154 | + id: data.id, | |
155 | + taskId: record.id, | |
156 | + orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 | |
157 | + }; | |
158 | + switch (type) { | |
159 | + case OrderTaskApprovalType.门店维度: | |
160 | + params.shopId = record.dataId; | |
161 | + break; | |
162 | + case OrderTaskApprovalType.销售顾问维度: | |
163 | + params.staffId = record.dataId; | |
164 | + break; | |
165 | + case OrderTaskApprovalType.新车一级管理维度: | |
166 | + params.firstManageId = record.dataId; | |
167 | + break; | |
168 | + case OrderTaskApprovalType.新车二级管理维度: | |
169 | + params.secondManageId = record.dataId; | |
170 | + break; | |
171 | + case OrderTaskApprovalType.新车三级管理维度: | |
172 | + params.thirdManageId = record.dataId; | |
173 | + break; | |
174 | + default: | |
175 | + break; | |
176 | + } | |
177 | + setAdviserTaskParams(params); | |
178 | + setAtpVisible(true); | |
179 | + }; | |
180 | + | |
181 | + // 销顾任务--查看车系任务 | |
182 | + const showSeriesModalByAdviser = (record: API.TaskListItem) => { | |
183 | + const params = { ...adviserTaskParams } as any; | |
184 | + params.taskId = record.id; | |
185 | + params.orderTaskApprovalType = OrderTaskApprovalType.车系; | |
186 | + params.staffId = record.dataId; | |
187 | + setSeriesTaskParams(params); | |
188 | + setStpVisible(true); | |
189 | + }; | |
190 | + | |
191 | + // 车系任务 | |
192 | + const showSeriesModal = ( | |
193 | + record: API.TaskListItem, | |
194 | + type: OrderTaskApprovalType | |
195 | + ) => { | |
196 | + const params: any = { | |
197 | + id: data.id, | |
198 | + taskId: record.id, | |
199 | + orderTaskApprovalType: OrderTaskApprovalType.车系, | |
200 | + }; | |
201 | + switch (type) { | |
202 | + case OrderTaskApprovalType.门店维度: | |
203 | + params.shopId = record.dataId; | |
204 | + break; | |
205 | + case OrderTaskApprovalType.销售顾问维度: | |
206 | + params.staffId = record.dataId; | |
207 | + break; | |
208 | + case OrderTaskApprovalType.新车一级管理维度: | |
209 | + params.firstManageId = record.dataId; | |
210 | + break; | |
211 | + case OrderTaskApprovalType.新车二级管理维度: | |
212 | + params.secondManageId = record.dataId; | |
213 | + break; | |
214 | + case OrderTaskApprovalType.新车三级管理维度: | |
215 | + params.thirdManageId = record.dataId; | |
216 | + break; | |
217 | + default: | |
218 | + break; | |
219 | + } | |
220 | + setSeriesTaskParams(params); | |
221 | + setStpVisible(true); | |
222 | + }; | |
223 | + | |
224 | + const handleAutoAssignRefresh = () => { | |
225 | + setAutoVisible(false); | |
226 | + setParams({}, true); | |
227 | + }; | |
228 | + const handleBatchSetRefresh = () => { | |
229 | + setBatchVisible(false); | |
230 | + setParams({}, true); | |
231 | + }; | |
232 | + | |
233 | + const columns: ColumnsType<API.ShopTaskItem> = [ | |
234 | + { | |
235 | + title: "门店", | |
236 | + width: 150, | |
237 | + dataIndex: "shopName", | |
238 | + }, | |
239 | + { | |
240 | + title: "零售任务(台)", | |
241 | + children: [ | |
242 | + { | |
243 | + title: "合计", | |
244 | + dataIndex: "taskCount", | |
245 | + key: "taskCount", | |
246 | + }, | |
247 | + { | |
248 | + title: "新能源车", | |
249 | + dataIndex: "newEnergyTaskCount", | |
250 | + key: "newEnergyTaskCount", | |
251 | + }, | |
252 | + { | |
253 | + title: "传统燃油车", | |
254 | + dataIndex: "fuelVehicleTaskCount", | |
255 | + key: "fuelVehicleTaskCount", | |
256 | + }, | |
257 | + ], | |
258 | + }, | |
259 | + { | |
260 | + title: "毛利任务(元)", | |
261 | + children: [ | |
262 | + { | |
263 | + title: "合计", | |
264 | + dataIndex: "grossProfitTaskTotal", | |
265 | + key: "grossProfitTaskTotal", | |
266 | + render: (text: string, record: API.ShopTaskItem) => { | |
267 | + return (record.taskCount * record.vehicleGrossProfitTask).toFixed( | |
268 | + 2 | |
269 | + ); | |
270 | + }, | |
271 | + }, | |
272 | + { | |
273 | + title: "单车", | |
274 | + dataIndex: "vehicleGrossProfitTask", | |
275 | + key: "vehicleGrossProfitTask", | |
276 | + }, | |
277 | + ], | |
278 | + }, | |
279 | + { | |
280 | + title: "线索到店成交(台)", | |
281 | + width: 100, | |
282 | + dataIndex: "clueDealTaskCount", | |
283 | + key: "clueDealTaskCount", | |
284 | + }, | |
285 | + { | |
286 | + title: "首客试驾成交(台)", | |
287 | + width: 100, | |
288 | + dataIndex: "testDriveTaskCount", | |
289 | + key: "testDriveTaskCount", | |
290 | + }, | |
291 | + { | |
292 | + title: "攻坚车任务(台)", | |
293 | + width: 100, | |
294 | + dataIndex: "tackCarTaskCount", | |
295 | + key: "tackCarTaskCount", | |
296 | + }, | |
297 | + { | |
298 | + title: "车系任务(台)", | |
299 | + width: 100, | |
300 | + dataIndex: "seriesTaskCount", | |
301 | + key: "seriesTaskCount", | |
302 | + }, | |
303 | + { | |
304 | + title: "销顾任务", | |
305 | + render: (text: string, record: API.ShopTaskItem) => { | |
306 | + return ( | |
307 | + <a | |
308 | + onClick={() => { | |
309 | + goToAdviserPage(record); | |
310 | + }} | |
311 | + > | |
312 | + 查看 | |
313 | + </a> | |
314 | + ); | |
315 | + }, | |
316 | + }, | |
317 | + { | |
318 | + title: "操作", | |
319 | + render: (text: string, record: API.ShopTaskItem) => { | |
320 | + return ( | |
321 | + <a | |
322 | + onClick={() => { | |
323 | + goToEditPage(record); | |
324 | + }} | |
325 | + > | |
326 | + {isReadOnly ? "查看" : "编辑"} | |
327 | + </a> | |
328 | + ); | |
329 | + }, | |
330 | + }, | |
331 | + ]; | |
332 | + | |
131 | 333 | return ( |
132 | 334 | <PageHeaderWrapper title="零售任务分配"> |
133 | 335 | <Card> |
134 | - <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
135 | - <DatePicker | |
136 | - placeholder="月度" | |
137 | - style={{ width: 260 }} | |
138 | - picker="month" | |
139 | - value={targetMonth} | |
140 | - onChange={handleChangeMonth} | |
141 | - allowClear={false} | |
142 | - /> | |
143 | - <Input.Search | |
144 | - allowClear | |
145 | - placeholder="门店名称" | |
146 | - style={{ width: 260, marginLeft: 15 }} | |
147 | - onSearch={(v) => { | |
148 | - setParams({ shopName: v }, true); | |
149 | - }} | |
150 | - /> | |
336 | + <Row | |
337 | + align="middle" | |
338 | + justify="space-between" | |
339 | + style={{ marginBottom: 20 }} | |
340 | + > | |
341 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
342 | + <DatePicker | |
343 | + placeholder="月度" | |
344 | + style={{ width: 260 }} | |
345 | + picker="month" | |
346 | + value={targetMonth} | |
347 | + onChange={handleChangeMonth} | |
348 | + allowClear={false} | |
349 | + /> | |
350 | + <Input.Search | |
351 | + allowClear | |
352 | + placeholder="门店名称" | |
353 | + style={{ width: 260, marginLeft: 15 }} | |
354 | + onSearch={(v) => { | |
355 | + setParams({ shopName: v }, true); | |
356 | + }} | |
357 | + onBlur={(e) => { | |
358 | + if (e.target.value.trim() === "") { | |
359 | + setParams({ shopName: "" }, true); | |
360 | + } | |
361 | + }} | |
362 | + /> | |
363 | + </Row> | |
364 | + {!isReadOnly && ( | |
365 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
366 | + <Button type="primary" onClick={() => setAutoVisible(true)}> | |
367 | + 零售任务快捷分配 | |
368 | + </Button> | |
369 | + <Button | |
370 | + type="primary" | |
371 | + style={{ marginLeft: 10 }} | |
372 | + onClick={() => setBatchVisible(true)} | |
373 | + > | |
374 | + 批量设置 | |
375 | + </Button> | |
376 | + </Row> | |
377 | + )} | |
151 | 378 | </Row> |
152 | 379 | <Table |
153 | 380 | rowKey="id" |
381 | + bordered | |
154 | 382 | loading={loading} |
383 | + columns={columns} | |
155 | 384 | dataSource={data.shopTaskList} |
156 | 385 | pagination={false} |
157 | 386 | scroll={{ y: 450 }} |
... | ... | @@ -168,66 +397,39 @@ function SaleTaskList() { |
168 | 397 | fontWeight: 500, |
169 | 398 | }} |
170 | 399 | > |
171 | - <Table.Summary.Cell align="left" index={1}> | |
172 | - 合计 | |
173 | - </Table.Summary.Cell> | |
174 | - <Table.Summary.Cell align="left" index={2}> | |
400 | + <Table.Summary.Cell index={1}>合计</Table.Summary.Cell> | |
401 | + <Table.Summary.Cell index={2}> | |
175 | 402 | {data.totalTaskCount} |
176 | 403 | </Table.Summary.Cell> |
177 | - <Table.Summary.Cell align="left" index={3}> | |
404 | + <Table.Summary.Cell index={3}> | |
178 | 405 | {data.newEnergyTaskCount} |
179 | 406 | </Table.Summary.Cell> |
180 | - <Table.Summary.Cell align="left" index={4}> | |
407 | + <Table.Summary.Cell index={4}> | |
181 | 408 | {data.fuelVehicleTaskCount} |
182 | 409 | </Table.Summary.Cell> |
183 | - <Table.Summary.Cell align="left" index={5}> | |
184 | - {data.vehicleGrossProfitTask} | |
410 | + <Table.Summary.Cell index={5}> | |
411 | + {data.totalGrossProfitTask} | |
185 | 412 | </Table.Summary.Cell> |
186 | - <Table.Summary.Cell index={6}> | |
413 | + <Table.Summary.Cell index={6}>-</Table.Summary.Cell> | |
414 | + <Table.Summary.Cell index={7}> | |
187 | 415 | {data.clueDealTaskCount} |
188 | 416 | </Table.Summary.Cell> |
189 | - <Table.Summary.Cell index={7}> | |
417 | + <Table.Summary.Cell index={8}> | |
190 | 418 | {data.testDriveTaskCount} |
191 | 419 | </Table.Summary.Cell> |
192 | - <Table.Summary.Cell index={8}> | |
420 | + <Table.Summary.Cell index={9}> | |
193 | 421 | {data.tackCarTaskCount} |
194 | 422 | </Table.Summary.Cell> |
195 | - <Table.Summary.Cell index={9}> | |
423 | + <Table.Summary.Cell index={10}> | |
196 | 424 | {data.seriesTaskCount} |
197 | 425 | </Table.Summary.Cell> |
198 | - <Table.Summary.Cell index={10} /> | |
426 | + <Table.Summary.Cell index={11} /> | |
427 | + <Table.Summary.Cell index={12} /> | |
199 | 428 | </Table.Summary.Row> |
200 | 429 | </Table.Summary> |
201 | 430 | ); |
202 | 431 | }} |
203 | - > | |
204 | - <Column title="门店" dataIndex="shopName" /> | |
205 | - <Column title="零售任务(台)" dataIndex="taskCount" /> | |
206 | - <Column title="新能源车任务(台)" dataIndex="newEnergyTaskCount" /> | |
207 | - <Column title="传统燃油车任务(台)" dataIndex="fuelVehicleTaskCount" /> | |
208 | - <Column title="车辆毛利任务(元)" dataIndex="vehicleGrossProfitTask" /> | |
209 | - <Column title="线索到店零售台数(台)" dataIndex="clueDealTaskCount" /> | |
210 | - <Column | |
211 | - title="首客试驾成交任务数(台)" | |
212 | - dataIndex="testDriveTaskCount" | |
213 | - /> | |
214 | - <Column title="攻坚车任务数(台)" dataIndex="tackCarTaskCount" /> | |
215 | - <Column title="车系任务数(台)" dataIndex="seriesTaskCount" /> | |
216 | - <Column | |
217 | - title="操作" | |
218 | - render={(text: string, record: API.ShopTaskItem) => { | |
219 | - return ( | |
220 | - <a | |
221 | - onClick={() => { | |
222 | - goToEditPage(record); | |
223 | - }} | |
224 | - > | |
225 | - {isReadOnly ? "查看" : "编辑"} | |
226 | - </a> | |
227 | - ); | |
228 | - }} | |
229 | - /> | |
230 | - </Table> | |
432 | + /> | |
231 | 433 | {data.revoke ? ( |
232 | 434 | <Row align="middle" justify="center" style={{ marginTop: 50 }}> |
233 | 435 | <Button onClick={cancelSaleTask}>撤销</Button> |
... | ... | @@ -271,82 +473,11 @@ function SaleTaskList() { |
271 | 473 | destroyOnClose |
272 | 474 | footer={null} |
273 | 475 | > |
274 | - <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | |
275 | - <DatePicker | |
276 | - placeholder="月度" | |
277 | - style={{ width: 230 }} | |
278 | - picker="month" | |
279 | - value={targetMonth} | |
280 | - allowClear={false} | |
281 | - disabled | |
282 | - /> | |
283 | - {/* <Input.Search | |
284 | - allowClear | |
285 | - placeholder="门店名称" | |
286 | - style={{ width: 263, marginLeft: 20 }} | |
287 | - onSearch={(v) => { | |
288 | - // todo setParams({ shopName: v }, true); | |
289 | - }} | |
290 | - /> */} | |
291 | - </Row> | |
292 | 476 | <EntryTaskPreview |
477 | + month={targetMonth} | |
293 | 478 | params={previewTaskParams} |
294 | - showAdviserModal={(record, type) => { | |
295 | - const params: any = { | |
296 | - id: data.id, | |
297 | - taskId: record.id, | |
298 | - orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 | |
299 | - }; | |
300 | - switch (type) { | |
301 | - case OrderTaskApprovalType.门店维度: | |
302 | - params.shopId = record.dataId; | |
303 | - break; | |
304 | - case OrderTaskApprovalType.销售顾问维度: | |
305 | - params.staffId = record.dataId; | |
306 | - break; | |
307 | - case OrderTaskApprovalType.新车一级管理维度: | |
308 | - params.firstManageId = record.dataId; | |
309 | - break; | |
310 | - case OrderTaskApprovalType.新车二级管理维度: | |
311 | - params.secondManageId = record.dataId; | |
312 | - break; | |
313 | - case OrderTaskApprovalType.新车三级管理维度: | |
314 | - params.thirdManageId = record.dataId; | |
315 | - break; | |
316 | - default: | |
317 | - break; | |
318 | - } | |
319 | - setAdviserTaskParams(params); | |
320 | - setAtpVisible(true); | |
321 | - }} | |
322 | - showSeriesModal={(record, type) => { | |
323 | - const params: any = { | |
324 | - id: data.id, | |
325 | - taskId: record.id, | |
326 | - orderTaskApprovalType: OrderTaskApprovalType.车系, | |
327 | - }; | |
328 | - switch (type) { | |
329 | - case OrderTaskApprovalType.门店维度: | |
330 | - params.shopId = record.dataId; | |
331 | - break; | |
332 | - case OrderTaskApprovalType.销售顾问维度: | |
333 | - params.staffId = record.dataId; | |
334 | - break; | |
335 | - case OrderTaskApprovalType.新车一级管理维度: | |
336 | - params.firstManageId = record.dataId; | |
337 | - break; | |
338 | - case OrderTaskApprovalType.新车二级管理维度: | |
339 | - params.secondManageId = record.dataId; | |
340 | - break; | |
341 | - case OrderTaskApprovalType.新车三级管理维度: | |
342 | - params.thirdManageId = record.dataId; | |
343 | - break; | |
344 | - default: | |
345 | - break; | |
346 | - } | |
347 | - setSeriesTaskParams(params); | |
348 | - setStpVisible(true); | |
349 | - }} | |
479 | + showAdviserModal={showAdviserModal} | |
480 | + showSeriesModal={showSeriesModal} | |
350 | 481 | /> |
351 | 482 | </Modal> |
352 | 483 | <Modal |
... | ... | @@ -359,14 +490,7 @@ function SaleTaskList() { |
359 | 490 | > |
360 | 491 | <AdviserTaskPreview |
361 | 492 | params={adviserTaskParams} |
362 | - showSeriesModal={(record) => { | |
363 | - const params = { ...adviserTaskParams } as any; | |
364 | - params.taskId = record.id; | |
365 | - params.orderTaskApprovalType = OrderTaskApprovalType.车系; | |
366 | - params.staffId = record.dataId; | |
367 | - setSeriesTaskParams(params); | |
368 | - setStpVisible(true); | |
369 | - }} | |
493 | + showSeriesModal={showSeriesModalByAdviser} | |
370 | 494 | /> |