index.tsx 16 KB

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