Browse Source

对接躲远融合

bill 3 years ago
parent
commit
b2e159b0d1

+ 1 - 0
package.json

@@ -18,6 +18,7 @@
     "canvas-nest.js": "^2.0.4",
     "classnames": "^2.3.1",
     "craco-less": "^2.0.0",
+    "icons": "link:@types/@ant-design/icons",
     "js-base64": "^3.7.2",
     "lodash": "^4.17.21",
     "mitt": "^3.0.0",

File diff suppressed because it is too large
+ 10785 - 0
pnpm-lock.yaml


+ 47 - 0
src/api/files.ts

@@ -0,0 +1,47 @@
+import axios from './instance'
+import { 
+  EXAMPLE_FILE_TYPE_LIST,
+  EXAMPLE_FILE_LIST,
+  INSERT_EXAMPLE_FILE,
+  DELETE_EXAMPLE_FILE
+ } from 'constant'
+
+import type { Example } from './example'
+
+export interface ExampleFileType {
+  filesTypeId: number,
+  filesTypeName: string,
+  tbStatus: number,
+  createTime: string,
+  updateTime: string,
+}
+
+export type ExampleFileTypes = ExampleFileType[]
+
+export interface ExampleFile {
+  filesId:	number,
+  caseId:	string,
+  filesTypeId: number,
+  filesTitle:	string,
+  filesUrl:	string,
+  tbStatus:	number,
+  createTime:	string,
+  updateTime:	string,
+}
+
+export type ExampleFiles = ExampleFile[]
+
+export const getExampleFileTypes = () => 
+  axios.get<ExampleFileTypes>(EXAMPLE_FILE_TYPE_LIST)
+
+export type GetExampleFilesProps = { caseId: Example['caseId'], filesTypeId?: ExampleFileType['filesTypeId'] }
+export const getExampleFiles = (props: GetExampleFilesProps) => 
+  axios.get<ExampleFiles>(EXAMPLE_FILE_LIST, { params: props })
+
+export type AddExampleFilesProps = Pick<ExampleFile, 'caseId' | 'filesTitle' | 'filesUrl' | 'filesTypeId'>
+export const addExampleFile = (props: AddExampleFilesProps) => 
+  axios.post<ExampleFiles>(INSERT_EXAMPLE_FILE, props)
+
+export type DeleteExampleFileProps = Pick<ExampleFile, 'caseId' | 'filesId'>
+export const deleteExampleFile = (props: DeleteExampleFileProps) => 
+  axios.post<ExampleFiles>(DELETE_EXAMPLE_FILE, props)

+ 3 - 1
src/api/index.ts

