Prechádzať zdrojové kódy

feat: 添加案件管理

bill 3 rokov pred
rodič
commit
67971e300c

+ 19 - 0
src/api/example.ts

@@ -0,0 +1,19 @@
+import axios from './instance'
+import { EXAMPLE_SCENE_LIST } from 'constant'
+
+import type { PagingRequest, PagingResult } from 'api'
+
+export interface Example {
+  caseId: number
+  caseTitle: number
+  createTime: string
+  name: string
+  tbStatus: string
+  userName: number
+}
+
+export type Examples = Example[]
+
+export type GetExamplesParams = PagingRequest<{caseTitle?: string}>
+export const getExamples = (props: GetExamplesParams) => 
+  axios.post<PagingResult<Example[]>>(EXAMPLE_SCENE_LIST, props)

+ 2 - 1
src/api/index.ts

@@ -21,4 +21,5 @@ export type PagingResult<T> = {
 
 export * from './scene'
 export * from './instance'
-export * from './user'
+export * from './user'
+export * from './example'

+ 20 - 0
src/components/actions-button/index.tsx

@@ -0,0 +1,20 @@
+import { Button} from 'antd'
+
+export type ColumnAction = { 
+  text: any, 
+  action: () => any, 
+  bind?: { [key in string]: any } 
+}
+
+export const ActionsButton = ({ actions }: {actions: ColumnAction[]}) => (
+  <>
+    {actions.map(({text, action, bind}) => 
+      <Button 
+        type="link" 
+        onClick={action} 
+        {...(bind || {})}
+        key={text}
+        children={text} />
+    )}
+  </>
+)

+ 3 - 1
src/components/index.ts

@@ -1,4 +1,6 @@
 export * from './async-component'
 export * from './route-menu'
 export * from './tabs'
-export * from './background'
+export * from './background'
+export * from './actions-button'
+export * from './table'

+ 29 - 0
src/components/table/index.tsx

@@ -0,0 +1,29 @@
+import { Table as ATable } from 'antd'
+
+import type { ColumnType, ColumnGroupType } from 'antd/es/table';
+import type { Paging } from 'hook'
+
+export type TableProps<T> = { 
+  data: T[],
+  columns: (ColumnGroupType<T> | ColumnType<T>)[],
+  paging: Paging,
+  rowKey?: string,
+  onChangePaging?: (paging: Partial<Paging>) => void
+}
+export const Table = <T extends object>({ data, columns, paging, onChangePaging, rowKey = 'id' }: TableProps<T>) => {
+  return (
+    <ATable
+      columns={columns} 
+      dataSource={data} 
+      rowKey={rowKey}
+      pagination={{
+        showSizeChanger: false,
+        pageSize: paging.pageSize,
+        total: paging.total,
+        onChange: onChangePaging && ((page, pageSize) => onChangePaging({ pageNum: page, pageSize }))
+      }}
+    />
+  )
+}
+
+export default Table

+ 1 - 8
src/components/tabs/style.module.scss

@@ -2,13 +2,6 @@
   :global(.ant-tabs-nav) {
     background: #fff;
     padding: 0 24px;
-  }
-
-  :global(.ant-tabs-content) {
-    padding: 0 25px;
-  }
-
-  .tab-panel {
-    background: #fff;
+    margin: 0;
   }
 }

+ 4 - 1
src/constant/api.ts

@@ -22,4 +22,7 @@ export const SCENE_LIST = `/fusion/scene/list`
 // 三维模型
 export const MODEL_SCENE_LIST = `/fusion/model/list`
 export const UPLOAD_MODEL = `/fusion/model/uploadObj`
-export const DELETE_MODEL = `/fusion/model/delete`
+export const DELETE_MODEL = `/fusion/model/delete`
+
+// 案件
+export const EXAMPLE_SCENE_LIST = `/fusion/case/list`

+ 16 - 0
src/public.scss

@@ -8,4 +8,20 @@ html, body, #root {
 #root {
   display: flex;
   flex-direction: column;
