Commit df8ffe92b59080066234c4413f678d6e48bf8d65

Authored by 王强
2 parents ca760b7f cfe11064

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 &#39;@/utils/request&#39;; @@ -3,26 +3,30 @@ import request from &#39;@/utils/request&#39;;
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 &#39;@/utils/request&#39;; @@ -3,25 +3,28 @@ import request from &#39;@/utils/request&#39;;
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 },