bill 2 лет назад
Родитель
Сommit
9190f8a5ed

+ 49 - 25
src/api/board.ts

@@ -1,5 +1,9 @@
 import axios from './instance'
-import { DELETE_DRAW_FILE, GET_DRAW_FILE, INSERT_DRAW_FILE, UPDATE_DRAW_FILE } from 'constant'
+import { 
+  INSERT_EXAMPLE_FILE_IMAGE, 
+  FETCH_FILE_INFO, 
+  UPLOAD_HEADS
+} from 'constant'
 
 import type { 
   brokenLine,
@@ -16,7 +20,12 @@ import type {
   fingerPrint,
   corpse,
   theBlood,
+  compass,
+  title,
+  bgImage
 } from 'views/draw-file/board'
+import { ExampleFile } from './files'
+import { jsonToForm } from 'utils'
 
 export interface Pos { 
   x: number,
@@ -33,6 +42,19 @@ interface CurrencyShapeData extends ShapeData {
   height: number
 }
 
+export interface TitleShapeData extends ShapeData {
+  text: string
+  type: typeof title
+}
+export interface BgImageShapeData extends ShapeData {
+  url: string
+  type: typeof bgImage
+}
+export interface CompassShapeData extends ShapeData {
+  url: string
+  type: typeof compass
+}
+
 export interface BrokenLineShapeData extends ShapeData {
   type: typeof brokenLine
   points: Pos[]
@@ -103,12 +125,11 @@ export interface FootPrintShapeData extends CurrencyShapeData {
 
 export type BoardShapeData = BrokenLineShapeData | TextShapeData | TableShapeData | RectShapeData | CircularShapeData 
   | ArrowShapeData | IconShapeData | CigaretteShapeData | FireointShapeData | FootPrintShapeData | ShoePrintShapeData 
-  | FingerPrintShapeData | CorpseShapeData | TheBloodShapeData
+  | FingerPrintShapeData | CorpseShapeData | TheBloodShapeData | TitleShapeData | BgImageShapeData | CompassShapeData
 
 
 export interface BoardData {
   id: number
-  bgImage: string | null
   shapes: BoardShapeData[]
 }
 
@@ -117,34 +138,37 @@ export enum BoardType {
   scene = '1'
 }
 
+
 export const BoardTypeDesc = {
   [BoardType.map]: '方位图',
   [BoardType.scene]: '现场图',
 }
 
-export const getBoardById = (params: { id: number, type: BoardType }) => {
-  axios.get<BoardData>(GET_DRAW_FILE, { params: params })
-  return {
-    id: params.id,
-    shapes: [],
-    bgImage: null
-  }
-}
-
-export const delBoard = (params: { id: number }) => {
-  axios.post<undefined>(DELETE_DRAW_FILE, params)
-}
+export type SaveBoardProps = Pick<ExampleFile, 'caseId' | 'filesTitle'> & {
+  filesId?:	number,
+  imgType: BoardType,
+  content: BoardData,
+  file: File
+}
+
+export const saveBoard = (props: SaveBoardProps) => 
+  axios<Required<SaveBoardProps>>({
+    url: INSERT_EXAMPLE_FILE_IMAGE, 
+    method: 'POST',
+    headers: UPLOAD_HEADS,
+    data: jsonToForm({
+      filesTypeId: 1,
+      ...props,
+      content: JSON.stringify(props.content)
+    })
+  })
   
-
-export const addBoard = (params: { type: BoardType, data: BoardData }) => {
-  axios.post<BoardData>(INSERT_DRAW_FILE, params)
+export type FetchBoardData = Omit<SaveBoardProps, 'content' | 'file'> & { content: string, filesUrl: string }
+export const getBoardById = async (params: { id: number }) => {
+  const data = await axios.get<FetchBoardData>(FETCH_FILE_INFO, { params: { filesId: params.id } })
+  const boardData = JSON.parse(data.content) as BoardData
   return {
-    ...params.data,
-    id: 1,
+    ...data,
+    content: boardData
   }
-}
-
-export const setBoard = (params: {id: number, data: BoardData}) => {
-  axios.post<undefined>(UPDATE_DRAW_FILE, params)
-  return params.data
 }

+ 3 - 0
src/api/files.ts

@@ -8,6 +8,7 @@ import {
  } from 'constant'
 
 import type { Example } from './example'
+import type { BoardType } from './board'
 import { jsonToForm } from 'utils'
 
 export interface ExampleFileType {
@@ -29,6 +30,7 @@ export interface ExampleFile {
   tbStatus:	number,
   createTime:	string,
   updateTime:	string,
+  imgType?: BoardType
 }
 
 export type ExampleFiles = ExampleFile[]
@@ -49,6 +51,7 @@ export const addExampleFile = (props: AddExampleFilesProps) =>
     data: jsonToForm(props),
   })
 
+
 export type DeleteExampleFileProps = Pick<ExampleFile, 'caseId' | 'filesId'>
 export const deleteExampleFile = (props: DeleteExampleFileProps) => 
   axios.post<ExampleFiles>(DELETE_EXAMPLE_FILE, props)

Разница между файлами не показана из-за своего большого размера
+ 4 - 0
src/assets/svg/compass.svg


+ 2 - 0
src/constant/api.ts

@@ -40,6 +40,8 @@ export const EXAMPLE_FILE_TYPE_LIST = `/fusion/caseFilesType/allList`
 export const EXAMPLE_FILE_LIST = `/fusion/caseFiles/allList`
 export const INSERT_EXAMPLE_FILE = `/fusion/caseFiles/add`
 export const DELETE_EXAMPLE_FILE = `/fusion/caseFiles/delete`
+export const INSERT_EXAMPLE_FILE_IMAGE = `/fusion/caseFiles/addOrUpdateImg`
+export const FETCH_FILE_INFO = '/fusion/caseFiles/info'
 
 export const GET_DRAW_FILE = '/fusion/caseFiles/draw'
 export const INSERT_DRAW_FILE = '/fusion/caseFiles/draw'

+ 0 - 91
src/store/board.ts

@@ -1,91 +0,0 @@
-import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
-import { getBoardById, setBoard, addBoard } from 'api'
-
-
-import type { BoardData } from 'api'
-import type { StoreState } from 'store'
-
-export * from 'api/board'
-export { BoardType, BoardTypeDesc } from 'api'
-
-export interface BoardState {
-  list: BoardData[]
-  currentIndex: number
-}
-
-const initialState: BoardState = { 
-  list: [{ shapes: [], id: -1, bgImage: null  }], 
-  currentIndex: 0
-}
-
-const replaceState = (state: BoardState, { payload }: { payload: BoardData }) => {
-  state.list = [payload]
-  state.currentIndex = 0
-}
-const pushState = (state: BoardState, { payload }: { payload: BoardData }) => {
-  const current = state.list[state.currentIndex]
-  const newList = state.list.slice(0, state.currentIndex + 1)
-  newList.push({
-    ...payload,
-    id: current.id,
-  })
-  state.list = newList
-  state.currentIndex += 1
-}
-
-const boardSlice = createSlice({
-  name: 'board',
-  initialState,
-  reducers: {
-    forward(state, { payload = 1 }: { payload: number }) {
-      let move = state.currentIndex + payload
-      state.currentIndex = move > state.list.length - 1 ? state.list.length - 1 : move
-      console.log('forward')
-    },
-    backoff(state, { payload = 1 }: { payload: number }) {
-      let move = state.currentIndex - payload
-      state.currentIndex = move < 0 ? 0 : move
-      return state
-    },
-    push: pushState,
-    replace: replaceState,
-    destory(state) {
-      replaceState(state, { payload: initialState.list[0] })
-    }
-  },
-  extraReducers(builder) {
-    builder
-      .addCase(insertBoard.fulfilled, (state, action) => {
-        const current = state.list[state.currentIndex]
-        state.list = state.list.map(data => ({
-          ...data,
-          bgImage: data.bgImage === current.bgImage ? action.payload.bgImage : data.bgImage,
-          id: action.payload.id
-        }))
-      })
-      .addCase(fetchBoard.fulfilled, replaceState)
-  }
-})
-
-export const boardName = boardSlice.name
-export const boardReducer = boardSlice.reducer
-
-export const currentBoard = (state: StoreState) => state.board.list[state.board.currentIndex]
-export const boardStatus = (state: StoreState) => {
-  const len = state.board.list.length
-  const index = state.board.currentIndex
-
-  return {
-    canBack: index !== 0,
-    canForward: index < len - 1,
-    top: index === len - 1
-  }
-}
-
-export const copyBoard = (boardData: BoardData): BoardData => {
-  return JSON.parse(JSON.stringify(boardData))
-}
-
-export const fetchBoard = createAsyncThunk('fetch/board', getBoardById)
-export const insertBoard = createAsyncThunk('insert/board', addBoard)
-export const updateBoard = createAsyncThunk('update/board', setBoard)

+ 1 - 4
src/store/index.tsx

@@ -5,7 +5,6 @@ import { sceneReducer, sceneName } from './scene'
 import { userReducers, userName } from './user'
 import { exampleReducer, exampleName } from './example'
 import { exampleFileName, exampleFileReducer } from './files'
-import { boardName, boardReducer } from './board'
 
 import type { TypedUseSelectorHook } from 'react-redux'
 
@@ -15,7 +14,6 @@ export const store = configureStore({
     [userName]: userReducers,
     [exampleName]: exampleReducer,
     [exampleFileName]: exampleFileReducer,
-    [boardName]: boardReducer
   }
 })
 
@@ -36,5 +34,4 @@ export default store
 export * from './scene'
 export * from './user'
 export * from './example'
-export * from './files'
-export * from './board'
+export * from './files'

+ 29 - 3
src/views/draw-file/board/index.d.ts

@@ -1,10 +1,32 @@
 import { metas as fMetas } from './shape'
-import type { BoardData, BoardShapeData } from 'store'
+import type { BoardData, BoardShapeData } from 'api'
 import type { Emitter } from 'mitt'
-
+import {
+  brokenLine,
+  text,
+  table,
+  rect,
+  circular,
+  arrow,
+  icon,
+  cigarette,
+  fireoint,
+  footPrint,
+  shoePrint,
+  fingerPrint,
+  corpse,
+  theBlood,
+  compass,
+  title,
+  bgImage
+} from './shape'
 
 type Metas = typeof fMetas
-export type ShapeType = keyof Metas
+export type ShapeType = brokenLine | text | table | rect | circular | arrow | icon | cigarette |
+  fireoint | footPrint | shoePrint | fingerPrint | corpse | theBlood | compass | title | bgImage
+
+export type MetaShapeType = keyof Metas
+
 export interface Pos {
   x: number
   y: number
@@ -39,6 +61,7 @@ export type Board = {
   setImage(url: string): void
   getCurrentStore(): BoardData
   drawStore(store: BoardData): void
+  getStore(): BoardData
   export(): Promise<Blob>
   destroy(): void
 }
@@ -64,6 +87,9 @@ export {
   fingerPrint,
   corpse,
   theBlood,
+  compass,
+  title,
+  bgImage
 } from './shape'
 
 export default create

+ 3 - 1
src/views/draw-file/board/index.js

@@ -78,7 +78,9 @@ export const create = (store, canvas) => {
       refs.baseMap = null
       generateRefs(newStore)
     },
-
+    getStore() {
+      return store
+    },
     unSelectShape() {
       layer.uiControl.clearUI()
     },

+ 5 - 0
src/views/draw-file/board/shape.js

@@ -14,6 +14,7 @@ import shoePrintReverSVG from 'assets/svg/shoePrintRever.svg'
 import fingerPrintSVG from 'assets/svg/fingerPrint.svg'
 import corpseSVG from 'assets/svg/corpse.svg'
 import theBloodSVG from 'assets/svg/theBlood.svg'
+import compassSVG from 'assets/svg/compass.svg'
 
 export const brokenLine = 'Wall'
 export const text = 'Tag'
@@ -31,6 +32,9 @@ export const shoePrintRever = 'RightShoePrint'
 export const fingerPrint = 'FingerPrint'
 export const corpse = 'DeadBody'
 export const theBlood = 'BloodStain'
+export const title = 'Title'
+export const bgImage = 'BgImage'
+export const compass = 'Compass'
 
 export const labels = [
   brokenLine,
@@ -71,4 +75,5 @@ export const metas = {
   [fingerPrint]: { desc: '指纹', icon: fingerPrintSVG },
   [corpse]: { desc: '尸体', icon: corpseSVG },
   [theBlood]: { desc: '血迹', icon: theBloodSVG },
+  [compass]: { desc: '指南针', icon: compassSVG  }
 }

+ 1 - 1
src/views/draw-file/eshape.tsx

@@ -168,8 +168,8 @@ export type EShapeProps = {
 }
 export const EShape = ({ board }: EShapeProps) => {
   const [shape, setShape] = useState<BoardShape | null>(null)
+  
   const Edit = shape && shapeCompontes[shape.data.type]
-
   useEffect(() => {
     if (board.current) {
       const boardRef = board.current

+ 35 - 19
src/views/draw-file/header.tsx

@@ -3,18 +3,16 @@ import { Button } from "antd"
 import { useNavigate, fillRoutePath, RoutePath, usePathData } from "router"
 import { saveAs } from 'utils'
 import style from './style.module.scss'
-import { 
-  useSelector, 
-  useDispatch, 
-  insertBoard, 
-  updateBoard, 
-  currentBoard, 
+import {
   BoardTypeDesc, 
-  BoardType 
-} from "store"
+  BoardType,
+  saveBoard,
+  TitleShapeData
+} from 'api'
 
 import { RefObject, useEffect, useState } from "react"
-import type { Board } from "./board"
+import { Board, title } from "./board"
+import type { SaveBoardProps } from 'api'
 
 type HeaderProps = {
   board: RefObject<Board>
@@ -24,8 +22,9 @@ const Header = ({ board, type }: HeaderProps) => {
   const [backDisabled, setBackDisabled] = useState(true)
   const [forwardDisabled, setForwradDisabled] = useState(true)
   const path = usePathData()
-  const current = useSelector(currentBoard)
-  const dispatch = useDispatch()
+  const caseId = path!.caseId
+  const pathId = Number(path!.id)
+  const pathType = path!.type as BoardType
   const navigate = useNavigate()
   const exportPng = async () => {
     if (board.current) {
@@ -33,15 +32,32 @@ const Header = ({ board, type }: HeaderProps) => {
       saveAs(blob, '现场图.png')
     }
   }
+
+  
   const save = async () => {
-    if (current.id === -1) {
-      const data = await dispatch(insertBoard({ type, data: current })).unwrap()
-      navigate(
-        fillRoutePath(RoutePath.drawFile, { ...path as any, id: data.id.toString() }),
-        { replace: true }
-      )
-    } else {
-      dispatch(updateBoard({ id: current.id, data: current }))
+    if (board.current) {
+      const store = board.current.getStore()
+      const titleShape = store.shapes.find(shape => shape.type === title) as TitleShapeData
+      const blob = await board.current.export()
+      const isNew = pathId === -1
+      const body: SaveBoardProps = {
+        caseId, 
+        imgType: pathType,
+        file: new File([blob], `${pathType}_${pathId}.png`),
+        filesTitle: titleShape?.text || `${caseId}_${BoardTypeDesc[pathType]}`, 
+        content: board.current.getStore()
+      }
+      if (!isNew) {
+        body.filesId = pathId
+      }
+      const data = await saveBoard(body)
+
+      if (isNew) {
+        navigate(
+          fillRoutePath(RoutePath.drawFile, { ...path as any, id: data.filesId.toString() }),
+          { replace: true }
+        )
+      }
     }
   }
 

+ 16 - 32
src/views/draw-file/index.tsx

@@ -1,39 +1,23 @@
 import { Layout } from 'antd'
-import { useRef, forwardRef, useImperativeHandle, memo, useCallback, useEffect } from 'react'
+import { useRef, forwardRef, useImperativeHandle, memo, useCallback, useEffect, useState } from 'react'
 import { usePathData } from 'router'
-import { inRevise } from 'utils'
 import style from './style.module.scss'
 import boardFactory from './board'
 import DfSlider from './slider'
 import DfHeader from './header'
 import EShape from './eshape'
-import { 
-  currentBoard, 
-  useDispatch, 
-  useSelector, 
-  fetchBoard, 
-  copyBoard, 
-  BoardType 
-} from 'store'
+import { useDispatch } from 'store'
+import { getBoardById, BoardType, BoardData } from 'api'
 
 import type { Board } from './board'
 
 const { Header, Sider, Content  } = Layout
 
-const DfBoard = memo(forwardRef((_: {}, ref) => {
-  const boardData = useSelector(currentBoard)
+const DfBoard = memo(forwardRef(({ data: boardData }: { data: BoardData }, ref) => {
   const board = useRef<Board>()
-  const dispatch = useDispatch()
-
   const createBoard = useCallback((dom: HTMLCanvasElement | null) => {
     if (dom) {
-      const boardRef = boardFactory(copyBoard(boardData), dom)
-      boardRef.bus.on('storeChange', () => {
-        dispatch({ 
-          type: 'board/push', 
-          payload: copyBoard(boardRef.getCurrentStore())
-        })
-      })
+      const boardRef = boardFactory(boardData, dom)
       board.current = boardRef
     } else if (board.current) {
       board.current.destroy()
@@ -42,12 +26,6 @@ const DfBoard = memo(forwardRef((_: {}, ref) => {
   }, [])
   useImperativeHandle(ref, () => board.current)
 
-  useEffect(() => {
-    if (board.current && inRevise(board.current.getCurrentStore(), boardData)) {
-      board.current.drawStore(copyBoard(boardData))
-    }
-  }, [boardData, board])
-
   return (
     <div className={style['df-board']}>
       <div className={style['df-board-content']}>
@@ -63,16 +41,22 @@ export const DrawFile = () => {
   const pathId = Number(path!.id)
   const caseId = Number(path!.caseId)
   const pathType = path!.type as BoardType
-  const boardData = useSelector(currentBoard)
+  const [boardData, setBoardData] = useState<BoardData>()
   const board = useRef<Board>(null)
 
   useEffect(() => {
-    if (pathId !== boardData.id) {
+    if (!boardData || pathId !== boardData.id) {
+      console.log(pathId, boardData?.id)
       if (pathId !== -1) {
-        dispatch(fetchBoard({ id: pathId, type: pathType }))
+        getBoardById({ id: pathId }).then((data) => setBoardData({ ...data.content, id: data.filesId! }))
+      } else {
+        setBoardData({
+          id: -1,
+          shapes: []
+        })
       }
     }
-  }, [pathId, pathType, boardData.id, dispatch])
+  }, [pathId, boardData?.id, boardData])
 
   useEffect(() => () => {
     dispatch({ type: 'board/destory' })
@@ -89,7 +73,7 @@ export const DrawFile = () => {
         </Sider>
         <Content className={style['def-content']}>
           { <EShape board={ board } /> }
-          { pathId === boardData.id && <DfBoard ref={board} /> }
+          { pathId === boardData?.id && <DfBoard data={boardData} ref={board} /> }
         </Content>
       </Layout>
     </Layout>

+ 4 - 5
src/views/draw-file/slider.tsx

@@ -1,12 +1,12 @@
 import { Button } from 'antd'
 import { useEffect, useState } from 'react'
 import { metas, labels, images } from './board'
-import { BoardType, BoardTypeDesc } from 'store'
+import { BoardType, BoardTypeDesc } from 'api'
 import { SelectMap, SelectFuse } from './modal'
 import shapes from './shapes'
 import style from './style.module.scss'
 
-import type { ShapeType, Board } from './board'
+import type { MetaShapeType, Board } from './board'
 import type { RefObject } from 'react'
 import type { Tagging } from 'api'
 
@@ -17,8 +17,8 @@ type SliderProps = {
 }
 export const DfSlider = ({ board, type, caseId }: SliderProps) => {
   const [selectMode, setSelectMode] = useState(false)
-  const [currentShape, setCurrentShape] = useState<ShapeType>()
-  const getEle = (shapeType: ShapeType) => {
+  const [currentShape, setCurrentShape] = useState<MetaShapeType>()
+  const getEle = (shapeType: MetaShapeType) => {
     const Shape = shapes[shapeType]
     return (
       <div 
@@ -32,7 +32,6 @@ export const DfSlider = ({ board, type, caseId }: SliderProps) => {
     )
   }
   const setBoardImage = async (blob: Blob | null, taggings: Tagging[]) => {
-    console.log(taggings)
     if (blob) {
       const url = URL.createObjectURL(blob)
       board.current?.setImage(url)

+ 11 - 0
src/views/files/columns.tsx

@@ -5,9 +5,12 @@ import { fetchExampleFiles, useDispatch } from "store"
 
 import type { ExampleFile } from "api"
 import type { ColumnsType } from 'antd/es/table'
+import { RoutePath, useNavigate, fillRoutePath } from "router"
 
 const OperActions = (data: ExampleFile) => {
   const dispatch = useDispatch()
+  const navigate = useNavigate()
+
   const actions = [
     {
       text: '查看', 
@@ -23,6 +26,14 @@ const OperActions = (data: ExampleFile) => {
       },
     },
   ]
+  if (data.imgType !== undefined && data.imgType !== null) {
+    actions.splice(1, 0, {
+      text: '编辑',
+      action: () => {
+        navigate(fillRoutePath(RoutePath.drawFile, { type: data.imgType!, caseId: data.caseId, id: data.filesId.toString() }))
+      }
+    })
+  }
   return <ActionsButton actions={actions} />
 }
 

+ 5 - 2
src/views/files/index.tsx

@@ -11,10 +11,12 @@ import {
   fetchExampleFileTypes, 
   fetchExampleFiles,
   exampleTypeFiles,
-  DrawType,
+  DrawType
+} from 'store'
+import {
   BoardTypeDesc,
   BoardType
-} from 'store'
+} from 'api'
 
 import type { ExampleFileType } from 'store'
 import type { Example } from 'api'
@@ -34,6 +36,7 @@ export const FileTable = (props: FileTableProps) => {
   const renderUpload = insert && <AddExampleFile {...props} onClose={() => setInser(false)} />
   const renderRraws = props.type === DrawType && Object.keys(BoardTypeDesc).map((type) => (
     <Button 
+      key={type}
       type="primary" 
       children={`创建${BoardTypeDesc[type as BoardType]}`}
       onClick={() => navigate(fillRoutePath(RoutePath.drawFile, { type, caseId: props.caseId.toString(), id: '-1' }))}