index.jsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. import ContactDetail from '@/pages/Customer/Client/components/ClientDetail'
  2. import CompanyDetail from '@/pages/Customer/Company/components/CompanyDetail'
  3. import AddContact from '@/pages/Customer/Client/components/AddClient'
  4. import ProTable from '@ant-design/pro-table'
  5. import { Input, Button, message, Tooltip, Select } from 'antd'
  6. import { connect, useModel } from 'umi'
  7. import React, { useEffect, useState, useRef, useMemo } from 'react'
  8. import consts from '@/consts'
  9. import LazyCascader from '@/components/LazyCascader'
  10. import { useModal } from '@/components/Modal'
  11. import { formatValues } from '@/components/LazyCascader'
  12. import PersonLabel from '../Company/components/PersonLabel'
  13. import {
  14. TagTypeEnum,
  15. TagDataTypeEnum,
  16. LabelModeType
  17. } from '../Company/components/PersonLabel/const'
  18. import { Add, Down, Check } from '@icon-park/react'
  19. import { useTableScroll } from '@/hooks/useAutoTable'
  20. import { apiAddClient, queryClient } from '@/services/customer'
  21. import { apiBatchLink } from '@/services/customer'
  22. import styles from './index.less'
  23. import { refactorSortField } from '@/utils/utils'
  24. const Client = props => {
  25. const { toggleDrawer, setDrawerProps } = useModal()
  26. const {
  27. personTagColorMap,
  28. teamTagColorMap,
  29. personTags,
  30. teamTags,
  31. dispatch,
  32. shouldUpdate,
  33. loading
  34. } = props
  35. const { initialState: { permData } = { permData: {} } } = useModel('@@initialState')
  36. const tRef = useRef()
  37. const [tagState, setTagState] = useState({
  38. tagIds: [],
  39. tagCooperationIds: []
  40. })
  41. const tag = useMemo(() => {
  42. const personTagMap = {}
  43. const teamTagMap = {}
  44. const personOptions = []
  45. const teamOptions = []
  46. personTags.forEach(item => {
  47. // personOptions.push({ label: item.name, value: item.id })
  48. personTagMap[item.id] = {
  49. text: (
  50. <>
  51. <span
  52. className="w-12px h-12px inline-block rounded-1/2 mr-3px"
  53. style={{ backgroundColor: personTagColorMap[item.id] }}
  54. />
  55. <span>{item.name}</span>
  56. </>
  57. )
  58. }
  59. })
  60. teamTags.forEach(item => {
  61. // teamOptions.push({ label: item.name, value: item.id })
  62. teamTagMap[item.id] = {
  63. text: (
  64. <>
  65. <span
  66. className="w-12px h-12px inline-block mr-3px"
  67. style={{ backgroundColor: teamTagColorMap[item.id] }}
  68. />
  69. <span>{item.name}</span>
  70. </>
  71. )
  72. }
  73. })
  74. return { personTagMap, teamTagMap, personOptions, teamOptions }
  75. }, [loading])
  76. const toolbarOptionsChange = (type, ids) => {
  77. setTagState({ ...tagState, [type]: ids })
  78. }
  79. const [selectKeys, setSelectKeys] = useState([])
  80. const hanleRowSelectChange = selectedRowKeys => {
  81. setSelectKeys(selectedRowKeys)
  82. }
  83. const [state, setState] = useState({
  84. params: { dataPermission: 'oneself' },
  85. id: '',
  86. orderIds: []
  87. })
  88. // 重置到默认值,包括表单
  89. const initData = () => {
  90. tRef.current.reset()
  91. }
  92. // 刷新
  93. const refreshData = () => {
  94. tRef.current.reload()
  95. }
  96. const handleAddClient = async values => {
  97. const payload = formatValues(values)
  98. const { code = -1, msg = '新增失败' } = await apiAddClient(payload)
  99. if (code === consts.RET_CODE.SUCCESS) {
  100. message.success(msg)
  101. initData()
  102. }
  103. }
  104. // 展示drawer
  105. const showDrawer = id => {
  106. setDrawerProps({
  107. closable: true,
  108. onClose: () => toggleDrawer(),
  109. bodyStyle: {
  110. height: '100vh',
  111. overflowY: 'hidden'
  112. },
  113. children: <ContactDetail dataId={id} orderIds={state.orderIds} />
  114. })
  115. toggleDrawer()
  116. }
  117. // 展示客户
  118. const showDrawerComapny = id => {
  119. setDrawerProps({
  120. closable: true,
  121. onClose: () => toggleDrawer(),
  122. bodyStyle: {
  123. height: '100vh',
  124. overflowY: 'hidden'
  125. },
  126. children: <CompanyDetail dataId={id} orderIds={state.orderIds} />
  127. })
  128. toggleDrawer()
  129. }
  130. const columns = [
  131. {
  132. title: '关键字',
  133. dataIndex: 'search',
  134. hideInTable: true,
  135. search: false
  136. },
  137. {
  138. title: '联系人',
  139. dataIndex: 'clientName',
  140. key: 'clientName',
  141. width: 50,
  142. fixed: 'left',
  143. render: (clientName, record) => (
  144. <span
  145. onClick={() => showDrawer(record.id)}
  146. className="text-primary cursor-pointer hover:text-[#967bbd]">
  147. {clientName}
  148. </span>
  149. ),
  150. sorter: (a, b) => a.clientName.localeCompare(b.clientName),
  151. search: false
  152. },
  153. {
  154. title: '客户名称',
  155. dataIndex: 'companyName',
  156. key: 'companyName',
  157. width: 150,
  158. ellipsis: true,
  159. render: (companyName, record) => (
  160. <span onClick={() => showDrawerComapny(record.companyId)} className={styles.textPurple}>
  161. {companyName}
  162. </span>
  163. ),
  164. sorter: (a, b) => a.companyName.localeCompare(b.companyName),
  165. search: false
  166. },
  167. {
  168. title: '部门',
  169. dataIndex: 'department',
  170. key: 'department',
  171. ellipsis: true,
  172. width: 75,
  173. search: false
  174. },
  175. // {
  176. // title: '电话',
  177. // dataIndex: 'phone',
  178. // key: 'phone',
  179. // width: 150
  180. // },
  181. {
  182. title: '手机',
  183. dataIndex: 'telephone',
  184. key: 'telephone',
  185. width: 75,
  186. search: false
  187. },
  188. {
  189. title: 'QQ',
  190. dataIndex: 'qq',
  191. key: 'qq',
  192. width: 75,
  193. search: false
  194. },
  195. {
  196. title: '邮箱',
  197. dataIndex: 'email',
  198. key: 'email',
  199. width: 75,
  200. search: false
  201. },
  202. {
  203. title: '职务',
  204. dataIndex: 'position',
  205. key: 'position',
  206. width: 75,
  207. search: false
  208. },
  209. {
  210. title: '办公室',
  211. dataIndex: 'office',
  212. key: 'office',
  213. width: 75,
  214. ellipsis: true,
  215. search: false
  216. },
  217. {
  218. title: '地区',
  219. dataIndex: 'districtName',
  220. key: 'districtName',
  221. width: 75,
  222. search: false
  223. },
  224. {
  225. title: '个人标签',
  226. dataIndex: 'tagIds',
  227. key: 'tagIds',
  228. width: 60,
  229. filters: true,
  230. search: false,
  231. // onFilter: true,
  232. valueEnum: tag.personTagMap,
  233. // valueType: 'select',
  234. // fieldProps: {
  235. // mode: 'multiple',
  236. // options: tag.personOptions,
  237. // showArrow: true
  238. // },
  239. render: (_, record) => (
  240. <div className="flex items-center flex-wrap">
  241. {record.tagIds.map(item => (
  242. <Tooltip title={personTags.find(i => i.id === item)?.name} key={record.id + item}>
  243. <span
  244. className="inline-block w-12px h-12px rounded-1/2 mr-3px"
  245. style={{ backgroundColor: personTagColorMap[item] }}
  246. />
  247. </Tooltip>
  248. ))}
  249. <PersonLabel
  250. dataId={record.id}
  251. tagIds={record.tagIds}
  252. tagType={TagTypeEnum.PERSONTAG}
  253. tagColumn={TagDataTypeEnum.CLIENT}
  254. checkCallBack={refreshData}
  255. modeType={LabelModeType.column}>
  256. <Add theme="filled" size="20" fill="#868e96" className="cursor-pointer" />
  257. </PersonLabel>
  258. </div>
  259. )
  260. },
  261. {
  262. title: '协作标签',
  263. dataIndex: 'tagCooperationIds',
  264. key: 'tagCooperationIds',
  265. width: 60,
  266. filters: true,
  267. search: false,
  268. valueEnum: tag.teamTagMap,
  269. // valueType: 'select',
  270. // fieldProps: {
  271. // mode: 'multiple',
  272. // options: tag.teamOptions,
  273. // showArrow: true
  274. // },
  275. render: (_, record) => (
  276. <div className="flex items-center flex-wrap">
  277. {record.tagCooperationIds.map(item => (
  278. <Tooltip title={teamTags.find(i => i.id === item)?.name} key={record.id + item}>
  279. <span
  280. key={record.id + item}
  281. className="inline-block w-12px h-12px mr-3px"
  282. style={{ backgroundColor: teamTagColorMap[item] }}
  283. />
  284. </Tooltip>
  285. ))}
  286. <PersonLabel
  287. dataId={record.id}
  288. tagIds={record.tagCooperationIds}
  289. tagType={TagTypeEnum.TEAMTAG}
  290. tagColumn={TagDataTypeEnum.CLIENT}
  291. checkCallBack={refreshData}
  292. modeType={LabelModeType.column}>
  293. <Add theme="filled" size="20" fill="#868e96" className="cursor-pointer" />
  294. </PersonLabel>
  295. </div>
  296. )
  297. },
  298. {
  299. title: '地址',
  300. dataIndex: 'address',
  301. key: 'address',
  302. ellipsis: true,
  303. width: 50,
  304. search: false
  305. },
  306. {
  307. title: '乘车',
  308. dataIndex: 'ride',
  309. key: 'ride',
  310. ellipsis: true,
  311. width: 50,
  312. search: false
  313. },
  314. {
  315. title: '地标',
  316. dataIndex: 'landmarks',
  317. key: 'landmarks',
  318. ellipsis: true,
  319. width: 50,
  320. search: false
  321. },
  322. {
  323. title: '住宿',
  324. dataIndex: 'stay',
  325. key: 'stay',
  326. ellipsis: true,
  327. width: 50,
  328. search: false
  329. },
  330. {
  331. title: '备注',
  332. dataIndex: 'mark',
  333. key: 'mark',
  334. ellipsis: true,
  335. width: 50,
  336. search: false
  337. },
  338. // {
  339. // title: '软件锁',
  340. // dataIndex: 'keynum',
  341. // key: 'keynum',
  342. // width: 50,
  343. // render: keynum => <a>{keynum}</a>,
  344. // search: false
  345. // },
  346. {
  347. title: '创建人',
  348. dataIndex: 'staffName',
  349. key: 'staffName',
  350. width: 50,
  351. search: false
  352. }
  353. ]
  354. const [columnsStateMap, setColumnsStateMap] = useState({
  355. phone: {
  356. show: false
  357. },
  358. qq: {
  359. show: false
  360. },
  361. position: {
  362. show: false
  363. },
  364. office: {
  365. show: false
  366. },
  367. local: {
  368. show: false
  369. },
  370. stay: {
  371. show: false
  372. },
  373. keynum: {
  374. show: false
  375. },
  376. mark: {
  377. show: false
  378. },
  379. ride: {
  380. show: false
  381. },
  382. landmarks: {
  383. show: false
  384. },
  385. address: {
  386. show: false
  387. },
  388. staffName: {
  389. show: false
  390. }
  391. })
  392. const { getScrollRef, redoHeight } = useTableScroll(columns, selectKeys)
  393. // 默认刷新一次列表请求数据
  394. useEffect(() => {
  395. // initData()
  396. if (!personTags.length) {
  397. dispatch({
  398. type: 'client/fetchTags',
  399. payload: { tagType: TagTypeEnum.PERSONTAG, tagColumn: TagDataTypeEnum.CLIENT }
  400. })
  401. }
  402. if (!teamTags.length) {
  403. dispatch({
  404. type: 'client/fetchTags',
  405. payload: { tagType: TagTypeEnum.TEAMTAG, tagColumn: TagDataTypeEnum.CLIENT }
  406. })
  407. }
  408. if (shouldUpdate) {
  409. tRef.current.reload()
  410. }
  411. }, [shouldUpdate])
  412. const handleSearch = value => {
  413. tRef.current.reset()
  414. setState({ ...state, params: { ...state.params, search: value } })
  415. }
  416. const onDistrictChange = value => {
  417. tRef.current.reset()
  418. const [province = '', city = '', area = ''] = value
  419. setState({ ...state, params: { ...state.params, province, city, area } })
  420. }
  421. // 批量设置-确认触发
  422. const tagsSettingConfirm = async clientIds => {
  423. if (tagState.tagIds.length) {
  424. await apiBatchLink({
  425. tagIds: tagState.tagIds,
  426. dataIds: clientIds,
  427. dataType: TagDataTypeEnum.CLIENT,
  428. tagType: TagTypeEnum.PERSONTAG
  429. })
  430. }
  431. if (tagState.tagCooperationIds.length) {
  432. await apiBatchLink({
  433. tagIds: tagState.tagCooperationIds,
  434. dataIds: clientIds,
  435. dataType: TagDataTypeEnum.CLIENT,
  436. tagType: TagTypeEnum.TEAMTAG
  437. })
  438. }
  439. refreshData()
  440. }
  441. const renderTableAlert = ({ selectedRowKeys }) => {
  442. return (
  443. <div className="flex flex-row">
  444. <div className="mr-5px">
  445. <PersonLabel
  446. tagType={TagTypeEnum.PERSONTAG}
  447. tagColumn={TagDataTypeEnum.CLIENT}
  448. checkCallBack={toolbarOptionsChange}
  449. modeType={LabelModeType.toolbar}>
  450. {tagState.tagIds.length ? (
  451. <Button>
  452. {tagState.tagIds.map(item => (
  453. <span
  454. key={item}
  455. className="w-12px h-12px inline-block rounded-1/2 mr-3px"
  456. style={{ backgroundColor: personTagColorMap[item] }}
  457. />
  458. ))}
  459. <Down />
  460. </Button>
  461. ) : (
  462. <Button>
  463. 个人标签
  464. <Down />
  465. </Button>
  466. )}
  467. </PersonLabel>
  468. </div>
  469. <div className="mr-5">
  470. <PersonLabel
  471. tagType={TagTypeEnum.TEAMTAG}
  472. tagColumn={TagDataTypeEnum.CLIENT}
  473. checkCallBack={toolbarOptionsChange}
  474. modeType={LabelModeType.toolbar}>
  475. {tagState.tagCooperationIds.length ? (
  476. <Button>
  477. {tagState.tagCooperationIds.map(item => (
  478. <span
  479. key={item}
  480. className="w-12px h-12px inline-block mr-3px"
  481. style={{ backgroundColor: teamTagColorMap[item] }}
  482. />
  483. ))}
  484. <Down />
  485. </Button>
  486. ) : (
  487. <Button>
  488. 协作标签
  489. <Down />
  490. </Button>
  491. )}
  492. </PersonLabel>
  493. </div>
  494. <Button onClick={() => tagsSettingConfirm(selectedRowKeys)}>
  495. <Check size={14} />
  496. 确认
  497. </Button>
  498. </div>
  499. )
  500. }
  501. return (
  502. <div className={styles.contactContent}>
  503. <ProTable
  504. bordered
  505. params={state.params}
  506. actionRef={tRef}
  507. rowKey={record => record.id}
  508. columns={columns}
  509. request={async (params, sort, filter) => {
  510. const srotOrder = refactorSortField(sort)
  511. const {
  512. code = -1,
  513. data: { client = [], total = 0 }
  514. } = await queryClient({ ...params, ...srotOrder, ...filter })
  515. setState({ ...state, orderIds: client.map(item => item.id) })
  516. return {
  517. data: client,
  518. success: code === consts.RET_CODE.SUCCESS,
  519. total
  520. }
  521. }}
  522. tableAlertRender={renderTableAlert}
  523. rowSelection={{
  524. onChange: hanleRowSelectChange,
  525. type: 'checkbox',
  526. columnWidth: 25
  527. }}
  528. // search={false}
  529. search={{ filterType: 'light' }}
  530. toolbar={{
  531. search: (
  532. <Tooltip placement="bottom" title="输入联系人、客户名称、电话邮箱等">
  533. <Input.Search
  534. style={{ width: '300px' }}
  535. allowClear={true}
  536. placeholder={`在${
  537. permData?.customer?.find(item => item.value === state.params.dataPermission)
  538. ?.label
  539. }下搜索`}
  540. onSearch={handleSearch}
  541. />
  542. </Tooltip>
  543. ),
  544. // search: {
  545. // allowClear: true,
  546. // onSearch: handleSearch,
  547. // placeholder: '输入联系人、客户名称、电话邮箱等'
  548. // },
  549. actions: [
  550. <LazyCascader
  551. key="lazyCascader"
  552. onChange={onDistrictChange}
  553. changeOnSelect={true}
  554. bordered={false}
  555. className="hover:bg-[rgba(0,0,0,0.1)]"
  556. />,
  557. <AddContact onConfirm={handleAddClient} key="addContactBtn" />
  558. ]
  559. }}
  560. headerTitle={
  561. <Select
  562. defaultValue="oneself"
  563. dropdownMatchSelectWidth={false}
  564. onChange={e => {
  565. setState({ ...state, params: { ...state.params, dataPermission: e } })
  566. }}
  567. options={permData?.customer || []}
  568. />
  569. }
  570. onLoadingChange={loadingStatus => {
  571. loadingStatus && redoHeight()
  572. }}
  573. scroll={getScrollRef}
  574. columnsStateMap={columnsStateMap}
  575. onColumnsStateChange={map => setColumnsStateMap(map)}
  576. />
  577. </div>
  578. )
  579. }
  580. export default connect(({ client, refresh, loading }) => ({
  581. personTagColorMap: client.personTagColorMap,
  582. personTags: client.personTags,
  583. teamTagColorMap: client.teamTagColorMap,
  584. teamTags: client.teamTags,
  585. loading: loading.models.client,
  586. shouldUpdate: refresh.client
  587. }))(Client)