+}
+
+.content-layout {
+  margin: 24px;
+  padding: 0 24px;
+  background: #fff;
+}
+
+.content-header {
+  padding: 24px 0;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.content-header-search {
+  margin-left: 20px;
 }

+ 2 - 1
src/react-app-env.d.ts

@@ -25,4 +25,5 @@ type ExtractRouteParams<T> = {
 
 declare module 'canvas-nest.js' {
   export default any
-}
+}
+

+ 1 - 1
src/setupProxy.js

@@ -4,7 +4,7 @@ const { createProxyMiddleware } = require('http-proxy-middleware')
 module.exports = function (app) {
   app.use(
     createProxyMiddleware('/api', {
-      target: 'http://192.168.0.38:8808',
+      target: 'http://192.168.0.47:8808',
       changeOrigin: true,
       pathRewrite: {
         '^/api': ''

+ 41 - 0
src/store/example.ts

@@ -0,0 +1,41 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
+import { initialThukState, createArrayThunkSelects, thunkStatusAutoSet } from './help'
+import { getExamples } from 'api'
+
+import type { ThunkState } from './help'
+import type { Examples } from 'api'
+import type { StoreState } from './'
+
+export type ExampleState = ThunkState<{value: Examples}>
+
+const initialState: ExampleState = {
+  ...initialThukState,
+  value: []
+}
+
+const exampleSlice = createSlice({
+  name: 'example',
+  initialState,
+  reducers: {
+  },
+  extraReducers(builder) {
+    thunkStatusAutoSet(
+      builder, 
+      fetchExamples,
+      (state, data) => state.value = data.list
+    )
+  }
+})
+
+export const exampleName = exampleSlice.name
+export const exampleReducer = exampleSlice.reducer
+export const {
+  listSelector: examplesSelector,
+  findSelector: exampleSelector,
+  filterSelector: filterExamplesSelector
+} = createArrayThunkSelects(
+  (state: StoreState) => state.example.value,
+  'caseId'
+)
+
+export const fetchExamples = createAsyncThunk('fetch/example', getExamples)

+ 4 - 1
src/store/index.tsx

@@ -3,6 +3,7 @@ import { Provider } from 'react-redux'
 import { useDispatch as useDispatchRaw, useSelector as useSelectorRaw } from 'react-redux'
 import { sceneReducer, sceneName } from './scene'
 import { userReducers, userName } from './user'
+import { exampleReducer, exampleName } from './example'
 
 import type { TypedUseSelectorHook } from 'react-redux'
 
@@ -10,6 +11,7 @@ export const store = configureStore({
   reducer: {
     [sceneName]: sceneReducer,
     [userName]: userReducers,
+    [exampleName]: exampleReducer
   }
 })
 
@@ -28,4 +30,5 @@ export const AppStore = ({ children }: { children: any }) => (
 
 export default store
 export * from './scene'
-export * from './user'
+export * from './user'
+export * from './example'

+ 0 - 2
src/store/scene.ts

@@ -18,8 +18,6 @@ import type { ThunkState } from './help'
 import type { Scenes, Scene } from 'api'
 import type { StoreState } from './'
 
-
-export type { Scenes, Scene } from 'api'
 export type SceneState = ThunkState<{value: Scenes}>
 
 const initialState: SceneState = {

+ 75 - 0
src/views/example/columns.tsx

@@ -0,0 +1,75 @@
+
+import { ActionsButton } from 'components'
+import { Dropdown, Menu, Button } from 'antd'
+import { DownOutlined } from '@ant-design/icons'
+
+import type { Example } from 'api'
+import type { ColumnsType } from 'antd/es/table';
+import type { ColumnAction } from 'components'
+import type { MenuProps } from 'antd'
+
+
+export type ExampleColumn = ColumnsType<Example>[number]
+
+const titleColumn: ExampleColumn = {
+  title: '标题',
+  dataIndex: 'caseTitle',
+  key: 'caseTitle',
+}
+
+const timeColumn: ExampleColumn = {
+  title: '创建时间',
+  dataIndex: 'createTime',
+  key: 'createTime',
+}
+
+export const SceneAction = ({ example }: { example: Example }) => {
+  const actions: ColumnAction[] = [
+    { text: '查看', action() {} },
+    { text: '删除', action() {}, bind: { danger: true } }
+  ]
+  const menus = [
+    { key: 'sceneManage', label: '场景管理' },
+    { key: 'fuse', label: '多元融合' },
+    { key: 'getView', label: '试图提取' },
+    { key: 'record', label: '屏幕录制' },
+    { key: 'file', label: '卷宗管理' },
+  ]
+  const handleMenuClick: MenuProps['onClick']  = (menu) => {
+    switch(menu.key) {
+      case 'sceneManage': 
+        break;
+      case 'fuse': 
+        break;
+      case 'getView': 
+        break;
+      case 'record': 
+        break;
+      case 'file': 
+        break;
+    }
+  }
+
+  return (
+    <>
+      <Dropdown overlay={<Menu items={menus} onClick={handleMenuClick} />}>
+        <Button type="link" >
+          编辑<DownOutlined />
+        </Button>
+      </Dropdown>
+      <ActionsButton actions={actions} />
+    </>
+  )
+}
+
+export const actionColumn: ExampleColumn = {
+  title: '操作',
+  key: 'action',
+  render: (_, record) => <SceneAction example={record} /> 
+}
+
+export const exampleColumns = [
+  titleColumn, 
+  timeColumn,
+  actionColumn
+]

+ 22 - 0
src/views/example/header.tsx

@@ -0,0 +1,22 @@
+import { Input, Button } from 'antd'
+
+import type { GetExamplesParams, PagingRequest } from 'api'
+
+type ListHeaderProps = {
+  onBeforeCreate?: () => void,
+  onSearch: (params: (GetExamplesParams extends PagingRequest<infer T> ? T: never)) => void
+}
+
+export const ExampleHeader = ({ onSearch, onBeforeCreate }: ListHeaderProps) => {
+  return (
+    <div className='content-header'>
+      <Button type="primary" children="上传数据" onClick={onBeforeCreate} />
+      <Input.Search
+        className='content-header-search'
+        placeholder="输入名称搜索" 
+        onSearch={caseTitle => onSearch({ caseTitle }) }
+        style={{ width: 264 }} 
+      />
+    </div>
+  )
+}

+ 29 - 1
src/views/example/index.tsx

@@ -1,5 +1,33 @@
+import { ExampleHeader } from './header'
+import { exampleColumns } from './columns'
+import { Table } from 'components'
+import { useThunkPaging } from 'hook'
+import { 
+  useSelector, 
+  examplesSelector,
+  fetchExamples,
+} from 'store'
+
+
 export const ExamplePage = () => {
-  return <div>123123</div>
+  const examples = useSelector(examplesSelector)
+  const states = useThunkPaging({ caseTitle: '' }, fetchExamples)
+  const [[paging, setPaging], [, setParams]] = states
+
+  return (
+    <div className='content-layout'>
+      <ExampleHeader 
+        onSearch={setParams}
+      />
+      <Table 
+        rowKey={'caseId'}
+        columns={exampleColumns} 
+        data={examples}
+        paging={paging}
+        onChangePaging={setPaging}
+      />
+    </div>
+  )
 }
 
 export default ExamplePage

+ 0 - 0
src/views/example/sign.tsx


+ 38 - 43
src/views/scene/cloumns.tsx

@@ -1,4 +1,5 @@
 import { Button } from 'antd'
+import { ColumnAction, ActionsButton } from 'components'
 import { 
   SceneType, 
   QuoteSceneStatusDesc, 
@@ -16,25 +17,25 @@ import {
 import type { ModelScene, QuoteScene, Scene } from 'api'
 import type { ColumnsType } from 'antd/es/table';
 
-type Column<T = Scene> = ColumnsType<T>[number]
+export type SceneColumn<T = Scene> = ColumnsType<T>[number]
 
-const titleColumn: Column = {
+export const titleColumn: SceneColumn = {
   title: '名称',
   dataIndex: 'title',
   key: 'title',
 }
 
-const sncodeColumn: Column = {
+export const sncodeColumn: SceneColumn = {
   title: 'SN码',
   dataIndex: 'snCode',
   key: 'snCode',
 }
-const timeColumn: Column = {
+export const timeColumn: SceneColumn = {
   title: '拍摄时间',
   dataIndex: 'createTime',
   key: 'createTime',
 }
-const quoteStatusColumn: Column<QuoteScene> = {
+export const quoteStatusColumn: SceneColumn<QuoteScene> = {
   title: '状态',
   dataIndex: 'status',
   key: 'status',
@@ -42,13 +43,13 @@ const quoteStatusColumn: Column<QuoteScene> = {
 }
 
 
-const rawTypeColumn: Column<ModelScene> = {
+export const rawTypeColumn: SceneColumn<ModelScene> = {
   title: '原始数据格式',
   dataIndex: 'rawType',
   key: 'rawType',
 }
 
-const modelStatusColumn: Column<ModelScene> = {
+export const modelStatusColumn: SceneColumn<ModelScene> = {
   title: '状态',
   dataIndex: 'status',
   key: 'status',
@@ -70,43 +71,37 @@ const modelStatusColumn: Column<ModelScene> = {
   }
 }
 
-export const useTypeColumns = (type: SceneType, refresh?: () => void): Column<Scene>[] => {
+export const SceneAction = ({ scene }: { scene: Scene }) => {
   const dispatch = useDispatch()
-  const actionColumn: Column = {
-    title: '操作',
-    key: 'action',
-    render: (_, record) => {
-      type Action = { text: any, action: () => any, bind?: { [key in string]: any } }
-      const actions: Action[] = []
-        
-      if (sceneIsSuccess(record)) {
-        actions.push(
-          { text: '查看', action: openSceneQueryPage.bind(null, record) },
-          { text: '编辑', action: openSceneEditPage.bind(null, record) }
-        )
-      }
-        
-      if (record.type === SceneType.SWKK) {
-        actions.push({ text: '仿真', action: () => {} })
-      } else if (record.type === SceneType.SWMX && record.status !== ModelSceneStatus.RUN) {
-        actions.push({ 
-          text: '删除', 
-          action: () => dispatch(deleteModelScene(record.id)), 
-          bind: { danger: true } 
-        })
-      }
-  
-      return actions.map(({text, action, bind}) => 
-        <Button 
-          type="link" 
-          onClick={action} 
-          {...(bind || {})}
-          key={text}
-          children={text} />
-      )
-    }
+  const actions: ColumnAction[] = []
+  if (sceneIsSuccess(scene)) {
+    actions.push(
+      { text: '查看', action: openSceneQueryPage.bind(null, scene) },
+      { text: '编辑', action: openSceneEditPage.bind(null, scene) }
+    )
+  }
+    
+  if (scene.type === SceneType.SWKK) {
+    actions.push({ text: '仿真', action: () => {} })
+  } else if (scene.type === SceneType.SWMX && scene.status !== ModelSceneStatus.RUN) {
+    actions.push({ 
+      text: '删除', 
+      action: () => dispatch(deleteModelScene(scene.id)), 
+      bind: { danger: true } 
+    })
   }
 
+  return <ActionsButton actions={actions} />
+}
+
+
+export const actionColumn: SceneColumn = {
+  title: '操作',
+  key: 'action',
+  render: (_, record) => <SceneAction scene={record} /> 
+}
+
+export const getSceneColumns = (type: SceneType): SceneColumn<Scene>[] => {
   switch(type) {
     case SceneType.SWKJ:
     case SceneType.SWSS:
@@ -117,13 +112,13 @@ export const useTypeColumns = (type: SceneType, refresh?: () => void): Column<Sc
         timeColumn,
         quoteStatusColumn,
         actionColumn
-      ] as Column<Scene>[]
+      ] as SceneColumn<Scene>[]
     case SceneType.SWMX:
       return [
         titleColumn,
         rawTypeColumn,
         modelStatusColumn,
         actionColumn
-      ] as Column<Scene>[]
+      ] as SceneColumn<Scene>[]
   }
 }

+ 46 - 0
src/views/scene/header.tsx

@@ -0,0 +1,46 @@
+import { SceneType } from "constant"
+import { memo } from 'react'
+import { Button, Upload, message, Input } from 'antd'
+import { useDispatch, uploadModelScene } from 'store'
+
+import type { GetSceneByTypeParams, PagingRequest } from 'api'
+import type { UploadProps } from 'antd'
+
+
+type ListHeaderProps = {
+  type: SceneType
+  onDataChange?: () => void,
+  onSearch: (params: (GetSceneByTypeParams extends PagingRequest<infer T> ? Omit<T, 'type'> : never)) => void
+}
+
+export const SceneHeader = memo(({ type, onSearch, onDataChange }: ListHeaderProps) => {
+  const dispatch = useDispatch()
+  const onUpload: UploadProps['beforeUpload'] = file => {
+    const isZip = ['application/zip', 'application/rar'].includes(file.type)
+    if (!isZip) {
+      message.error('只能上传zip或rar文件')
+    }
+    dispatch(uploadModelScene({ file }))
+      .unwrap()
+      .finally(onDataChange)
+    return Upload.LIST_IGNORE
+  }
+
+  const renderUpload = type === SceneType.SWMX && (
+    <Upload beforeUpload={onUpload} multiple={false} >
+      <Button type="primary" children="上传数据" />
+    </Upload>
+  )
+
+  return (
+    <div className='content-header'>
+      { renderUpload }
+      <Input.Search
+        className='content-table-search'
+        placeholder="输入名称搜索" 
+        onSearch={sceneName => onSearch({ sceneName }) }
+        style={{ width: 264 }} 
+      />
+    </div>
+  )
+})

+ 35 - 5
src/views/scene/index.tsx

@@ -1,7 +1,38 @@
 import { SceneType, SceneTypeDesc } from 'constant'
 import { useStoreState } from 'hook'
-import { Tabs } from 'components'
-import { SceneList } from './list'
+import { Tabs, Table } from 'components'
+import { getSceneColumns } from './columns'
+import { useThunkPaging } from 'hook'
+import { SceneHeader } from './header'
+import { 
+  useSelector, 
+  filterScenesSelector,
+  fetchScenes,
+} from 'store'
+
+
+const SceneTabContent = ({type}: {type: SceneType}) => {
+  const scenes = useSelector((state) => filterScenesSelector(state, { type }))
+  const states = useThunkPaging({ type, sceneName: '' }, fetchScenes)
+  const [[paging, setPaging], [, setParams], refresh] = states
+  const columns = getSceneColumns(type)
+
+  return (
+    <div className='content-layout'>
+      <SceneHeader 
+        type={type} 
+        onSearch={setParams} 
+        onDataChange={refresh} 
+      />
+      <Table 
+        columns={columns}
+        data={scenes}
+        paging={paging}
+        onChangePaging={setPaging}
+      />
+    </div>
+  )
+}
 
 const ScenePage = () => {
   const [type, setType] = useStoreState(
@@ -11,14 +42,13 @@ const ScenePage = () => {
   )
   const tabItems = Object.entries(SceneTypeDesc)
     .map(([key, val]) => [Number(key) as SceneType, val] as const)
-
-
+  
   return (
     <Tabs
       items={tabItems} 
       active={type} 
       onChange={type => setType(Number(type))} 
-      renderContent={type => <SceneList type={type} />}
+      renderContent={type => <SceneTabContent type={type} />}
     />
   )
 }

+ 0 - 82
src/views/scene/list.tsx

@@ -1,82 +0,0 @@
-import style from './style.module.scss'
-import { SceneType } from "constant"
-import { memo } from 'react'
-import { Table, Input, Button, Upload, message } from 'antd'
-import { useTypeColumns } from './cloumns'
-import { useThunkPaging } from 'hook'
-import { 
-  useSelector, 
-  useDispatch,
-  filterScenesSelector,
-  fetchScenes,
-  uploadModelScene
-} from 'store'
-
-import { GetSceneByTypeParams, PagingRequest } from 'api'
-import type { UploadProps } from 'antd'
-
-const { Search } = Input
-
-type ListHeaderProps = SceneListProps & {
-  onDataChange?: () => void,
-  onSearch: (params: (GetSceneByTypeParams extends PagingRequest<infer T> ? Omit<T, 'type'> : never)) => void
-}
-const ListHeader = memo(({ type, onSearch, onDataChange }: ListHeaderProps) => {
-  const dispatch = useDispatch()
-  const onUpload: UploadProps['beforeUpload'] = file => {
-    const isZip = ['application/zip', 'application/rar'].includes(file.type)
-    if (!isZip) {
-      message.error('只能上传zip或rar文件')
-    }
-    dispatch(uploadModelScene({ file }))
-      .unwrap()
-      .finally(onDataChange)
-    return Upload.LIST_IGNORE
-  }
-
-  const renderUpload = type === SceneType.SWMX && (
-    <Upload beforeUpload={onUpload} multiple={false} >
-      <Button type="primary" children="上传数据" />
-    </Upload>
-  )
-
-  return (
-    <div className={style['table-header']}>
-      { renderUpload }
-      <Search
-        className={style['table-search']}
-        placeholder="输入名称搜索" 
-        onSearch={sceneName => onSearch({ sceneName }) }
-        style={{ width: 264 }} 
-      />
-    </div>
-  )
-})
-
-export type SceneListProps = { type: SceneType }
-export const SceneList = memo(({ type }: SceneListProps) => {
-  const scenes = useSelector((state) => filterScenesSelector(state, { type }))
-  const states = useThunkPaging({ type, sceneName: '' }, fetchScenes)
-  const [[paging, setPaging], [, setParams], refresh] = states
-  const cloumns = useTypeColumns(type, refresh)
-
-  return (
-    <div className={style['table-layout']}>
-      <ListHeader type={type} onSearch={setParams} onDataChange={refresh} />
-
-      <Table
-        columns={cloumns} 
-        dataSource={scenes} 
-        rowKey="id"
-        pagination={{
-          showSizeChanger: false,
-          pageSize: paging.pageSize,
-          total: paging.total,
-          onChange: (page, pageSize) => setPaging({ pageNum: page, pageSize })
-        }}
-      />
-    </div>
-  )
-})
-
-export default SceneList

+ 0 - 13
src/views/scene/style.module.scss

@@ -1,13 +0,0 @@
-.table-layout {
-  padding: 0 24px;
-}
-
-.table-header {
-  padding: 24px 0;
-  display: flex;
-  justify-content: flex-end;
-}
-
-.table-search {
-  margin-left: 20px;
-}