index.jsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import { Button, Checkbox, Popover, Input, message, Spin } from 'antd'
  2. import React, { useState, useRef, useEffect } from 'react'
  3. import { connect } from 'dva'
  4. import styles from './index.scss'
  5. import { apiConnOrDelTag, apiUpdateTag } from '@/services/customer'
  6. import consts from '@/basic/consts'
  7. const DropdownTypeEnum = {
  8. check: 0,
  9. edit: 1
  10. }
  11. // 数据类型
  12. export const TagDataTypeEnum = {
  13. CONTACT: 1, // 联系人
  14. COMPANY: 2 // 客户
  15. }
  16. // 标签类型
  17. export const TagTypeEnum = {
  18. PERSONTAG: 0, // 个人
  19. TEAMTAG: 1 // 协作
  20. }
  21. export const LabelModeType = {
  22. column: 0, // 列操作
  23. toolbar: 1 // 常用于表格批量操作
  24. }
  25. export const LabelStatusColorMap = {
  26. [TagTypeEnum.PERSONTAG]: [
  27. '#16A085',
  28. '#2980B9',
  29. '#8E44AD',
  30. '#f90000',
  31. '#B8651B',
  32. '#2C3E50',
  33. '#efd200',
  34. '#f47300'
  35. ],
  36. [TagTypeEnum.TEAMTAG]: [
  37. '#F8AC59',
  38. '#FF69B4',
  39. '#999999',
  40. '#7186ab',
  41. '#778b72',
  42. '#c292ca',
  43. '#a14751',
  44. '#57ECCC'
  45. ]
  46. }
  47. const PersonLabel = props => {
  48. const {
  49. children,
  50. dataId,
  51. tagIds = [],
  52. tagType,
  53. tagColumn,
  54. modeType = LabelModeType.column,
  55. checkCallBack,
  56. contactPersonTags,
  57. companyPersonTags,
  58. dispatch
  59. } = props
  60. const [dropdownType, setDropdownType] = useState(DropdownTypeEnum.check)
  61. const [checkedIds, setCheckedIds] = useState([])
  62. useEffect(() => {
  63. if (modeType === LabelModeType.column && tagIds.length) {
  64. setCheckedIds(tagIds)
  65. }
  66. }, [tagIds])
  67. const [showOkBtn, setShowOkBtn] = useState(false)
  68. const initData = () => {
  69. dispatch({
  70. type: `${tagColumn === TagDataTypeEnum.COMPANY ? 'contact' : 'company'}/fetchTags`,
  71. payload: { tagType, tagColumn }
  72. })
  73. }
  74. // 列操作->更新个人标签勾选情况
  75. const handleOnConfirm = async () => {
  76. const payload = {
  77. tagIds: checkedIds,
  78. dataId,
  79. dataType: tagColumn,
  80. tagType
  81. }
  82. const { code = -1 } = await apiConnOrDelTag(payload)
  83. if (code === consts.RET_CODE.SUCCESS) {
  84. setShowOkBtn(false)
  85. }
  86. }
  87. const menu = (
  88. <div className="zh-mg-tb-7 zh-mg-lf-14">
  89. <DropDownMenu
  90. checkedIds={checkedIds}
  91. setCheckedIds={setCheckedIds}
  92. dataId={dataId}
  93. personTags={tagColumn === TagDataTypeEnum.CONTACT ? contactPersonTags : companyPersonTags}
  94. menuType={dropdownType}
  95. tagType={tagType}
  96. tagColumn={tagColumn}
  97. modeType={modeType}
  98. checkCallBack={checkCallBack}
  99. initCallBack={initData}
  100. />
  101. <div className="zh-mg-top-5">
  102. {dropdownType === DropdownTypeEnum.check ? (
  103. <div className="zh-justify-between">
  104. <span
  105. onClick={() => setDropdownType(DropdownTypeEnum.edit)}
  106. className="zh-primary zh-pointer">
  107. 编辑标签
  108. </span>
  109. {modeType === LabelModeType.column && showOkBtn ? (
  110. <Button size="small" onClick={() => handleOnConfirm()}>
  111. 确认
  112. </Button>
  113. ) : null}
  114. </div>
  115. ) : (
  116. <>
  117. <Button onClick={() => setDropdownType(DropdownTypeEnum.check)}>返回</Button>
  118. </>
  119. )}
  120. </div>
  121. </div>
  122. )
  123. return (
  124. <Popover content={menu} trigger="click" className={styles.dropdownMenu}>
  125. {children}
  126. </Popover>
  127. )
  128. }
  129. const DropDownMenu = props => {
  130. // 保存选中的checkbox ids
  131. const {
  132. checkedIds,
  133. setCheckedIds,
  134. dataId,
  135. menuType,
  136. tagType,
  137. tagColumn,
  138. personTags,
  139. modeType,
  140. checkCallBack,
  141. initCallBack
  142. } = props
  143. const [activeState, setActiveState] = useState({
  144. id: '',
  145. activeId: '',
  146. loading: false
  147. })
  148. const inputRef = useRef(null)
  149. const handleOnchange = async (e, id) => {
  150. const { checked } = e.target
  151. let canRender = true
  152. const ids = checked ? [...checkedIds, id] : checkedIds.filter(item => item !== id)
  153. if (modeType === LabelModeType.column) {
  154. const { code = -1, msg = '操作失败' } = await apiConnOrDelTag({
  155. type: checked,
  156. payload: { dataId, tagId: id, dataType: tagColumn, tagType }
  157. })
  158. if (code !== consts.RET_CODE.SUCCESS) {
  159. canRender = false
  160. message.error(msg)
  161. }
  162. checkCallBack()
  163. } else {
  164. checkCallBack &&
  165. checkCallBack(tagType === TagTypeEnum.PERSONTAG ? 'tagIds' : 'tagCooperationIds', ids)
  166. }
  167. if (canRender) {
  168. setCheckedIds(ids)
  169. }
  170. }
  171. const labelOnClick = id => {
  172. setActiveState({ ...activeState, id, activeId: id })
  173. }
  174. useEffect(() => {
  175. activeState.id && inputRef.current?.focus()
  176. }, [activeState.id])
  177. const labelTextChange = async (e, oldVal) => {
  178. const { value: newVal = '' } = e.currentTarget
  179. if (newVal && oldVal !== newVal) {
  180. const { code = -1 } = await apiUpdateTag({ name: newVal, id: activeState.id })
  181. if (code === consts.RET_CODE.SUCCESS) {
  182. setActiveState({ ...activeState, loading: true })
  183. initCallBack()
  184. const delayUpdate = () => {
  185. setTimeout(() => {
  186. setActiveState({ ...activeState, activeId: '', loading: false })
  187. message.success('标签已更新')
  188. }, 500)
  189. }
  190. await delayUpdate()
  191. }
  192. }
  193. }
  194. return (
  195. <ul className={styles.labelContent}>
  196. {menuType === DropdownTypeEnum.check
  197. ? personTags.map((item, idx) => (
  198. <li key={item.id} className={styles.menuItem}>
  199. <div className={styles.labelText}>
  200. <div
  201. className={[
  202. styles.statusIcon,
  203. tagType === TagTypeEnum.PERSONTAG ? styles.suqare : ''
  204. ].join(' ')}
  205. style={{ backgroundColor: LabelStatusColorMap[tagType][idx] }}
  206. />
  207. <span>{item.name}</span>
  208. </div>
  209. <div className={styles.checkSpan}>
  210. <Checkbox
  211. checked={checkedIds.includes(item.id)}
  212. onChange={e => handleOnchange(e, item.id)}
  213. />
  214. </div>
  215. </li>
  216. ))
  217. : null}
  218. {menuType === DropdownTypeEnum.edit
  219. ? personTags.map((item, idx) => (
  220. <Spin
  221. key={item.id}
  222. spinning={item.id === activeState.id ? activeState.loading : false}
  223. wrapperClassName={[
  224. styles.menuItem,
  225. styles.editMenuItem,
  226. item.id === activeState.activeId ? styles.active : ''
  227. ].join(' ')}>
  228. <li onClick={() => labelOnClick(item.id)} style={{ width: '100%' }}>
  229. <div className={styles.editLabel}>
  230. <div
  231. className={[
  232. styles.statusIcon,
  233. tagType === TagTypeEnum.PERSONTAG ? styles.suqare : ''
  234. ].join(' ')}
  235. style={{ backgroundColor: LabelStatusColorMap[tagType][idx] }}
  236. />
  237. {activeState.activeId === item.id ? (
  238. <Input
  239. ref={inputRef}
  240. size="small"
  241. bordered={false}
  242. defaultValue={item.name}
  243. onBlur={e => labelTextChange(e, item.name)}
  244. onPressEnter={e => labelTextChange(e, item.name)}
  245. />
  246. ) : (
  247. <span>{item.name}</span>
  248. )}
  249. </div>
  250. </li>
  251. </Spin>
  252. ))
  253. : null}
  254. </ul>
  255. )
  256. }
  257. export default connect(({ contact, company }) => ({
  258. contactPersonTags: contact.personTags,
  259. companyPersonTags: company.personTags
  260. }))(PersonLabel)