Commit 75ceab7cda766cc323aaf19e00fac5eef4084f67
Merge remote-tracking branch 'origin/master' into quality
Showing
10 changed files
with
790 additions
and
1 deletions
config/routers/crm_new.ts
... | ... | @@ -82,7 +82,11 @@ export default [ |
82 | 82 | component: './crm_new/Settings/subpages/CloseClue', |
83 | 83 | }, |
84 | 84 | { |
85 | + path: '/crm/clueConnect', // 线索有效接通配置 | |
86 | + component: './crm_new/CluesConnectTargetEffectively', | |
87 | + }, | |
88 | + { | |
85 | 89 | path: '/crm/clueShareBrand', // 线索品牌共享分组 |
86 | 90 | component: './crm_new/Settings/subpages/ClueBrandSharing', |
87 | - }, | |
91 | + } | |
88 | 92 | ]; |
89 | 93 | \ No newline at end of file | ... | ... |
src/components/CarTableTreeAuth/SelectSpec.tsx
0 → 100644
1 | +import React, { useEffect, useState } from "react"; | |
2 | +import { | |
3 | + Table, | |
4 | + Select, | |
5 | +} from "antd"; | |
6 | +import { | |
7 | + CarOptionVo, | |
8 | +} from "@/pages/stock/Components/api"; | |
9 | +import { ColumnsType } from 'antd/lib/table'; | |
10 | + | |
11 | +interface Props { | |
12 | + onChange?: (pa: any) => void; | |
13 | + value?: any[]; | |
14 | + disabled?: boolean; | |
15 | + specList?: CarOptionVo[] | |
16 | +} | |
17 | + | |
18 | +interface Item { | |
19 | + label: string; | |
20 | + value: number; | |
21 | + key?: number; | |
22 | +} | |
23 | +const { Column } = Table; | |
24 | +const { Option } = Select; | |
25 | + | |
26 | +const columns: ColumnsType<CarOptionVo> = [ | |
27 | + { | |
28 | + title: '车型', | |
29 | + dataIndex: 'name', | |
30 | + }, | |
31 | + { | |
32 | + title: '配置代码', | |
33 | + dataIndex: 'specConfigCodeList', | |
34 | + render: (t) => t.map((i: string, key: number) => (<div key={i}>{key + 1}.{i}</div>)) | |
35 | + }, | |
36 | +]; | |
37 | +export default function index({ onChange: onSelected, value, disabled, specList = [] }: Props) { | |
38 | + const rowSelection = { | |
39 | + onChange: (selectedRowKeys: React.Key[], selectedRows: CarOptionVo[]) => { | |
40 | + onSelected && onSelected(selectedRows); | |
41 | + }, | |
42 | + }; | |
43 | + | |
44 | + return ( | |
45 | + <> | |
46 | + <Table | |
47 | + rowSelection={{ | |
48 | + type: "checkbox", | |
49 | + selectedRowKeys: value && value.map(item => item.id), | |
50 | + ...rowSelection, | |
51 | + }} | |
52 | + pagination={false} | |
53 | + // loading={} | |
54 | + rowKey="id" | |
55 | + size="small" | |
56 | + scroll={{ y: 500 }} | |
57 | + columns={columns} | |
58 | + dataSource={specList} | |
59 | + /> | |
60 | + </> | |
61 | + ); | |
62 | +} | ... | ... |
src/components/CarTableTreeAuth/entity.ts
0 → 100644
1 | +export interface CarAuthList { | |
2 | + authType: number; | |
3 | + brandId: number; | |
4 | + brandName: number; | |
5 | + children?: Array<SeriesAuth> | |
6 | +} | |
7 | + | |
8 | +interface SeriesAuth { | |
9 | + authType: number; | |
10 | + seriesId: number; | |
11 | + seriesName: string; | |
12 | + children?: Array<SpecAuth> | |
13 | +} | |
14 | +interface SpecAuth { | |
15 | + authType: number; | |
16 | + specId: number; | |
17 | + specName: string; | |
18 | +} | |
0 | 19 | \ No newline at end of file | ... | ... |
src/components/CarTableTreeAuth/index.tsx
0 → 100644
1 | +import React, { useEffect, useState } from "react"; | |
2 | +import { | |
3 | + Button, | |
4 | + Card, | |
5 | + Table, | |
6 | + Modal, | |
7 | + Form, | |
8 | + Select, | |
9 | + Space, | |
10 | + Spin, | |
11 | + message, | |
12 | +} from "antd"; | |
13 | +import { | |
14 | + CarOptionVo, | |
15 | + getOnsaleSeriesApi, | |
16 | + getOnsaleSpecApi, | |
17 | +} from "@/pages/stock/Components/api"; | |
18 | +import { PlusOutlined } from '@ant-design/icons'; | |
19 | +import { ColumnsType } from 'antd/lib/table'; | |
20 | +import SelectSpec from './SelectSpec'; | |
21 | +import { CarAuthList } from './entity'; | |
22 | +import { getBrandFilterApi } from '@/common/api'; | |
23 | + | |
24 | +interface Props { | |
25 | + onChange?: (pa: any) => void; | |
26 | + value?: CarAuthList[]; | |
27 | + disabled?: boolean; | |
28 | + brandList?: CommonApi.OptionVO[]; | |
29 | + /**品牌是否多选,默认true */ | |
30 | + brandMultiple?: boolean; | |
31 | +} | |
32 | + | |
33 | +interface Item { | |
34 | + label: string; | |
35 | + value: number; | |
36 | + key?: number; | |
37 | +} | |
38 | +const { Column } = Table; | |
39 | +const { Option } = Select; | |
40 | + | |
41 | +export default function index({ onChange, value, disabled, brandMultiple = true, brandList }: Props) { | |
42 | + const [form] = Form.useForm(); | |
43 | + // 控制Modal是否可见 | |
44 | + const [visible, setVisible] = useState<boolean>(false); | |
45 | + // 控制是否可见车型下拉框 | |
46 | + const [level, setLevel] = useState<number>(1); | |
47 | + //存储表格当前车系 | |
48 | + const [currentItem, setCurrentItem] = useState<any>(); | |
49 | + const [brandData, setBrandData] = useState<CommonApi.OptionVO[]>([]); | |
50 | + //存储Modal中可选的车系 | |
51 | + const [series, setSeries] = useState<any>([]); | |
52 | + //存储接口返回的车型 | |
53 | + const [specList, setSpecList] = useState<any>([]); | |
54 | + //存储Modal选择的品牌和车系作为表格的数据源 | |
55 | + const [savaData, setSavadata] = useState<any>([]); | |
56 | + // 存储已经选中的车系 | |
57 | + const [selectedSeries, setSelectedSeries] = useState<any[]>([]); | |
58 | + const [fetchLoading, setFetchLoading] = useState(false); | |
59 | + | |
60 | + useEffect(() => { | |
61 | + if (value && value.length) { | |
62 | + setSelectedSeries([...value]); | |
63 | + setSavadata([...value]); | |
64 | + } | |
65 | + }, [value]); | |
66 | + | |
67 | + useEffect(() => { | |
68 | + if (brandList && Array.isArray(brandList)) { | |
69 | + setBrandData(brandList || []); | |
70 | + } | |
71 | + }, [brandList]); | |
72 | + | |
73 | + function getBrands() { | |
74 | + if (brandData.length) { | |
75 | + return; | |
76 | + } | |
77 | + setFetchLoading(true); | |
78 | + getBrandFilterApi().then(res => { | |
79 | + setBrandData(res.data || []); | |
80 | + setFetchLoading(false); | |
81 | + }).catch(err => { | |
82 | + message.error(err.message); | |
83 | + setFetchLoading(false); | |
84 | + }); | |
85 | + } | |
86 | + | |
87 | + const _onOk = () => { | |
88 | + form | |
89 | + .validateFields() | |
90 | + .then((fileds) => { | |
91 | + if (level === 2) { | |
92 | + const { series } = fileds; | |
93 | + const _series = series.map((item: Item) => ({ | |
94 | + seriesId: item.value, | |
95 | + seriesName: item.label, | |
96 | + authType: 1, | |
97 | + })); | |
98 | + | |
99 | + const currentSeries = (savaData.filter((i: any) => i.brandId === currentItem.brandId)[0] || {}).children || []; | |
100 | + /**判断已有数据保留 */ | |
101 | + if (currentSeries.length) { | |
102 | + const selectedIds = currentSeries.map(i => i.seriesId); | |
103 | + let newAuthSeries: any[] = []; | |
104 | + _series.forEach((item: any) => { | |
105 | + if (selectedIds.includes(item.seriesId)) { | |
106 | + newAuthSeries = newAuthSeries.concat(currentSeries.filter((list: any) => list.seriesId === item.seriesId)); | |
107 | + } else { | |
108 | + newAuthSeries.push(item); | |
109 | + } | |
110 | + }); | |
111 | + currentItem.children = newAuthSeries; | |
112 | + } else { | |
113 | + currentItem.children = _series; | |
114 | + } | |
115 | + | |
116 | + const tempData = savaData.map((item: any) => (item.brandId === currentItem.brandId ? { ...currentItem, authType: series.length ? 2 : 1 } : { ...item, authType: 1 })); | |
117 | + setSavadata([...tempData]); | |
118 | + onChange && onChange(tempData); | |
119 | + setVisible(false); | |
120 | + return; | |
121 | + } | |
122 | + if (level === 3) { | |
123 | + const { spec } = fileds; | |
124 | + const _spec = spec.map((item: any) => ({ | |
125 | + specId: item.id, | |
126 | + specName: item.name, | |
127 | + })); | |
128 | + currentItem.children = _spec; | |
129 | + currentItem.authType = 2; | |
130 | + const tempData = savaData.map((item: any) => { | |
131 | + if (item.children) { | |
132 | + const newChildren = item.children.map((i: any) => (i.seriesId === currentItem.seriesId ? { ...currentItem, authType: spec.length ? 2 : 1 } : i)); | |
133 | + item.children = newChildren; | |
134 | + } | |
135 | + return { ...item, authType: 1 }; | |
136 | + }); | |
137 | + setSavadata([...tempData]); | |
138 | + onChange && onChange(tempData); | |
139 | + setVisible(false); | |
140 | + return; | |
141 | + } | |
142 | + const { brand } = fileds; | |
143 | + const tempArray = (brandMultiple ? brand : [brand]).map((item: any) => ({ | |
144 | + brandName: item.label, | |
145 | + brandId: item.value, | |
146 | + authType: 1, //1全部2部分 | |
147 | + })); | |
148 | + const selectedBrands: number[] = savaData.map((i: any) => i.brandId); | |
149 | + let newAuthCar: any[] = []; | |
150 | + tempArray.forEach((item: any) => { | |
151 | + if (selectedBrands.includes(item.brandId)) { | |
152 | + newAuthCar = newAuthCar.concat(savaData.filter(list => list.brandId === item.brandId)); | |
153 | + } else { | |
154 | + newAuthCar.push(item); | |
155 | + } | |
156 | + }); | |
157 | + setSavadata(newAuthCar); | |
158 | + onChange && onChange(newAuthCar); | |
159 | + setVisible(false); | |
160 | + }) | |
161 | + .catch((err) => console.log(err.message)); | |
162 | + }; | |
163 | + | |
164 | + // 选择部分车辆表格==》删除车系 | |
165 | + const onDelete = (record: any) => { | |
166 | + const _savaData = savaData.filter( | |
167 | + (item: any) => item.seriesId != record.seriesId | |
168 | + ); | |
169 | + const _selectedSeries = selectedSeries.filter( | |
170 | + (item) => item.seriesId != record.seriesId | |
171 | + ); | |
172 | + setSavadata([..._savaData]); | |
173 | + onChange && onChange(_savaData); | |
174 | + setSelectedSeries([..._selectedSeries]); | |
175 | + }; | |
176 | + | |
177 | + // 选择部分车辆表格==》编辑选择部分车系、车型 | |
178 | + const onSelectSpec = async (record: any) => { | |
179 | + setCurrentItem(record); | |
180 | + setVisible(true); | |
181 | + if (record.brandId) { | |
182 | + setLevel(2); | |
183 | + setFetchLoading(true); | |
184 | + try { | |
185 | + const { data } = await getOnsaleSeriesApi(record.brandId); | |
186 | + const selectedSeries = savaData.filter((brand: any) => brand.brandId === record.brandId)[0].children; | |
187 | + selectedSeries && form.setFieldValue("series", selectedSeries.map(i => ({ value: i.seriesId, label: i.seriesName }))); | |
188 | + setSeries(data); | |
189 | + setFetchLoading(false); | |
190 | + } catch (err: any) { | |
191 | + message.error(err.message); | |
192 | + setFetchLoading(false); | |
193 | + } | |
194 | + } | |
195 | + if (record.seriesId) { | |
196 | + setLevel(3); | |
197 | + setFetchLoading(true); | |
198 | + try { | |
199 | + const { data } = await getOnsaleSpecApi(record.seriesId); | |
200 | + record.children && form.setFieldValue("spec", record.children.map(i => ({ id: i.specId, name: i.specName }))); | |
201 | + setSpecList(data); | |
202 | + setFetchLoading(false); | |
203 | + } catch (err: any) { | |
204 | + message.error(err.message); | |
205 | + setFetchLoading(false); | |
206 | + } | |
207 | + } | |
208 | + }; | |
209 | + | |
210 | + return ( | |
211 | + <> | |
212 | + <Card> | |
213 | + {!disabled && ( | |
214 | + <div style={{ display: "flex", justifyContent: "flex-end" }}> | |
215 | + <Button | |
216 | + type="link" | |
217 | + disabled={disabled} | |
218 | + icon={<PlusOutlined />} | |
219 | + onClick={() => { | |
220 | + getBrands(); | |
221 | + setVisible(true); | |
222 | + setLevel(1); | |
223 | + savaData.length && form.setFieldValue("brand", savaData.map((i: any) => ({ value: i.brandId, label: i.brandName }))); | |
224 | + // form.resetFields(); | |
225 | + }} | |
226 | + > | |
227 | + 新增 | |
228 | + </Button> | |
229 | + </div> | |
230 | + )} | |
231 | + | |
232 | + <Table | |
233 | + dataSource={value} | |
234 | + rowKey={(record) => String(record.brandId || record.seriesId || record.specId)} | |
235 | + > | |
236 | + <Column | |
237 | + title="品牌" | |
238 | + dataIndex="brandName" | |
239 | + key="brandId" | |
240 | + /> | |
241 | + <Column | |
242 | + title="车系" | |
243 | + dataIndex="seriesName" | |
244 | + key="seriesId" | |
245 | + render={(text, record: any) => { | |
246 | + return (text || record.specName || (record.children && record.children.length !== 0) ? ( | |
247 | + <span> | |
248 | + {text} | |
249 | + </span> | |
250 | + ) : ( | |
251 | + <span style={{ color: "#999" }}>全部车系</span> | |
252 | + )); | |
253 | + }} | |
254 | + /> | |
255 | + <Column | |
256 | + title="车型" | |
257 | + dataIndex="specName" | |
258 | + key="specId" | |
259 | + render={(text, record: any) => { | |
260 | + return (text || (record.children && record.children.length !== 0) ? ( | |
261 | + <span> | |
262 | + {text} | |
263 | + </span> | |
264 | + ) : ( | |
265 | + <span style={{ color: "#999" }}>全部车型</span> | |
266 | + )); | |
267 | + }} | |
268 | + /> | |
269 | + {!disabled && ( | |
270 | + <Column | |
271 | + title="操作" | |
272 | + render={(_, record: any, index) => { | |
273 | + return ( | |
274 | + <Space> | |
275 | + <Button | |
276 | + type={record.brandId ? "primary" : "link"} | |
277 | + style={{ padding: 2 }} | |
278 | + onClick={() => onSelectSpec(record)} | |
279 | + disabled={disabled} | |
280 | + > | |
281 | + {record.brandId ? "编辑车系" : (record.seriesId ? "编辑车型" : "")} | |
282 | + </Button> | |
283 | + {/* <Popconfirm | |
284 | + title="确定删除?" | |
285 | + okText="确定" | |
286 | + style={{ padding: 0 }} | |
287 | + cancelText="取消" | |
288 | + onConfirm={() => onDelete(record)} | |
289 | + > | |
290 | + <Button type="link" danger disabled={disabled}> | |
291 | + 删除 | |
292 | + </Button> | |
293 | + </Popconfirm> */} | |
294 | + </Space> | |
295 | + ); | |
296 | + }} | |
297 | + /> | |
298 | + )} | |
299 | + </Table> | |
300 | + </Card> | |
301 | + | |
302 | + {/* 选择品牌和车系 */} | |
303 | + <Modal | |
304 | + title="选择车辆信息" | |
305 | + visible={visible} | |
306 | + onOk={() => form.submit()} | |
307 | + onCancel={() => { | |
308 | + setVisible(false); | |
309 | + }} | |
310 | + maskClosable={false} | |
311 | + afterClose={() => { | |
312 | + form.resetFields(); | |
313 | + setCurrentItem({}); | |
314 | + }} | |
315 | + > | |
316 | + <Spin spinning={fetchLoading}> | |
317 | + <Form onFinish={_onOk} form={form}> | |
318 | + {level === 1 ? ( | |
319 | + <Form.Item | |
320 | + label="品牌" | |
321 | + name="brand" | |
322 | + > | |
323 | + <Select | |
324 | + labelInValue | |
325 | + mode={brandMultiple ? "multiple" : undefined} | |
326 | + placeholder="选择品牌" | |
327 | + > | |
328 | + {brandData.map((item) => ( | |
329 | + <Option value={item.id} key={item.id}> | |
330 | + {item.name} | |
331 | + </Option> | |
332 | + ))} | |
333 | + </Select> | |
334 | + </Form.Item> | |
335 | + ) : null} | |
336 | + | |
337 | + {/* 车系 */} | |
338 | + {level === 2 && ( | |
339 | + <Form.Item | |
340 | + label="车系" | |
341 | + name="series" | |
342 | + // rules={[{ required: true, message: "请选择车系" }]} | |
343 | + > | |
344 | + <Select labelInValue mode="multiple" placeholder="选择车系" allowClear> | |
345 | + {series.map((item: any) => ( | |
346 | + <Option value={item.id} key={item.id}> | |
347 | + {item.name} | |
348 | + </Option> | |
349 | + ))} | |
350 | + </Select> | |
351 | + </Form.Item> | |
352 | + )} | |
353 | + {level === 3 ? ( | |
354 | + <Form.Item | |
355 | + label="" | |
356 | + name="spec" | |
357 | + > | |
358 | + {/* <Select labelInValue mode="multiple" placeholder="选择车型" allowClear> | |
359 | + {specList.map((item: any) => ( | |
360 | + <Option value={item.id} key={item.id}> | |
361 | + {item.name} | |
362 | + </Option> | |
363 | + ))} | |
364 | + </Select> */} | |
365 | + <SelectSpec specList={specList} /> | |
366 | + </Form.Item> | |
367 | + ) : null} | |
368 | + </Form> | |
369 | + </Spin> | |
370 | + </Modal> | |
371 | + </> | |
372 | + ); | |
373 | +} | ... | ... |
src/pages/crm_new/CluesConnectTargetEffectively/api.ts
0 → 100644
1 | +import request from '@/utils/request'; | |
2 | +import { CRM_HOST } from '@/utils/host'; | |
3 | + | |
4 | +interface RequestParams { | |
5 | + keywords?: string | |
6 | +} | |
7 | + | |
8 | +export interface Result { | |
9 | + id?: number // 配置id | |
10 | + dialAims?: number // 线索接通目标 | |
11 | + displayName?: string // 显示名称 | |
12 | + shopList?: ShopList[] // 门店列表 | |
13 | +} | |
14 | + | |
15 | +export interface ShopList { | |
16 | + shopId?: number // 门店id | |
17 | + shopName?: string // 门店名称 | |
18 | +} | |
19 | + | |
20 | +/** 查询线索拨通目标配置列表 */ | |
21 | +export function getConfigApi(params: RequestParams) { | |
22 | + return request.get<Result[]>(`${CRM_HOST}/erp/clue/dial/aims/config/list`, {params}); | |
23 | +} | |
24 | + | |
25 | +/** 查询线索拨通目标已配置的门店 */ | |
26 | +export function getHaveShopListApi() { | |
27 | + return request.get<number[]>(`${CRM_HOST}/erp/clue/dial/aims/config/already/exists/shopIds`); | |
28 | +} | |
29 | + | |
30 | +/** 保存线索拨通目标配置 */ | |
31 | +export function saveConfigApi(params: Result) { | |
32 | + return request.post<Result>(`${CRM_HOST}/erp/clue/dial/aims/config/save`, params); | |
33 | +} | |
34 | + | |
35 | +/** 删除线索拨通目标配置 */ | |
36 | +export function deleteConfigApi(id?: number) { | |
37 | + return request.post<Result>(`${CRM_HOST}/erp/clue/dial/aims/config/delete`, {id}, {contentType: 'form-urlencoded'}); | |
38 | +} | |
0 | 39 | \ 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 | 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 | 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({keywords: value[0]?.label || 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="space-between" 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 | 32 | \ No newline at end of file | ... | ... |