@@ -22,4 +22,6 @@ export type PagingResult<T> = {
 export * from './scene'
 export * from './instance'
 export * from './user'
-export * from './example'
+export * from './example'
+export * from './files'
+export * from './sys'

+ 2 - 1
src/api/instance.ts

@@ -2,6 +2,7 @@ import { axiosFactory } from './setup'
 import { message } from 'antd'
 import { LOGIN, ResCodeDesc } from 'constant'
 import { showLoading, hideLoading } from 'components/loading'
+import { baseURI } from 'env'
 
 const instance = axiosFactory()
 
@@ -42,6 +43,6 @@ addHook({
 })
 
 addUnsetTokenURLS(LOGIN)
-setDefaultURI('/api')
+setDefaultURI(baseURI)
 
 export default axios

+ 28 - 0
src/api/sys.ts

@@ -0,0 +1,28 @@
+import { axios } from './instance'
+import { jsonToForm, UploadProgressCallback, uploadProgressFactory } from 'utils'
+import { UPLOAD_HEADS, UPLOAD_FILE } from 'constant'
+
+import type { UploadProps } from 'antd'
+
+export const uploadFile = (file: File, progressCallback?: UploadProgressCallback) => 
+  axios<string>({
+    url: UPLOAD_FILE,
+    method: "POST",
+    data: jsonToForm({ file }), 
+    headers: UPLOAD_HEADS,
+    onUploadProgress: progressCallback && uploadProgressFactory(progressCallback)
+  })
+
+export const AntUploadProps: UploadProps = {
+  async customRequest(option) {
+    try {
+      const url = await uploadFile(option.file as File, (percent) => {
+        option.onProgress && option.onProgress({ percent })
+      })
+      option.onSuccess &&  option.onSuccess(url)
+      return url
+    } catch(e: any) {
+      option.onError &&  option.onError(e)
+    }
+  }
+}

+ 10 - 1
src/constant/api.ts

@@ -29,4 +29,13 @@ export const EXAMPLE_LIST = `/fusion/case/list`
 export const SET_EXAMPLE = `/fusion/case/addOrUpdate`
 export const DELETE_EXAMPLE = `/fusion/case/delete`
 export const EXAMPLE_SCENE_LIST = `/fusion/case/sceneList`
-export const REP_EXAMPLE_SCENES = `/fusion/case/addScene`
+export const REP_EXAMPLE_SCENES = `/fusion/case/addScene`
+
+// 案件卷宗
+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 UPLOAD_FILE = `/fusion/upload/file`

+ 1 - 1
src/constant/scene.ts

@@ -21,7 +21,7 @@ export const SceneTypeDomain: { [key in SceneType]: string } = {
   [SceneType.SWKK]: 'https://test.4dkankan.com',
   [SceneType.SWKJ]: 'https://test.4dkankan.com',
   [SceneType.SWSS]: 'https://uat-laser.4dkankan.com/uat',
-  [SceneType.SWMX]: 'https://uat-laser.4dkankan.com/uat',
+  [SceneType.SWMX]: process.env.NODE_ENV === 'development' ? 'http://localhost:5173' : 'https://uat-laser.4dkankan.com/uat',
 }
 
 export const SceneTypePaths: { [key in SceneType]: string[] } = {

+ 3 - 0
src/env.ts

@@ -0,0 +1,3 @@
+export const baseURI = '/api'
+
+export const fuseCodeDomain = `http://localhost:5173`

+ 58 - 0
src/store/files.ts

@@ -0,0 +1,58 @@
+import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
+import { createArrayThunkSelects } from './help'
+import { 
+  getExampleFileTypes, 
+  getExampleFiles
+} from 'api'
+
+import type { ExampleFiles, ExampleFileTypes } from 'api'
+import type { StoreState } from './'
+
+
+export type ExampleFileState = {
+  value: ExampleFiles, 
+  types: ExampleFileTypes
+}
+
+const initialState: ExampleFileState = {
+  value: [],
+  types: []
+}
+
+const exampleFileSlice = createSlice({
+  name: 'example/file',
+  initialState,
+  reducers: {
+  },
+  extraReducers(builder) {
+    builder
+      .addCase(fetchExampleFiles.fulfilled, (state, action) => {
+        state.value = action.payload
+      })
+      .addCase(fetchExampleFileTypes.fulfilled, (state, action) => {
+        state.types = action.payload
+      })
+  }
+})
+
+
+export const exampleFileName = exampleFileSlice.name
+export const exampleFileReducer = exampleFileSlice.reducer
+export const {
+  listSelector: exampleFilesSelector,
+  findSelector: exampleFileSelector,
+  filterSelector: filterExampleFilesSelector
+} = createArrayThunkSelects(
+  (state: StoreState) => state[exampleFileName].value,
+  'filesId'
+)
+export const exampleTypeFiles = (state: StoreState) => {
+  const { types, value } = state['example/file']
+  return types.map(type => ({
+    ...type,
+    children: value.filter(file => file.filesTypeId === type.filesTypeId)
+  }))
+}
+
+export const fetchExampleFiles = createAsyncThunk('fetch/example/files', getExampleFiles)
+export const fetchExampleFileTypes = createAsyncThunk('fetch/example/filesTypes', getExampleFileTypes)

+ 5 - 2
src/store/index.tsx

@@ -4,6 +4,7 @@ import { useDispatch as useDispatchRaw, useSelector as useSelectorRaw } from 're
 import { sceneReducer, sceneName } from './scene'
 import { userReducers, userName } from './user'
 import { exampleReducer, exampleName } from './example'
+import { exampleFileName, exampleFileReducer } from './files'
 
 import type { TypedUseSelectorHook } from 'react-redux'
 
@@ -11,7 +12,8 @@ export const store = configureStore({
   reducer: {
     [sceneName]: sceneReducer,
     [userName]: userReducers,
-    [exampleName]: exampleReducer
+    [exampleName]: exampleReducer,
+    [exampleFileName]: exampleFileReducer
   }
 })
 
@@ -31,4 +33,5 @@ export const AppStore = ({ children }: { children: any }) => (
 export default store
 export * from './scene'
 export * from './user'
-export * from './example'
+export * from './example'
+export * from './files'

+ 2 - 1
src/utils/index.ts

@@ -4,4 +4,5 @@ export * from './route'
 export * from './encode'
 export * from './setState'
 export * from './serve'
-export * from './sys'
+export * from './sys'
+export * from './url'

+ 9 - 0
src/utils/url.ts

@@ -0,0 +1,9 @@
+export const getHref = (domain: string, pathname = '', params: { [key in string]: string | null } = {}) => {
+  const url = new URL(pathname, domain)
+
+  for (const [name, val] of Object.entries(params)) {
+    url.searchParams.append(name, val || '')
+  }
+
+  return url.href
+}

+ 2 - 162
src/views/example/edit.tsx

@@ -1,22 +1,9 @@
-import { getExampleScenes } from 'api'
-import { useEffect, useState } from 'react'
-import { SceneType, QuoteSceneStatus, SceneTypeDesc } from 'constant';
-import { Table } from 'components'
+import { useState } from 'react'
 import { Modal, Button, Input, message } from 'antd'
 import { EditOutlined, CheckOutlined } from '@ant-design/icons'
-import { ScenePage, sceneTitleColumn, sceneTimeColumn, sceneActionColumn } from 'views/scene'
-import { fetchScenes, filterScenesSelector, useSelector, getScenesIdents, getSceneIdent } from 'store'
-import { useThunkPaging, useRefersh } from 'hook'
 import style from './style.module.scss'
 
-import type { Example, Scene, SceneIdents } from "api";
-import type { SceneColumn } from 'views/scene'
-
-export type ExampleScenesProps = Pick<Example, 'caseId'> & { 
-  onClose: () => void,
-  onChangeScenes: (newSceneIds: SelectScenesProps['sceneIdents'], oldSceneIds: SelectScenesProps['sceneIdents']) => void
-  // onAddScene: () => void
-}
+import type { Example } from "api";
 
 export type EditExampleTitleProps = {
   example: Example
@@ -85,151 +72,4 @@ export const InsertExample = (props: InsertExampleProps) => {
       </div>
     </Modal>
   )
-}
-
-export const ExampleScenes = (props: ExampleScenesProps) => {
-  const [scenes, setScenes] = useState<Scene[]>([])
-  const [inSelectMode, setInSelectMode] = useState(false)
-  const idents = getScenesIdents(scenes)
-  const columns: SceneColumn[] = [
-    sceneTitleColumn, 
-    {
-      title: '类型',
-      key: 'title',
-      render: (_, scene) => SceneTypeDesc[scene.type]
-    },
-    sceneTimeColumn, 
-    sceneActionColumn
-  ]
-  const fetchExampleScenes = () => {
-    getExampleScenes({ caseId: props.caseId })
-      .then(setScenes)
-  }
-  useEffect(fetchExampleScenes, [props.caseId])
-
-  const renderSelectMode = inSelectMode 
-    && <SelectScenes 
-        onClose={() => setInSelectMode(false)}
-        sceneIdents={idents}
-        onSelect={async newIdents => {
-          await props.onChangeScenes(newIdents, idents)
-          await fetchExampleScenes()
-          setInSelectMode(false)
-        }}
-      />
-  const renderSelf = (
-    <Modal 
-      width="800px"
-      title="案件场景管理" 
-      visible={true} 
-      onOk={props.onClose} 
-      onCancel={props.onClose}
-      okText="确定"
-      cancelText="取消"
-    >
-      <div className={style['model-header']}>
-        <Button type="primary" onClick={() => setInSelectMode(true)}>
-          添加场景
-        </Button>
-      </div>
-      <Table 
-        rowKey={'id'}
-        columns={columns} 
-        data={scenes}
-      />
-    </Modal>
-  )
-  
-  return <>
-    {renderSelectMode}
-    {renderSelf}
-  </>
-}
-
-export type SelectScenesProps = { 
-  sceneIdents: SceneIdents
-  onClose: () => void,
-  onSelect: (ids: SelectScenesProps['sceneIdents']) => void
-}
-export const SelectScenes = ({ sceneIdents, ...props }: SelectScenesProps) => {
-  let idents:SceneIdents = sceneIdents.map(ident => ({
-    ...ident,
-    numList: [...ident.numList]
-  }))
-  const getTypeIdents = (type: SceneType) => idents.find(ident => ident.type === type) as SceneIdents[number]
-
-  const getSelectIds = (type: SceneType, scenes: Scene[]) => {
-    const typeIdents = getTypeIdents(type)
-    const selectedIds = []
-    for (const scene of scenes) {
-      const sceneIdent = getSceneIdent(scene)
-      if (typeIdents.numList.includes(sceneIdent)) {
-        selectedIds.push(scene.id)
-      }
-    }
-    return selectedIds
-  }
-  const replaceIdents = (type: SceneType, scenes: Scene[], selectScenes: Scene[]) => {
-    const typeIdents = getTypeIdents(type)
-    for (const scene of scenes) {
-      const sceneIdent = getSceneIdent(scene)
-      const inSelect = selectScenes.includes(scene)
-      const identIndex = typeIdents?.numList.indexOf(sceneIdent)
-      if (~identIndex && !inSelect) {
-        typeIdents.numList.splice(identIndex, 1)
-      } else if (!~identIndex && inSelect) {
-        typeIdents.numList.push(sceneIdent)
-      }
-    }
-  }
-
-  const Content = ({type}: {type: SceneType}) => {
-    const scenes = useSelector((state) => filterScenesSelector(state, { type }))
-    const states = useThunkPaging({ type, sceneName: '', status: QuoteSceneStatus.SUCCESS }, fetchScenes)
-    const [[paging, setPaging], [, setParams]] = states
-    const selectedIds = getSelectIds(type, scenes)
-    const refersh = useRefersh()
-
-    const rowSelection: any = { 
-      selectedRowKeys: selectedIds, 
-      onChange(ids: string, selectScenes: Scene[]) {
-        replaceIdents(type, scenes, selectScenes)
-        refersh()
-      }
-    };
-    
-    return (
-      <div>
-        <div className={style['model-header']}>
-          <Input.Search
-            className='content-header-search'
-            placeholder="输入名称搜索" 
-            onSearch={sceneName => setParams({ sceneName }) }
-            style={{ width: 264 }} 
-          />
-        </div>
-        <Table 
-          columns={[sceneTitleColumn, sceneTimeColumn]}
-          rowSelection={rowSelection}
-          data={scenes}
-          paging={paging}
-          onChangePaging={setPaging}
-        />
-      </div>
-    )
-  }
-
-  return (
-    <Modal 
-      width="800px"
-      title="添加场景" 
-      visible={true} 
-      onOk={() => props.onSelect(idents)} 
-      onCancel={props.onClose}
-      okText="确定"
-      cancelText="取消"
-    >
-      <ScenePage TabContent={Content} />
-    </Modal>
-  )
 }

+ 201 - 0
src/views/example/files/list.tsx

@@ -0,0 +1,201 @@
+import { useEffect, useRef, useState } from 'react'
+import { ActionsButton } from 'components'
+import style from '../style.module.scss'
+import { AntUploadProps, addExampleFile, deleteExampleFile } from 'api'
+import { UploadOutlined } from '@ant-design/icons'
+import { confirm } from 'utils'
+import { 
+  Modal, 
+  Button, 
+  Table, 
+  Empty,
+  Form, 
+  Input, 
+  Select,
+  Upload,
+  message,
+  ConfigProvider
+} from 'antd'
+import { 
+  useDispatch, 
+  useSelector, 
+  fetchExampleFileTypes, 
+  fetchExampleFiles,
+  exampleTypeFiles
+} from 'store'
+
+import type { Example, ExampleFile } from "api";
+import type { ColumnsType } from 'antd/es/table'
+import type { FormInstance } from 'antd/es/form'
+import type { UploadProps } from 'antd'
+
+
+export type ExampleScenesProps = Pick<Example, 'caseId'> & { onClose: () => void,}
+
+export const ExampleFiles = (props: ExampleScenesProps) => {
+  const [inertCaseId, setInsertCaseId] = useState<Example['caseId'] | null>(null)
+  const typeFiles = useSelector(exampleTypeFiles)
+  const dispatch = useDispatch()
+  const fileColumns: ColumnsType<ExampleFile> = [
+    {
+      width: '300px',
+      title: '名称',
+      dataIndex: 'filesTitle',
+      key: 'filesTitle',
+    },
+    {
+      width: '200px',
+      title: '创建时间',
+      dataIndex: 'createTime',
+      key: 'createTime',
+    },
+    {
+      title: '操作',
+      key: 'action',
+      render(data: ExampleFile) {
+        const actions = [
+          {
+            text: '查看', 
+            action: () => {
+              window.open(data.filesUrl)
+            }, 
+          },
+          {
+            text: '删除', 
+            action: async () => {
+              if (await confirm('确定要删除此数据?')) {
+                await deleteExampleFile(data)
+                dispatch(fetchExampleFiles({ caseId: props.caseId }))
+              }
+            }, 
+          },
+        ]
+        return <ActionsButton actions={actions} />
+      }
+    },
+  ]
+
+
+  useEffect(() => {
+    dispatch(fetchExampleFileTypes())
+    dispatch(fetchExampleFiles({ caseId: props.caseId }))
+  }, [dispatch, props.caseId])
+
+  const total = typeFiles.reduce((t, c) => t + c.children.length ,0)
+
+  const renderContent = total 
+    ? typeFiles.map((typeFile, i) => 
+      !!typeFile.children.length && (
+          <>
+            <p className={style['file-type-name']}>{typeFile.filesTypeName}</p>
+            <Table 
+              showHeader={false}
+              key={typeFile.filesTypeId}
+              pagination={false}
+              columns={fileColumns}
+              dataSource={typeFile.children}
+            />
+          </>
+        )
+      )
+    : <Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />;
+  
+  const renderAddFile = inertCaseId 
+    && <AddExampleFile caseId={inertCaseId} onClose={() => setInsertCaseId(null)} />
+
+  return (
+    <Modal 
+      width="800px"
+      title="卷宗管理" 
+      visible={true} 
+      onOk={props.onClose} 
+      onCancel={props.onClose}
+      okText="确定"
+      cancelText="取消"
+    >
+      {renderAddFile}
+      <div className={style['model-header']}>
+        <Button type="primary" onClick={() => setInsertCaseId(props.caseId)}>
+          上传附件
+        </Button>
+      </div>
+      <ConfigProvider renderEmpty={() => null}>
+        <Table 
+          className={style['header-table']}
+          pagination={false}
+          columns={fileColumns}
+          dataSource={[]}
+        />
+      </ConfigProvider>
+      { renderContent }
+    </Modal>
+  )
+}
+
+export type AddExampleFileProps = Pick<Example, 'caseId'> & { onClose: () => void,}
+export const AddExampleFile = (props: AddExampleFileProps) => {
+  const dispatch = useDispatch()
+  const from = useRef<FormInstance | null>(null)
+  const types = useSelector(state => state['example/file'].types)
+  const renderTypeOptions = types.map(type => (
+    <Select.Option value={type.filesTypeId} children={type.filesTypeName} key={type.filesTypeId} />
+  ))
+  const onFinish = async (values: any) => {
+    if (!values.filesUrl.fileList.length) {
+      message.error('附件文件不能为空!')
+    }
+    await addExampleFile({
+      ...values,
+      caseId: props.caseId,
+      filesUrl: values.filesUrl.fileList[0].response
+    })
+    props.onClose()
+    dispatch(fetchExampleFiles({ caseId: props.caseId }))
+  };
+  const onSubmit = () => {
+    from.current?.submit()
+  }
+
+  const uploadProps: UploadProps = {
+    ...AntUploadProps,
+    listType: 'picture',
+    maxCount: 1,
+    onPreview(file) {
+      window.open(file.response)
+    }
+  };
+
+  return (
+    <Modal 
+      width="400px"
+      title="上传附件" 
+      visible={true} 
+      onOk={onSubmit} 
+      onCancel={props.onClose}
+      okText="确定"
+      cancelText="取消"
+    >
+      <Form 
+        labelCol={{span: 8}} 
+        wrapperCol={{span: 16}}  
+        name="control-ref" 
+        ref={from} 
+        onFinish={onFinish}
+      >
+        <Form.Item name="filesTypeId" label="附件类型" rules={[{ required: true, message: '附件类型不能为空' }]}>
+          <Select placeholder="选择附件类型">
+            {renderTypeOptions}
+          </Select>
+        </Form.Item>
+        <Form.Item name="filesUrl" label="上传附件" rules={[{ required: true, message: '附件文件不能为空' }]}>
+          <Upload {...uploadProps}>
+            <Button icon={<UploadOutlined />}>请上传pdf/word/jpg格式文件</Button>
+          </Upload>
+        </Form.Item>
+        <Form.Item name="filesTitle" label="附件标题" rules={[{ required: true, message: '附件标题不能为空' }]}>
+          <Input />
+        </Form.Item>
+      </Form>
+    </Modal>
+  )
+}

+ 27 - 9
src/views/example/index.tsx

@@ -5,10 +5,13 @@ import { useThunkPaging } from 'hook'
 import { Dropdown, Menu, Button } from 'antd'
 import { DownOutlined } from '@ant-design/icons'
 import { useSelector, examplesSelector, fetchExamples } from 'store'
-import { ExampleScenes, EditExampleTitle, InsertExample } from './edit'
-import { setExample, repExampleScenes, deleteExample } from 'api'
+import { EditExampleTitle, InsertExample } from './edit'
+import { setExample, repExampleScenes, deleteExample, getToken } from 'api'
+import { ExampleScenes } from './scene/list'
 import { useState } from 'react'
-import { confirm } from 'utils'
+import { confirm, getHref } from 'utils'
+import { ExampleFiles } from './files/list'
+import { fuseCodeDomain } from 'env'
 
 import type { ColumnAction } from 'components'
 import type { MenuProps } from 'antd'
@@ -67,7 +70,8 @@ export const ExamplePage = () => {
   const examples = useSelector(examplesSelector)
   const states = useThunkPaging({ caseTitle: '' }, fetchExamples)
   const [[paging, setPaging], [, setParams], refresh] = states
-  const [editId, setEditId] = useState<Example['caseId'] | null>(null)
+  const [scenesCaseId, setScenesCaseId] = useState<Example['caseId'] | null>(null)
+  const [fileCaseId, setFileCaseId] = useState<Example['caseId'] | null>(null)
   const [inInsert, setInInsert] = useState(false)
   const columns: ExampleColumn[] = [
     {
@@ -95,7 +99,15 @@ export const ExamplePage = () => {
             }
           }}
           example={record}
-          sceneManage={() => setEditId(record.caseId)}
+          sceneManage={() => setScenesCaseId(record.caseId)}
+          file={() => setFileCaseId(record.caseId)}
+          fuse={() => {
+            const params = { 
+              token: getToken(),
+              caseId: record.caseId.toString()
+            }
+            window.open(getHref(fuseCodeDomain, '', params))
+          }}
         /> 
       )
     }
@@ -109,17 +121,23 @@ export const ExamplePage = () => {
           setInInsert(false)
         }}
       />
-  const renderEditScene = editId 
+  const renderEditScene = scenesCaseId 
     && <ExampleScenes
-        caseId={editId} 
-        onClose={() => setEditId(null)} 
-        onChangeScenes={(idents) => repExampleScenes({ sceneNumParam: idents, caseId: editId })}
+        caseId={scenesCaseId} 
+        onClose={() => setScenesCaseId(null)} 
+        onChangeScenes={(idents) => repExampleScenes({ sceneNumParam: idents, caseId: scenesCaseId })}
+      />
+  const renderEditFile = fileCaseId
+    && <ExampleFiles
+        caseId={fileCaseId} 
+        onClose={() => setFileCaseId(null)} 
       />
 
   return (
     <div className='content-layout'>
       { renderAddExample }
       { renderEditScene }
+      { renderEditFile }
       <ExampleHeader 
         onBeforeCreate={() => setInInsert(true)}
         onSearch={setParams}

+ 78 - 0
src/views/example/scene/list.tsx

@@ -0,0 +1,78 @@
+import { getExampleScenes } from 'api'
+import { useEffect, useState } from 'react'
+import { SceneTypeDesc } from 'constant';
+import { Table } from 'components'
+import { Modal, Button } from 'antd'
+import { sceneTitleColumn, sceneTimeColumn, sceneActionColumn } from 'views/scene'
+import { getScenesIdents } from 'store'
+import { SelectScenes } from './select'
+import style from '../style.module.scss'
+
+import type { Example, Scene } from "api";
+import type { SceneColumn } from 'views/scene'
+import type { SelectScenesProps } from './select'
+
+
+export type ExampleScenesProps = Pick<Example, 'caseId'> & { 
+  onClose: () => void,
+  onChangeScenes: (newSceneIds: SelectScenesProps['sceneIdents'], oldSceneIds: SelectScenesProps['sceneIdents']) => void
+}
+
+export const ExampleScenes = (props: ExampleScenesProps) => {
+  const [scenes, setScenes] = useState<Scene[]>([])
+  const [inSelectMode, setInSelectMode] = useState(false)
+  const idents = getScenesIdents(scenes)
+  const columns: SceneColumn[] = [
+    sceneTitleColumn, 
+    {
+      title: '类型',
+      key: 'title',
+      render: (_, scene) => SceneTypeDesc[scene.type]
+    },
+    sceneTimeColumn, 
+    sceneActionColumn
+  ]
+  const fetchExampleScenes = () => {
+    getExampleScenes({ caseId: props.caseId })
+      .then(setScenes)
+  }
+  useEffect(fetchExampleScenes, [props.caseId])
+
+  const renderSelectMode = inSelectMode 
+    && <SelectScenes 
+        onClose={() => setInSelectMode(false)}
+        sceneIdents={idents}
+        onSelect={async newIdents => {
+          await props.onChangeScenes(newIdents, idents)
+          await fetchExampleScenes()
+          setInSelectMode(false)
+        }}
+      />
+  const renderSelf = (
+    <Modal 
+      width="800px"
+      title="案件场景管理" 
+      visible={true} 
+      onOk={props.onClose} 
+      onCancel={props.onClose}
+      okText="确定"
+      cancelText="取消"
+    >
+      <div className={style['model-header']}>
+        <Button type="primary" onClick={() => setInSelectMode(true)}>
+          添加场景
+        </Button>
+      </div>
+      <Table 
+        rowKey={'modelId'}
+        columns={columns} 
+        data={scenes}
+      />
+    </Modal>
+  )
+  
+  return <>
+    {renderSelectMode}
+    {renderSelf}
+  </>
+}

+ 100 - 0
src/views/example/scene/select.tsx

@@ -0,0 +1,100 @@
+import { SceneType, QuoteSceneStatus } from 'constant';
+import { Table } from 'components'
+import { Modal, Input } from 'antd'
+import { ScenePage, sceneTitleColumn, sceneTimeColumn } from 'views/scene'
+import { fetchScenes, filterScenesSelector, useSelector, getSceneIdent } from 'store'
+import { useThunkPaging, useRefersh } from 'hook'
+import style from '../style.module.scss'
+
+import type { Scene, SceneIdents } from "api";
+
+export type SelectScenesProps = { 
+  sceneIdents: SceneIdents
+  onClose: () => void,
+  onSelect: (ids: SelectScenesProps['sceneIdents']) => void
+}
+
+export const SelectScenes = ({ sceneIdents, ...props }: SelectScenesProps) => {
+  let idents:SceneIdents = sceneIdents.map(ident => ({
+    ...ident,
+    numList: [...ident.numList]
+  }))
+  const getTypeIdents = (type: SceneType) => idents.find(ident => ident.type === type) as SceneIdents[number]
+
+  const getSelectIds = (type: SceneType, scenes: Scene[]) => {
+    const typeIdents = getTypeIdents(type)
+    const selectedIds = []
+    for (const scene of scenes) {
+      const sceneIdent = getSceneIdent(scene)
+      if (typeIdents.numList.includes(sceneIdent)) {
+        selectedIds.push(scene.id)
+      }
+    }
+    return selectedIds
+  }
+  const replaceIdents = (type: SceneType, scenes: Scene[], selectScenes: Scene[]) => {
+    const typeIdents = getTypeIdents(type)
+    for (const scene of scenes) {
+      const sceneIdent = getSceneIdent(scene)
+      const inSelect = selectScenes.includes(scene)
+      const identIndex = typeIdents?.numList.indexOf(sceneIdent)
+      if (~identIndex && !inSelect) {
+        typeIdents.numList.splice(identIndex, 1)
+      } else if (!~identIndex && inSelect) {
+        typeIdents.numList.push(sceneIdent)
+      }
+    }
+  }
+
+  const Content = ({type}: {type: SceneType}) => {
+    const scenes = useSelector((state) => filterScenesSelector(state, { type }))
+    const states = useThunkPaging({ type, sceneName: '', status: QuoteSceneStatus.SUCCESS }, fetchScenes)
+    const [[paging, setPaging], [, setParams]] = states
+    const selectedIds = getSelectIds(type, scenes)
+    const refersh = useRefersh()
+
+    const rowSelection: any = { 
+      selectedRowKeys: selectedIds, 
+      onChange(ids: string, selectScenes: Scene[]) {
+        replaceIdents(type, scenes, selectScenes)
+        refersh()
+      }
+    };
+    
+    return (
+      <div>
+        <div className={style['model-header']}>
+          <Input.Search
+            className='content-header-search'
+            placeholder="输入名称搜索" 
+            onSearch={sceneName => setParams({ sceneName }) }
+            style={{ width: 264 }} 
+          />
+        </div>
+        <Table 
+          columns={[sceneTitleColumn, sceneTimeColumn]}
+          rowSelection={rowSelection}
+          data={scenes}
+          paging={paging}
+          onChangePaging={setPaging}
+        />
+      </div>
+    )
+  }
+
+  return (
+    <Modal 
+      width="800px"
+      title="添加场景" 
+      visible={true} 
+      onOk={() => props.onSelect(idents)} 
+      onCancel={props.onClose}
+      okText="确定"
+      cancelText="取消"
+    >
+      <ScenePage TabContent={Content} />
+    </Modal>
+  )
+}
+
+export default SelectScenes

+ 10 - 0
src/views/example/style.module.scss

@@ -31,4 +31,14 @@
     margin-right: 10px;
     flex: none;
   }
+}
+
+.file-type-name {
+  padding: 16px;
+  border-bottom: 1px solid #f0f0f0;
+  color: #26559B;
+}
+
+.header-table :global(.ant-table-tbody) {
+  display: none;
 }

+ 3 - 4
src/views/scene/columns.tsx

@@ -57,11 +57,10 @@ export const modelSceneStatusColumn: SceneColumn<ModelScene> = {
     }
 
     const color = {
-      ghost: record.status === ModelSceneStatus.ERR,
-      primary: record.status === ModelSceneStatus.RUN
+      // ghost: record.status === ModelSceneStatus.ERR,
+      // primary: record.status === ModelSceneStatus.RUN
     }
-
-    return <Button type="text" {...color} children={desc} />
+    return <Button type="text" {...color} children={desc}  />
   }
 }
 

File diff suppressed because it is too large
+ 0 - 9764
yarn.lock