index.jsx 16 KB

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