Commit df8ffe92b59080066234c4413f678d6e48bf8d65
Merge branch 'bug_fix' of gitlab.feewee.cn:FEV2/fw-cms into common
Showing
30 changed files
with
1425 additions
and
111 deletions
build/admin.tar.gz
No preview for this file type
config/routers/crm_new.ts
... | ... | @@ -53,4 +53,12 @@ export default [ |
53 | 53 | path: '/crm/recordDetail/:recordId', // 第三方渠道导入记录详情 |
54 | 54 | component: './crm_new/ChannelImport/subpages/RecordDetail', |
55 | 55 | }, |
56 | + { | |
57 | + path: '/crm/tragetProportion', // 目标和占比设置 | |
58 | + component: './crm_new/TargetAndProportion', | |
59 | + }, | |
60 | + { | |
61 | + path: '/crm/addTragetProportion', // 添加目标和占比设置 | |
62 | + component: './crm_new/TargetAndProportion/subpages/AddNewSetting', | |
63 | + }, | |
56 | 64 | ]; |
57 | 65 | \ No newline at end of file | ... | ... |
config/routers/order3.ts
... | ... | @@ -202,4 +202,8 @@ export default [ |
202 | 202 | path: "/order3/orderSetting/deliveryVideoConfig", |
203 | 203 | component: "./order3/OrderSetting/DeliveryVideoConfig", |
204 | 204 | }, |
205 | + { // 零售线索占比配置 | |
206 | + path: "/order3/retailTaskConfiguration", | |
207 | + component: "./order3/RetailTaskConfiguration", | |
208 | + }, | |
205 | 209 | ]; |
206 | 210 | \ No newline at end of file | ... | ... |
src/components/ShopSelectNew/api.ts
1 | 1 | /* |
2 | 2 | * @Date: 2021-07-08 16:53:36 |
3 | 3 | * @LastEditors: wangqiang@feewee.cn |
4 | - * @LastEditTime: 2022-05-25 11:32:53 | |
4 | + * @LastEditTime: 2022-10-08 16:17:27 | |
5 | 5 | */ |
6 | 6 | import { http } from "@/typing/http"; |
7 | 7 | import request from "@/utils/request"; |
8 | 8 | import { OOP_HOST } from "@/utils/host"; |
9 | 9 | import { LabelValueType } from "rc-tree-select/lib/interface"; |
10 | 10 | |
11 | -export interface QueryParams { | |
12 | - type: 1 | 2; // 1 集团维度 2 自定义门店维度 | |
13 | - shops?: string; // 门店集合 ,分割 | |
14 | - keywords?: string; // 门店关键字搜索 | |
11 | +export interface ShopSelectNewOptions { | |
15 | 12 | bizTypes?: string; // 业态类型集合 ,分割 |
16 | 13 | brands?: string; // 品牌id集合 ,分割 |
17 | 14 | regions?: string; // 区域编码集合 ,分割 |
18 | 15 | dealers?: string; // 商家id集合 ,分割 |
19 | 16 | } |
20 | 17 | |
18 | +export interface QueryParams extends ShopSelectNewOptions { | |
19 | + type: 1 | 2; // 1 集团维度 2 自定义门店维度 | |
20 | + shops?: string; // 门店集合 ,分割 | |
21 | + keywords?: string; // 门店关键字搜索 | |
22 | +} | |
23 | + | |
21 | 24 | export interface ShopItem { |
22 | 25 | shopId?: number; //门店id |
23 | 26 | shopFullName?: string; //门店全称 | ... | ... |
src/components/ShopSelectNew/components/ShopModal.tsx
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | * @Author: wangqiang@feewee.cn |
3 | 3 | * @Date: 2022-05-25 10:03:00 |
4 | 4 | * @LastEditors: wangqiang@feewee.cn |
5 | - * @LastEditTime: 2022-06-10 15:57:29 | |
5 | + * @LastEditTime: 2022-10-08 16:33:31 | |
6 | 6 | */ |
7 | 7 | import { InitialReturn } from "@/hooks/useInitail"; |
8 | 8 | import { |
... | ... | @@ -29,6 +29,7 @@ import { |
29 | 29 | Value, |
30 | 30 | Option, |
31 | 31 | QueryParams, |
32 | + ShopSelectNewOptions, | |
32 | 33 | } from "../api"; |
33 | 34 | |
34 | 35 | interface Props { |
... | ... | @@ -47,6 +48,7 @@ interface Props { |
47 | 48 | multiple?: boolean; |
48 | 49 | optionInitial: InitialReturn<Option, QueryParams>; |
49 | 50 | listInitial: InitialReturn<ShopItem[], QueryParams>; |
51 | + defaultOptions?: ShopSelectNewOptions; | |
50 | 52 | } |
51 | 53 | |
52 | 54 | export default function ShopModal({ |
... | ... | @@ -62,6 +64,7 @@ export default function ShopModal({ |
62 | 64 | multiple, |
63 | 65 | optionInitial, |
64 | 66 | listInitial, |
67 | + defaultOptions = {}, | |
65 | 68 | }: Props) { |
66 | 69 | const fetchListByName = debounce( |
67 | 70 | (keywords) => listInitial.setParams({ type, keywords }, true), |
... | ... | @@ -245,14 +248,26 @@ export default function ShopModal({ |
245 | 248 | ? listInitial.params.bizTypes.split(",") |
246 | 249 | : undefined |
247 | 250 | } |
248 | - onChange={(bizs) => listInitial.setParams({ type, bizTypes: bizs?.join(",") }, true)} | |
251 | + onChange={(bizs) => listInitial.setParams( | |
252 | + { | |
253 | + type, | |
254 | + bizTypes: bizs.length | |
255 | + ? bizs?.join(",") | |
256 | + : defaultOptions.bizTypes, | |
257 | + }, | |
258 | + true | |
259 | + )} | |
249 | 260 | showSearch |
250 | 261 | optionFilterProp="children" |
251 | 262 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} |
252 | 263 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
253 | 264 | > |
254 | 265 | {optionInitial.data.bizList?.map((biz) => ( |
255 | - <Select.Option key={biz.type} value={"" + biz.type!}> | |
266 | + <Select.Option | |
267 | + key={biz.type} | |
268 | + value={"" + biz.type!} | |
269 | + disabled={defaultOptions.bizTypes?.includes("" + biz.type)} | |
270 | + > | |
256 | 271 | {biz.name} |
257 | 272 | </Select.Option> |
258 | 273 | ))} |
... | ... | @@ -266,14 +281,26 @@ export default function ShopModal({ |
266 | 281 | ? listInitial.params.regions.split(",") |
267 | 282 | : undefined |
268 | 283 | } |
269 | - onChange={(regions) => listInitial.setParams({ type, regions: regions?.join(",") }, true)} | |
284 | + onChange={(regions) => listInitial.setParams( | |
285 | + { | |
286 | + type, | |
287 | + regions: regions.length | |
288 | + ? regions?.join(",") | |
289 | + : defaultOptions.regions, | |
290 | + }, | |
291 | + true | |
292 | + )} | |
270 | 293 | showSearch |
271 | 294 | optionFilterProp="children" |
272 | 295 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} |
273 | 296 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
274 | 297 | > |
275 | 298 | {optionInitial.data.regionList?.map((region) => ( |
276 | - <Select.Option key={region.bh} value={region.bh!}> | |
299 | + <Select.Option | |
300 | + key={region.bh} | |
301 | + value={region.bh!} | |
302 | + disabled={defaultOptions.regions?.includes("" + region.bh)} | |
303 | + > | |
277 | 304 | {region.fullName} |
278 | 305 | </Select.Option> |
279 | 306 | ))} |
... | ... | @@ -288,7 +315,12 @@ export default function ShopModal({ |
288 | 315 | : undefined |
289 | 316 | } |
290 | 317 | onChange={(brandList) => listInitial.setParams( |
291 | - { type, brands: brandList?.join(",") }, | |
318 | + { | |
319 | + type, | |
320 | + brands: brandList.length | |
321 | + ? brandList?.join(",") | |
322 | + : defaultOptions.brands, | |
323 | + }, | |
292 | 324 | true |
293 | 325 | )} |
294 | 326 | showSearch |
... | ... | @@ -297,7 +329,11 @@ export default function ShopModal({ |
297 | 329 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
298 | 330 | > |
299 | 331 | {optionInitial.data.brandList?.map((brand) => ( |
300 | - <Select.Option key={brand.id} value={"" + brand.id!}> | |
332 | + <Select.Option | |
333 | + key={brand.id} | |
334 | + value={"" + brand.id!} | |
335 | + disabled={defaultOptions.brands?.includes("" + brand.id)} | |
336 | + > | |
301 | 337 | {brand.name} |
302 | 338 | </Select.Option> |
303 | 339 | ))} |
... | ... | @@ -312,7 +348,12 @@ export default function ShopModal({ |
312 | 348 | : undefined |
313 | 349 | } |
314 | 350 | onChange={(dealerList) => listInitial.setParams( |
315 | - { type, dealers: dealerList?.join(",") }, | |
351 | + { | |
352 | + type, | |
353 | + dealers: dealerList.length | |
354 | + ? dealerList?.join(",") | |
355 | + : defaultOptions.dealers, | |
356 | + }, | |
316 | 357 | true |
317 | 358 | )} |
318 | 359 | showSearch |
... | ... | @@ -321,7 +362,11 @@ export default function ShopModal({ |
321 | 362 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
322 | 363 | > |
323 | 364 | {optionInitial.data.dealerList?.map((dealer) => ( |
324 | - <Select.Option key={dealer.id} value={"" + dealer.id!}> | |
365 | + <Select.Option | |
366 | + key={dealer.id} | |
367 | + value={"" + dealer.id!} | |
368 | + disabled={defaultOptions.dealers?.includes("" + dealer.id)} | |
369 | + > | |
325 | 370 | {dealer.name} |
326 | 371 | </Select.Option> |
327 | 372 | ))} | ... | ... |
src/components/ShopSelectNew/index.tsx
... | ... | @@ -2,7 +2,7 @@ |
2 | 2 | * @Author: wangqiang@feewee.cn |
3 | 3 | * @Date: 2022-05-25 09:14:31 |
4 | 4 | * @LastEditors: wangqiang@feewee.cn |
5 | - * @LastEditTime: 2022-07-12 09:41:43 | |
5 | + * @LastEditTime: 2022-10-08 16:36:39 | |
6 | 6 | */ |
7 | 7 | import useInitial from "@/hooks/useInitail"; |
8 | 8 | import { Tag } from "antd"; |
... | ... | @@ -14,7 +14,12 @@ import React, { |
14 | 14 | useImperativeHandle, |
15 | 15 | useState, |
16 | 16 | } from "react"; |
17 | -import { Value, getShopListChooseOptionsApi, getShopListApi } from "./api"; | |
17 | +import { | |
18 | + Value, | |
19 | + getShopListChooseOptionsApi, | |
20 | + getShopListApi, | |
21 | + ShopSelectNewOptions, | |
22 | +} from "./api"; | |
18 | 23 | import Close from "./components/Close"; |
19 | 24 | import ShopModal from "./components/ShopModal"; |
20 | 25 | import "./style.less"; |
... | ... | @@ -28,6 +33,14 @@ interface Props { |
28 | 33 | placeholder?: string; |
29 | 34 | type?: 1 | 2; // 1 集团维度 2 自定义门店维度 |
30 | 35 | shopIds?: number[]; // 仅查询门店ID列表,与 type = 2 时搭配使用 |
36 | + /** | |
37 | + * 默认筛选参数,下拉框中不可删除; | |
38 | + * 用于特殊业务,例如 售前CRM-零售任务 选择门店时,需要默认查询 新车销售 的门店信息,则传入 | |
39 | + defaultOptions={{ | |
40 | + bizTypes: "1", | |
41 | + }} | |
42 | + */ | |
43 | + defaultOptions?: ShopSelectNewOptions; | |
31 | 44 | } |
32 | 45 | |
33 | 46 | export interface ShopSelectNewRef { |
... | ... | @@ -46,6 +59,7 @@ function ShopSelectNew( |
46 | 59 | type = 1, |
47 | 60 | disabled = false, |
48 | 61 | shopIds, |
62 | + defaultOptions = {}, | |
49 | 63 | }: Props, |
50 | 64 | ref: Ref<ShopSelectNewRef> |
51 | 65 | ) { |
... | ... | @@ -62,6 +76,7 @@ function ShopSelectNew( |
62 | 76 | [], |
63 | 77 | { |
64 | 78 | type, |
79 | + ...defaultOptions, | |
65 | 80 | }, |
66 | 81 | delay |
67 | 82 | ); |
... | ... | @@ -173,6 +188,7 @@ function ShopSelectNew( |
173 | 188 | setVisible={setVisible} |
174 | 189 | optionInitial={optionInitial} |
175 | 190 | listInitial={listInitial} |
191 | + defaultOptions={defaultOptions} | |
176 | 192 | /> |
177 | 193 | )} |
178 | 194 | </div> | ... | ... |
src/pages/crm_new/TargetAndProportion/api.ts
0 → 100644
1 | +import { http } from '@/typing/http'; | |
2 | +import request from '@/utils/request'; | |
3 | +import { CRM_HOST } from '@/utils/host'; | |
4 | + | |
5 | +export interface SaveParams { | |
6 | + id?: number // 配置id | |
7 | + shopList?: ShopList[] // 门店列表 | |
8 | + operationType?: number // 0删除 1新增 2更新 | |
9 | + clueAimExclusive?: number // 线索目标 专属线索转化率 | |
10 | + clueAimManufacturer?: number // 线索目标 厂家下发转化率配置 | |
11 | + clueAimBroker?: number // 经纪人推荐目标占比转化率配置 | |
12 | + clueAimCas?: number // 保有客推荐目标占比转化率配置 | |
13 | + clueAimStaff?: number // 员工推荐目标比 | |
14 | + clueAimVisit?: number // 定巡展车展目标比 | |
15 | + clueDealAim?: number // 计算线索到店目标:线索到店成交潜客转化率 | |
16 | + gatherAim?: number // 计算月集客目标集客转化率配置不能小于0 | |
17 | + gatherAimExclusive?: number // 专属线索转化集客目标要求月集客目标的不能小于0 | |
18 | + gatherLiveAimDirect?: number // 直播/短视频直接集客目标占比 | |
19 | + potentialAim?: number // 集客转潜客占比 | |
20 | + potentialAimExclusive?: number // 潜客转化率 | |
21 | +} | |
22 | + | |
23 | +export interface ShopList { | |
24 | + shopName?: string // 门店名称 | |
25 | + shopId?: number // 门店id | |
26 | +} | |
27 | + | |
28 | +export interface List { | |
29 | + id?: number // 配置id | |
30 | + shopName?: string // 门店名称 | |
31 | +} | |
32 | +/** | |
33 | + * @description: 查询配置列表 | |
34 | + */ | |
35 | +export function getListApi(): http.PromiseResp<List[]> { | |
36 | + return request.get(`${CRM_HOST}/erp/config/find/aims/rate/list`); | |
37 | +} | |
38 | + | |
39 | +/** | |
40 | + * @description: 查询配置详情 | |
41 | + */ | |
42 | +export function getConfigApi(id?: number): http.PromiseResp<SaveParams> { | |
43 | + return request.get(`${CRM_HOST}/erp/config/find/aims/rate/id`, {params: {id}}); | |
44 | +} | |
45 | + | |
46 | +/** | |
47 | + * @description: 目标和占比设置 新增、更新 | |
48 | + */ | |
49 | +export function fetchOperationApi(parmas: SaveParams): http.PromiseResp<string> { | |
50 | + return request.post(`${CRM_HOST}/erp/config/edit/aims/rate/all`, parmas); | |
51 | +} | |
52 | + | |
53 | +/** | |
54 | + * @description: 删除配置 | |
55 | + */ | |
56 | +export function fetchDeleteConfigApi(id?: number): http.PromiseResp<string> { | |
57 | + return request.post(`${CRM_HOST}/erp/config/delete/aim`, {id}, {contentType: 'form-urlencoded'}); | |
58 | +} | |
59 | + | |
60 | +/** | |
61 | + * @description: 查询门店 | |
62 | + */ | |
63 | +export function getShopListApi(): http.PromiseResp<List[]> { | |
64 | + return request.get(`${CRM_HOST}/erp/config/find/aims/shop`); | |
65 | +} | ... | ... |
src/pages/crm_new/TargetAndProportion/components/List.tsx
0 → 100644
1 | +import React, { useState } from "react"; | |
2 | +import { Popconfirm, Table, message, Space } from "antd"; | |
3 | +import { history } from 'umi'; | |
4 | +import { SaveParams, fetchOperationApi, fetchDeleteConfigApi, List } from '../api'; | |
5 | +import {useStore} from '../index'; | |
6 | + | |
7 | +const Column = Table.Column; | |
8 | + | |
9 | +export default function Index() { | |
10 | + const { statusData, setStatusData, data, setLoading } = useStore(); | |
11 | + function handleToEdit(value: List, operationType: number) { | |
12 | + const state = {id: value.id, operationType}; | |
13 | + history.push({pathname: '/crm/addTragetProportion', state}); | |
14 | + } | |
15 | + | |
16 | + function handleToDelete(operationType: number, id?: number) { | |
17 | + fetchDeleteConfigApi(id) | |
18 | + .then(res => { | |
19 | + message.success(res.result); | |
20 | + setLoading(true); | |
21 | + }) | |
22 | + .catch(e => { | |
23 | + message.error(e.message); | |
24 | + }); | |
25 | + } | |
26 | + | |
27 | + function handleToShopDetail(value?: List) { | |
28 | + setStatusData({...statusData, shop: true, id: value?.id}); | |
29 | + } | |
30 | + | |
31 | + function handleToTargetDetail(value: List = {}) { | |
32 | + setStatusData({...statusData, target: true, id: value?.id}); | |
33 | + } | |
34 | + | |
35 | + return ( | |
36 | + <div> | |
37 | + <Table | |
38 | + dataSource={data} | |
39 | + pagination={false} | |
40 | + loading={false} | |
41 | + rowKey="id" | |
42 | + > | |
43 | + <Column | |
44 | + title="适用门店" | |
45 | + align="left" | |
46 | + dataIndex="shopName" | |
47 | + render={(_text, record: List) => <span style={{color: "#4189FD"}} onClick={() => handleToShopDetail(record)}>{record.shopName || '--'}</span>} | |
48 | + /> | |
49 | + <Column | |
50 | + title="目标占比" | |
51 | + align="left" | |
52 | + render={(_text, record: List) => <span style={{color: "#4189FD"}} onClick={() => handleToTargetDetail(record)}>查看</span>} | |
53 | + /> | |
54 | + <Column | |
55 | + title="操作" | |
56 | + align="left" | |
57 | + render={(_text, record: List) => { | |
58 | + return ( | |
59 | + <Space> | |
60 | + <a style={{color: "#4189FD"}} onClick={() => handleToEdit(record, 2)}>编辑</a> | |
61 | + <Popconfirm | |
62 | + title="是否删除?" | |
63 | + okText="确定" | |
64 | + cancelText="取消" | |
65 | + onConfirm={() => handleToDelete(0, record.id)} | |
66 | + > | |
67 | + <span> | |
68 | + <a style={{color: "red"}}>删除</a> | |
69 | + </span> | |
70 | + </Popconfirm> | |
71 | + </Space> | |
72 | + ); | |
73 | + }} | |
74 | + /> | |
75 | + </Table> | |
76 | + </div> | |
77 | + ); | |
78 | +} | ... | ... |
src/pages/crm_new/TargetAndProportion/components/ShopModal.tsx
0 → 100644
1 | +import React, {useEffect, useState} from 'react'; | |
2 | +import {Modal, Button, List, message} from 'antd'; | |
3 | +import { useStore } from '../index'; | |
4 | +import { getConfigApi, SaveParams } from '../api'; | |
5 | +import styles from '../index.less'; | |
6 | + | |
7 | +interface Data { | |
8 | + data: SaveParams | |
9 | + loading: boolean | |
10 | +} | |
11 | +export default function Index() { | |
12 | + const { statusData, setStatusData } = useStore(); | |
13 | + const [data, setData] = useState<Data>({data: {}, loading: false}); | |
14 | + function handleCancel() { | |
15 | + setStatusData({...statusData, shop: false, id: undefined}); | |
16 | + } | |
17 | + | |
18 | + useEffect(() => { | |
19 | + if (statusData.shop && statusData.id) { | |
20 | + setData({...data, loading: true}); | |
21 | + getConfigApi(statusData.id) | |
22 | + .then(res => { | |
23 | + setData({data: res.data || {}, loading: false}); | |
24 | + message.success(res.result); | |
25 | + }) | |
26 | + .catch(e => { | |
27 | + setData({data: {}, loading: false}); | |
28 | + message.error(e.message); | |
29 | + }); | |
30 | + } | |
31 | + }, [statusData.shop]); | |
32 | + | |
33 | + return ( | |
34 | + <Modal | |
35 | + destroyOnClose | |
36 | + title={<div className={styles.lineWrap}><span className={styles.line} /><span className={styles.lineText}>适用门店</span></div>} | |
37 | + visible={statusData.shop} | |
38 | + maskClosable={false} | |
39 | + onCancel={handleCancel} | |
40 | + footer={[]} | |
41 | + className={styles.modal} | |
42 | + > | |
43 | + <List | |
44 | + size="large" | |
45 | + loading={data.loading} | |
46 | + style={{height: '300px', overflow: 'scroll'}} | |
47 | + dataSource={data.data?.shopList || []} | |
48 | + renderItem={(item: any) => <List.Item style={{justifyContent: 'center'}}>{item.shopName}</List.Item>} | |
49 | + /> | |
50 | + <div | |
51 | + style={{ | |
52 | + textAlign: 'center', | |
53 | + marginTop: 12, | |
54 | + height: 32, | |
55 | + lineHeight: '32px', | |
56 | + }} | |
57 | + ><Button type="primary" onClick={handleCancel}>返回</Button> | |
58 | + </div> | |
59 | + </Modal> | |
60 | + ); | |
61 | +} | |
0 | 62 | \ No newline at end of file | ... | ... |
src/pages/crm_new/TargetAndProportion/components/TargetModal.tsx
0 → 100644
1 | +import React, {useState, useEffect} from 'react'; | |
2 | +import {Modal, message, Button, Row, Col} from 'antd'; | |
3 | +import { useStore } from '../index'; | |
4 | +import { isNil } from 'lodash'; | |
5 | +import { getConfigApi, SaveParams } from '../api'; | |
6 | +import styles from '../index.less'; | |
7 | + | |
8 | +interface Data { | |
9 | + data: SaveParams | |
10 | + loading: boolean | |
11 | +} | |
12 | + | |
13 | +export default function Index() { | |
14 | + const { statusData, setStatusData } = useStore(); | |
15 | + const [data, setData] = useState<Data>({data: {}, loading: false}); | |
16 | + | |
17 | + useEffect(() => { | |
18 | + if (statusData.target && statusData.id) { | |
19 | + setData({...data, loading: true}); | |
20 | + getConfigApi(statusData.id) | |
21 | + .then(res => { | |
22 | + setData({data: res.data || {}, loading: false}); | |
23 | + message.success(res.result); | |
24 | + }) | |
25 | + .catch(e => { | |
26 | + setData({data: {}, loading: false}); | |
27 | + message.error(e.message); | |
28 | + }); | |
29 | + } | |
30 | + }, [statusData.target]); | |
31 | + | |
32 | + function handleCancel() { | |
33 | + setStatusData({...statusData, target: false}); | |
34 | + } | |
35 | + | |
36 | + function renderData(value?: number) { | |
37 | + const _value = isNil(value) ? "--" : `${(value*100).toFixed(2)}%`; | |
38 | + return _value; | |
39 | + } | |
40 | + return ( | |
41 | + <Modal | |
42 | + destroyOnClose | |
43 | + title={<div className={styles.lineWrap}><span className={styles.line} /><span className={styles.lineText}>目标占比</span></div>} | |
44 | + visible={statusData.target} | |
45 | + maskClosable={false} | |
46 | + onCancel={handleCancel} | |
47 | + footer={[]} | |
48 | + width={800} | |
49 | + > | |
50 | + <div> | |
51 | + <> | |
52 | + <p className={styles.title}>线索</p> | |
53 | + <p className={styles.text}>计算线索目标:<span className={styles.textTitle}>专属线索转化率</span><span className={styles.text}>{renderData(data.data?.clueAimExclusive)}</span></p> | |
54 | + <Row className={styles.wrap}> | |
55 | + <Col className={styles.wrapItem}><span className={styles.text}>线索目标占比设置:</span></Col> | |
56 | + <Col className={styles.wrapItem}> | |
57 | + <p className={styles.lineSpace}><span className={styles.textTitle}>厂家下发线索目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimManufacturer)}</span>;<span className={styles.textTitle}>经纪人推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimBroker)}</span></p> | |
58 | + <p><span className={styles.textTitle}>保有客推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimCas)}</span>;<span className={styles.textTitle}>员工推荐目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimStaff)}</span></p> | |
59 | + <p><span className={styles.textTitle}>定巡展车展目标占比</span><span className={styles.text}>{renderData(data.data?.clueAimVisit)}</span></p> | |
60 | + </Col> | |
61 | + </Row> | |
62 | + </> | |
63 | + | |
64 | + <> | |
65 | + <p className={styles.title}>集客</p> | |
66 | + <p className={styles.lineSpace}>计算月集客目标:<span className={styles.textTitle}>集客转化率</span><span className={styles.text}>{renderData(data.data?.gatherAim)}</span></p> | |
67 | + <p className={styles.lineSpace}>月集客目标中:<span className={styles.textTitle}>专属线索转集客占比</span><span className={styles.text}>{renderData(data.data?.gatherAimExclusive)}</span></p> | |
68 | + <p className={styles.text}>直接集客目标中:<span className={styles.textTitle}>直播/短视频直接集客目标占比</span><span className={styles.text}>{renderData(data.data?.gatherLiveAimDirect)}</span></p> | |
69 | + </> | |
70 | + | |
71 | + <> | |
72 | + <p className={styles.title}>潜客</p> | |
73 | + <p className={styles.lineSpace}>月潜客目标中:<span className={styles.textTitle}>集客转潜客占比</span><span className={styles.text}>{renderData(data.data?.potentialAim)}</span></p> | |
74 | + <p className={styles.lineSpace}>计算月集客目标:<span className={styles.textTitle}>潜客转化率</span><span className={styles.text}>{renderData(data.data?.potentialAimExclusive)}</span></p> | |
75 | + <p className={styles.lineSpace}>计算线索到店目标:<span className={styles.textTitle}>线索到店成交潜客转化率</span><span className={styles.text}>{renderData(data.data?.clueDealAim)}</span></p> | |
76 | + </> | |
77 | + </div> | |
78 | + | |
79 | + <div | |
80 | + style={{ | |
81 | + textAlign: 'center', | |
82 | + marginTop: 12, | |
83 | + height: 32, | |
84 | + lineHeight: '32px', | |
85 | + }} | |
86 | + ><Button type="primary" onClick={handleCancel}>返回</Button> | |
87 | + </div> | |
88 | + </Modal> | |
89 | + ); | |
90 | +} | |
0 | 91 | \ No newline at end of file | ... | ... |
src/pages/crm_new/TargetAndProportion/index.less
0 → 100644
1 | +.title { | |
2 | + font-size: 18px; | |
3 | + color: #333; | |
4 | + font-weight: 600; | |
5 | +} | |
6 | + | |
7 | +.warp { | |
8 | + display: flex; | |
9 | + direction: row; | |
10 | + align-items: flex-start; | |
11 | + justify-content: flex-start; | |
12 | +} | |
13 | + | |
14 | +.wrapItem { | |
15 | + display: inline-block; | |
16 | +} | |
17 | + | |
18 | +.text { | |
19 | + color: #333; | |
20 | + font-size: 14px; | |
21 | + font-weight: 500; | |
22 | +} | |
23 | + | |
24 | +.textTitle { | |
25 | + color: #333; | |
26 | + font-size: 14px; | |
27 | + font-weight: 400; | |
28 | +} | |
29 | + | |
30 | +.modal { | |
31 | + height: 400px; | |
32 | +} | |
33 | + | |
34 | +.line { | |
35 | + width: 4px; | |
36 | + height: 20px; | |
37 | + background-color: #4189FD; | |
38 | + border-radius: 2px; | |
39 | + display: inline-block; | |
40 | +} | |
41 | +.lineText { | |
42 | + font-size: 18px; | |
43 | + color: #262626; | |
44 | + font-weight: bold; | |
45 | + display: inline-block; | |
46 | + margin-left: 8px; | |
47 | +} | |
48 | + | |
49 | +.lineWrap { | |
50 | + display: flex; | |
51 | + align-items: center; | |
52 | +} | |
53 | + | |
54 | +.lineSpace { | |
55 | + margin-bottom: 10px; | |
56 | + font-size: 14px; | |
57 | + color: #333; | |
58 | + font-weight: 500; | |
59 | +} | |
0 | 60 | \ No newline at end of file | ... | ... |
src/pages/crm_new/TargetAndProportion/index.tsx
0 → 100644
1 | +import React, {useEffect, useState} from 'react'; | |
2 | +import { Card, Button, Row } from 'antd'; | |
3 | +import { PageHeaderWrapper } from '@ant-design/pro-layout'; | |
4 | +import { createStore } from '@/hooks/moz'; | |
5 | +import store from './store'; | |
6 | +import List from './components/List'; | |
7 | +import { history } from 'umi'; | |
8 | +import ShopModal from './components/ShopModal'; | |
9 | +import TargetModal from './components/TargetModal'; | |
10 | + | |
11 | +export const { Provider, useStore } = createStore(store); | |
12 | + | |
13 | +function Index() { | |
14 | + const { loading } = useStore(); | |
15 | + function handleToAdd() { | |
16 | + const state = { operationType: 1}; | |
17 | + history.push({pathname: '/crm/addTragetProportion', state}); | |
18 | + } | |
19 | + | |
20 | + return ( | |
21 | + <PageHeaderWrapper loading={loading} title="目标和占比设置"> | |
22 | + <Card> | |
23 | + <Row justify="end" style={{ marginBottom: 20 }}> | |
24 | + <Button type="primary" onClick={() => handleToAdd()}>新增</Button> | |
25 | + </Row> | |
26 | + <List /> | |
27 | + </Card> | |
28 | + <ShopModal /> | |
29 | + <TargetModal /> | |
30 | + </PageHeaderWrapper> | |
31 | + ); | |
32 | +} | |
33 | + | |
34 | +export default () => <Provider><Index /></Provider>; | ... | ... |
src/pages/crm_new/TargetAndProportion/store.ts
0 → 100644
1 | +import React, { useState } from 'react'; | |
2 | +import useInitail from '@/hooks/useInitail'; | |
3 | +// import useInitial from '@/hooks/useInitail'; | |
4 | +import { getListApi } from './api'; | |
5 | + | |
6 | +interface StatusData { | |
7 | + shop: boolean // 门店显示 | |
8 | + target: boolean // 目标和占比显示 | |
9 | + id?: number | |
10 | +} | |
11 | +export default function useStore() { | |
12 | + const [statusData, setStatusData] = useState<StatusData>({shop: false, target: false, id: undefined}); | |
13 | + const { data, loading, errMsg, setLoading } = useInitail(getListApi, [], null); | |
14 | + return { | |
15 | + statusData, | |
16 | + setStatusData, | |
17 | + data, | |
18 | + loading, | |
19 | + errMsg, | |
20 | + setLoading, | |
21 | + }; | |
22 | +} | |
0 | 23 | \ No newline at end of file | ... | ... |
src/pages/crm_new/TargetAndProportion/subpages/AddNewSetting/index.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Button, Form, message, InputNumber, Divider, Card, Row, Col, Select} from 'antd'; | |
3 | +import ShopSelectNew from '@/components/ShopSelectNew'; | |
4 | +import { PageHeaderWrapper } from '@ant-design/pro-layout'; | |
5 | +import { history } from 'umi'; | |
6 | +import { SaveParams, fetchOperationApi, getConfigApi, getShopListApi } from '../../api'; | |
7 | +import { debounce } from 'lodash'; | |
8 | +import currency from 'currency.js'; | |
9 | +import useInitail from '@/hooks/useInitail'; | |
10 | + | |
11 | +enum TitleEnum { | |
12 | + "新增" =1, | |
13 | + "编辑" | |
14 | +} | |
15 | + | |
16 | +const Option = Select.Option; | |
17 | + | |
18 | +export default function ApplyModal() { | |
19 | + const {id, operationType=1} = history.location.state as SaveParams || {}; | |
20 | + const [form] = Form.useForm(); | |
21 | + const [loading, setLoading] = useState<boolean>(false); | |
22 | + const tip = "厂家下发线索目标占比+经纪人推荐目标占比+保有客推荐目标占比+员工推荐目标占比+定巡展车展目标占比之和需等于100"; | |
23 | + // const { data: shop } = useInitail(getShopListApi, [], null); | |
24 | + | |
25 | + useEffect(() => { | |
26 | + if (operationType === 2) { | |
27 | + getConfigApi(id) | |
28 | + .then(res => { | |
29 | + const {clueAimExclusive=0, clueAimManufacturer=0, clueAimBroker=0, clueAimCas=0, clueAimStaff=0, gatherAim=0, gatherAimExclusive=0, gatherLiveAimDirect=0, potentialAim=0, potentialAimExclusive=0, clueAimVisit=0, shopList, clueDealAim=0} = res.data || {}; | |
30 | + form.setFieldsValue({ | |
31 | + clueAimExclusive: (clueAimExclusive*100).toFixed(2), | |
32 | + clueAimManufacturer: (clueAimManufacturer*100).toFixed(2), | |
33 | + clueAimBroker: (clueAimBroker*100).toFixed(2), | |
34 | + clueAimCas: (clueAimCas*100).toFixed(2), | |
35 | + clueAimStaff: (clueAimStaff*100).toFixed(2), | |
36 | + clueAimVisit: (clueAimVisit*100).toFixed(2), | |
37 | + gatherAim: (gatherAim*100).toFixed(2), | |
38 | + gatherAimExclusive: (gatherAimExclusive*100).toFixed(2), | |
39 | + gatherLiveAimDirect: (gatherLiveAimDirect*100).toFixed(2), | |
40 | + potentialAim: (potentialAim*100).toFixed(2), | |
41 | + potentialAimExclusive: (potentialAimExclusive*100).toFixed(2), | |
42 | + clueDealAim: (clueDealAim*100).toFixed(2), | |
43 | + shopList: shopList?.map(v => ({label: v.shopName, value: v.shopId})) | |
44 | + }); | |
45 | + }) | |
46 | + .catch(e => { | |
47 | + message.error(e.message); | |
48 | + }); | |
49 | + } | |
50 | + }, [operationType]); | |
51 | + | |
52 | + async function onSubmit() { | |
53 | + const params = await form.validateFields(); | |
54 | + const count = currency(params.clueAimManufacturer) | |
55 | + .add(params.clueAimBroker) | |
56 | + .add(params.clueAimCas) | |
57 | + .add(params.clueAimStaff) | |
58 | + .add(params.clueAimVisit).value; | |
59 | + if (count !=100) { | |
60 | + message.error(tip); | |
61 | + return; | |
62 | + } | |
63 | + const shopList = params.shopList.map((v:any) => ({shopName: v.label, shopId: v.value})); | |
64 | + const _params: SaveParams = { | |
65 | + clueAimExclusive: +(params.clueAimExclusive / 100).toFixed(4), | |
66 | + clueAimManufacturer: +(params.clueAimManufacturer / 100).toFixed(4), | |
67 | + clueAimBroker: +(params.clueAimBroker / 100).toFixed(4), | |
68 | + clueAimCas: +(params.clueAimCas / 100).toFixed(4), | |
69 | + clueAimStaff: +(params.clueAimStaff /100).toFixed(4), | |
70 | + clueAimVisit: +(params.clueAimVisit /100).toFixed(4), | |
71 | + gatherAim: +(params.gatherAim / 100).toFixed(4), | |
72 | + gatherAimExclusive: +(params.gatherAimExclusive /100).toFixed(4), | |
73 | + gatherLiveAimDirect: +(params.gatherLiveAimDirect /100).toFixed(4), | |
74 | + potentialAim: +(params.potentialAim /100).toFixed(4), | |
75 | + potentialAimExclusive: +(params.potentialAimExclusive /100).toFixed(4), | |
76 | + clueDealAim: +(params.clueDealAim /100).toFixed(4), | |
77 | + shopList, | |
78 | + id, | |
79 | + operationType | |
80 | + }; | |
81 | + setLoading(true); | |
82 | + fetchOperationApi(_params) | |
83 | + .then(res => { | |
84 | + message.success(res.result); | |
85 | + setLoading(false); | |
86 | + history.goBack(); | |
87 | + }) | |
88 | + .catch(e => { | |
89 | + message.error(e.message); | |
90 | + setLoading(false); | |
91 | + }); | |
92 | + } | |
93 | + | |
94 | + function handleBack() { | |
95 | + history.goBack(); | |
96 | + } | |
97 | + | |
98 | + return ( | |
99 | + <PageHeaderWrapper title={TitleEnum[operationType]}> | |
100 | + <Card> | |
101 | + <Form | |
102 | + form={form} | |
103 | + labelCol={{ span: 10 }} | |
104 | + wrapperCol={{ span: 10 }} | |
105 | + > | |
106 | + <Form.Item | |
107 | + label="适用门店" | |
108 | + name="shopList" | |
109 | + rules={[{ required: true, message: "请选择门店" }]} | |
110 | + labelAlign="right" | |
111 | + > | |
112 | + <ShopSelectNew defaultOptions={{bizTypes: "1"}} placeholder="请选择门店" multiple /> | |
113 | + </Form.Item> | |
114 | + {/* <Form.Item | |
115 | + label="适用门店" | |
116 | + name="shopList" | |
117 | + rules={[{ required: true, message: "请选择门店" }]} | |
118 | + > | |
119 | + <Select | |
120 | + optionFilterProp="children" | |
121 | + mode="multiple" | |
122 | + labelInValue | |
123 | + allowClear | |
124 | + style={{ width: "100%" }} | |
125 | + placeholder="请选择门店" | |
126 | + > | |
127 | + {shop && shop.map((shop: any) => ( | |
128 | + <Option value={shop.id} key={shop.id}> | |
129 | + {shop.shopName} | |
130 | + </Option> | |
131 | + ))} | |
132 | + </Select> | |
133 | + </Form.Item> */} | |
134 | + <Divider orientation="left" orientationMargin="0">线索</Divider> | |
135 | + | |
136 | + <Form.Item name="clueAimExclusive" labelAlign="right" label="计算线索目标:专属线索转化率" rules={[{ required: true, message: "请输入!" }]}> | |
137 | + <InputNumber | |
138 | + style={{width: '100%'}} | |
139 | + controls={false} | |
140 | + min={0} | |
141 | + max={100} | |
142 | + formatter={value => `${value}%`} | |
143 | + precision={2} | |
144 | + parser={value => value?.replace('%', '')} | |
145 | + /> | |
146 | + </Form.Item> | |
147 | + <Form.Item | |
148 | + name="clueAimManufacturer" | |
149 | + labelAlign="right" | |
150 | + label="线索目标占比设置:厂家下发线索目标占比" | |
151 | + rules={[{ required: true, message: "请输入!" }]} | |
152 | + > | |
153 | + <InputNumber | |
154 | + style={{width: '100%'}} | |
155 | + controls={false} | |
156 | + min={0} | |
157 | + max={100} | |
158 | + formatter={value => `${value}%`} | |
159 | + precision={2} | |
160 | + parser={value => value?.replace('%', '')} | |
161 | + /> | |
162 | + </Form.Item> | |
163 | + <Form.Item | |
164 | + name="clueAimBroker" | |
165 | + label="经纪人推荐目标占比" | |
166 | + labelAlign="right" | |
167 | + rules={[{ required: true, message: "请输入!" }]} | |
168 | + > | |
169 | + <InputNumber | |
170 | + style={{width: '100%'}} | |
171 | + controls={false} | |
172 | + min={0} | |
173 | + max={100} | |
174 | + formatter={value => `${value}%`} | |
175 | + precision={2} | |
176 | + parser={value => value?.replace('%', '')} | |
177 | + /> | |
178 | + </Form.Item> | |
179 | + <Form.Item | |
180 | + name="clueAimCas" | |
181 | + label="保有客推荐目标占比" | |
182 | + labelAlign="right" | |
183 | + rules={[{ required: true, message: "请输入!" }]} | |
184 | + > | |
185 | + <InputNumber | |
186 | + style={{width: '100%'}} | |
187 | + controls={false} | |
188 | + min={0} | |
189 | + max={100} | |
190 | + formatter={value => `${value}%`} | |
191 | + precision={2} | |
192 | + parser={value => value?.replace('%', '')} | |
193 | + /> | |
194 | + </Form.Item> | |
195 | + <Form.Item | |
196 | + name="clueAimStaff" | |
197 | + label="员工推荐目标占比" | |
198 | + labelAlign="right" | |
199 | + rules={[{ required: true, message: "请输入!" }]} | |
200 | + > | |
201 | + <InputNumber | |
202 | + style={{width: '100%'}} | |
203 | + controls={false} | |
204 | + min={0} | |
205 | + max={100} | |
206 | + formatter={value => `${value}%`} | |
207 | + precision={2} | |
208 | + parser={value => value?.replace('%', '')} | |
209 | + /> | |
210 | + </Form.Item> | |
211 | + <Form.Item | |
212 | + name="clueAimVisit" | |
213 | + label="定巡展车展目标占比" | |
214 | + labelAlign="right" | |
215 | + rules={[{ required: true, message: "请输入!" }]} | |
216 | + > | |
217 | + <InputNumber | |
218 | + style={{width: '100%'}} | |
219 | + controls={false} | |
220 | + min={0} | |
221 | + max={100} | |
222 | + formatter={value => `${value}%`} | |
223 | + precision={2} | |
224 | + parser={value => value?.replace('%', '')} | |
225 | + /> | |
226 | + </Form.Item> | |
227 | + | |
228 | + <Divider orientation="left" orientationMargin="0">集客</Divider> | |
229 | + <Form.Item name="gatherAim" labelAlign="right" label="计算月集客目标:集客转化率" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
230 | + <InputNumber | |
231 | + style={{width: '100%'}} | |
232 | + controls={false} | |
233 | + min={0.01} | |
234 | + max={100} | |
235 | + formatter={value => `${value}%`} | |
236 | + precision={2} | |
237 | + parser={value => value?.replace('%', '')} | |
238 | + /> | |
239 | + </Form.Item> | |
240 | + <Form.Item name="gatherAimExclusive" labelAlign="right" label="月集客目标中:专属线索转集客占比" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
241 | + <InputNumber | |
242 | + style={{width: '100%'}} | |
243 | + controls={false} | |
244 | + min={0.01} | |
245 | + max={100} | |
246 | + formatter={value => `${value}%`} | |
247 | + precision={2} | |
248 | + parser={value => value?.replace('%', '')} | |
249 | + /> | |
250 | + </Form.Item> | |
251 | + <Form.Item name="gatherLiveAimDirect" labelAlign="right" label="直接集客目标中:直播/短视频直接集客目标占比" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
252 | + <InputNumber | |
253 | + style={{width: '100%'}} | |
254 | + controls={false} | |
255 | + min={0.01} | |
256 | + max={100} | |
257 | + formatter={value => `${value}%`} | |
258 | + precision={2} | |
259 | + parser={value => value?.replace('%', '')} | |
260 | + /> | |
261 | + </Form.Item> | |
262 | + | |
263 | + <Divider orientation="left" orientationMargin="0">潜客</Divider> | |
264 | + <Form.Item name="potentialAim" labelAlign="right" label="月潜客目标中:集客转潜客占比" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
265 | + <InputNumber | |
266 | + style={{width: '100%'}} | |
267 | + controls={false} | |
268 | + min={0.01} | |
269 | + max={100} | |
270 | + formatter={value => `${value}%`} | |
271 | + precision={2} | |
272 | + parser={value => value?.replace('%', '')} | |
273 | + /> | |
274 | + </Form.Item> | |
275 | + <Form.Item name="potentialAimExclusive" labelAlign="right" label="计算月潜客目标:潜客转化率" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
276 | + <InputNumber | |
277 | + style={{width: '100%'}} | |
278 | + controls={false} | |
279 | + min={0.01} | |
280 | + max={100} | |
281 | + formatter={value => `${value}%`} | |
282 | + precision={2} | |
283 | + parser={value => value?.replace('%', '')} | |
284 | + /> | |
285 | + </Form.Item> | |
286 | + | |
287 | + <Form.Item name="clueDealAim" labelAlign="right" label="计算线索到店目标:线索到店成交潜客转化率" rules={[{ required: true, message: "请输入大于0的数!" }]}> | |
288 | + <InputNumber | |
289 | + style={{width: '100%'}} | |
290 | + controls={false} | |
291 | + min={0.01} | |
292 | + max={100} | |
293 | + formatter={value => `${value}%`} | |
294 | + precision={2} | |
295 | + parser={value => value?.replace('%', '')} | |
296 | + /> | |
297 | + </Form.Item> | |
298 | + </Form> | |
299 | + | |
300 | + <Row align="middle" justify="center"> | |
301 | + <Col span={8} /> | |
302 | + <Col span={8}> | |
303 | + <Row justify="space-between"> | |
304 | + <Col span={12}> | |
305 | + <Button type="default" htmlType="button" onClick={handleBack}> | |
306 | + 返回 | |
307 | + </Button> | |
308 | + </Col> | |
309 | + <Col span={12}> | |
310 | + <Button loading={loading} type="primary" onClick={debounce(onSubmit, 380)}> | |
311 | + 确定 | |
312 | + </Button> | |
313 | + </Col> | |
314 | + </Row> | |
315 | + </Col> | |
316 | + <Col span={8} /> | |
317 | + </Row> | |
318 | + </Card> | |
319 | + | |
320 | + </PageHeaderWrapper> | |
321 | + ); | |
322 | +} | |
0 | 323 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailManualAdjust/api.ts
... | ... | @@ -3,26 +3,30 @@ import request from '@/utils/request'; |
3 | 3 | import { ORDER3 } from '@/utils/host'; |
4 | 4 | |
5 | 5 | export interface ListItem { |
6 | - totalTaskCount?: number,//任务总数 | |
6 | + totalTaskCount?: number, //任务总数 | |
7 | 7 | shopTaskList?: ShopTaskList[], |
8 | - year?: number,//年份 | |
8 | + year?: number, //年份 | |
9 | 9 | id?: number, |
10 | - month?: number,//月份 | |
10 | + month?: number, //月份 | |
11 | 11 | canModified?: boolean//是否可调整 |
12 | 12 | } |
13 | 13 | |
14 | 14 | export interface ShopTaskList { |
15 | - shopId?: number,//门店id | |
16 | - shopName?: string,//门店名称 | |
17 | - taskCount?: number,//任务数量 | |
15 | + shopId?: number, //门店id | |
16 | + shopName?: string, //门店名称 | |
17 | + taskCount?: number, //任务数量 | |
18 | + clueDealTaskCount?: number // 线索到店零售台数 | |
19 | + clueDealTaskRate?: number // 线索到店零售占比 | |
18 | 20 | staffTaskList?: StaffTaskList[]//员工任务列表 |
19 | 21 | } |
20 | 22 | |
21 | 23 | interface StaffTaskList { |
22 | - staffId?: number,//员工id | |
23 | - staffName?: string,//员工名称 | |
24 | - taskCount?: number,//任务数量 | |
24 | + id?: number | |
25 | + staffId?: number, //员工id | |
26 | + staffName?: string, //员工名称 | |
27 | + taskCount?: number, //任务数量 | |
25 | 28 | regularMonth?: number//转正几个月 |
29 | + clueDealTaskCount?: number // 线索到店零售台数 | |
26 | 30 | } |
27 | 31 | export interface saveParams { |
28 | 32 | id?: number, |
... | ... | @@ -45,4 +49,3 @@ export function autoAlloctionApi(params: saveParams) { |
45 | 49 | export function save(id?: number) { |
46 | 50 | return request.post(`${ORDER3}/erp/sales/task/submit`, { id }, { contentType: 'form-urlencoded' }); |
47 | 51 | } |
48 | - | ... | ... |
src/pages/order3/RetailManualAdjust/component/Modal.tsx
1 | -import React, { useState } from 'react' | |
2 | -import { Row, Modal, Table, Button, InputNumber } from 'antd' | |
3 | -import Column from 'antd/lib/table/Column' | |
1 | +import React, { useState } from 'react'; | |
2 | +import { Row, Modal, Table, Button, InputNumber, message } from 'antd'; | |
3 | +import Column from 'antd/lib/table/Column'; | |
4 | 4 | import { ShopTaskList, ListItem } from '../api'; |
5 | +import { isNil } from 'lodash'; | |
5 | 6 | |
6 | 7 | interface Props { |
7 | 8 | setVisible: any, |
... | ... | @@ -17,16 +18,48 @@ interface Props { |
17 | 18 | export default function EModal({ setVisible, visible, current = {}, index, setList, list = {}, setCurrent, isHandledit }: Props) { |
18 | 19 | const [editable, setEditable] = useState<boolean>(false); |
19 | 20 | |
21 | + /** | |
22 | + * 修改零售任务 | |
23 | + * @param e | |
24 | + * @param innerIndex | |
25 | + */ | |
20 | 26 | function _onChange(e: number, innerIndex: number) { |
21 | 27 | //改变整体外层数据 |
22 | - const data = list; | |
23 | - index && e && (data.shopTaskList![index].staffTaskList![innerIndex].taskCount = e); | |
24 | - index && setList({ ...data }); | |
28 | + const data = JSON.parse(JSON.stringify(list)); | |
29 | + const currentData = JSON.parse(JSON.stringify(current)); | |
30 | + if (!isNil(index) && e) { | |
31 | + data.shopTaskList![index].staffTaskList![innerIndex].taskCount = e; | |
32 | + data.shopTaskList![index].staffTaskList![innerIndex].clueDealTaskCount = Math.ceil((data.shopTaskList![index].clueDealTaskRate || 0)/100 *(e || 0)); | |
33 | + currentData.staffTaskList![innerIndex].taskCount = e; | |
34 | + currentData.staffTaskList![innerIndex].clueDealTaskCount = Math.ceil((data.shopTaskList![index].clueDealTaskRate || 0)/100 *(e || 0)); | |
35 | + } | |
36 | + setList({ ...data }); | |
25 | 37 | //回显改变数据 |
26 | - const currentData = current; | |
27 | - currentData.staffTaskList![innerIndex].taskCount = e; | |
28 | 38 | setCurrent({ ...currentData }); |
29 | 39 | } |
40 | + | |
41 | + /** | |
42 | + * 修改线索到店零售台数 | |
43 | + * @param e | |
44 | + * @param innerIndex | |
45 | + */ | |
46 | + function hanldeChangeClue(e: number, innerIndex: number) { | |
47 | + //改变整体外层数据 | |
48 | + const data = JSON.parse(JSON.stringify(list)); | |
49 | + const currentData = JSON.parse(JSON.stringify(current)); | |
50 | + if (!isNil(index) && data.shopTaskList![index].staffTaskList[innerIndex].taskCount < e) { | |
51 | + message.error("线索到店零售台数不能大于零售任务!"); | |
52 | + return; | |
53 | + } | |
54 | + if (!isNil(index) && e) { | |
55 | + data.shopTaskList![index].staffTaskList![innerIndex].clueDealTaskCount = e; | |
56 | + currentData.staffTaskList![innerIndex].clueDealTaskCount = e; | |
57 | + } | |
58 | + setList({ ...data }); | |
59 | + //回显改变数据 | |
60 | + setCurrent({ ...currentData }); | |
61 | + } | |
62 | + | |
30 | 63 | function handleClick() { |
31 | 64 | setVisible(false); |
32 | 65 | setEditable(false); |
... | ... | @@ -46,33 +79,44 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi |
46 | 79 | rowKey="staffId" |
47 | 80 | pagination={false} |
48 | 81 | > |
49 | - <Column title="销售顾问" align="center" | |
82 | + <Column | |
83 | + title="销售顾问" | |
84 | + align="center" | |
50 | 85 | render={(value: any) => { |
51 | 86 | if (value.regularMonth) { |
52 | - return <span>{value.staffName}(转正第{value.regularMonth}个月)</span> | |
87 | + return <span>{value.staffName}(转正第{value.regularMonth}个月)</span>; | |
53 | 88 | } else { |
54 | - return <span>{value.staffName}</span> | |
89 | + return <span>{value.staffName}</span>; | |
55 | 90 | } |
56 | 91 | }} |
57 | 92 | /> |
58 | - <Column title="零售任务(台)" dataIndex="taskCount" align="center" | |
93 | + <Column | |
94 | + title="零售任务(台)" | |
95 | + dataIndex="taskCount" | |
96 | + align="center" | |
59 | 97 | render={(value, record, index) => { |
60 | 98 | if (editable) { |
61 | - return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} /> | |
99 | + return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; | |
62 | 100 | } else { |
63 | - return <span>{value}</span> | |
101 | + return <span>{value}</span>; | |
64 | 102 | } |
65 | 103 | }} |
66 | 104 | /> |
105 | + <Column | |
106 | + title="线索到店零售台数" | |
107 | + dataIndex="clueDealTaskCount" | |
108 | + align="center" | |
109 | + render={(value, record, index) => <span>{value}</span>} | |
110 | + /> | |
67 | 111 | </Table> |
68 | 112 | <Row justify="center" style={{ marginTop: 20 }}> |
69 | - {isHandledit ? | |
113 | + {isHandledit ? ( | |
70 | 114 | <> |
71 | - <Button type="primary" style={{ marginRight: 15, width: 110 }} onClick={handleClick} >确定</Button> | |
72 | - <Button type="default" style={{ width: 110 }} onClick={() => setEditable(!editable)} >手动编辑</Button> | |
73 | - </> : null | |
74 | - } | |
115 | + <Button type="primary" style={{ marginRight: 15, width: 110 }} onClick={handleClick}>确定</Button> | |
116 | + <Button type="default" style={{ width: 110 }} onClick={() => setEditable(!editable)}>手动编辑</Button> | |
117 | + </> | |
118 | + ) : null} | |
75 | 119 | </Row> |
76 | 120 | </Modal> |
77 | - ) | |
121 | + ); | |
78 | 122 | } |
79 | 123 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailManualAdjust/index.tsx
1 | -import React, { useState, useEffect } from 'react'; | |
1 | +import React, { useState, useEffect, useRef } from 'react'; | |
2 | 2 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; |
3 | 3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; |
4 | 4 | import { getRetailManualList, ListItem, autoAlloctionApi, ShopTaskList, save, saveParams } from './api'; |
... | ... | @@ -24,7 +24,8 @@ export default function TacklingCarModels() { |
24 | 24 | const [edit, setEdit] = useState<boolean>(false); |
25 | 25 | const [list, setList] = useState<ListItem>({}); |
26 | 26 | const [orginDara, setOrginDara] = useState<ListItem>({}); |
27 | - let index; | |
27 | + const index = useRef(); | |
28 | + // let index; | |
28 | 29 | |
29 | 30 | useEffect(() => { |
30 | 31 | getList(newDay); |
... | ... | @@ -53,9 +54,30 @@ export default function TacklingCarModels() { |
53 | 54 | }); |
54 | 55 | } |
55 | 56 | |
57 | + /** | |
58 | + * 修改零售任务台数 | |
59 | + * @param e | |
60 | + * @param index | |
61 | + */ | |
56 | 62 | function _onChange(e: number, index: number) { |
57 | - const data = list; | |
63 | + const data = JSON.parse(JSON.stringify(list)); | |
58 | 64 | data.shopTaskList![index].taskCount = e; |
65 | + data.shopTaskList![index].clueDealTaskCount = Math.ceil((e || 0)*((data.shopTaskList![index].clueDealTaskRate || 0)/100)); | |
66 | + setList({ ...data }); | |
67 | + } | |
68 | + | |
69 | + /** | |
70 | + * 修改线索到店零售台数 | |
71 | + * @param e | |
72 | + * @param index | |
73 | + */ | |
74 | + function handleChangeClue(e: number, index: number) { | |
75 | + const data = JSON.parse(JSON.stringify(list)); | |
76 | + if (data.shopTaskList[index].taskCount < e) { | |
77 | + message.error("线索到店零售台数不能大于零售任务!"); | |
78 | + return; | |
79 | + } | |
80 | + data.shopTaskList![index].clueDealTaskCount = e; | |
59 | 81 | setList({ ...data }); |
60 | 82 | } |
61 | 83 | |
... | ... | @@ -73,6 +95,7 @@ export default function TacklingCarModels() { |
73 | 95 | }; |
74 | 96 | autoAllocationSave(params); |
75 | 97 | } |
98 | + | |
76 | 99 | //提交审批 |
77 | 100 | function saveClick() { |
78 | 101 | setLoading(true); |
... | ... | @@ -88,6 +111,7 @@ export default function TacklingCarModels() { |
88 | 111 | setSaveLoading(false); |
89 | 112 | }); |
90 | 113 | } |
114 | + | |
91 | 115 | //自动提交 |
92 | 116 | function autoAllocationSave(params: saveParams) { |
93 | 117 | setConfirmLoading(true); |
... | ... | @@ -135,8 +159,11 @@ export default function TacklingCarModels() { |
135 | 159 | ); |
136 | 160 | }} |
137 | 161 | > |
138 | - <Column title="门店" dataIndex="shopName" width={400} align="center" /> | |
139 | - <Column title="零售任务(台)" dataIndex="taskCount" align="center" | |
162 | + <Column title="门店" dataIndex="shopName" align="center" /> | |
163 | + <Column | |
164 | + title="零售任务(台)" | |
165 | + dataIndex="taskCount" | |
166 | + align="center" | |
140 | 167 | render={(value, record, index) => { |
141 | 168 | if (edit) { |
142 | 169 | return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; |
... | ... | @@ -145,30 +172,35 @@ export default function TacklingCarModels() { |
145 | 172 | } |
146 | 173 | }} |
147 | 174 | /> |
148 | - <Column title="操作" align="center" | |
149 | - render={(value, record, index) => ( | |
175 | + <Column | |
176 | + title="线索到店零售台数" | |
177 | + dataIndex="clueDealTaskCount" | |
178 | + align="center" | |
179 | + render={(value, record, index) => <span>{value}</span>} | |
180 | + /> | |
181 | + <Column | |
182 | + title="操作" | |
183 | + align="center" | |
184 | + render={(value, record, _index) => ( | |
150 | 185 | <> |
151 | - <Button type="link" onClick={() => { setVisible(true); setCurrent(value); index = index }}>销售顾问任务</Button> | |
186 | + <Button type="link" onClick={() => { setVisible(true); setCurrent(value); index.current = _index; }}>销售顾问任务</Button> | |
152 | 187 | </> |
153 | 188 | )} |
154 | 189 | /> |
155 | 190 | </Table> |
156 | - <EModal setVisible={setVisible} visible={visible} current={current} index={index} setList={setList} list={list} setCurrent={setCurrent} isHandledit={edit} /> | |
191 | + <EModal setVisible={setVisible} visible={visible} current={current} index={index.current} setList={setList} list={list} setCurrent={setCurrent} isHandledit={edit} /> | |
157 | 192 | <Row justify="center" style={{ marginTop: 15 }}> |
158 | 193 | {list.canModified ? |
159 | 194 | ( |
160 | 195 | <> |
161 | 196 | {edit ? |
162 | 197 | <Button style={{ width: 110 }} type="primary" onClick={handleClick} loading={confirmLoading}>确定</Button> : |
163 | - <Button style={{ width: 110 }} type="primary" onClick={saveClick} loading={saveLoading}>提交审批</Button> | |
164 | - } | |
198 | + <Button style={{ width: 110 }} type="primary" onClick={saveClick} loading={saveLoading}>提交审批</Button>} | |
165 | 199 | {edit ? null : |
166 | - <Button style={{ width: 110, marginLeft: 15, backgroundColor: '#FFF' }} onClick={() => setEdit(true)} type="default">自动调整</Button> | |
167 | - } | |
200 | + <Button style={{ width: 110, marginLeft: 15, backgroundColor: '#FFF' }} onClick={() => setEdit(true)} type="default">自动调整</Button>} | |
168 | 201 | {edit ? |
169 | 202 | <Button style={{ width: 110, marginLeft: 15 }} type="default" onClick={setReset} loading={confirmLoading}>取消</Button> : |
170 | - null | |
171 | - } | |
203 | + null} | |
172 | 204 | </> |
173 | 205 | ) |
174 | 206 | : null} | ... | ... |
src/pages/order3/RetailTask/api.ts
... | ... | @@ -3,25 +3,28 @@ import request from '@/utils/request'; |
3 | 3 | import { ORDER3 } from '@/utils/host'; |
4 | 4 | |
5 | 5 | export interface ListItem { |
6 | - totalTaskCount?: number,//任务总数 | |
6 | + totalTaskCount?: number, //任务总数 | |
7 | 7 | shopTaskList?: ShopTaskList[], |
8 | - year?: number,//年份 | |
8 | + year?: number, //年份 | |
9 | 9 | id?: number, |
10 | - month?: number,//月份 | |
10 | + month?: number, //月份 | |
11 | 11 | canModified?: boolean//是否可调整 |
12 | 12 | } |
13 | 13 | |
14 | 14 | export interface ShopTaskList { |
15 | - shopId?: number,//门店id | |
16 | - shopName?: string,//门店名称 | |
17 | - taskCount?: number,//任务数量 | |
15 | + shopId?: number//门店id | |
16 | + shopName?: string//门店名称 | |
17 | + taskCount?: number//任务数量 | |
18 | + clueDealTaskCount?: number // 线索到店零售台数 | |
19 | + clueDealTaskRate?: number // 线索到店零售占比 | |
18 | 20 | staffTaskList?: StaffTaskList[]//员工任务列表 |
19 | 21 | } |
20 | 22 | |
21 | 23 | interface StaffTaskList { |
22 | - staffId?: number,//员工id | |
23 | - staffName?: string,//员工名称 | |
24 | - taskCount?: number,//任务数量 | |
24 | + staffId?: number//员工id | |
25 | + staffName?: string//员工名称 | |
26 | + taskCount?: number//任务数量 | |
27 | + clueDealTaskCount?: number // 线索到店零售台数 | |
25 | 28 | regularMonth?: number//转正几个月 |
26 | 29 | } |
27 | 30 | export interface saveParams { |
... | ... | @@ -44,5 +47,4 @@ export function saveHandelTack(params: saveParams) { |
44 | 47 | /*提交 */ |
45 | 48 | export function save(id?: number) { |
46 | 49 | return request.post(`${ORDER3}/erp/sales/task/submit`, { id }, { contentType: 'form-urlencoded' }); |
47 | -} | |
48 | - | |
50 | +} | |
49 | 51 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailTask/component/Modal.tsx
1 | -import React, { useState } from 'react' | |
2 | -import { Row, Modal, Table, Button, InputNumber } from 'antd' | |
3 | -import Column from 'antd/lib/table/Column' | |
1 | +import React, { useState } from 'react'; | |
2 | +import { Row, Modal, Table, Button, InputNumber, message } from 'antd'; | |
3 | +import Column from 'antd/lib/table/Column'; | |
4 | 4 | import { ShopTaskList, ListItem } from '../api'; |
5 | +import { isNil } from 'lodash'; | |
5 | 6 | |
6 | 7 | interface Props { |
7 | 8 | setVisible: any, |
... | ... | @@ -19,14 +20,41 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi |
19 | 20 | |
20 | 21 | function _onChange(e: number, innerIndex: number) { |
21 | 22 | //改变整体外层数据 |
22 | - const data = list; | |
23 | - index && e && (data.shopTaskList![index].staffTaskList![innerIndex].taskCount = e); | |
24 | - index && setList({ ...data }); | |
23 | + const data = JSON.parse(JSON.stringify(list)); | |
24 | + const currentData = JSON.parse(JSON.stringify(current)); | |
25 | + if (!isNil(index)) { | |
26 | + data.shopTaskList![index].staffTaskList![innerIndex].taskCount = e || 0; | |
27 | + data.shopTaskList![index].staffTaskList![innerIndex].clueDealTaskCount = Math.ceil((data.shopTaskList![index].clueDealTaskRate || 0)/100 *(e || 0)); | |
28 | + currentData.staffTaskList![innerIndex].taskCount = e || 0; | |
29 | + currentData.staffTaskList![innerIndex].clueDealTaskCount = Math.ceil((data.shopTaskList![index].clueDealTaskRate || 0)/100 *(e || 0)); | |
30 | + } | |
31 | + setList({ ...data }); | |
25 | 32 | //回显改变数据 |
26 | - const currentData = current; | |
27 | - currentData.staffTaskList![innerIndex].taskCount = e || 0; | |
28 | 33 | setCurrent({ ...currentData }); |
29 | 34 | } |
35 | + | |
36 | + /** | |
37 | + * 修改零售任务 | |
38 | + * @param e | |
39 | + * @param innerIndex | |
40 | + */ | |
41 | + function hanldeChangeClue(e: number, innerIndex: number) { | |
42 | + //改变整体外层数据 | |
43 | + const data = JSON.parse(JSON.stringify(list)); | |
44 | + const currentData = JSON.parse(JSON.stringify(current)); | |
45 | + if (!isNil(index) && data.shopTaskList![index].staffTaskList[innerIndex].taskCount < e) { | |
46 | + message.error("线索到店零售台数不能大于零售任务!"); | |
47 | + return; | |
48 | + } | |
49 | + if (!isNil(index)) { | |
50 | + data.shopTaskList![index].staffTaskList![innerIndex].clueDealTaskCount = e || 0; | |
51 | + currentData.staffTaskList![innerIndex].clueDealTaskCount = e || 0; | |
52 | + } | |
53 | + setList({ ...data }); | |
54 | + //回显改变数据 | |
55 | + setCurrent({ ...currentData }); | |
56 | + } | |
57 | + | |
30 | 58 | function handleClick() { |
31 | 59 | setVisible(false); |
32 | 60 | setEditable(false); |
... | ... | @@ -51,9 +79,9 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi |
51 | 79 | align="center" |
52 | 80 | render={(value: any) => { |
53 | 81 | if (value.regularMonth) { |
54 | - return <span>{value.staffName}(转正第{value.regularMonth}个月)</span> | |
82 | + return <span>{value.staffName}(转正第{value.regularMonth}个月)</span>; | |
55 | 83 | } else { |
56 | - return <span>{value.staffName}</span> | |
84 | + return <span>{value.staffName}</span>; | |
57 | 85 | } |
58 | 86 | }} |
59 | 87 | /> |
... | ... | @@ -63,9 +91,21 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi |
63 | 91 | align="center" |
64 | 92 | render={(value, record, index) => { |
65 | 93 | if (editable) { |
66 | - return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} /> | |
94 | + return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; | |
95 | + } else { | |
96 | + return <span>{value}</span>; | |
97 | + } | |
98 | + }} | |
99 | + /> | |
100 | + <Column | |
101 | + title="线索到店零售台数" | |
102 | + dataIndex="clueDealTaskCount" | |
103 | + align="center" | |
104 | + render={(value, record, index) => { | |
105 | + if (editable) { | |
106 | + return <InputNumber min={0} value={value} onChange={(e: any) => hanldeChangeClue(e, index)} />; | |
67 | 107 | } else { |
68 | - return <span>{value}</span> | |
108 | + return <span>{value}</span>; | |
69 | 109 | } |
70 | 110 | }} |
71 | 111 | /> |
... | ... | @@ -79,5 +119,5 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi |
79 | 119 | ) : null} |
80 | 120 | </Row> |
81 | 121 | </Modal> |
82 | - ) | |
122 | + ); | |
83 | 123 | } |
84 | 124 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailTask/index.tsx
1 | -import React, { useState, useEffect } from 'react'; | |
1 | +import React, { useState, useEffect, useRef } from 'react'; | |
2 | 2 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; |
3 | 3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; |
4 | 4 | import { getRetailList, ListItem, saveHandelTack, ShopTaskList, save, saveParams } from './api'; |
... | ... | @@ -24,7 +24,7 @@ export default function TacklingCarModels() { |
24 | 24 | const [date, setDate] = useState<number>(newDay); |
25 | 25 | const [list, setList] = useState<ListItem>({}); |
26 | 26 | const [orginDara, setOrginDara] = useState<ListItem>({}); |
27 | - let index; | |
27 | + const index = useRef(0); | |
28 | 28 | |
29 | 29 | useEffect(() => { |
30 | 30 | getList(newDay); |
... | ... | @@ -57,9 +57,26 @@ export default function TacklingCarModels() { |
57 | 57 | setDate(date.valueOf()); |
58 | 58 | getList(date); |
59 | 59 | } |
60 | + | |
60 | 61 | function _onChange(e: number, index: number) { |
61 | - const data = list; | |
62 | + const data = JSON.parse(JSON.stringify(list)); | |
62 | 63 | data.shopTaskList![index].taskCount = e || 0; |
64 | + data.shopTaskList![index].clueDealTaskCount = Math.ceil((e || 0)*((data.shopTaskList![index].clueDealTaskRate || 0)/100)); | |
65 | + setList({ ...data }); | |
66 | + } | |
67 | + | |
68 | + /** | |
69 | + * 修改线索到店零售台数 | |
70 | + * @param e | |
71 | + * @param index | |
72 | + */ | |
73 | + function handleChangeClue(e: number, index: number) { | |
74 | + const data = JSON.parse(JSON.stringify(list)); | |
75 | + if (data.shopTaskList[index].taskCount < e) { | |
76 | + message.error("线索到店零售台数不能大于零售任务!"); | |
77 | + return; | |
78 | + } | |
79 | + data.shopTaskList![index].clueDealTaskCount = e || 0; | |
63 | 80 | setList({ ...data }); |
64 | 81 | } |
65 | 82 | //保存 |
... | ... | @@ -121,54 +138,69 @@ export default function TacklingCarModels() { |
121 | 138 | }); |
122 | 139 | return ( |
123 | 140 | <> |
124 | - {total == 0 ? null : | |
125 | - <Table.Summary.Row > | |
141 | + {total == 0 ? null : ( | |
142 | + <Table.Summary.Row> | |
126 | 143 | <Table.Summary.Cell index={1}><span style={{ display: 'block', textAlign: 'center' }}>合计</span></Table.Summary.Cell> |
127 | 144 | <Table.Summary.Cell index={2}> |
128 | 145 | <span style={{ display: 'block', textAlign: 'center' }}>{total}</span> |
129 | 146 | </Table.Summary.Cell> |
130 | - <Table.Summary.Cell index={3}></Table.Summary.Cell> | |
147 | + <Table.Summary.Cell index={3} /> | |
131 | 148 | </Table.Summary.Row> |
132 | - } | |
149 | + )} | |
133 | 150 | </> |
134 | 151 | ); |
135 | 152 | }} |
136 | 153 | > |
137 | 154 | <Column title="门店" dataIndex="shopName" width={400} align="center" /> |
138 | - <Column title="零售任务(台)" dataIndex="taskCount" align="center" | |
155 | + <Column | |
156 | + title="零售任务(台)" | |
157 | + dataIndex="taskCount" | |
158 | + align="center" | |
139 | 159 | render={(value, record, index) => { |
140 | 160 | if (edit) { |
141 | - return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} /> | |
161 | + return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; | |
142 | 162 | } else { |
143 | - return <span>{value}</span> | |
163 | + return <span>{value}</span>; | |
144 | 164 | } |
145 | 165 | }} |
146 | 166 | /> |
147 | - <Column title="操作" align="center" | |
148 | - render={(value, record, index) => ( | |
167 | + <Column | |
168 | + title="线索到店零售台数" | |
169 | + dataIndex="clueDealTaskCount" | |
170 | + align="center" | |
171 | + render={(value, record, index) => { | |
172 | + if (edit) { | |
173 | + return <InputNumber min={0} value={value} onChange={(e: any) => handleChangeClue(e, index)} />; | |
174 | + } else { | |
175 | + return <span>{value}</span>; | |
176 | + } | |
177 | + }} | |
178 | + /> | |
179 | + <Column | |
180 | + title="操作" | |
181 | + align="center" | |
182 | + render={(value, record, _index) => ( | |
149 | 183 | <> |
150 | - <Button type="link" onClick={() => { setVisible(true); setCurrent(value), index = index }}>销售顾问任务</Button> | |
184 | + <Button type="link" onClick={() => { setVisible(true); setCurrent(value); index.current = _index; }}>销售顾问任务</Button> | |
151 | 185 | </> |
152 | 186 | )} |
153 | 187 | /> |
154 | 188 | </Table> |
155 | - <EModal setVisible={setVisible} visible={visible} current={current} index={index} setList={setList} list={list} setCurrent={setCurrent} isHandledit={edit} /> | |
189 | + <EModal setVisible={setVisible} visible={visible} current={current} index={index.current} setList={setList} list={list} setCurrent={setCurrent} isHandledit={edit} /> | |
156 | 190 | <Row justify="center" style={{ marginTop: 15 }}> |
157 | - {list.canModified ? | |
191 | + {list.canModified ? ( | |
158 | 192 | <> |
159 | 193 | {edit ? |
160 | 194 | <Button style={{ width: 110 }} type="primary" onClick={handleClick} loading={confirmLoading}>确定</Button> : |
161 | - <Button style={{ width: 110 }} type="primary" onClick={saveClick} loading={saveLoading}>提交审批</Button> | |
162 | - } | |
195 | + <Button style={{ width: 110 }} type="primary" onClick={saveClick} loading={saveLoading}>提交审批</Button>} | |
163 | 196 | <Button style={{ width: 110, marginLeft: 15, backgroundColor: '#FFF' }} type="default" onClick={() => setEdit(!edit)}>手动编辑</Button> |
164 | 197 | {edit ? |
165 | 198 | <Button style={{ width: 110, marginLeft: 15 }} type="default" onClick={setReset} loading={confirmLoading}>取消</Button> : |
166 | - null | |
167 | - } | |
168 | - </> : null | |
169 | - } | |
199 | + null} | |
200 | + </> | |
201 | + ) : null} | |
170 | 202 | </Row> |
171 | 203 | </Card> |
172 | - </PageHeaderWrapper > | |
173 | - ) | |
204 | + </PageHeaderWrapper> | |
205 | + ); | |
174 | 206 | } |
175 | 207 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailTaskConfiguration/api.ts
0 → 100644
1 | +import { http } from '@/typing/http'; | |
2 | +import request from '@/utils/request'; | |
3 | +import { ORDER3 } from '@/utils/host'; | |
4 | + | |
5 | +export interface List { | |
6 | + id?: number // id | |
7 | + retailRate?: number // 线索到店零售占比 | |
8 | + shopList?: ShopList[] // 门店列表 | |
9 | + displayName?: string // 门店名称集合 | |
10 | +} | |
11 | + | |
12 | +export interface ShopList { | |
13 | + shopId?: number // 门店id | |
14 | + shopName?: string // 门店名称 | |
15 | +} | |
16 | +/** 获取列表 */ | |
17 | +export function getRetailListApi(): http.PromiseResp<any> { | |
18 | + return request.get(`${ORDER3}/erp/clue/deal/rate/config/list`); | |
19 | +} | |
20 | + | |
21 | +/* 新增编辑线索到店零售占比配置*/ | |
22 | +export function saveRetailConfigApi(params?: List) { | |
23 | + return request.post(`${ORDER3}/erp/clue/deal/rate/config/save`, params); | |
24 | +} | |
25 | + | |
26 | +/* 删除线索到店零售占比配置*/ | |
27 | +export function fetchDeleteConfigApi(id?: number) { | |
28 | + return request.post(`${ORDER3}/erp/clue/deal/rate/config/delete`, { id }, { contentType: 'form-urlencoded' }); | |
29 | +} | |
0 | 30 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailTaskConfiguration/components/EditMidal.tsx
0 → 100644
1 | +import React, { useState, useEffect } from 'react'; | |
2 | +import { Button, Form, message, InputNumber, Select, Modal} from 'antd'; | |
3 | +import ShopSelectNew from '@/components/ShopSelectNew'; | |
4 | +import { debounce } from 'lodash'; | |
5 | +import currency from 'currency.js'; | |
6 | +import useInitail from '@/hooks/useInitail'; | |
7 | +import { useStore } from '../index'; | |
8 | +import { saveRetailConfigApi } from '../api'; | |
9 | + | |
10 | +const Option = Select.Option; | |
11 | + | |
12 | +export default function Index() { | |
13 | + const [form] = Form.useForm(); | |
14 | + const { visible, setVisible, current, setCurrent, setLoading } = useStore(); | |
15 | + const [confirm, setConfirm] = useState<boolean>(false); | |
16 | + | |
17 | + useEffect(() => { | |
18 | + if (visible && current.id) { | |
19 | + handleSetValue(); | |
20 | + } | |
21 | + }, [visible]); | |
22 | + | |
23 | + function handleCancle() { | |
24 | + setVisible(false); | |
25 | + setCurrent({}); | |
26 | + } | |
27 | + | |
28 | + async function handleSubmit() { | |
29 | + const params = await form.validateFields(); | |
30 | + setConfirm(true); | |
31 | + const _params = { | |
32 | + id: current.id, | |
33 | + retailRate: params.retailRate, | |
34 | + shopList: params.shopList?.map((v: any) => ({shopId: v.value, shopName: v.label})) | |
35 | + }; | |
36 | + saveRetailConfigApi(_params) | |
37 | + .then(res => { | |
38 | + message.success(res.result); | |
39 | + setVisible(false); | |
40 | + setConfirm(false); | |
41 | + setLoading(true); | |
42 | + setCurrent({}); | |
43 | + }) | |
44 | + .catch(e => { | |
45 | + message.error(e.message); | |
46 | + setConfirm(false); | |
47 | + }); | |
48 | + } | |
49 | + | |
50 | + function handleSetValue() { | |
51 | + form.setFieldsValue({ | |
52 | + retailRate: current.retailRate, | |
53 | + shopList: current.shopList?.map(v => ({label: v.shopName, value: v.shopId})) | |
54 | + }); | |
55 | + } | |
56 | + return ( | |
57 | + <Modal | |
58 | + title={current.id ? "编辑" : "新增"} | |
59 | + destroyOnClose | |
60 | + visible={visible} | |
61 | + maskClosable={false} | |
62 | + onCancel={handleCancle} | |
63 | + afterClose={() => form.setFieldsValue({shopList: [], retailRate: undefined})} | |
64 | + style={{minWidth: "500px"}} | |
65 | + footer={[ | |
66 | + <Button key="cancel" onClick={handleCancle} style={{marginLeft: 10}}>取消</Button>, | |
67 | + <Button key="submit" onClick={handleSubmit} type="primary" htmlType="submit" loading={confirm}>确认</Button> | |
68 | + ]} | |
69 | + > | |
70 | + <Form | |
71 | + form={form} | |
72 | + labelCol={{ span: 10 }} | |
73 | + wrapperCol={{ span: 10 }} | |
74 | + > | |
75 | + <Form.Item | |
76 | + label="适用门店" | |
77 | + name="shopList" | |
78 | + rules={[{ required: true, message: "请选择门店" }]} | |
79 | + labelAlign="right" | |
80 | + > | |
81 | + <ShopSelectNew defaultOptions={{bizTypes: "1"}} placeholder="请选择门店" multiple /> | |
82 | + </Form.Item> | |
83 | + | |
84 | + <Form.Item | |
85 | + label="线索到店零售台数占比" | |
86 | + name="retailRate" | |
87 | + rules={[{ required: true, message: "请输入" }]} | |
88 | + > | |
89 | + <InputNumber | |
90 | + style={{width: '100%'}} | |
91 | + controls={false} | |
92 | + min={0} | |
93 | + max={100} | |
94 | + formatter={value => `${value}%`} | |
95 | + precision={2} | |
96 | + parser={value => value?.replace('%', '')} | |
97 | + /> | |
98 | + </Form.Item> | |
99 | + | |
100 | + </Form> | |
101 | + | |
102 | + </Modal> | |
103 | + ); | |
104 | +} | |
0 | 105 | \ No newline at end of file | ... | ... |
src/pages/order3/RetailTaskConfiguration/components/List.tsx
0 → 100644
1 | +import React from "react"; | |
2 | +import { message, Popconfirm, Table, Space } from "antd"; | |
3 | +import { useStore } from '../index'; | |
4 | +import { fetchDeleteConfigApi, List } from '../api'; | |
5 | +import { isNil } from 'lodash'; | |
6 | + | |
7 | +const Column = Table.Column; | |
8 | + | |
9 | +export default function TableList() { | |
10 | + const {data, loading, setLoading, setVisible, setCurrent, setStatusData } = useStore(); | |
11 | + | |
12 | + function handleDelete(id?: number) { | |
13 | + fetchDeleteConfigApi(id) | |
14 | + .then(res => { | |