index.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { CloseOutlined, ColumnWidthOutlined, SearchOutlined } from "@ant-design/icons"
  2. import { Button, Checkbox, Input, InputNumber, message, Modal, Popover, Space, Spin, Tooltip } from "antd"
  3. import React, { useCallback, useEffect, useState } from "react"
  4. import './index.less'
  5. import { ReactComponent as DrawStem } from '@/assets/drawStem.svg'
  6. import { SortableContainer, SortableElement, SortableHandle } from "react-sortable-hoc";
  7. import arrayMove from "array-move";
  8. const DragHandle: any = SortableHandle(() => <DrawStem className='draw' />);
  9. const SortableItem: any = SortableElement(({ item, del, setConfig }: any) => {
  10. const [visible, setVisible] = useState<boolean>(false)
  11. const [width, setWidth] = useState<number>(item?.width || 0)
  12. return <li className='liDraw'>
  13. <div className='lileft'>
  14. <DragHandle />
  15. <div>{item?.title}</div>
  16. </div>
  17. <Space style={{ float: 'right', marginRight: 10 }}>
  18. <Popover
  19. content={
  20. <div>
  21. <InputNumber min={0} max={500} onChange={(value) => {
  22. if (value) {
  23. setWidth(value)
  24. }
  25. }} value={width} size='small' />
  26. <Button type='primary' size='small' onClick={() => {
  27. setConfig({ ...item, width })
  28. setVisible(false)
  29. }}>OK</Button>
  30. </div>
  31. }
  32. trigger="click"
  33. open={visible}
  34. placement='left'
  35. onOpenChange={(visible) => { setVisible(visible) }}
  36. >
  37. <Tooltip title='宽度设置'><a><ColumnWidthOutlined /></a></Tooltip>
  38. </Popover>
  39. </Space>
  40. {item?.label !== 'pos_type' && <Tooltip title='删除'><div className="clear" onClick={() => { del() }}><a><CloseOutlined /></a></div></Tooltip>}
  41. </li>
  42. });
  43. /** 外层 */
  44. const SortableList: any = SortableContainer(({ children }: { children: any }) => (<ul className='selectedList'>{children}</ul>));
  45. interface dataProps {
  46. title: string,
  47. label: string,
  48. dataIndex: string,
  49. disabled: boolean
  50. }
  51. interface DataProps {
  52. label: string,
  53. data: dataProps[],
  54. fieldSHow?: {
  55. label: string,
  56. saveField: string,
  57. defaultValue: any[],
  58. data: any[]
  59. },
  60. bcColor?: string
  61. }
  62. /**
  63. * 自定义列
  64. */
  65. type customProps = {
  66. visible?: boolean,
  67. onChange?: (data: any) => void,
  68. onClose?: () => void,
  69. config: any[],//配置表
  70. configName: string,//配置表名称
  71. columns: any[],//配置菜单
  72. version: string, // 版本号
  73. sysFixed?: {
  74. left: number,
  75. right: number
  76. }
  77. }
  78. function CustomListModel(props: customProps) {
  79. const { visible, onChange, onClose, config, configName, columns, version, sysFixed = { left: 0, right: 1 } } = props
  80. // dataA 总数据
  81. // 数据开始
  82. const [dataA, setDataA] = useState<DataProps[]>(config)//备份原始数据
  83. const [data, setData] = useState<DataProps[]>(config)//变动的数据
  84. const [search, setSearch] = useState<string>('') // 搜索字段
  85. const [selectData, setSelectData] = useState<dataProps[]>([])//选择的数据
  86. const [serverData, setServerData] = useState<dataProps[]>([])//备份的数据
  87. const [fieldData, setFieldData] = useState<any>({})
  88. const [fixed, setFixed] = useState<{ left: number, right: number }>(localStorage.getItem(`myAdMonitorConfigFixed${version}_` + configName) ? JSON.parse(localStorage.getItem(`myAdMonitorConfigFixed${version}_` + configName) as any) : { left: sysFixed.left, right: sysFixed.right })
  89. // 数据结束
  90. // 处理表格里列字段展示
  91. useEffect(() => {
  92. let mySelectFieldData = localStorage.getItem(`myAdFieldConfig${version}_` + configName)
  93. let newSelectFieldData: any = {}
  94. if (mySelectFieldData) {
  95. newSelectFieldData = JSON.parse(mySelectFieldData)
  96. }
  97. data?.forEach((item) => {
  98. if (item?.fieldSHow) {
  99. const { saveField, defaultValue } = item?.fieldSHow
  100. if (!newSelectFieldData[saveField]) {
  101. newSelectFieldData[saveField] = defaultValue
  102. }
  103. }
  104. })
  105. if (Object.keys(newSelectFieldData).length > 0) {
  106. setFieldData({ ...newSelectFieldData })
  107. }
  108. }, [data])
  109. // 获取数据
  110. useEffect(() => {
  111. getUserList()
  112. }, [configName])
  113. // 获取个人配置
  114. const getUserList = () => {
  115. let mySelectData = localStorage.getItem(`myAdMonitorConfig${version}_` + configName)
  116. if (mySelectData) {//获取自定义配置
  117. let newMySelectData = JSON.parse(mySelectData)
  118. newMySelectData = newMySelectData.filter((item: any) => item && columns.some(c => c.dataIndex === item.dataIndex))//去除空项,并去除多余不存在的
  119. setSelectData(newMySelectData)
  120. setServerData(newMySelectData)
  121. setListWidth(newMySelectData)
  122. localStorage.setItem(`myAdMonitorConfig${version}_` + configName, JSON.stringify(newMySelectData))//重新存下本地,避免更新后数据不对
  123. } else {//使用默认配置
  124. let newSelectData: any[] = []
  125. config?.forEach((item) => {
  126. item?.data?.forEach((d: { default: any }) => {
  127. if (d.default) {
  128. newSelectData[d.default - 1] = d
  129. }
  130. })
  131. })
  132. setSelectData(newSelectData)
  133. setServerData(newSelectData)
  134. setListWidth(newSelectData)
  135. }
  136. }
  137. // 首次赋值默认宽
  138. const setListWidth = useCallback((selectData) => {
  139. if (selectData?.length === columns?.length && !selectData?.every((item: any) => item.width)) {
  140. let newSelectData: any[] = []
  141. newSelectData = selectData?.map((item: { [x: string]: any }, index: string | number) => {
  142. item['width'] = columns[index as any]['width']
  143. return item
  144. })
  145. setSelectData(newSelectData)
  146. setServerData(newSelectData)
  147. }
  148. }, [columns])
  149. //首次赋值默认宽
  150. // useEffect(() => {
  151. // console.log('首次赋值默认宽====>',selectData,columns)
  152. // if (selectData?.length === columns?.length && !selectData?.every((item: any) => item.width)) {
  153. // let newSelectData: any[] = []
  154. // newSelectData = selectData?.map((item, index) => {
  155. // item['width'] = columns[index]['width']
  156. // return item
  157. // })
  158. // setSelectData(newSelectData)
  159. // setServerData(newSelectData)
  160. // }
  161. // }, [selectData,columns])
  162. // 搜索 过滤
  163. const searchHandle = useCallback((value: string) => {
  164. setSearch(value)
  165. if (search) {
  166. let newData1: any[] = []
  167. dataA.forEach((item: DataProps) => {
  168. let newData = item.data.filter((listItem: dataProps) => {
  169. if (listItem?.title?.includes(value)) {
  170. return listItem
  171. } else {
  172. return null
  173. }
  174. })
  175. if (newData?.length > 0) {
  176. newData1.push({ ...item, data: newData })
  177. }
  178. })
  179. setData([...newData1])
  180. } else {
  181. setData(dataA)
  182. }
  183. }, [data, dataA, search])
  184. // 选中数据
  185. const selectHandle = useCallback((checked: boolean, data: dataProps) => {
  186. if (checked) {
  187. setSelectData([...selectData, { ...data }])
  188. } else {
  189. let newSelectData = selectData.filter((item: dataProps) => {
  190. if (item.dataIndex === data.dataIndex) {
  191. return null
  192. } else {
  193. return item
  194. }
  195. })
  196. setSelectData(newSelectData)
  197. }
  198. }, [selectData])
  199. // 每个大项 全选
  200. const checkAllHandle = useCallback((checked: boolean, value: string) => {
  201. let oldData = data.find((item: DataProps) => item.label === value)
  202. console.log(oldData)
  203. if (oldData) {
  204. if (checked) {
  205. let newSelectData: any[] = selectData?.filter(item => item)
  206. oldData.data.forEach((item: dataProps) => {
  207. if (!item.disabled && selectData.findIndex((selsectItem: dataProps) => selsectItem?.dataIndex === item?.dataIndex) === -1) {
  208. newSelectData.push(item)
  209. }
  210. })
  211. setSelectData([...newSelectData])
  212. } else {
  213. let newSelectData = selectData.filter((item: dataProps) => {
  214. if (oldData?.data?.some((dataItem: dataProps) => dataItem.dataIndex === 'pos_type' ? false : dataItem?.dataIndex === item.dataIndex)) {
  215. return null
  216. } else {
  217. return item
  218. }
  219. })
  220. setSelectData(newSelectData)
  221. }
  222. }
  223. }, [selectData, data])
  224. const onSortEnd = ({ oldIndex, newIndex }: { oldIndex: number, newIndex: number }) => {
  225. setSelectData(arrayMove(selectData, oldIndex, newIndex))
  226. }
  227. // 提交
  228. const onSubmit = useCallback(() => {
  229. if (selectData.length > 0) {
  230. localStorage.setItem(`myAdMonitorConfig${version}_` + configName, JSON.stringify(selectData))
  231. localStorage.setItem(`myAdMonitorConfigFixed${version}_` + configName, JSON.stringify(fixed))
  232. localStorage.setItem(`myAdFieldConfig${version}_` + configName, JSON.stringify(fieldData))
  233. message.success('保存成功')
  234. onChange && onChange({ selectData, fixed })
  235. onClose && onClose()
  236. } else {
  237. message.error('请选择最少一项!!!')
  238. }
  239. }, [serverData, selectData, fixed, fieldData])
  240. // 恢复默认
  241. const defaultConfig = useCallback(() => {
  242. localStorage.setItem('REFRESH', new Date().getTime().toString())
  243. localStorage.removeItem(`myAdMonitorConfig${version}_` + configName)
  244. localStorage.removeItem(`myAdMonitorConfigFixed${version}_` + configName)
  245. localStorage.removeItem(`myAdFieldConfig${version}_` + configName)
  246. setFixed({ ...sysFixed })
  247. getUserList()
  248. message.success('恢复默认成功')
  249. onChange && onChange('')
  250. onClose && onClose()
  251. }, [])
  252. // 设置悬浮
  253. const changeFixed = useCallback((name: string, value: number) => {
  254. let v = Number(value)
  255. if (!isNaN(v)) {
  256. setFixed({ ...fixed, [name]: value })
  257. }
  258. }, [fixed])
  259. //取消操作
  260. const cancel = () => {
  261. setSelectData(serverData)
  262. onClose && onClose()
  263. }
  264. // 设置配置参数
  265. const setConfig = useCallback((data) => {
  266. let newServerData = selectData?.map((item) => {
  267. if (item.dataIndex === data.dataIndex) {
  268. item = data
  269. }
  270. return item
  271. })
  272. setSelectData(newServerData)
  273. }, [selectData])
  274. // 处理显示字段
  275. const fieldHandle = (saveField: string, data: any, checked: boolean) => {
  276. let newFieldData = JSON.parse(JSON.stringify(fieldData))
  277. if (checked) {
  278. if (Object.keys(newFieldData).includes(saveField)) {
  279. newFieldData[saveField] = [...newFieldData[saveField], data]
  280. } else {
  281. newFieldData[saveField] = [data]
  282. }
  283. } else {
  284. newFieldData[saveField] = newFieldData[saveField].filter((item: any) => item.key !== data.key && item.type === data.type)
  285. }
  286. setFieldData(newFieldData)
  287. }
  288. return <Modal
  289. title={null}
  290. footer={null}
  291. open={visible}
  292. width={1100}
  293. className='customListModel'
  294. onCancel={cancel}
  295. maskClosable={false}
  296. >
  297. <div className='content'>
  298. <div className='left'>
  299. <div className='leftSearch'>
  300. <Input size="small" placeholder="输入关键词,搜索数据指标" onChange={(e) => { searchHandle(e.target.value) }} value={search} prefix={<SearchOutlined style={{ color: '#a3a3a3', fontSize: 18 }} />} allowClear bordered={false} />
  301. </div>
  302. <div className="dataList">
  303. <Spin spinning={false}>
  304. {data?.map((item: DataProps, index: number) => {
  305. return <dl key={index}>
  306. {item?.fieldSHow && <div style={{ backgroundColor: '#f7f7f7' }}>
  307. <dt style={{ color: '#ff7875' }}>{item.fieldSHow.label}</dt>
  308. <div style={{ padding: '0 20px', boxSizing: 'border-box' }}>
  309. {item?.fieldSHow?.data?.map((item1: any, index) => {
  310. return <dl key={'field' + index}>
  311. <dt>{item1.label}</dt>
  312. {item1.data.map((item2: any, index: number) => <dd key={'item2' + index}><Checkbox className='checkbox' onChange={(e) => fieldHandle((item.fieldSHow as any).saveField, item2, e.target.checked)} checked={fieldData[(item.fieldSHow as any).saveField]?.some((field: any) => field.key === item2.key && field.type === item2.type)} disabled={fieldData[(item.fieldSHow as any).saveField]?.length > 0 ? fieldData[(item.fieldSHow as any).saveField][0].type === item1.label ? false : true : false}>{item2?.label}</Checkbox></dd>)}
  313. </dl>
  314. })}
  315. </div>
  316. </div>}
  317. <dt>
  318. <Checkbox onChange={(e) => { checkAllHandle(e.target.checked, item.label) }} className='checkbox'>
  319. <span style={item?.bcColor ? { backgroundColor: item?.bcColor } : {}}>{item.label}</span>
  320. </Checkbox>
  321. </dt>
  322. {item.data?.map((listItem: dataProps, listIndex: number) => {
  323. return <dd key={item.label + listIndex}>
  324. <Checkbox onChange={(e) => { selectHandle(e.target.checked, listItem) }} disabled={listItem.label === 'pos_type' || listItem?.disabled} checked={selectData.some((selectItem: { dataIndex: string }) => selectItem?.dataIndex === listItem?.dataIndex)} className='checkbox'>{listItem?.title}</Checkbox>
  325. </dd>
  326. })}
  327. </dl>
  328. })}
  329. </Spin>
  330. </div>
  331. </div>
  332. <div className='right'>
  333. <div className='rightTitle'>
  334. <div className="rightTitleLeft">已选<span>{selectData?.filter(item => item)?.length}</span>项</div>
  335. <a onClick={defaultConfig} style={{ marginRight: 30 }}>恢复默认</a>
  336. </div>
  337. <div style={{ display: 'flex', flexFlow: 'row nowarp', padding: '0 28px', justifyContent: 'space-between' }}>
  338. <div>左侧固定列数:<InputNumber size='small' style={{ width: 50 }} onChange={(value) => { changeFixed('left', value as number) }} value={fixed.left} max={100} min={0} /></div>
  339. <div>右侧固定列数:<InputNumber size='small' style={{ width: 50 }} onChange={(value) => { changeFixed('right', value as number) }} value={fixed.right} max={100} min={0} /></div>
  340. </div>
  341. <SortableList axis='y' onSortEnd={onSortEnd} useDragHandle>
  342. {selectData.map((item: dataProps, index: number) => <SortableItem key={'li' + index} index={index} item={item} del={() => { selectHandle(false, item) }} setConfig={setConfig} />)}
  343. </SortableList>
  344. <div className='rightOperate'>
  345. <div style={{ margin: '0 auto' }}>
  346. <Button onClick={cancel}>取消</Button>
  347. <Button type='primary' className='confirm' loading={false} onClick={() => { onSubmit() }} disabled={selectData?.length === 0}>确认</Button>
  348. </div>
  349. </div>
  350. </div>
  351. </div>
  352. </Modal>
  353. }
  354. export default CustomListModel