eshape.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. import { useCallback, useEffect, useMemo, useState } from "react"
  2. import { Button, Form, Modal, Input } from 'antd'
  3. import { CloseOutlined, PlusOutlined, RotateRightOutlined } from '@ant-design/icons'
  4. import style from './style.module.scss'
  5. import { title, compass } from './board'
  6. import ReactEditeTable, { InputEditor } from 'react-edit-table'
  7. import { alert } from 'utils'
  8. import type { Board, BoardShape, ShapeType, ExtractShape } from "./board"
  9. import type { ComponentType } from 'react'
  10. import { useSelector } from "store"
  11. import { usePathData } from "router"
  12. // import { Select } from 'antd'
  13. // import InputColor from 'react-input-color';
  14. // const ColorInput = ({ shape }: { shape: BoardShape }) => (
  15. // <Form.Item label="颜色" className={style['def-color-item']}>
  16. // <InputColor
  17. // initialValue={shape.data.color || '#000'}
  18. // onChange={(color) => shape.setColor(color.rgba)}
  19. // />
  20. // </Form.Item>
  21. // )
  22. // const sizeOptions = [6,7,8,9,10,11,12,13,14,16,18,20,28,36,48,72]
  23. // .map(size => ({ label: `${size}px`, value: size }))
  24. // const FontSizeInput = ({ shape }: { shape: ExtractShape<'fontSize'> }) => (
  25. // <Form.Item label="字号">
  26. // <Select
  27. // defaultValue={shape.data.fontSize}
  28. // style={{ width: 80 }}
  29. // onChange={(size) => shape.setFontSize(size)}
  30. // options={sizeOptions}
  31. // />
  32. // </Form.Item>
  33. // )
  34. const TextInput = ({ shape }: { shape: ExtractShape<'text'> }) => {
  35. const [text, setText] = useState(shape.data.text)
  36. const onChang = () => {
  37. shape.setText(text)
  38. // if (text !== shape.data.text) {
  39. // shape.setText(text)
  40. // }
  41. }
  42. return (
  43. <Form.Item label="内容">
  44. <Input.Group compact>
  45. <Input
  46. maxLength={50}
  47. style={{ width: 120 }}
  48. value={text}
  49. onKeyDown={
  50. ev => {
  51. ev.key === 'Enter' && onChang()
  52. ev.stopPropagation()
  53. }
  54. }
  55. onBlur={onChang}
  56. onChange={ev => setText(ev.target.value)}
  57. />
  58. <Button type="primary" onClick={onChang}>确定</Button>
  59. </Input.Group>
  60. </Form.Item>
  61. )
  62. }
  63. const ContentInput = ({ shape }: { shape: ExtractShape<'content'> }) => {
  64. const [edit, setEdit] = useState(false)
  65. const [content, setContent] = useState(shape.data.content)
  66. const refer = content.filter(item => item.rowIndex === 0)
  67. const tableAttrs = useMemo(() => {
  68. const dataSource: string[][] = []
  69. content.forEach(item => {
  70. let columns = dataSource[item.rowIndex]
  71. if (!columns) {
  72. columns = dataSource[item.rowIndex] = []
  73. }
  74. columns[item.colIndex] = item.value
  75. })
  76. return {
  77. columns: refer.map((item, i) => ({
  78. title: `列${i + 1}`,
  79. dataIndex: i,
  80. key: i,
  81. editor: {
  82. type: 'input',
  83. component: InputEditor
  84. }
  85. })),
  86. dataSource
  87. }
  88. }, [content, refer])
  89. const sortContent = () =>
  90. content.sort((a, b) => {
  91. const rowDiff: number = a.rowIndex - b.rowIndex
  92. if (rowDiff) {
  93. return rowDiff
  94. } else {
  95. return a.colIndex - b.colIndex
  96. }
  97. })
  98. const onChange = (data: any) => {
  99. const newContent = [...content]
  100. const item = newContent.find(item => item.rowIndex === data.rowIndex && item.colIndex === data.key)
  101. item!.value = data.newValue
  102. setContent(newContent)
  103. }
  104. const onDelete = (data: any) => {
  105. if (content.length <= 2) {
  106. return alert("表格最少需要保留一行!")
  107. }
  108. const newContent = sortContent()
  109. const startIndex = newContent.findIndex(item => item.rowIndex === data.rowIndex)
  110. const endIndex = startIndex + refer.length
  111. setContent([
  112. ...newContent.slice(0, startIndex),
  113. ...newContent.slice(endIndex).map(item => ({...item, rowIndex: --item.rowIndex}))
  114. ])
  115. }
  116. const onInsert = () => {
  117. const maxRow = Math.max(...content.map(item => item.rowIndex))
  118. setContent([
  119. ...content,
  120. ...refer.map(item => ({ ...item, value: '', rowIndex: maxRow + 1 }))
  121. ])
  122. }
  123. const onSubmit = useCallback(() => {
  124. const rowEls = Array.from(document.querySelectorAll('#edit-table .body-container .row-container')) as HTMLDivElement[]
  125. const bound = rowEls.map((row, rowIndex) => {
  126. const cells = Array.from(row.querySelectorAll('.cell')) as HTMLDivElement[]
  127. return cells.slice(0, -1).map((cell, colIndex) => ({
  128. width: cell.offsetWidth,
  129. height: row.offsetHeight - 1,
  130. value: content.find(item => item.rowIndex === rowIndex && item.colIndex === colIndex)!.value,
  131. colIndex,
  132. rowIndex
  133. }))
  134. }).flat()
  135. setContent(bound)
  136. shape.setContent(bound)
  137. console.log(bound)
  138. setEdit(false)
  139. }, [content, shape])
  140. useEffect(() => {
  141. if (!edit) {
  142. setContent(shape.data.content || [['', '']])
  143. }
  144. }, [edit, shape.data.content])
  145. useEffect(() => {
  146. if (shape.autoSet) {
  147. setEdit(true)
  148. setTimeout(onSubmit, 100)
  149. }
  150. // eslint-disable-next-line react-hooks/exhaustive-deps
  151. }, [])
  152. return (
  153. <Form.Item label="内容">
  154. <Button type="primary" onClick={() => setEdit(true)}>编辑</Button>
  155. <Modal open={edit} onCancel={() => setEdit(false)} onOk={onSubmit} width="430px" className="edit-table-layout">
  156. <div id="edit-table" onKeyDown={ev => ev.stopPropagation()}>
  157. { tableAttrs.dataSource.length &&
  158. <ReactEditeTable
  159. {...tableAttrs}
  160. onDelete={onDelete}
  161. onChange={onChange}
  162. />
  163. }
  164. <div className={style['add-table-row']}>
  165. <Button onClick={onInsert} type="primary">
  166. <PlusOutlined className="icon" /> 行
  167. </Button>
  168. </div>
  169. </div>
  170. </Modal>
  171. </Form.Item>
  172. )
  173. }
  174. const RotateInput = ({ shape }: { shape: ExtractShape<'rotate'> }) => (
  175. <Button
  176. onClick={() => shape.setRotate((shape.data.rotate + 90) % 360)}
  177. type="primary"
  178. style={{marginRight: '10px'}}
  179. >
  180. <RotateRightOutlined /> 旋转
  181. </Button>
  182. )
  183. const shapeCompontes: { [key in ShapeType]?: ComponentType<{ shape: any }> } = {
  184. Tag: TextInput,
  185. Table: ContentInput,
  186. Compass: RotateInput,
  187. Title: TextInput
  188. }
  189. export type EShapeProps = {
  190. board: Board
  191. }
  192. export const EShape = ({ board }: EShapeProps) => {
  193. const [shape, setShape] = useState<BoardShape | null>(null)
  194. const Edit = shape && shapeCompontes[shape.data.type]
  195. const disabledDelete: boolean = ([title, compass] as any).includes(shape?.data.type)
  196. const renderDelete = !disabledDelete && (
  197. <Form.Item label="删除">
  198. <Button type="primary" onClick={() => shape!.delete()}>删除</Button>
  199. </Form.Item>
  200. )
  201. useEffect(() => {
  202. board.bus.on('selectShape', setShape)
  203. return () => {
  204. board.bus.off('selectShape', setShape)
  205. }
  206. }, [board])
  207. useEffect(() => {
  208. const keydownHandler = (ev: KeyboardEvent) => {
  209. if (['Backspace', 'Delete'].includes(ev.key) && shape && !disabledDelete) {
  210. shape.delete()
  211. }
  212. }
  213. window.addEventListener('keydown', keydownHandler)
  214. return () => {
  215. window.removeEventListener('keydown', keydownHandler)
  216. }
  217. }, [board, shape, disabledDelete])
  218. const path = usePathData()
  219. const user = useSelector(store => store.user.value)
  220. useEffect(() => {
  221. if (board && path?.id === '-1') {
  222. board.calcTableShape([
  223. ["案发时间", ""],
  224. ["案发地点", ""],
  225. ["绘图单位", ""],
  226. ["绘图人", ""],
  227. ["绘图时间", ""]
  228. ]).then(data => {
  229. board.setDefaultTable(data.content, null)
  230. board.initHistory()
  231. })
  232. } else {
  233. board.initHistory()
  234. }
  235. }, [user, board, path?.id])
  236. return shape ? (
  237. <div className={style['def-shape-edit']} style={{ visibility: shape.autoSet ? "hidden" : 'visible' }}>
  238. { Edit && <Edit shape={shape} /> }
  239. { renderDelete }
  240. <div
  241. className={`ant-form-item ${style['def-close-shape-edit']}`}
  242. onClick={() => setShape(null)}
  243. >
  244. <CloseOutlined className={`${style['icon']}`} onClick={() => board.unSelectShape()} />
  245. </div>
  246. </div>
  247. ) : <></>
  248. }
  249. export default EShape