Commit bdad4eb5d7ff3f9455e5ed25e8a4802cbfe07e80
1 parent
7c25275f
调试自动分配接口
Showing
6 changed files
with
740 additions
and
99 deletions
src/pages/order3/SaleTask/api.ts
@@ -109,6 +109,25 @@ export function submitSaleTask(params: { id: number }): PromiseResp<boolean> { | @@ -109,6 +109,25 @@ export function submitSaleTask(params: { id: number }): PromiseResp<boolean> { | ||
109 | return request.post(`${ORDER3_HOST}/erp/sales/task/submit`, params); | 109 | return request.post(`${ORDER3_HOST}/erp/sales/task/submit`, params); |
110 | } | 110 | } |
111 | 111 | ||
112 | +export interface AutoAssignItem { | ||
113 | + shopId: number; | ||
114 | + taskCount: number; | ||
115 | + newEnergyTaskCount: number; | ||
116 | + fuelVehicleTaskCount: number; | ||
117 | + tackCarTaskCount: number; | ||
118 | +} | ||
119 | + | ||
120 | +export interface AutoAssignSaleTaskReq { | ||
121 | + id: number; | ||
122 | + assignTask: boolean; | ||
123 | + shopTaskList: AutoAssignItem[]; | ||
124 | +} | ||
125 | + | ||
126 | +/** 自动分配零售任务 */ | ||
127 | +export function autoAssignSaleTask(params: AutoAssignSaleTaskReq): PromiseResp<boolean> { | ||
128 | + return request.post(`${ORDER3_HOST}/erp/sales/task/auto/assign`, params); | ||
129 | +} | ||
130 | + | ||
112 | export interface BrandItem { | 131 | export interface BrandItem { |
113 | id: number; | 132 | id: number; |
114 | initial: string; | 133 | initial: string; |
@@ -188,7 +207,9 @@ export interface PreviewTaskRes { | @@ -188,7 +207,9 @@ export interface PreviewTaskRes { | ||
188 | } | 207 | } |
189 | 208 | ||
190 | /** 预览任务 */ | 209 | /** 预览任务 */ |
191 | -export function previewTask(params: PreviewTaskReq): PromiseResp<PreviewTaskRes> { | 210 | +export function previewTask( |
211 | + params: PreviewTaskReq | ||
212 | +): PromiseResp<PreviewTaskRes> { | ||
192 | return request.get(`${ORDER3_HOST}/erp/sales/task/approve/info`, { | 213 | return request.get(`${ORDER3_HOST}/erp/sales/task/approve/info`, { |
193 | params, | 214 | params, |
194 | }); | 215 | }); |
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 | + Input, | ||
7 | + Row, | ||
8 | + Button, | ||
9 | + message, | ||
10 | + Modal, | ||
11 | + InputNumber, | ||
12 | +} from "antd"; | ||
13 | +import type { FormInstance } from "antd/es/form"; | ||
14 | +import * as API from "../api"; | ||
15 | +import styles from "./index.less"; | ||
16 | +import { MAX_NUM } from "../entity"; | ||
17 | + | ||
18 | +type EditableTableProps = Parameters<typeof Table>[0]; | ||
19 | +type ColumnTypes = Exclude<EditableTableProps["columns"], undefined>; | ||
20 | +interface Item { | ||
21 | + id: string; | ||
22 | + shopName: string; | ||
23 | + taskCount: number; | ||
24 | + newEnergyTaskCount: number; | ||
25 | + fuelVehicleTaskCount: number; | ||
26 | + tackCarTaskCount: number; | ||
27 | +} | ||
28 | +interface EditableRowProps { | ||
29 | + index: number; | ||
30 | +} | ||
31 | +interface EditableCellProps { | ||
32 | + title: React.ReactNode; | ||
33 | + editable: boolean; | ||
34 | + children: React.ReactNode; | ||
35 | + dataIndex: keyof Item; | ||
36 | + record: Item; | ||
37 | + handleSave: (record: Item) => void; | ||
38 | +} | ||
39 | + | ||
40 | +const defaultColumns: (ColumnTypes[number] & { | ||
41 | + editable?: boolean; | ||
42 | + dataIndex: string; | ||
43 | +})[] = [ | ||
44 | + { | ||
45 | + title: "门店", | ||
46 | + dataIndex: "shopName", | ||
47 | + editable: false, | ||
48 | + }, | ||
49 | + { | ||
50 | + title: "零售任务(台)", | ||
51 | + dataIndex: "taskCount", | ||
52 | + editable: true, | ||
53 | + }, | ||
54 | + { | ||
55 | + title: "新能源车任务(台)", | ||
56 | + dataIndex: "newEnergyTaskCount", | ||
57 | + editable: true, | ||
58 | + }, | ||
59 | + { | ||
60 | + title: "传统燃油车任务(台)", | ||
61 | + dataIndex: "fuelVehicleTaskCount", | ||
62 | + editable: false, | ||
63 | + }, | ||
64 | + { | ||
65 | + title: "攻坚车任务数(台)", | ||
66 | + dataIndex: "tackCarTaskCount", | ||
67 | + editable: true, | ||
68 | + }, | ||
69 | +]; | ||
70 | + | ||
71 | +interface SaleTaskAutoAssignProps { | ||
72 | + id: number; | ||
73 | + value?: API.ShopTaskItem[]; | ||
74 | + onCancel: () => void; | ||
75 | +} | ||
76 | + | ||
77 | +export default function SaleTaskAutoAssign({ | ||
78 | + id, | ||
79 | + value, | ||
80 | + onCancel, | ||
81 | +}: SaleTaskAutoAssignProps) { | ||
82 | + const EditableContext = React.createContext<FormInstance<any> | null>(null); | ||
83 | + const [dataSource, setDataSource] = useState<API.ShopTaskItem[]>([]); | ||
84 | + | ||
85 | + useEffect(() => { | ||
86 | + setDataSource(value ? [...value] : []); | ||
87 | + }, [value]); | ||
88 | + | ||
89 | + const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => { | ||
90 | + const [form] = Form.useForm(); | ||
91 | + return ( | ||
92 | + <Form form={form} component={false}> | ||
93 | + <EditableContext.Provider value={form}> | ||
94 | + <tr {...props} /> | ||
95 | + </EditableContext.Provider> | ||
96 | + </Form> | ||
97 | + ); | ||
98 | + }; | ||
99 | + | ||
100 | + const EditableCell: React.FC<EditableCellProps> = ({ | ||
101 | + title, | ||
102 | + editable, | ||
103 | + children, | ||
104 | + dataIndex, | ||
105 | + record, | ||
106 | + handleSave, | ||
107 | + ...restProps | ||
108 | + }) => { | ||
109 | + const [editing, setEditing] = useState(false); | ||
110 | + const inputRef = useRef<InputRef>(null); | ||
111 | + const form = useContext(EditableContext)!; | ||
112 | + | ||
113 | + useEffect(() => { | ||
114 | + if (editing) { | ||
115 | + inputRef.current!.focus(); | ||
116 | + } | ||
117 | + }, [editing]); | ||
118 | + | ||
119 | + const toggleEdit = () => { | ||
120 | + setEditing(!editing); | ||
121 | + form.setFieldsValue({ [dataIndex]: record[dataIndex] }); | ||
122 | + }; | ||
123 | + | ||
124 | + const save = async () => { | ||
125 | + try { | ||
126 | + const values = await form.validateFields(); | ||
127 | + toggleEdit(); | ||
128 | + handleSave({ ...record, ...values }); | ||
129 | + } catch (errInfo) { | ||
130 | + console.log("Save failed:", errInfo); | ||
131 | + } | ||
132 | + }; | ||
133 | + | ||
134 | + let childNode = children; | ||
135 | + | ||
136 | + if (editable) { | ||
137 | + childNode = editing ? ( | ||
138 | + <Form.Item | ||
139 | + noStyle | ||
140 | + name={dataIndex} | ||
141 | + rules={[ | ||
142 | + { | ||
143 | + required: true, | ||
144 | + message: `请输入${title}`, | ||
145 | + }, | ||
146 | + ]} | ||
147 | + > | ||
148 | + <InputNumber | ||
149 | + ref={inputRef} | ||
150 | + min={0} | ||
151 | + max={MAX_NUM} | ||
152 | + style={{ width: "80px" }} | ||
153 | + onPressEnter={save} | ||
154 | + onBlur={save} | ||
155 | + /> | ||
156 | + </Form.Item> | ||
157 | + ) : ( | ||
158 | + <div className="editable-cell-value-wrap" onClick={toggleEdit}> | ||
159 | + {children} | ||
160 | + </div> | ||
161 | + ); | ||
162 | + } | ||
163 | + | ||
164 | + return <td {...restProps}>{childNode}</td>; | ||
165 | + }; | ||
166 | + | ||
167 | + const handleSave = (row: API.ShopTaskItem) => { | ||
168 | + const newData = [...dataSource]; | ||
169 | + const index = newData.findIndex((item) => row.id === item.id); | ||
170 | + const item = newData[index]; | ||
171 | + if (row.taskCount !== 0 && row.newEnergyTaskCount > row.taskCount) { | ||
172 | + message.warn("新能源车任务台数不得超过零售任务台数"); | ||
173 | + return; | ||
174 | + } | ||
175 | + const newRow = { | ||
176 | + ...item, | ||
177 | + ...row, | ||
178 | + fuelVehicleTaskCount: row.taskCount - row.newEnergyTaskCount, | ||
179 | + }; | ||
180 | + if (row.taskCount === 0) { | ||
181 | + newRow.taskCount = 0; | ||
182 | + newRow.newEnergyTaskCount = 0; | ||
183 | + newRow.fuelVehicleTaskCount = 0; | ||
184 | + } | ||
185 | + newData.splice(index, 1, newRow); | ||
186 | + console.log("handleSave newData", newData); | ||
187 | + setDataSource(newData); | ||
188 | + }; | ||
189 | + | ||
190 | + const autoAssignSaleTask = (isAssignToAdviser: boolean) => { | ||
191 | + Modal.confirm({ | ||
192 | + title: isAssignToAdviser | ||
193 | + ? "确认分配到门店和顾问吗?" | ||
194 | + : "确认分配到门店吗?", | ||
195 | + zIndex: 1002, | ||
196 | + onOk: async () => { | ||
197 | + const hide = message.loading("分配中,请稍候", 0); | ||
198 | + API.autoAssignSaleTask({ | ||
199 | + id, | ||
200 | + shopTaskList: dataSource.map((item) => ({ | ||
201 | + shopId: item.shopId, | ||
202 | + taskCount: item.taskCount, | ||
203 | + newEnergyTaskCount: item.newEnergyTaskCount, | ||
204 | + fuelVehicleTaskCount: item.fuelVehicleTaskCount, | ||
205 | + tackCarTaskCount: item.tackCarTaskCount, | ||
206 | + })), | ||
207 | + assignTask: isAssignToAdviser, | ||
208 | + }) | ||
209 | + .then((res) => { | ||
210 | + message.success("分配成功"); | ||
211 | + }) | ||
212 | + .catch((error: any) => { | ||
213 | + message.error(error.message ?? "请求失败"); | ||
214 | + }) | ||
215 | + .finally(() => { | ||
216 | + hide(); | ||
217 | + }); | ||
218 | + }, | ||
219 | + }); | ||
220 | + }; | ||
221 | + | ||
222 | + const components = { | ||
223 | + body: { | ||
224 | + row: EditableRow, | ||
225 | + cell: EditableCell, | ||
226 | + }, | ||
227 | + }; | ||
228 | + | ||
229 | + const columns = defaultColumns.map((col) => { | ||
230 | + if (!col.editable) { | ||
231 | + return col; | ||
232 | + } | ||
233 | + return { | ||
234 | + ...col, | ||
235 | + onCell: (record: API.ShopTaskItem) => ({ | ||
236 | + record, | ||
237 | + editable: col.editable, | ||
238 | + dataIndex: col.dataIndex, | ||
239 | + title: col.title, | ||
240 | + handleSave, | ||
241 | + }), | ||
242 | + }; | ||
243 | + }); | ||
244 | + | ||
245 | + return ( | ||
246 | + <> | ||
247 | + <Table | ||
248 | + components={components} | ||
249 | + rowClassName={() => "editable-row"} | ||
250 | + bordered | ||
251 | + rowKey="id" | ||
252 | + dataSource={dataSource} | ||
253 | + columns={columns as ColumnTypes} | ||
254 | + /> | ||
255 | + <Row align="middle" justify="center" style={{ marginTop: 20 }}> | ||
256 | + <Button onClick={onCancel}>取消</Button> | ||
257 | + <Button | ||
258 | + type="primary" | ||
259 | + style={{ marginLeft: 10 }} | ||
260 | + onClick={() => autoAssignSaleTask(false)} | ||
261 | + > | ||
262 | + 分配到门店 | ||
263 | + </Button> | ||
264 | + <Button | ||
265 | + type="primary" | ||
266 | + style={{ marginLeft: 10 }} | ||
267 | + onClick={() => autoAssignSaleTask(true)} | ||
268 | + > | ||
269 | + 分配到门店和顾问 | ||
270 | + </Button> | ||
271 | + </Row> | ||
272 | + </> | ||
273 | + ); | ||
274 | +} |
src/pages/order3/SaleTask/components/SaleTaskBatchSet.tsx
0 → 100644
1 | +import { PlusOutlined } from "@ant-design/icons"; | ||
2 | +import { Button, Card, Col, Form, InputNumber, Row, Select } from "antd"; | ||
3 | +import styles from "./index.less"; | ||
4 | +import React from "react"; | ||
5 | +import { MAX_NUM } from "../entity"; | ||
6 | + | ||
7 | +export default function SaleTaskBatchSet() { | ||
8 | + const [form] = Form.useForm(); | ||
9 | + | ||
10 | + const assignToShop = async () => { | ||
11 | + await form.validateFields(); | ||
12 | + const values = form.getFieldsValue(); | ||
13 | + console.log(values); | ||
14 | + }; | ||
15 | + | ||
16 | + const assignToBoth = async () => { | ||
17 | + await form.validateFields(); | ||
18 | + const values = form.getFieldsValue(); | ||
19 | + console.log(values); | ||
20 | + }; | ||
21 | + | ||
22 | + return ( | ||
23 | + <Form form={form} name="sale-task-batch-set-form" autoComplete="off"> | ||
24 | + <Form.List name="vehicleGrossProfitTask"> | ||
25 | + {(fields, { add, remove }) => ( | ||
26 | + <Card> | ||
27 | + <Row | ||
28 | + align="middle" | ||
29 | + justify="space-between" | ||
30 | + style={{ marginBottom: 15 }} | ||
31 | + > | ||
32 | + <h4 className={styles.title}>单车毛利任务</h4> | ||
33 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | ||
34 | + 新增 | ||
35 | + </Button> | ||
36 | + </Row> | ||
37 | + {fields.map(({ key, name, ...restField }) => ( | ||
38 | + <Row gutter={16} key={key}> | ||
39 | + <Col className="gutter-row" span={6}> | ||
40 | + <Form.Item | ||
41 | + {...restField} | ||
42 | + name={[name, "vehicleGrossProfitTask"]} | ||
43 | + rules={[{ required: true, message: "请填写单车毛利任务" }]} | ||
44 | + > | ||
45 | + <InputNumber | ||
46 | + formatter={(value) => `${value}元`} | ||
47 | + parser={(value: any) => value.replace("元", "")} | ||
48 | + min={0} | ||
49 | + max={MAX_NUM} | ||
50 | + style={{ width: "100%" }} | ||
51 | + precision={2} | ||
52 | + placeholder="请填写单车毛利任务" | ||
53 | + /> | ||
54 | + </Form.Item> | ||
55 | + </Col> | ||
56 | + <Col className="gutter-row" span={15}> | ||
57 | + <Form.Item | ||
58 | + {...restField} | ||
59 | + name={[name, "shop"]} | ||
60 | + rules={[{ required: true, message: "请选择适用门店" }]} | ||
61 | + > | ||
62 | + <Select | ||
63 | + showSearch | ||
64 | + allowClear | ||
65 | + labelInValue | ||
66 | + loading={false} | ||
67 | + placeholder="请选择适用门店" | ||
68 | + style={{ width: "100%" }} | ||
69 | + fieldNames={{ value: "id", label: "name" }} | ||
70 | + options={[]} | ||
71 | + /> | ||
72 | + </Form.Item> | ||
73 | + </Col> | ||
74 | + <Col className="gutter-row" span={3}> | ||
75 | + <Button | ||
76 | + type="link" | ||
77 | + style={{ color: "#999" }} | ||
78 | + onClick={() => remove(name)} | ||
79 | + > | ||
80 | + 删除 | ||
81 | + </Button> | ||
82 | + </Col> | ||
83 | + </Row> | ||
84 | + ))} | ||
85 | + </Card> | ||
86 | + )} | ||
87 | + </Form.List> | ||
88 | + <Form.List name="testDriveTaskCount"> | ||
89 | + {(fields, { add, remove }) => ( | ||
90 | + <Card style={{ marginTop: 20 }}> | ||
91 | + <Row | ||
92 | + align="middle" | ||
93 | + justify="space-between" | ||
94 | + style={{ marginBottom: 15 }} | ||
95 | + > | ||
96 | + <h4 className={styles.title}>首客试驾成交</h4> | ||
97 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | ||
98 | + 新增 | ||
99 | + </Button> | ||
100 | + </Row> | ||
101 | + {fields.map(({ key, name, ...restField }) => ( | ||
102 | + <Row gutter={16} key={key}> | ||
103 | + <Col className="gutter-row" span={6}> | ||
104 | + <Form.Item | ||
105 | + {...restField} | ||
106 | + name={[name, "testDriveTaskCount"]} | ||
107 | + rules={[{ required: true, message: "请填写首客试驾成交" }]} | ||
108 | + > | ||
109 | + <InputNumber | ||
110 | + formatter={(value) => `${value}台`} | ||
111 | + parser={(value: any) => value.replace("台", "")} | ||
112 | + min={0} | ||
113 | + max={MAX_NUM} | ||
114 | + style={{ width: "100%" }} | ||
115 | + precision={0} | ||
116 | + placeholder="请填写首客试驾成交" | ||
117 | + /> | ||
118 | + </Form.Item> | ||
119 | + </Col> | ||
120 | + <Col className="gutter-row" span={15}> | ||
121 | + <Form.Item | ||
122 | + {...restField} | ||
123 | + name={[name, "shop"]} | ||
124 | + rules={[{ required: true, message: "请选择适用门店" }]} | ||
125 | + > | ||
126 | + <Select | ||
127 | + showSearch | ||
128 | + allowClear | ||
129 | + labelInValue | ||
130 | + loading={false} | ||
131 | + placeholder="请选择适用门店" | ||
132 | + style={{ width: "100%" }} | ||
133 | + fieldNames={{ value: "id", label: "name" }} | ||
134 | + options={[]} | ||
135 | + /> | ||
136 | + </Form.Item> | ||
137 | + </Col> | ||
138 | + <Col className="gutter-row" span={3}> | ||
139 | + <Button | ||
140 | + type="link" | ||
141 | + style={{ color: "#999" }} | ||
142 | + onClick={() => remove(name)} | ||
143 | + > | ||
144 | + 删除 | ||
145 | + </Button> | ||
146 | + </Col> | ||
147 | + </Row> | ||
148 | + ))} | ||
149 | + </Card> | ||
150 | + )} | ||
151 | + </Form.List> | ||
152 | + <Form.List name="tackCarTaskCount"> | ||
153 | + {(fields, { add, remove }) => ( | ||
154 | + <Card style={{ marginTop: 20 }}> | ||
155 | + <Row | ||
156 | + align="middle" | ||
157 | + justify="space-between" | ||
158 | + style={{ marginBottom: 15 }} | ||
159 | + > | ||
160 | + <h4 className={styles.title}>攻坚车任务</h4> | ||
161 | + <Button type="link" onClick={() => add()} icon={<PlusOutlined />}> | ||
162 | + 新增 | ||
163 | + </Button> | ||
164 | + </Row> | ||
165 | + {fields.map(({ key, name, ...restField }) => ( | ||
166 | + <Row gutter={16} key={key}> | ||
167 | + <Col className="gutter-row" span={6}> | ||
168 | + <Form.Item | ||
169 | + {...restField} | ||
170 | + name={[name, "tackCarTaskCount"]} | ||
171 | + rules={[{ required: true, message: "请填写攻坚车任务" }]} | ||
172 | + > | ||
173 | + <InputNumber | ||
174 | + formatter={(value) => `${value}台`} | ||
175 | + parser={(value: any) => value.replace("台", "")} | ||
176 | + min={0} | ||
177 | + max={MAX_NUM} | ||
178 | + style={{ width: "100%" }} | ||
179 | + precision={0} | ||
180 | + placeholder="请填写攻坚车任务" | ||
181 | + /> | ||
182 | + </Form.Item> | ||
183 | + </Col> | ||
184 | + <Col className="gutter-row" span={15}> | ||
185 | + <Form.Item | ||
186 | + {...restField} | ||
187 | + name={[name, "shop"]} | ||
188 | + rules={[{ required: true, message: "请选择适用门店" }]} | ||
189 | + > | ||
190 | + <Select | ||
191 | + showSearch | ||
192 | + allowClear | ||
193 | + labelInValue | ||
194 | + loading={false} | ||
195 | + placeholder="请选择适用门店" | ||
196 | + style={{ width: "100%" }} | ||
197 | + fieldNames={{ value: "id", label: "name" }} | ||
198 | + options={[]} | ||
199 | + /> | ||
200 | + </Form.Item> | ||
201 | + </Col> | ||
202 | + <Col className="gutter-row" span={3}> | ||
203 | + <Button | ||
204 | + type="link" | ||
205 | + style={{ color: "#999" }} | ||
206 | + onClick={() => remove(name)} | ||
207 | + > | ||
208 | + 删除 | ||
209 | + </Button> | ||
210 | + </Col> | ||
211 | + </Row> | ||
212 | + ))} | ||
213 | + </Card> | ||
214 | + )} | ||
215 | + </Form.List> | ||
216 | + <Row align="middle" justify="center" style={{ marginTop: 20 }}> | ||
217 | + <Button onClick={() => {}}>取消</Button> | ||
218 | + <Button | ||
219 | + type="primary" | ||
220 | + style={{ marginLeft: 10 }} | ||
221 | + onClick={assignToShop} | ||
222 | + > | ||
223 | + 分配到门店 | ||
224 | + </Button> | ||
225 | + <Button | ||
226 | + type="primary" | ||
227 | + style={{ marginLeft: 10 }} | ||
228 | + onClick={assignToBoth} | ||
229 | + > | ||
230 | + 分配到门店和顾问 | ||
231 | + </Button> | ||
232 | + </Row> | ||
233 | + </Form> | ||
234 | + ); | ||
235 | +} |
src/pages/order3/SaleTask/components/index.less
0 → 100644
src/pages/order3/SaleTask/index.tsx
@@ -15,12 +15,14 @@ import { history } from "umi"; | @@ -15,12 +15,14 @@ import { history } from "umi"; | ||
15 | import moment, { Moment } from "moment"; | 15 | import moment, { Moment } from "moment"; |
16 | import useInitial from "@/hooks/useInitail"; | 16 | import useInitial from "@/hooks/useInitail"; |
17 | import { Provider, useStore } from "./store"; | 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 | import EntryTaskPreview from "./components/EntryTaskPreview"; | 19 | import EntryTaskPreview from "./components/EntryTaskPreview"; |
20 | import { OrderTaskApprovalType } from "./entity"; | 20 | import { OrderTaskApprovalType } from "./entity"; |
21 | import AdviserTaskPreview from "./components/AdviserTaskPreview"; | 21 | import AdviserTaskPreview from "./components/AdviserTaskPreview"; |
22 | import SeriesTaskPreview from "./components/SeriesTaskPreview"; | 22 | import SeriesTaskPreview from "./components/SeriesTaskPreview"; |
23 | import ApproveModal from "@/pages/order3/Common/ApproveModal"; | 23 | import ApproveModal from "@/pages/order3/Common/ApproveModal"; |
24 | +import SaleTaskAutoAssign from "./components/SaleTaskAutoAssign"; | ||
25 | +import SaleTaskBatchSet from "./components/SaleTaskBatchSet"; | ||
24 | 26 | ||
25 | const { Column } = Table; | 27 | const { Column } = Table; |
26 | 28 | ||
@@ -42,6 +44,9 @@ function SaleTaskList() { | @@ -42,6 +44,9 @@ function SaleTaskList() { | ||
42 | const [stpVisible, setStpVisible] = useState(false); | 44 | const [stpVisible, setStpVisible] = useState(false); |
43 | const [seriesTaskParams, setSeriesTaskParams] = useState({}); | 45 | const [seriesTaskParams, setSeriesTaskParams] = useState({}); |
44 | 46 | ||
47 | + const [autoVisible, setAutoVisible] = useState(false); | ||
48 | + const [batchVisible, setBatchVisible] = useState(false); | ||
49 | + | ||
45 | const { data, loading, setParams } = useInitial( | 50 | const { data, loading, setParams } = useInitial( |
46 | API.getSaleTaskApi, | 51 | API.getSaleTaskApi, |
47 | {} as API.GetSaleTaskApiRes, | 52 | {} as API.GetSaleTaskApiRes, |
@@ -71,6 +76,19 @@ function SaleTaskList() { | @@ -71,6 +76,19 @@ function SaleTaskList() { | ||
71 | }); | 76 | }); |
72 | }; | 77 | }; |
73 | 78 | ||
79 | + // 查看销顾任务 | ||
80 | + const goToAdviserPage = (record: API.ShopTaskItem) => { | ||
81 | + history.push({ | ||
82 | + pathname: "/order3/saleTask/edit", | ||
83 | + query: { | ||
84 | + readOnly: isReadOnly ? "1" : "0", | ||
85 | + shopId: String(record.shopId), | ||
86 | + taskDate: String(targetMonth.valueOf()), | ||
87 | + currTab: "2", | ||
88 | + }, | ||
89 | + }); | ||
90 | + }; | ||
91 | + | ||
74 | // 查看流程进度 | 92 | // 查看流程进度 |
75 | const viewProcess = () => { | 93 | const viewProcess = () => { |
76 | setApprove({ | 94 | setApprove({ |
@@ -128,26 +146,122 @@ function SaleTaskList() { | @@ -128,26 +146,122 @@ function SaleTaskList() { | ||
128 | setEtpVisible(true); | 146 | setEtpVisible(true); |
129 | }; | 147 | }; |
130 | 148 | ||
149 | + // 销顾任务 | ||
150 | + const showAdviserModal = ( | ||
151 | + record: API.TaskListItem, | ||
152 | + type: OrderTaskApprovalType | ||
153 | + ) => { | ||
154 | + const params: any = { | ||
155 | + id: data.id, | ||
156 | + taskId: record.id, | ||
157 | + orderTaskApprovalType: OrderTaskApprovalType.门店维度, // 只有门店有查看销顾任务 | ||
158 | + }; | ||
159 | + switch (type) { | ||
160 | + case OrderTaskApprovalType.门店维度: | ||
161 | + params.shopId = record.dataId; | ||
162 | + break; | ||
163 | + case OrderTaskApprovalType.销售顾问维度: | ||
164 | + params.staffId = record.dataId; | ||
165 | + break; | ||
166 | + case OrderTaskApprovalType.新车一级管理维度: | ||
167 | + params.firstManageId = record.dataId; | ||
168 | + break; | ||
169 | + case OrderTaskApprovalType.新车二级管理维度: | ||
170 | + params.secondManageId = record.dataId; | ||
171 | + break; | ||
172 | + case OrderTaskApprovalType.新车三级管理维度: | ||
173 | + params.thirdManageId = record.dataId; | ||
174 | + break; | ||
175 | + default: | ||
176 | + break; | ||
177 | + } | ||
178 | + setAdviserTaskParams(params); | ||
179 | + setAtpVisible(true); | ||
180 | + }; | ||
181 | + | ||
182 | + // 销顾任务--查看车系任务 | ||
183 | + const showSeriesModalByAdviser = (record: API.TaskListItem) => { | ||
184 | + const params = { ...adviserTaskParams } as any; | ||
185 | + params.taskId = record.id; | ||
186 | + params.orderTaskApprovalType = OrderTaskApprovalType.车系; | ||
187 | + params.staffId = record.dataId; | ||
188 | + setSeriesTaskParams(params); | ||
189 | + setStpVisible(true); | ||
190 | + }; | ||
191 | + | ||
192 | + // 车系任务 | ||
193 | + const showSeriesModal = ( | ||
194 | + record: API.TaskListItem, | ||
195 | + type: OrderTaskApprovalType | ||
196 | + ) => { | ||
197 | + const params: any = { | ||
198 | + id: data.id, | ||
199 | + taskId: record.id, | ||
200 | + orderTaskApprovalType: OrderTaskApprovalType.车系, | ||
201 | + }; | ||
202 | + switch (type) { | ||
203 | + case OrderTaskApprovalType.门店维度: | ||
204 | + params.shopId = record.dataId; | ||
205 | + break; | ||
206 | + case OrderTaskApprovalType.销售顾问维度: | ||
207 | + params.staffId = record.dataId; | ||
208 | + break; | ||
209 | + case OrderTaskApprovalType.新车一级管理维度: | ||
210 | + params.firstManageId = record.dataId; | ||
211 | + break; | ||
212 | + case OrderTaskApprovalType.新车二级管理维度: | ||
213 | + params.secondManageId = record.dataId; | ||
214 | + break; | ||
215 | + case OrderTaskApprovalType.新车三级管理维度: | ||
216 | + params.thirdManageId = record.dataId; | ||
217 | + break; | ||
218 | + default: | ||
219 | + break; | ||
220 | + } | ||
221 | + setSeriesTaskParams(params); | ||
222 | + setStpVisible(true); | ||
223 | + }; | ||
224 | + | ||
131 | return ( | 225 | return ( |
132 | <PageHeaderWrapper title="零售任务分配"> | 226 | <PageHeaderWrapper title="零售任务分配"> |
133 | <Card> | 227 | <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 | - /> | 228 | + <Row |
229 | + align="middle" | ||
230 | + justify="space-between" | ||
231 | + style={{ marginBottom: 20 }} | ||
232 | + > | ||
233 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | ||
234 | + <DatePicker | ||
235 | + placeholder="月度" | ||
236 | + style={{ width: 260 }} | ||
237 | + picker="month" | ||
238 | + value={targetMonth} | ||
239 | + onChange={handleChangeMonth} | ||
240 | + allowClear={false} | ||
241 | + /> | ||
242 | + <Input.Search | ||
243 | + allowClear | ||
244 | + placeholder="门店名称" | ||
245 | + style={{ width: 260, marginLeft: 15 }} | ||
246 | + onSearch={(v) => { | ||
247 | + setParams({ shopName: v }, true); | ||
248 | + }} | ||
249 | + /> | ||
250 | + </Row> | ||
251 | + {!isReadOnly && ( | ||
252 | + <Row align="middle" justify="start" style={{ marginBottom: 20 }}> | ||
253 | + <Button type="primary" onClick={() => setAutoVisible(true)}> | ||
254 | + 零售任务快捷分配 | ||
255 | + </Button> | ||
256 | + <Button | ||
257 | + type="primary" | ||
258 | + style={{ marginLeft: 10 }} | ||
259 | + onClick={() => setBatchVisible(true)} | ||
260 | + > | ||
261 | + 批量设置 | ||
262 | + </Button> | ||
263 | + </Row> | ||
264 | + )} | ||
151 | </Row> | 265 | </Row> |
152 | <Table | 266 | <Table |
153 | rowKey="id" | 267 | rowKey="id" |
@@ -168,34 +282,34 @@ function SaleTaskList() { | @@ -168,34 +282,34 @@ function SaleTaskList() { | ||
168 | fontWeight: 500, | 282 | fontWeight: 500, |
169 | }} | 283 | }} |
170 | > | 284 | > |
171 | - <Table.Summary.Cell align="left" index={1}> | ||
172 | - 合计 | ||
173 | - </Table.Summary.Cell> | ||
174 | - <Table.Summary.Cell align="left" index={2}> | 285 | + <Table.Summary.Cell index={1}>合计</Table.Summary.Cell> |
286 | + <Table.Summary.Cell index={2}> | ||
175 | {data.totalTaskCount} | 287 | {data.totalTaskCount} |
176 | </Table.Summary.Cell> | 288 | </Table.Summary.Cell> |
177 | - <Table.Summary.Cell align="left" index={3}> | 289 | + <Table.Summary.Cell index={3}> |
178 | {data.newEnergyTaskCount} | 290 | {data.newEnergyTaskCount} |
179 | </Table.Summary.Cell> | 291 | </Table.Summary.Cell> |
180 | - <Table.Summary.Cell align="left" index={4}> | 292 | + <Table.Summary.Cell index={4}> |
181 | {data.fuelVehicleTaskCount} | 293 | {data.fuelVehicleTaskCount} |
182 | </Table.Summary.Cell> | 294 | </Table.Summary.Cell> |
183 | - <Table.Summary.Cell align="left" index={5}> | 295 | + <Table.Summary.Cell index={5} /> |
296 | + <Table.Summary.Cell index={6}> | ||
184 | {data.vehicleGrossProfitTask} | 297 | {data.vehicleGrossProfitTask} |
185 | </Table.Summary.Cell> | 298 | </Table.Summary.Cell> |
186 | - <Table.Summary.Cell index={6}> | 299 | + <Table.Summary.Cell index={7}> |
187 | {data.clueDealTaskCount} | 300 | {data.clueDealTaskCount} |
188 | </Table.Summary.Cell> | 301 | </Table.Summary.Cell> |
189 | - <Table.Summary.Cell index={7}> | 302 | + <Table.Summary.Cell index={8}> |
190 | {data.testDriveTaskCount} | 303 | {data.testDriveTaskCount} |
191 | </Table.Summary.Cell> | 304 | </Table.Summary.Cell> |
192 | - <Table.Summary.Cell index={8}> | 305 | + <Table.Summary.Cell index={9}> |
193 | {data.tackCarTaskCount} | 306 | {data.tackCarTaskCount} |
194 | </Table.Summary.Cell> | 307 | </Table.Summary.Cell> |
195 | - <Table.Summary.Cell index={9}> | 308 | + <Table.Summary.Cell index={10}> |
196 | {data.seriesTaskCount} | 309 | {data.seriesTaskCount} |
197 | </Table.Summary.Cell> | 310 | </Table.Summary.Cell> |
198 | - <Table.Summary.Cell index={10} /> | 311 | + <Table.Summary.Cell index={11} /> |
312 | + <Table.Summary.Cell index={12} /> | ||
199 | </Table.Summary.Row> | 313 | </Table.Summary.Row> |
200 | </Table.Summary> | 314 | </Table.Summary> |
201 | ); | 315 | ); |
@@ -205,7 +319,16 @@ function SaleTaskList() { | @@ -205,7 +319,16 @@ function SaleTaskList() { | ||
205 | <Column title="零售任务(台)" dataIndex="taskCount" /> | 319 | <Column title="零售任务(台)" dataIndex="taskCount" /> |
206 | <Column title="新能源车任务(台)" dataIndex="newEnergyTaskCount" /> | 320 | <Column title="新能源车任务(台)" dataIndex="newEnergyTaskCount" /> |
207 | <Column title="传统燃油车任务(台)" dataIndex="fuelVehicleTaskCount" /> | 321 | <Column title="传统燃油车任务(台)" dataIndex="fuelVehicleTaskCount" /> |
208 | - <Column title="车辆毛利任务(元)" dataIndex="vehicleGrossProfitTask" /> | 322 | + <Column title="单车毛利任务(元)" dataIndex="vehicleGrossProfitTask" /> |
323 | + <Column | ||
324 | + title="合计(元)" | ||
325 | + dataIndex="total" | ||
326 | + render={(text: string, record: API.ShopTaskItem) => { | ||
327 | + return (record.taskCount * record.vehicleGrossProfitTask).toFixed( | ||
328 | + 2 | ||
329 | + ); | ||
330 | + }} | ||
331 | + /> | ||
209 | <Column title="线索到店零售台数(台)" dataIndex="clueDealTaskCount" /> | 332 | <Column title="线索到店零售台数(台)" dataIndex="clueDealTaskCount" /> |
210 | <Column | 333 | <Column |
211 | title="首客试驾成交任务数(台)" | 334 | title="首客试驾成交任务数(台)" |
@@ -214,6 +337,20 @@ function SaleTaskList() { | @@ -214,6 +337,20 @@ function SaleTaskList() { | ||
214 | <Column title="攻坚车任务数(台)" dataIndex="tackCarTaskCount" /> | 337 | <Column title="攻坚车任务数(台)" dataIndex="tackCarTaskCount" /> |
215 | <Column title="车系任务数(台)" dataIndex="seriesTaskCount" /> | 338 | <Column title="车系任务数(台)" dataIndex="seriesTaskCount" /> |
216 | <Column | 339 | <Column |
340 | + title="销顾任务" | ||
341 | + render={(text: string, record: API.ShopTaskItem) => { | ||
342 | + return ( | ||
343 | + <a | ||
344 | + onClick={() => { | ||
345 | + goToAdviserPage(record); | ||
346 | + }} | ||
347 | + > | ||
348 | + 查看 | ||
349 | + </a> | ||
350 | + ); | ||
351 | + }} | ||
352 | + /> | ||
353 | + <Column | ||
217 | title="操作" | 354 | title="操作" |
218 | render={(text: string, record: API.ShopTaskItem) => { | 355 | render={(text: string, record: API.ShopTaskItem) => { |
219 | return ( | 356 | return ( |
@@ -291,62 +428,8 @@ function SaleTaskList() { | @@ -291,62 +428,8 @@ function SaleTaskList() { | ||
291 | </Row> | 428 | </Row> |
292 | <EntryTaskPreview | 429 | <EntryTaskPreview |
293 | params={previewTaskParams} | 430 | 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 | - }} | 431 | + showAdviserModal={showAdviserModal} |
432 | + showSeriesModal={showSeriesModal} | ||
350 | /> | 433 | /> |
351 | </Modal> | 434 | </Modal> |
352 | <Modal | 435 | <Modal |
@@ -359,14 +442,7 @@ function SaleTaskList() { | @@ -359,14 +442,7 @@ function SaleTaskList() { | ||
359 | > | 442 | > |
360 | <AdviserTaskPreview | 443 | <AdviserTaskPreview |
361 | params={adviserTaskParams} | 444 | 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 | - }} | 445 | + showSeriesModal={showSeriesModalByAdviser} |
370 | /> | 446 | /> |
371 | </Modal> | 447 | </Modal> |
372 | <Modal | 448 | <Modal |
@@ -379,6 +455,36 @@ function SaleTaskList() { | @@ -379,6 +455,36 @@ function SaleTaskList() { | ||
379 | > | 455 | > |
380 | <SeriesTaskPreview params={seriesTaskParams} /> | 456 | <SeriesTaskPreview params={seriesTaskParams} /> |
381 | </Modal> | 457 | </Modal> |
458 | + <Modal | ||
459 | + width={800} | ||
460 | + title="零售任务快捷分配" | ||
461 | + open={autoVisible} | ||
462 | + onCancel={() => { | ||
463 | + setAutoVisible(false); | ||
464 | + setParams({}, true); | ||
465 | + }} | ||
466 | + destroyOnClose | ||
467 | + footer={null} | ||
468 | + > | ||
469 | + <SaleTaskAutoAssign | ||
470 | + id={data.id} | ||
471 | + value={data.shopTaskList} | ||
472 | + onCancel={() => { | ||
473 | + setAutoVisible(false); | ||
474 | + setParams({}, true); | ||
475 | + }} | ||
476 | + /> | ||
477 | + </Modal> | ||
478 | + <Modal | ||
479 | + width={800} | ||
480 | + title="批量设置" | ||
481 | + open={batchVisible} | ||
482 | + onCancel={() => setBatchVisible(false)} | ||
483 | + destroyOnClose | ||
484 | + footer={null} | ||
485 | + > | ||
486 | + <SaleTaskBatchSet /> | ||
487 | + </Modal> | ||
382 | <ApprovalProgressModal | 488 | <ApprovalProgressModal |
383 | visible={approve.visible} | 489 | visible={approve.visible} |
384 | orderNo={approve.orderNo} | 490 | orderNo={approve.orderNo} |
src/pages/order3/SaleTask/subpages/TaskEdit/index.tsx
@@ -20,8 +20,9 @@ function TaskEdit() { | @@ -20,8 +20,9 @@ function TaskEdit() { | ||
20 | const readOnly = querys?.readOnly === "1"; | 20 | const readOnly = querys?.readOnly === "1"; |
21 | const shopId = querys?.shopId; | 21 | const shopId = querys?.shopId; |
22 | const taskDate = querys?.taskDate; | 22 | const taskDate = querys?.taskDate; |
23 | + const queryTab = querys?.currTab; | ||
23 | const { shopTaskItem, setShopTaskItem, setIsReadOnly } = useStore(); | 24 | const { shopTaskItem, setShopTaskItem, setIsReadOnly } = useStore(); |
24 | - const [currStep, setCurrStep] = useState("1"); | 25 | + const [currTab, setCurrTab] = useState(queryTab ?? "1"); |
25 | const [shopTaskForm] = Form.useForm(); | 26 | const [shopTaskForm] = Form.useForm(); |
26 | 27 | ||
27 | // 获取门店零售任务详情 | 28 | // 获取门店零售任务详情 |
@@ -46,8 +47,8 @@ function TaskEdit() { | @@ -46,8 +47,8 @@ function TaskEdit() { | ||
46 | title={<span>当前选择门店:{shopTaskItem?.shopName}</span>} | 47 | title={<span>当前选择门店:{shopTaskItem?.shopName}</span>} |
47 | > | 48 | > |
48 | <Tabs | 49 | <Tabs |
49 | - defaultActiveKey={currStep} | ||
50 | - onChange={(activeKey) => setCurrStep(activeKey)} | 50 | + defaultActiveKey={currTab} |
51 | + onChange={(activeKey) => setCurrTab(activeKey)} | ||
51 | items={[ | 52 | items={[ |
52 | { | 53 | { |
53 | label: `门店任务分配${readOnly ? "详情" : ""}`, | 54 | label: `门店任务分配${readOnly ? "详情" : ""}`, |