Commit d03a9f2ff367dba0d99f5f11680fdf8dc05a542a
1 parent
0a763d83
线索目标接通配置
Showing
7 changed files
with
333 additions
and
1 deletions
config/routers/crm_new.ts
@@ -77,8 +77,12 @@ export default [ | @@ -77,8 +77,12 @@ export default [ | ||
77 | path: '/crm/threePartyPlatformClue', // 三方平台线索配置 | 77 | path: '/crm/threePartyPlatformClue', // 三方平台线索配置 |
78 | component: './crm_new/ThreePartyPlatformClue', | 78 | component: './crm_new/ThreePartyPlatformClue', |
79 | }, | 79 | }, |
80 | - { | 80 | + { |
81 | path: '/crm/closeClue', // 暂停区域线索站岗分配 | 81 | path: '/crm/closeClue', // 暂停区域线索站岗分配 |
82 | component: './crm_new/Settings/subpages/CloseClue', | 82 | component: './crm_new/Settings/subpages/CloseClue', |
83 | }, | 83 | }, |
84 | + { | ||
85 | + path: '/crm/clueConnect', // 线索有效接通配置 | ||
86 | + component: './crm_new/CluesConnectTargetEffectively', | ||
87 | + }, | ||
84 | ]; | 88 | ]; |
85 | \ No newline at end of file | 89 | \ No newline at end of file |
src/pages/crm_new/CluesConnectTargetEffectively/api.ts
0 → 100644
1 | +import request from '@/utils/request'; | ||
2 | +import { CRM_HOST } from '@/utils/host'; | ||
3 | + | ||
4 | +export interface Result { | ||
5 | + id?: number // 配置id | ||
6 | + dialAims?: number // 线索接通目标 | ||
7 | + displayName?: string // 显示名称 | ||
8 | + shopList?: ShopList[] // 门店列表 | ||
9 | +} | ||
10 | + | ||
11 | +export interface ShopList { | ||
12 | + shopId?: number // 门店id | ||
13 | + shopName?: string // 门店名称 | ||
14 | +} | ||
15 | + | ||
16 | +/** 查询线索拨通目标配置列表 */ | ||
17 | +export function getConfigApi() { | ||
18 | + return request.get<Result[]>(`${CRM_HOST}/erp/clue/dial/aims/config/list`); | ||
19 | +} | ||
20 | + | ||
21 | +/** 查询线索拨通目标已配置的门店 */ | ||
22 | +export function getHaveShopListApi() { | ||
23 | + return request.get<number[]>(`${CRM_HOST}/erp/clue/dial/aims/config/already/exists/shopIds`); | ||
24 | +} | ||
25 | + | ||
26 | +/** 保存线索拨通目标配置 */ | ||
27 | +export function saveConfigApi(params: Result) { | ||
28 | + return request.post<Result>(`${CRM_HOST}/erp/clue/dial/aims/config/save`, params); | ||
29 | +} | ||
30 | + | ||
31 | +/** 删除线索拨通目标配置 */ | ||
32 | +export function deleteConfigApi(id?: number) { | ||
33 | + return request.post<Result>(`${CRM_HOST}/erp/clue/dial/aims/config/delete`, {id}, {contentType: 'form-urlencoded'}); | ||
34 | +} | ||
0 | \ No newline at end of file | 35 | \ No newline at end of file |
src/pages/crm_new/CluesConnectTargetEffectively/components/EditModal.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 { useStore } from '../index'; | ||
5 | +import { saveConfigApi, getHaveShopListApi } from '../api'; | ||
6 | +import { debounce } from 'lodash'; | ||
7 | + | ||
8 | +const Option = Select.Option; | ||
9 | + | ||
10 | +export default function Index() { | ||
11 | + const [form] = Form.useForm(); | ||
12 | + const { current, setCurrent, setLoading } = useStore(); | ||
13 | + const [confirm, setConfirm] = useState<boolean>(false); | ||
14 | + const [disabledShopIds, setDisabledShopIds] = useState<number[]>([]); | ||
15 | + | ||
16 | + useEffect(() => { | ||
17 | + if (current.visible && current.data.id) { | ||
18 | + handleSetValue(); | ||
19 | + const shopList = current.data.shopList || []; | ||
20 | + getHaveShopListApi() | ||
21 | + .then(res => { | ||
22 | + const shopIdList = res.data || []; | ||
23 | + const _shopIds = shopList.map(v => v.shopId) || []; | ||
24 | + const disabledShop = shopIdList.filter(v => !(_shopIds.includes(v))) || []; | ||
25 | + setDisabledShopIds(disabledShop); | ||
26 | + }) | ||
27 | + .catch(e => { | ||
28 | + message.error(e.message); | ||
29 | + }); | ||
30 | + } else { | ||
31 | + getHaveShopListApi() | ||
32 | + .then(res => { | ||
33 | + const shopIdList = res.data || []; | ||
34 | + setDisabledShopIds(shopIdList); | ||
35 | + }) | ||
36 | + .catch(e => { | ||
37 | + message.error(e.message); | ||
38 | + }); | ||
39 | + } | ||
40 | + }, [current.visible]); | ||
41 | + | ||
42 | + function handleCancle() { | ||
43 | + setCurrent({visible: false, data: {}}); | ||
44 | + } | ||
45 | + | ||
46 | + async function handleSubmit() { | ||
47 | + const params = await form.validateFields(); | ||
48 | + setConfirm(true); | ||
49 | + const _params = { | ||
50 | + id: current.data?.id, | ||
51 | + dialAims: params.dialAims, | ||
52 | + shopList: params.shopList?.map((v: any) => ({shopId: v.value, shopName: v.label})) | ||
53 | + }; | ||
54 | + saveConfigApi(_params) | ||
55 | + .then(res => { | ||
56 | + message.success(res.result); | ||
57 | + setConfirm(false); | ||
58 | + setCurrent({visible: false, data: {}}); | ||
59 | + setLoading(true); | ||
60 | + }) | ||
61 | + .catch(e => { | ||
62 | + message.error(e.message); | ||
63 | + setConfirm(false); | ||
64 | + }); | ||
65 | + } | ||
66 | + | ||
67 | + function handleSetValue() { | ||
68 | + form.setFieldsValue({ | ||
69 | + dialAims: current.data.dialAims, | ||
70 | + shopList: current.data.shopList?.map((v: any) => ({label: v.shopName, value: v.shopId})) | ||
71 | + }); | ||
72 | + } | ||
73 | + | ||
74 | + function handleResetValue() { | ||
75 | + form.setFieldsValue({shopList: [], dialAims: undefined}); | ||
76 | + } | ||
77 | + return ( | ||
78 | + <Modal | ||
79 | + title={current.data.id ? "编辑" : "新增"} | ||
80 | + destroyOnClose | ||
81 | + visible={current.visible} | ||
82 | + maskClosable={false} | ||
83 | + onCancel={handleCancle} | ||
84 | + afterClose={() => handleResetValue()} | ||
85 | + width="60%" | ||
86 | + footer={[ | ||
87 | + <Button key="cancel" disabled={confirm} onClick={handleCancle} style={{marginLeft: 10}}>取消</Button>, | ||
88 | + <Button key="submit" disabled={confirm} onClick={debounce(handleSubmit, 380)} type="primary" htmlType="submit" loading={confirm}>确认</Button> | ||
89 | + ]} | ||
90 | + > | ||
91 | + <Form | ||
92 | + form={form} | ||
93 | + labelCol={{ span: 8 }} | ||
94 | + wrapperCol={{ span: 10 }} | ||
95 | + > | ||
96 | + | ||
97 | + <Form.Item | ||
98 | + label="线索有效接通目标" | ||
99 | + name="dialAims" | ||
100 | + rules={[{ required: true, message: "请输入" }]} | ||
101 | + > | ||
102 | + <InputNumber | ||
103 | + style={{width: '100%'}} | ||
104 | + controls={false} | ||
105 | + min={0} | ||
106 | + formatter={value => `${value}条/天`} | ||
107 | + precision={0} | ||
108 | + parser={value => value?.replace('条/天', '')} | ||
109 | + /> | ||
110 | + </Form.Item> | ||
111 | + <Form.Item | ||
112 | + label="适用门店" | ||
113 | + name="shopList" | ||
114 | + rules={[{ required: true, message: "请选择门店" }]} | ||
115 | + labelAlign="right" | ||
116 | + > | ||
117 | + <ShopSelectNew disabledShopIds={disabledShopIds} defaultOptions={{bizTypes: "1"}} placeholder="请选择门店" multiple /> | ||
118 | + </Form.Item> | ||
119 | + | ||
120 | + </Form> | ||
121 | + | ||
122 | + </Modal> | ||
123 | + ); | ||
124 | +} | ||
0 | \ No newline at end of file | 125 | \ No newline at end of file |
src/pages/crm_new/CluesConnectTargetEffectively/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 {deleteConfigApi, Result } from '../api'; | ||
5 | + | ||
6 | +const Column = Table.Column; | ||
7 | + | ||
8 | +export default function TableList() { | ||
9 | + const {data, loading, setLoading, setShopData, setCurrent } = useStore(); | ||
10 | + | ||
11 | + function handleDelete(id?: number) { | ||
12 | + deleteConfigApi(id) | ||
13 | + .then(res => { | ||
14 | + message.success(res.result); | ||
15 | + setLoading(true); | ||
16 | + }) | ||
17 | + .catch(e => { | ||
18 | + message.error(e.message); | ||
19 | + }); | ||
20 | + } | ||
21 | + | ||
22 | + return ( | ||
23 | + <div> | ||
24 | + <Table | ||
25 | + dataSource={data} | ||
26 | + pagination={false} | ||
27 | + loading={loading} | ||
28 | + rowKey="id" | ||
29 | + > | ||
30 | + <Column | ||
31 | + title="上班期间线索有效接通目标(条/天)" | ||
32 | + align="left" | ||
33 | + dataIndex="dialAims" | ||
34 | + /> | ||
35 | + <Column | ||
36 | + title="适用门店" | ||
37 | + align="left" | ||
38 | + dataIndex="displayName" | ||
39 | + render={(_text: string, record: Result) => <span style={{color: "#4189FD"}} onClick={() => setShopData({visible: true, data: record.shopList || []})}>{_text}</span>} | ||
40 | + /> | ||
41 | + <Column | ||
42 | + title="操作" | ||
43 | + align="left" | ||
44 | + render={(_text, record: Result) => { | ||
45 | + return ( | ||
46 | + <Space> | ||
47 | + <a onClick={() => setCurrent({visible: true, data: record})} style={{ display: "block", color: "#4189FD" }}>编辑</a> | ||
48 | + <Popconfirm | ||
49 | + title="是否删除?" | ||
50 | + okText="确定" | ||
51 | + cancelText="取消" | ||
52 | + onConfirm={() => handleDelete(record.id)} | ||
53 | + > | ||
54 | + <a style={{color: "#EC3F2F"}}>删除</a> | ||
55 | + </Popconfirm> | ||
56 | + </Space> | ||
57 | + ); | ||
58 | + }} | ||
59 | + /> | ||
60 | + </Table> | ||
61 | + </div> | ||
62 | + ); | ||
63 | +} |
src/pages/crm_new/CluesConnectTargetEffectively/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 | + | ||
5 | +export default function Index() { | ||
6 | + const {shopData, setShopData } = useStore(); | ||
7 | + | ||
8 | + function handleCancel() { | ||
9 | + setShopData({visible: false, data: []}); | ||
10 | + } | ||
11 | + | ||
12 | + return ( | ||
13 | + <Modal | ||
14 | + destroyOnClose | ||
15 | + title="适用门店" | ||
16 | + visible={shopData.visible} | ||
17 | + maskClosable={false} | ||
18 | + onCancel={handleCancel} | ||
19 | + footer={[]} | ||
20 | + > | ||
21 | + <List | ||
22 | + size="large" | ||
23 | + style={{height: '300px', overflow: 'scroll'}} | ||
24 | + dataSource={shopData?.data || []} | ||
25 | + renderItem={(item: any) => <List.Item style={{justifyContent: 'center'}}>{item.shopName}</List.Item>} | ||
26 | + /> | ||
27 | + <div | ||
28 | + style={{ | ||
29 | + textAlign: 'center', | ||
30 | + marginTop: 12, | ||
31 | + height: 32, | ||
32 | + lineHeight: '32px', | ||
33 | + }} | ||
34 | + ><Button type="primary" onClick={handleCancel}>返回</Button> | ||
35 | + </div> | ||
36 | + </Modal> | ||
37 | + ); | ||
38 | +} | ||
0 | \ No newline at end of file | 39 | \ No newline at end of file |
src/pages/crm_new/CluesConnectTargetEffectively/index.tsx
0 → 100644
1 | +import React, {useState} from 'react'; | ||
2 | +import { Card, Button, Row, Col } 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 EditModal from './components/EditModal'; | ||
8 | +import ShopModal from './components/ShopModal'; | ||
9 | +// import ShopSelectNew from '@/components/ShopSelectNew'; | ||
10 | + | ||
11 | +export const { Provider, useStore } = createStore(store); | ||
12 | + | ||
13 | +function Index() { | ||
14 | + const { setParams, setCurrent } = useStore(); | ||
15 | + const [selected, setSelected] = useState<any>([]); | ||
16 | + | ||
17 | + function handleOnChange(value: any) { | ||
18 | + setParams({shopId: value[0]?.value || undefined}, true); | ||
19 | + setSelected(value || []); | ||
20 | + } | ||
21 | + return ( | ||
22 | + <PageHeaderWrapper title={<Row align="middle"><span style={{width: "5px", height: "20px", backgroundColor: "#448EF7", borderRadius: "3px", display: 'inline-block', marginRight: "10px"}} /><span>线索有效接通目标</span></Row>}> | ||
23 | + <Card> | ||
24 | + <Row justify="end" style={{ marginBottom: 20 }}> | ||
25 | + {/* <Col span={10}> | ||
26 | + <ShopSelectNew value={selected} onChange={handleOnChange} defaultOptions={{bizTypes: "1"}} placeholder="请选择门店" /> | ||
27 | + </Col> */} | ||
28 | + <Button onClick={() => setCurrent({visible: true, data: {}})} type="primary">新增</Button> | ||
29 | + </Row> | ||
30 | + <List /> | ||
31 | + <EditModal /> | ||
32 | + <ShopModal /> | ||
33 | + </Card> | ||
34 | + </PageHeaderWrapper> | ||
35 | + ); | ||
36 | +} | ||
37 | + | ||
38 | +export default () => <Provider><Index /></Provider>; |
src/pages/crm_new/CluesConnectTargetEffectively/store.ts
0 → 100644
1 | +import React, { useState } from 'react'; | ||
2 | +import useInitial from '@/hooks/useInitail'; | ||
3 | +import {getConfigApi, Result, ShopList} from './api'; | ||
4 | + | ||
5 | +interface Current { | ||
6 | + visible: boolean | ||
7 | + data: Result | ||
8 | +} | ||
9 | + | ||
10 | +interface ShopData { | ||
11 | + visible: boolean | ||
12 | + data: ShopList[] | ||
13 | +} | ||
14 | + | ||
15 | +export default function useStore() { | ||
16 | + const { data, loading, errMsg, setLoading, setParams, params } = useInitial(getConfigApi, [], {}); | ||
17 | + const [current, setCurrent] = useState<Current>({visible: false, data: {}}); | ||
18 | + const [shopData, setShopData] = useState<ShopData>({visible: false, data: []}); | ||
19 | + return { | ||
20 | + data, | ||
21 | + loading, | ||
22 | + errMsg, | ||
23 | + setLoading, | ||
24 | + setParams, | ||
25 | + params, | ||
26 | + current, | ||
27 | + setCurrent, | ||
28 | + shopData, | ||
29 | + setShopData | ||
30 | + }; | ||
31 | +} | ||
0 | \ No newline at end of file | 32 | \ No newline at end of file |