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,4 +53,12 @@ export default [ | ||
53 | path: '/crm/recordDetail/:recordId', // 第三方渠道导入记录详情 | 53 | path: '/crm/recordDetail/:recordId', // 第三方渠道导入记录详情 |
54 | component: './crm_new/ChannelImport/subpages/RecordDetail', | 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 | \ No newline at end of file | 65 | \ No newline at end of file |
config/routers/order3.ts
@@ -202,4 +202,8 @@ export default [ | @@ -202,4 +202,8 @@ export default [ | ||
202 | path: "/order3/orderSetting/deliveryVideoConfig", | 202 | path: "/order3/orderSetting/deliveryVideoConfig", |
203 | component: "./order3/OrderSetting/DeliveryVideoConfig", | 203 | component: "./order3/OrderSetting/DeliveryVideoConfig", |
204 | }, | 204 | }, |
205 | + { // 零售线索占比配置 | ||
206 | + path: "/order3/retailTaskConfiguration", | ||
207 | + component: "./order3/RetailTaskConfiguration", | ||
208 | + }, | ||
205 | ]; | 209 | ]; |
206 | \ No newline at end of file | 210 | \ No newline at end of file |
src/components/ShopSelectNew/api.ts
1 | /* | 1 | /* |
2 | * @Date: 2021-07-08 16:53:36 | 2 | * @Date: 2021-07-08 16:53:36 |
3 | * @LastEditors: wangqiang@feewee.cn | 3 | * @LastEditors: wangqiang@feewee.cn |
4 | - * @LastEditTime: 2022-05-25 11:32:53 | 4 | + * @LastEditTime: 2022-10-08 16:17:27 |
5 | */ | 5 | */ |
6 | import { http } from "@/typing/http"; | 6 | import { http } from "@/typing/http"; |
7 | import request from "@/utils/request"; | 7 | import request from "@/utils/request"; |
8 | import { OOP_HOST } from "@/utils/host"; | 8 | import { OOP_HOST } from "@/utils/host"; |
9 | import { LabelValueType } from "rc-tree-select/lib/interface"; | 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 | bizTypes?: string; // 业态类型集合 ,分割 | 12 | bizTypes?: string; // 业态类型集合 ,分割 |
16 | brands?: string; // 品牌id集合 ,分割 | 13 | brands?: string; // 品牌id集合 ,分割 |
17 | regions?: string; // 区域编码集合 ,分割 | 14 | regions?: string; // 区域编码集合 ,分割 |
18 | dealers?: string; // 商家id集合 ,分割 | 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 | export interface ShopItem { | 24 | export interface ShopItem { |
22 | shopId?: number; //门店id | 25 | shopId?: number; //门店id |
23 | shopFullName?: string; //门店全称 | 26 | shopFullName?: string; //门店全称 |
src/components/ShopSelectNew/components/ShopModal.tsx
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | * @Author: wangqiang@feewee.cn | 2 | * @Author: wangqiang@feewee.cn |
3 | * @Date: 2022-05-25 10:03:00 | 3 | * @Date: 2022-05-25 10:03:00 |
4 | * @LastEditors: wangqiang@feewee.cn | 4 | * @LastEditors: wangqiang@feewee.cn |
5 | - * @LastEditTime: 2022-06-10 15:57:29 | 5 | + * @LastEditTime: 2022-10-08 16:33:31 |
6 | */ | 6 | */ |
7 | import { InitialReturn } from "@/hooks/useInitail"; | 7 | import { InitialReturn } from "@/hooks/useInitail"; |
8 | import { | 8 | import { |
@@ -29,6 +29,7 @@ import { | @@ -29,6 +29,7 @@ import { | ||
29 | Value, | 29 | Value, |
30 | Option, | 30 | Option, |
31 | QueryParams, | 31 | QueryParams, |
32 | + ShopSelectNewOptions, | ||
32 | } from "../api"; | 33 | } from "../api"; |
33 | 34 | ||
34 | interface Props { | 35 | interface Props { |
@@ -47,6 +48,7 @@ interface Props { | @@ -47,6 +48,7 @@ interface Props { | ||
47 | multiple?: boolean; | 48 | multiple?: boolean; |
48 | optionInitial: InitialReturn<Option, QueryParams>; | 49 | optionInitial: InitialReturn<Option, QueryParams>; |
49 | listInitial: InitialReturn<ShopItem[], QueryParams>; | 50 | listInitial: InitialReturn<ShopItem[], QueryParams>; |
51 | + defaultOptions?: ShopSelectNewOptions; | ||
50 | } | 52 | } |
51 | 53 | ||
52 | export default function ShopModal({ | 54 | export default function ShopModal({ |
@@ -62,6 +64,7 @@ export default function ShopModal({ | @@ -62,6 +64,7 @@ export default function ShopModal({ | ||
62 | multiple, | 64 | multiple, |
63 | optionInitial, | 65 | optionInitial, |
64 | listInitial, | 66 | listInitial, |
67 | + defaultOptions = {}, | ||
65 | }: Props) { | 68 | }: Props) { |
66 | const fetchListByName = debounce( | 69 | const fetchListByName = debounce( |
67 | (keywords) => listInitial.setParams({ type, keywords }, true), | 70 | (keywords) => listInitial.setParams({ type, keywords }, true), |
@@ -245,14 +248,26 @@ export default function ShopModal({ | @@ -245,14 +248,26 @@ export default function ShopModal({ | ||
245 | ? listInitial.params.bizTypes.split(",") | 248 | ? listInitial.params.bizTypes.split(",") |
246 | : undefined | 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 | showSearch | 260 | showSearch |
250 | optionFilterProp="children" | 261 | optionFilterProp="children" |
251 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} | 262 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} |
252 | getPopupContainer={(triggerNode) => triggerNode.parentNode} | 263 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
253 | > | 264 | > |
254 | {optionInitial.data.bizList?.map((biz) => ( | 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 | {biz.name} | 271 | {biz.name} |
257 | </Select.Option> | 272 | </Select.Option> |
258 | ))} | 273 | ))} |
@@ -266,14 +281,26 @@ export default function ShopModal({ | @@ -266,14 +281,26 @@ export default function ShopModal({ | ||
266 | ? listInitial.params.regions.split(",") | 281 | ? listInitial.params.regions.split(",") |
267 | : undefined | 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 | showSearch | 293 | showSearch |
271 | optionFilterProp="children" | 294 | optionFilterProp="children" |
272 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} | 295 | style={{ minWidth: 260, marginBottom: 10, marginRight: 10 }} |
273 | getPopupContainer={(triggerNode) => triggerNode.parentNode} | 296 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
274 | > | 297 | > |
275 | {optionInitial.data.regionList?.map((region) => ( | 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 | {region.fullName} | 304 | {region.fullName} |
278 | </Select.Option> | 305 | </Select.Option> |
279 | ))} | 306 | ))} |
@@ -288,7 +315,12 @@ export default function ShopModal({ | @@ -288,7 +315,12 @@ export default function ShopModal({ | ||
288 | : undefined | 315 | : undefined |
289 | } | 316 | } |
290 | onChange={(brandList) => listInitial.setParams( | 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 | true | 324 | true |
293 | )} | 325 | )} |
294 | showSearch | 326 | showSearch |
@@ -297,7 +329,11 @@ export default function ShopModal({ | @@ -297,7 +329,11 @@ export default function ShopModal({ | ||
297 | getPopupContainer={(triggerNode) => triggerNode.parentNode} | 329 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
298 | > | 330 | > |
299 | {optionInitial.data.brandList?.map((brand) => ( | 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 | {brand.name} | 337 | {brand.name} |
302 | </Select.Option> | 338 | </Select.Option> |
303 | ))} | 339 | ))} |
@@ -312,7 +348,12 @@ export default function ShopModal({ | @@ -312,7 +348,12 @@ export default function ShopModal({ | ||
312 | : undefined | 348 | : undefined |
313 | } | 349 | } |
314 | onChange={(dealerList) => listInitial.setParams( | 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 | true | 357 | true |
317 | )} | 358 | )} |
318 | showSearch | 359 | showSearch |
@@ -321,7 +362,11 @@ export default function ShopModal({ | @@ -321,7 +362,11 @@ export default function ShopModal({ | ||
321 | getPopupContainer={(triggerNode) => triggerNode.parentNode} | 362 | getPopupContainer={(triggerNode) => triggerNode.parentNode} |
322 | > | 363 | > |
323 | {optionInitial.data.dealerList?.map((dealer) => ( | 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 | {dealer.name} | 370 | {dealer.name} |
326 | </Select.Option> | 371 | </Select.Option> |
327 | ))} | 372 | ))} |
src/components/ShopSelectNew/index.tsx
@@ -2,7 +2,7 @@ | @@ -2,7 +2,7 @@ | ||
2 | * @Author: wangqiang@feewee.cn | 2 | * @Author: wangqiang@feewee.cn |
3 | * @Date: 2022-05-25 09:14:31 | 3 | * @Date: 2022-05-25 09:14:31 |
4 | * @LastEditors: wangqiang@feewee.cn | 4 | * @LastEditors: wangqiang@feewee.cn |
5 | - * @LastEditTime: 2022-07-12 09:41:43 | 5 | + * @LastEditTime: 2022-10-08 16:36:39 |
6 | */ | 6 | */ |
7 | import useInitial from "@/hooks/useInitail"; | 7 | import useInitial from "@/hooks/useInitail"; |
8 | import { Tag } from "antd"; | 8 | import { Tag } from "antd"; |
@@ -14,7 +14,12 @@ import React, { | @@ -14,7 +14,12 @@ import React, { | ||
14 | useImperativeHandle, | 14 | useImperativeHandle, |
15 | useState, | 15 | useState, |
16 | } from "react"; | 16 | } from "react"; |
17 | -import { Value, getShopListChooseOptionsApi, getShopListApi } from "./api"; | 17 | +import { |
18 | + Value, | ||
19 | + getShopListChooseOptionsApi, | ||
20 | + getShopListApi, | ||
21 | + ShopSelectNewOptions, | ||
22 | +} from "./api"; | ||
18 | import Close from "./components/Close"; | 23 | import Close from "./components/Close"; |
19 | import ShopModal from "./components/ShopModal"; | 24 | import ShopModal from "./components/ShopModal"; |
20 | import "./style.less"; | 25 | import "./style.less"; |
@@ -28,6 +33,14 @@ interface Props { | @@ -28,6 +33,14 @@ interface Props { | ||
28 | placeholder?: string; | 33 | placeholder?: string; |
29 | type?: 1 | 2; // 1 集团维度 2 自定义门店维度 | 34 | type?: 1 | 2; // 1 集团维度 2 自定义门店维度 |
30 | shopIds?: number[]; // 仅查询门店ID列表,与 type = 2 时搭配使用 | 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 | export interface ShopSelectNewRef { | 46 | export interface ShopSelectNewRef { |
@@ -46,6 +59,7 @@ function ShopSelectNew( | @@ -46,6 +59,7 @@ function ShopSelectNew( | ||
46 | type = 1, | 59 | type = 1, |
47 | disabled = false, | 60 | disabled = false, |
48 | shopIds, | 61 | shopIds, |
62 | + defaultOptions = {}, | ||
49 | }: Props, | 63 | }: Props, |
50 | ref: Ref<ShopSelectNewRef> | 64 | ref: Ref<ShopSelectNewRef> |
51 | ) { | 65 | ) { |
@@ -62,6 +76,7 @@ function ShopSelectNew( | @@ -62,6 +76,7 @@ function ShopSelectNew( | ||
62 | [], | 76 | [], |
63 | { | 77 | { |
64 | type, | 78 | type, |
79 | + ...defaultOptions, | ||
65 | }, | 80 | }, |
66 | delay | 81 | delay |
67 | ); | 82 | ); |
@@ -173,6 +188,7 @@ function ShopSelectNew( | @@ -173,6 +188,7 @@ function ShopSelectNew( | ||
173 | setVisible={setVisible} | 188 | setVisible={setVisible} |
174 | optionInitial={optionInitial} | 189 | optionInitial={optionInitial} |
175 | listInitial={listInitial} | 190 | listInitial={listInitial} |
191 | + defaultOptions={defaultOptions} | ||
176 | /> | 192 | /> |
177 | )} | 193 | )} |
178 | </div> | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 323 | \ No newline at end of file |
src/pages/order3/RetailManualAdjust/api.ts
@@ -3,26 +3,30 @@ import request from '@/utils/request'; | @@ -3,26 +3,30 @@ import request from '@/utils/request'; | ||
3 | import { ORDER3 } from '@/utils/host'; | 3 | import { ORDER3 } from '@/utils/host'; |
4 | 4 | ||
5 | export interface ListItem { | 5 | export interface ListItem { |
6 | - totalTaskCount?: number,//任务总数 | 6 | + totalTaskCount?: number, //任务总数 |
7 | shopTaskList?: ShopTaskList[], | 7 | shopTaskList?: ShopTaskList[], |
8 | - year?: number,//年份 | 8 | + year?: number, //年份 |
9 | id?: number, | 9 | id?: number, |
10 | - month?: number,//月份 | 10 | + month?: number, //月份 |
11 | canModified?: boolean//是否可调整 | 11 | canModified?: boolean//是否可调整 |
12 | } | 12 | } |
13 | 13 | ||
14 | export interface ShopTaskList { | 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 | staffTaskList?: StaffTaskList[]//员工任务列表 | 20 | staffTaskList?: StaffTaskList[]//员工任务列表 |
19 | } | 21 | } |
20 | 22 | ||
21 | interface StaffTaskList { | 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 | regularMonth?: number//转正几个月 | 28 | regularMonth?: number//转正几个月 |
29 | + clueDealTaskCount?: number // 线索到店零售台数 | ||
26 | } | 30 | } |
27 | export interface saveParams { | 31 | export interface saveParams { |
28 | id?: number, | 32 | id?: number, |
@@ -45,4 +49,3 @@ export function autoAlloctionApi(params: saveParams) { | @@ -45,4 +49,3 @@ export function autoAlloctionApi(params: saveParams) { | ||
45 | export function save(id?: number) { | 49 | export function save(id?: number) { |
46 | return request.post(`${ORDER3}/erp/sales/task/submit`, { id }, { contentType: 'form-urlencoded' }); | 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 | import { ShopTaskList, ListItem } from '../api'; | 4 | import { ShopTaskList, ListItem } from '../api'; |
5 | +import { isNil } from 'lodash'; | ||
5 | 6 | ||
6 | interface Props { | 7 | interface Props { |
7 | setVisible: any, | 8 | setVisible: any, |
@@ -17,16 +18,48 @@ interface Props { | @@ -17,16 +18,48 @@ interface Props { | ||
17 | export default function EModal({ setVisible, visible, current = {}, index, setList, list = {}, setCurrent, isHandledit }: Props) { | 18 | export default function EModal({ setVisible, visible, current = {}, index, setList, list = {}, setCurrent, isHandledit }: Props) { |
18 | const [editable, setEditable] = useState<boolean>(false); | 19 | const [editable, setEditable] = useState<boolean>(false); |
19 | 20 | ||
21 | + /** | ||
22 | + * 修改零售任务 | ||
23 | + * @param e | ||
24 | + * @param innerIndex | ||
25 | + */ | ||
20 | function _onChange(e: number, innerIndex: number) { | 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 | setCurrent({ ...currentData }); | 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 | function handleClick() { | 63 | function handleClick() { |
31 | setVisible(false); | 64 | setVisible(false); |
32 | setEditable(false); | 65 | setEditable(false); |
@@ -46,33 +79,44 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | @@ -46,33 +79,44 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | ||
46 | rowKey="staffId" | 79 | rowKey="staffId" |
47 | pagination={false} | 80 | pagination={false} |
48 | > | 81 | > |
49 | - <Column title="销售顾问" align="center" | 82 | + <Column |
83 | + title="销售顾问" | ||
84 | + align="center" | ||
50 | render={(value: any) => { | 85 | render={(value: any) => { |
51 | if (value.regularMonth) { | 86 | if (value.regularMonth) { |
52 | - return <span>{value.staffName}(转正第{value.regularMonth}个月)</span> | 87 | + return <span>{value.staffName}(转正第{value.regularMonth}个月)</span>; |
53 | } else { | 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 | render={(value, record, index) => { | 97 | render={(value, record, index) => { |
60 | if (editable) { | 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 | } else { | 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 | </Table> | 111 | </Table> |
68 | <Row justify="center" style={{ marginTop: 20 }}> | 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 | </Row> | 119 | </Row> |
76 | </Modal> | 120 | </Modal> |
77 | - ) | 121 | + ); |
78 | } | 122 | } |
79 | \ No newline at end of file | 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 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; | 2 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; |
3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; | 3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; |
4 | import { getRetailManualList, ListItem, autoAlloctionApi, ShopTaskList, save, saveParams } from './api'; | 4 | import { getRetailManualList, ListItem, autoAlloctionApi, ShopTaskList, save, saveParams } from './api'; |
@@ -24,7 +24,8 @@ export default function TacklingCarModels() { | @@ -24,7 +24,8 @@ export default function TacklingCarModels() { | ||
24 | const [edit, setEdit] = useState<boolean>(false); | 24 | const [edit, setEdit] = useState<boolean>(false); |
25 | const [list, setList] = useState<ListItem>({}); | 25 | const [list, setList] = useState<ListItem>({}); |
26 | const [orginDara, setOrginDara] = useState<ListItem>({}); | 26 | const [orginDara, setOrginDara] = useState<ListItem>({}); |
27 | - let index; | 27 | + const index = useRef(); |
28 | + // let index; | ||
28 | 29 | ||
29 | useEffect(() => { | 30 | useEffect(() => { |
30 | getList(newDay); | 31 | getList(newDay); |
@@ -53,9 +54,30 @@ export default function TacklingCarModels() { | @@ -53,9 +54,30 @@ export default function TacklingCarModels() { | ||
53 | }); | 54 | }); |
54 | } | 55 | } |
55 | 56 | ||
57 | + /** | ||
58 | + * 修改零售任务台数 | ||
59 | + * @param e | ||
60 | + * @param index | ||
61 | + */ | ||
56 | function _onChange(e: number, index: number) { | 62 | function _onChange(e: number, index: number) { |
57 | - const data = list; | 63 | + const data = JSON.parse(JSON.stringify(list)); |
58 | data.shopTaskList![index].taskCount = e; | 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 | setList({ ...data }); | 81 | setList({ ...data }); |
60 | } | 82 | } |
61 | 83 | ||
@@ -73,6 +95,7 @@ export default function TacklingCarModels() { | @@ -73,6 +95,7 @@ export default function TacklingCarModels() { | ||
73 | }; | 95 | }; |
74 | autoAllocationSave(params); | 96 | autoAllocationSave(params); |
75 | } | 97 | } |
98 | + | ||
76 | //提交审批 | 99 | //提交审批 |
77 | function saveClick() { | 100 | function saveClick() { |
78 | setLoading(true); | 101 | setLoading(true); |
@@ -88,6 +111,7 @@ export default function TacklingCarModels() { | @@ -88,6 +111,7 @@ export default function TacklingCarModels() { | ||
88 | setSaveLoading(false); | 111 | setSaveLoading(false); |
89 | }); | 112 | }); |
90 | } | 113 | } |
114 | + | ||
91 | //自动提交 | 115 | //自动提交 |
92 | function autoAllocationSave(params: saveParams) { | 116 | function autoAllocationSave(params: saveParams) { |
93 | setConfirmLoading(true); | 117 | setConfirmLoading(true); |
@@ -135,8 +159,11 @@ export default function TacklingCarModels() { | @@ -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 | render={(value, record, index) => { | 167 | render={(value, record, index) => { |
141 | if (edit) { | 168 | if (edit) { |
142 | return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; | 169 | return <InputNumber min={0} value={value} onChange={(e: any) => _onChange(e, index)} />; |
@@ -145,30 +172,35 @@ export default function TacklingCarModels() { | @@ -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 | </Table> | 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 | <Row justify="center" style={{ marginTop: 15 }}> | 192 | <Row justify="center" style={{ marginTop: 15 }}> |
158 | {list.canModified ? | 193 | {list.canModified ? |
159 | ( | 194 | ( |
160 | <> | 195 | <> |
161 | {edit ? | 196 | {edit ? |
162 | <Button style={{ width: 110 }} type="primary" onClick={handleClick} loading={confirmLoading}>确定</Button> : | 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 | {edit ? null : | 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 | {edit ? | 201 | {edit ? |
169 | <Button style={{ width: 110, marginLeft: 15 }} type="default" onClick={setReset} loading={confirmLoading}>取消</Button> : | 202 | <Button style={{ width: 110, marginLeft: 15 }} type="default" onClick={setReset} loading={confirmLoading}>取消</Button> : |
170 | - null | ||
171 | - } | 203 | + null} |
172 | </> | 204 | </> |
173 | ) | 205 | ) |
174 | : null} | 206 | : null} |
src/pages/order3/RetailTask/api.ts
@@ -3,25 +3,28 @@ import request from '@/utils/request'; | @@ -3,25 +3,28 @@ import request from '@/utils/request'; | ||
3 | import { ORDER3 } from '@/utils/host'; | 3 | import { ORDER3 } from '@/utils/host'; |
4 | 4 | ||
5 | export interface ListItem { | 5 | export interface ListItem { |
6 | - totalTaskCount?: number,//任务总数 | 6 | + totalTaskCount?: number, //任务总数 |
7 | shopTaskList?: ShopTaskList[], | 7 | shopTaskList?: ShopTaskList[], |
8 | - year?: number,//年份 | 8 | + year?: number, //年份 |
9 | id?: number, | 9 | id?: number, |
10 | - month?: number,//月份 | 10 | + month?: number, //月份 |
11 | canModified?: boolean//是否可调整 | 11 | canModified?: boolean//是否可调整 |
12 | } | 12 | } |
13 | 13 | ||
14 | export interface ShopTaskList { | 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 | staffTaskList?: StaffTaskList[]//员工任务列表 | 20 | staffTaskList?: StaffTaskList[]//员工任务列表 |
19 | } | 21 | } |
20 | 22 | ||
21 | interface StaffTaskList { | 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 | regularMonth?: number//转正几个月 | 28 | regularMonth?: number//转正几个月 |
26 | } | 29 | } |
27 | export interface saveParams { | 30 | export interface saveParams { |
@@ -44,5 +47,4 @@ export function saveHandelTack(params: saveParams) { | @@ -44,5 +47,4 @@ export function saveHandelTack(params: saveParams) { | ||
44 | /*提交 */ | 47 | /*提交 */ |
45 | export function save(id?: number) { | 48 | export function save(id?: number) { |
46 | return request.post(`${ORDER3}/erp/sales/task/submit`, { id }, { contentType: 'form-urlencoded' }); | 49 | return request.post(`${ORDER3}/erp/sales/task/submit`, { id }, { contentType: 'form-urlencoded' }); |
47 | -} | ||
48 | - | 50 | +} |
49 | \ No newline at end of file | 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 | import { ShopTaskList, ListItem } from '../api'; | 4 | import { ShopTaskList, ListItem } from '../api'; |
5 | +import { isNil } from 'lodash'; | ||
5 | 6 | ||
6 | interface Props { | 7 | interface Props { |
7 | setVisible: any, | 8 | setVisible: any, |
@@ -19,14 +20,41 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | @@ -19,14 +20,41 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | ||
19 | 20 | ||
20 | function _onChange(e: number, innerIndex: number) { | 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 | setCurrent({ ...currentData }); | 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 | function handleClick() { | 58 | function handleClick() { |
31 | setVisible(false); | 59 | setVisible(false); |
32 | setEditable(false); | 60 | setEditable(false); |
@@ -51,9 +79,9 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | @@ -51,9 +79,9 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | ||
51 | align="center" | 79 | align="center" |
52 | render={(value: any) => { | 80 | render={(value: any) => { |
53 | if (value.regularMonth) { | 81 | if (value.regularMonth) { |
54 | - return <span>{value.staffName}(转正第{value.regularMonth}个月)</span> | 82 | + return <span>{value.staffName}(转正第{value.regularMonth}个月)</span>; |
55 | } else { | 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,9 +91,21 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | ||
63 | align="center" | 91 | align="center" |
64 | render={(value, record, index) => { | 92 | render={(value, record, index) => { |
65 | if (editable) { | 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 | } else { | 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,5 +119,5 @@ export default function EModal({ setVisible, visible, current = {}, index, setLi | ||
79 | ) : null} | 119 | ) : null} |
80 | </Row> | 120 | </Row> |
81 | </Modal> | 121 | </Modal> |
82 | - ) | 122 | + ); |
83 | } | 123 | } |
84 | \ No newline at end of file | 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 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; | 2 | import { PageHeaderWrapper } from '@ant-design/pro-layout'; |
3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; | 3 | import { Card, Table, Button, Row, DatePicker, Col, InputNumber, message } from 'antd'; |
4 | import { getRetailList, ListItem, saveHandelTack, ShopTaskList, save, saveParams } from './api'; | 4 | import { getRetailList, ListItem, saveHandelTack, ShopTaskList, save, saveParams } from './api'; |
@@ -24,7 +24,7 @@ export default function TacklingCarModels() { | @@ -24,7 +24,7 @@ export default function TacklingCarModels() { | ||
24 | const [date, setDate] = useState<number>(newDay); | 24 | const [date, setDate] = useState<number>(newDay); |
25 | const [list, setList] = useState<ListItem>({}); | 25 | const [list, setList] = useState<ListItem>({}); |
26 | const [orginDara, setOrginDara] = useState<ListItem>({}); | 26 | const [orginDara, setOrginDara] = useState<ListItem>({}); |
27 | - let index; | 27 | + const index = useRef(0); |
28 | 28 | ||
29 | useEffect(() => { | 29 | useEffect(() => { |
30 | getList(newDay); | 30 | getList(newDay); |
@@ -57,9 +57,26 @@ export default function TacklingCarModels() { | @@ -57,9 +57,26 @@ export default function TacklingCarModels() { | ||
57 | setDate(date.valueOf()); | 57 | setDate(date.valueOf()); |
58 | getList(date); | 58 | getList(date); |
59 | } | 59 | } |
60 | + | ||
60 | function _onChange(e: number, index: number) { | 61 | function _onChange(e: number, index: number) { |
61 | - const data = list; | 62 | + const data = JSON.parse(JSON.stringify(list)); |
62 | data.shopTaskList![index].taskCount = e || 0; | 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 | setList({ ...data }); | 80 | setList({ ...data }); |
64 | } | 81 | } |
65 | //保存 | 82 | //保存 |
@@ -121,54 +138,69 @@ export default function TacklingCarModels() { | @@ -121,54 +138,69 @@ export default function TacklingCarModels() { | ||
121 | }); | 138 | }); |
122 | return ( | 139 | return ( |
123 | <> | 140 | <> |
124 | - {total == 0 ? null : | ||
125 | - <Table.Summary.Row > | 141 | + {total == 0 ? null : ( |
142 | + <Table.Summary.Row> | ||
126 | <Table.Summary.Cell index={1}><span style={{ display: 'block', textAlign: 'center' }}>合计</span></Table.Summary.Cell> | 143 | <Table.Summary.Cell index={1}><span style={{ display: 'block', textAlign: 'center' }}>合计</span></Table.Summary.Cell> |
127 | <Table.Summary.Cell index={2}> | 144 | <Table.Summary.Cell index={2}> |
128 | <span style={{ display: 'block', textAlign: 'center' }}>{total}</span> | 145 | <span style={{ display: 'block', textAlign: 'center' }}>{total}</span> |
129 | </Table.Summary.Cell> | 146 | </Table.Summary.Cell> |
130 | - <Table.Summary.Cell index={3}></Table.Summary.Cell> | 147 | + <Table.Summary.Cell index={3} /> |
131 | </Table.Summary.Row> | 148 | </Table.Summary.Row> |
132 | - } | 149 | + )} |
133 | </> | 150 | </> |
134 | ); | 151 | ); |
135 | }} | 152 | }} |
136 | > | 153 | > |
137 | <Column title="门店" dataIndex="shopName" width={400} align="center" /> | 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 | render={(value, record, index) => { | 159 | render={(value, record, index) => { |
140 | if (edit) { | 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 | } else { | 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 | </Table> | 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 | <Row justify="center" style={{ marginTop: 15 }}> | 190 | <Row justify="center" style={{ marginTop: 15 }}> |
157 | - {list.canModified ? | 191 | + {list.canModified ? ( |
158 | <> | 192 | <> |
159 | {edit ? | 193 | {edit ? |
160 | <Button style={{ width: 110 }} type="primary" onClick={handleClick} loading={confirmLoading}>确定</Button> : | 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 | <Button style={{ width: 110, marginLeft: 15, backgroundColor: '#FFF' }} type="default" onClick={() => setEdit(!edit)}>手动编辑</Button> | 196 | <Button style={{ width: 110, marginLeft: 15, backgroundColor: '#FFF' }} type="default" onClick={() => setEdit(!edit)}>手动编辑</Button> |
164 | {edit ? | 197 | {edit ? |
165 | <Button style={{ width: 110, marginLeft: 15 }} type="default" onClick={setReset} loading={confirmLoading}>取消</Button> : | 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 | </Row> | 202 | </Row> |
171 | </Card> | 203 | </Card> |
172 | - </PageHeaderWrapper > | ||
173 | - ) | 204 | + </PageHeaderWrapper> |
205 | + ); | ||
174 | } | 206 | } |
175 | \ No newline at end of file | 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 | \ No newline at end of file | 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 | \ No newline at end of file | 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 => { | ||
15 | + message.success(res.result); | ||
16 | + setLoading(true); | ||
17 | + }) | ||
18 | + .catch(e => { | ||
19 | + message.error(e.message); | ||
20 | + }); | ||
21 | + } | ||
22 | + | ||
23 | + function handleEdit(value?: List) { | ||
24 | + setCurrent(value || {}); | ||
25 | + setVisible(true); | ||
26 | + } | ||
27 | + | ||
28 | + function handleLookShop(value: any = {}) { | ||
29 | + setStatusData({visible: true, data: value.shopList || []}); | ||
30 | + } | ||
31 | + | ||
32 | + return ( | ||
33 | + <div> | ||
34 | + <Table | ||
35 | + dataSource={data} | ||
36 | + pagination={false} | ||
37 | + loading={loading} | ||
38 | + rowKey="id" | ||
39 | + > | ||
40 | + <Column | ||
41 | + title="适用门店" | ||
42 | + align="left" | ||
43 | + dataIndex="displayName" | ||
44 | + render={(_text, record) => <span onClick={() => handleLookShop(record)} style={{color: "#4189FD"}}>{_text}</span>} | ||
45 | + /> | ||
46 | + <Column | ||
47 | + title="线索到店零售台数占比" | ||
48 | + align="left" | ||
49 | + dataIndex="retailRate" | ||
50 | + render={(_text, record: any) => <span>{isNil(record?.retailRate) ? "--" : `${record.retailRate}%`}</span>} | ||
51 | + /> | ||
52 | + <Column | ||
53 | + title="操作" | ||
54 | + align="left" | ||
55 | + render={(_text, record: any) => { | ||
56 | + return ( | ||
57 | + <Space> | ||
58 | + <a onClick={() => handleEdit(record)} style={{ display: "block", color: "#4189FD" }}>编辑</a> | ||
59 | + <Popconfirm | ||
60 | + title="是否删除?" | ||
61 | + okText="确定" | ||
62 | + cancelText="取消" | ||
63 | + onConfirm={() => handleDelete(record.id)} | ||
64 | + > | ||
65 | + <a style={{color: "#EC3F2F"}}>删除</a> | ||
66 | + </Popconfirm> | ||
67 | + </Space> | ||
68 | + ); | ||
69 | + }} | ||
70 | + /> | ||
71 | + </Table> | ||
72 | + </div> | ||
73 | + ); | ||
74 | +} |
src/pages/order3/RetailTaskConfiguration/components/ShopModal.tsx
0 → 100644
1 | +import React, {useEffect, useState} from 'react'; | ||
2 | +import {Modal, Button, List} from 'antd'; | ||
3 | +import { useStore } from '../index'; | ||
4 | +import styles from '../index.less'; | ||
5 | + | ||
6 | +export default function Index() { | ||
7 | + const { statusData, setStatusData } = useStore(); | ||
8 | + function handleCancel() { | ||
9 | + setStatusData({visible: false, data: []}); | ||
10 | + } | ||
11 | + | ||
12 | + return ( | ||
13 | + <Modal | ||
14 | + destroyOnClose | ||
15 | + title={<div className={styles.lineWrap}><span className={styles.line} /><span className={styles.lineText}>适用门店</span></div>} | ||
16 | + visible={statusData.visible} | ||
17 | + maskClosable={false} | ||
18 | + onCancel={handleCancel} | ||
19 | + footer={[]} | ||
20 | + className={styles.modal} | ||
21 | + > | ||
22 | + <List | ||
23 | + size="large" | ||
24 | + style={{height: '300px', overflow: 'scroll'}} | ||
25 | + dataSource={statusData?.data || []} | ||
26 | + renderItem={(item: any) => <List.Item style={{justifyContent: 'center'}}>{item.shopName}</List.Item>} | ||
27 | + /> | ||
28 | + <div | ||
29 | + style={{ | ||
30 | + textAlign: 'center', | ||
31 | + marginTop: 12, | ||
32 | + height: 32, | ||
33 | + lineHeight: '32px', | ||
34 | + }} | ||
35 | + ><Button type="primary" onClick={handleCancel}>返回</Button> | ||
36 | + </div> | ||
37 | + </Modal> | ||
38 | + ); | ||
39 | +} | ||
0 | \ No newline at end of file | 40 | \ No newline at end of file |
src/pages/order3/RetailTaskConfiguration/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: 600; | ||
22 | +} | ||
23 | + | ||
24 | +.modal { | ||
25 | + height: 400px; | ||
26 | +} | ||
27 | + | ||
28 | +.line { | ||
29 | + width: 4px; | ||
30 | + height: 20px; | ||
31 | + background-color: #4189FD; | ||
32 | + border-radius: 2px; | ||
33 | + display: inline-block; | ||
34 | +} | ||
35 | + | ||
36 | +.lineText { | ||
37 | + font-size: 18px; | ||
38 | + color: #262626; | ||
39 | + font-weight: bold; | ||
40 | + display: inline-block; | ||
41 | + margin-left: 8px; | ||
42 | +} | ||
43 | + | ||
44 | +.lineWrap { | ||
45 | + display: flex; | ||
46 | + align-items: center; | ||
47 | +} | ||
48 | + | ||
49 | +.lineSpace { | ||
50 | + margin-bottom: 10px; | ||
51 | +} | ||
0 | \ No newline at end of file | 52 | \ No newline at end of file |
src/pages/order3/RetailTaskConfiguration/index.tsx
0 → 100644
1 | +import React 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 ShopModal from './components/ShopModal'; | ||
8 | +import EditModal from './components/EditMidal'; | ||
9 | + | ||
10 | +export const { Provider, useStore } = createStore(store); | ||
11 | + | ||
12 | +function SubsidizedInterest() { | ||
13 | + const {setVisible, setParams} = useStore(); | ||
14 | + return ( | ||
15 | + <PageHeaderWrapper title="线索到店零售占比配置"> | ||
16 | + <Card> | ||
17 | + <Row justify="end" style={{ marginBottom: 20 }}> | ||
18 | + <Button type="primary" onClick={() => setVisible(true)}>新增</Button> | ||
19 | + </Row> | ||
20 | + <List /> | ||
21 | + <ShopModal /> | ||
22 | + <EditModal /> | ||
23 | + </Card> | ||
24 | + </PageHeaderWrapper> | ||
25 | + ); | ||
26 | +} | ||
27 | + | ||
28 | +export default () => <Provider><SubsidizedInterest /></Provider>; |
src/pages/order3/RetailTaskConfiguration/store.ts
0 → 100644
1 | +import React, { useState } from 'react'; | ||
2 | +import usePagination from '@/hooks/usePagination'; | ||
3 | +import useInitial from '@/hooks/useInitail'; | ||
4 | +import { getRetailListApi, List, ShopList} from './api'; | ||
5 | + | ||
6 | +interface StatusData { | ||
7 | + visible: boolean | ||
8 | + data: ShopList[] | ||
9 | +} | ||
10 | + | ||
11 | +export default function useStore() { | ||
12 | + const { data, setParams, setLoading, loading } = useInitial(getRetailListApi, [], null); | ||
13 | + const [visible, setVisible] = useState<boolean>(false); | ||
14 | + const [current, setCurrent] = useState<List>({}); | ||
15 | + const [statusData, setStatusData] = useState<StatusData>({visible: false, data: []}); | ||
16 | + return { | ||
17 | + data, | ||
18 | + setParams, | ||
19 | + setLoading, | ||
20 | + loading, | ||
21 | + visible, | ||
22 | + setVisible, | ||
23 | + current, | ||
24 | + setCurrent, | ||
25 | + statusData, | ||
26 | + setStatusData | ||
27 | + }; | ||
28 | +} | ||
0 | \ No newline at end of file | 29 | \ No newline at end of file |
src/pages/pms/entity.ts
@@ -151,13 +151,13 @@ export const partTypeData = [ | @@ -151,13 +151,13 @@ export const partTypeData = [ | ||
151 | 151 | ||
152 | export const typeReceiverObj: {[key: string]: string} = { | 152 | export const typeReceiverObj: {[key: string]: string} = { |
153 | '售后工单锁定': '服务', | 153 | '售后工单锁定': '服务', |
154 | - '装潢订单锁定': '推荐', | 154 | + '装潢零售锁定': '推荐', |
155 | '调件锁定': '', | 155 | '调件锁定': '', |
156 | '': '', | 156 | '': '', |
157 | }; | 157 | }; |
158 | export const typeSenderObj: {[key: string]: string} = { | 158 | export const typeSenderObj: {[key: string]: string} = { |
159 | '售后工单锁定': '送修人', | 159 | '售后工单锁定': '送修人', |
160 | - '装潢订单锁定': '下单人', | 160 | + '装潢零售锁定': '下单人', |
161 | '调件锁定': '', | 161 | '调件锁定': '', |
162 | '': '', | 162 | '': '', |
163 | }; | 163 | }; |
src/pages/pms/storage/partShop/components/FlowDetailModal.tsx
@@ -76,6 +76,7 @@ export default function Index({item = {}, visible, onCancel}: Props) { | @@ -76,6 +76,7 @@ export default function Index({item = {}, visible, onCancel}: Props) { | ||
76 | {!!obj.carName && <div>{`车辆: ${obj.carName}`}</div>} | 76 | {!!obj.carName && <div>{`车辆: ${obj.carName}`}</div>} |
77 | {!!obj.vin && <div>{`车架号: ${obj.vin}`}</div>} | 77 | {!!obj.vin && <div>{`车架号: ${obj.vin}`}</div>} |
78 | 78 | ||
79 | + {!!obj.plateNo && <div>{`车牌号: ${obj.plateNo}`}</div>} | ||
79 | {!!obj.supplierName && <div>{`供应商: ${obj.supplierName}`}</div>} | 80 | {!!obj.supplierName && <div>{`供应商: ${obj.supplierName}`}</div>} |
80 | {!!obj.purchaseCnt && <div>{`外采种类: ${obj.purchaseCnt}`}</div>} | 81 | {!!obj.purchaseCnt && <div>{`外采种类: ${obj.purchaseCnt}`}</div>} |
81 | {!!obj.amount && <div>{`外采金额: ${obj.amount}`}</div>} | 82 | {!!obj.amount && <div>{`外采金额: ${obj.amount}`}</div>} |
src/pages/stock/Systems/TicketIncreaseToBeDone/index.tsx
@@ -84,7 +84,7 @@ export default function TicketIncreaseToBeDone() { | @@ -84,7 +84,7 @@ export default function TicketIncreaseToBeDone() { | ||
84 | }; | 84 | }; |
85 | const columns = [ | 85 | const columns = [ |
86 | { | 86 | { |
87 | - title: "推开增票代办", | 87 | + title: "开增票待办", |
88 | width: "25%", | 88 | width: "25%", |
89 | dataIndex: "appointShopName", | 89 | dataIndex: "appointShopName", |
90 | }, | 90 | }, |