shaogen1995 10 jam lalu
induk
melakukan
e4cc155458

+ 2 - 1
后台管理/public/myData/index.js

@@ -7,4 +7,5 @@ const nodeSocketUrl = 'https://sit-nmgzhanche.4dage.com'
 const nodeSocketPath = '/websocket/'
 
 // 大模型接口地址
-const modelUrl = 'xxxxxxxxxxxxxxxxxx'
+const modelUrl = 'http://192.168.20.44:11434/api/chat'
+const modelName = 'qwen3:4b-instruct-2507-q4_K_M'

+ 2 - 4
后台管理/src/components/MyPopconfirm.tsx

@@ -7,12 +7,11 @@ type Props = {
     | '取消'
     | '重置密码'
     | '退出登录'
-    | '清空数据'
+    | '清空记录'
     | '撤回'
     | '恢复'
     | '导入当前合格数据'
     | '取消关注'
-    | '清空'
   onConfirm: () => void
   Dom?: React.ReactNode
   loc?: 'bottom'
@@ -26,12 +25,11 @@ function MyPopconfirm({ txtK, onConfirm, Dom, loc, disabled }: Props) {
       取消: ['放弃编辑后,信息将不会保存!', '放弃'],
       重置密码: ['密码重制后为nmg123456,是否重置?', '重置'],
       退出登录: ['确定退出吗?', '确定'],
-      清空数据: ['确定清空吗?', '确定'],
       撤回: ['确定撤回吗?', '确定'],
       恢复: ['确定恢复吗?', '确定'],
       导入当前合格数据: ['确定导入吗?如数据太大可能需要等待', '确定'],
       取消关注: ['确定取消关注吗?', '确定'],
-      清空: ['确定清空数据嘛?', '确定']
+      清空记录: ['确定清空记录嘛?', '确定']
     }
     return Reflect.get(obj, txtK) || ['', '']
   }, [txtK])

+ 1 - 1
后台管理/src/index.tsx

@@ -22,7 +22,7 @@ root.render(
     locale={locale}
     theme={{
       token: {
-        colorPrimary: '#412607'
+        colorPrimary: '#695640'
       }
     }}
   >

+ 5 - 1
后台管理/src/pages/A1facility/index.module.scss

@@ -1,6 +1,10 @@
 .A1facility {
   background-color: #edeae0;
-  position: relative;
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
 
   :global {
     .A1main {

+ 101 - 0
后台管理/src/pages/A2content/A2tab1/index.module.scss

@@ -0,0 +1,101 @@
+.A2tab1 {
+  display: flex;
+  :global {
+    .A21tit {
+      font-size: 20px;
+      font-weight: 700;
+      color: var(--themeColor);
+    }
+
+    .A21left {
+      padding: 10px 15px;
+      width: calc(50% - 8px);
+      border: 1px solid var(--themeColor);
+      margin-right: 16px;
+      .A21l1 {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 10px;
+      }
+      textarea {
+        height: 160px;
+      }
+
+      .A21l2 {
+        margin-top: 15px;
+        .A21lBtn {
+          & > span {
+            color: #999;
+            margin-right: 10px;
+          }
+        }
+      }
+      .ant-table-cell {
+        padding: 8px !important;
+      }
+    }
+    .A21right {
+      padding: 10px 15px;
+      width: calc(50% - 8px);
+      border: 1px solid var(--themeColor);
+      .A21r1 {
+        display: flex;
+        justify-content: space-between;
+      }
+      .A21r2 {
+        border-radius: 4px;
+        margin: 15px 0;
+        height: calc(100% - 170px);
+        border: 1px solid #ccc;
+        .A21r2Main {
+          padding: 10px;
+          width: 100%;
+          height: 100%;
+          overflow: auto;
+          .A21r2Row {
+            font-size: 16px;
+            margin-bottom: 10px;
+            padding-right: 200px;
+            & > div {
+              font-weight: 700;
+              color: var(--themeColor);
+            }
+          }
+          .A21r2Row2 {
+            padding-right: 0px;
+            padding-left: 200px;
+
+            & > div {
+              display: flex;
+              justify-content: flex-end;
+            }
+            & > p {
+              display: flex;
+              justify-content: flex-end;
+            }
+          }
+        }
+        .A21r2Null {
+          display: flex;
+          width: 100%;
+          height: 100%;
+          justify-content: center;
+          align-items: center;
+          font-size: 16px;
+          letter-spacing: 4px;
+        }
+      }
+      .A21r3 {
+        display: flex;
+        & > div {
+          margin-left: 10px;
+          display: flex;
+          flex-direction: column;
+          justify-content: space-around;
+          align-items: center;
+        }
+      }
+    }
+  }
+}

+ 298 - 0
后台管理/src/pages/A2content/A2tab1/index.tsx

@@ -0,0 +1,298 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Switch } from 'antd'
+import TextArea from 'antd/es/input/TextArea'
+import { useDispatch, useSelector } from 'react-redux'
+import classNames from 'classnames'
+import {
+  API_A21del,
+  API_A21delLog,
+  API_A21getInfo,
+  API_A21getLog,
+  API_A21getMes,
+  API_A21send,
+  API_A21setInfo,
+  API_A21setLog,
+  API_A21switchFu
+} from '@/store/action/A2content'
+import { RootState } from '@/store'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { MessageFu } from '@/utils/message'
+import { handeUpFile } from '@/utils/upFile'
+import MyTable from '@/components/MyTable'
+import { downloadFileByUrl } from '@/utils/history'
+function A2tab1() {
+  const dispatch = useDispatch()
+
+  const getInfoFu = useCallback(() => {
+    dispatch(API_A21getInfo())
+  }, [dispatch])
+
+  useEffect(() => {
+    getInfoFu()
+  }, [getInfoFu])
+
+  const A21info = useSelector((state: RootState) => state.A2content.A21info)
+
+  const intro = useMemo(() => {
+    return A21info.intro || ''
+  }, [A21info.intro])
+
+  const file = useMemo(() => {
+    return A21info.file || []
+  }, [A21info.file])
+
+  const [value, setValue] = useState('')
+
+  useEffect(() => {
+    setValue(intro)
+  }, [intro])
+
+  const [txtShow, setTxtShow] = useState(false)
+
+  // 提示词修改 点击确定
+  const txtBtnOk = useCallback(async () => {
+    const res = await API_A21setInfo({ intro: value })
+
+    if (res.code === 0) {
+      MessageFu.success('修改成功')
+      setTxtShow(false)
+      getInfoFu()
+    }
+  }, [getInfoFu, value])
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传文件成功
+  const upSuccFu = useCallback(
+    async (data: any) => {
+      const fileArr = [...file, data]
+
+      const res = await API_A21setInfo({ fileIds: fileArr.map(v => v.id).join(',') })
+
+      if (res.code === 0) {
+        MessageFu.success('上传成功')
+        getInfoFu()
+      }
+    },
+    [file, getInfoFu]
+  )
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res: any = await API_A21del([id])
+      if (res.code === 0) {
+        MessageFu.success('删除成功')
+        getInfoFu()
+      }
+    },
+    [getInfoFu]
+  )
+
+  // 启用
+  const switchFu = useCallback(
+    async (id: number, val: 1 | 0 | null) => {
+      const usable = val === 1 ? 0 : 1
+
+      const res = await API_A21switchFu(id, usable)
+      if (res.code === 0) {
+        MessageFu.success('操作成功')
+        getInfoFu()
+      }
+    },
+    [getInfoFu]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '启用',
+        render: (item: any) => (
+          <Switch checked={item.display === 1} onChange={() => switchFu(item.id, item.display)} />
+        )
+      },
+      {
+        title: '操作',
+        render: (item: any) => (
+          <>
+            <Button size='small' type='text' onClick={() => downloadFileByUrl(item.filePath)}>
+              下载
+            </Button>
+            <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+          </>
+        )
+      }
+    ]
+  }, [delTableFu, switchFu])
+
+  // ---------------右侧----------------------
+  const [value2, setValue2] = useState('')
+
+  const [rInfo, setRInfo] = useState({ list: [] as any[], loding: false })
+
+  const getLogFu = useCallback(async () => {
+    const res = await API_A21getLog()
+    if (res.code === 0) {
+      setRInfo({ list: res.data || [], loding: true })
+      setTimeout(() => {
+        if (A21r2MainRef.current) {
+          A21r2MainRef.current.scrollTop = 999999999
+        }
+      }, 100)
+    }
+  }, [])
+
+  useEffect(() => {
+    getLogFu()
+  }, [getLogFu])
+
+  const A21r2MainRef = useRef<HTMLDivElement>(null)
+
+  // 发送消息
+  const sendMesFu = useCallback(async () => {
+    if (value2.trim().replaceAll(/\s/g, '') === '') {
+      setValue2('')
+      return MessageFu.warning('内容不能为空')
+    }
+
+    const res1 = await API_A21getMes()
+    if (res1.code === 0) {
+      const res2: any = await API_A21send(value2, rInfo.list, res1.data || [])
+
+      // 保存回答记录
+      const res3 = await API_A21setLog({ name: 'user', rtf: value2 })
+      const res4 = await API_A21setLog({ name: res2.message.role, rtf: res2.message.content })
+
+      if (res3.code === 0 && res4.code === 0) {
+        getLogFu()
+      }
+    }
+
+    setValue2('')
+  }, [getLogFu, rInfo.list, value2])
+
+  // 点击清空记录
+  const clearFu = useCallback(async () => {
+    const res = await API_A21delLog()
+    if (res.code === 0) {
+      MessageFu.success('清空记录成功')
+      getLogFu()
+    }
+  }, [getLogFu])
+
+  return (
+    <div className={styles.A2tab1}>
+      <input
+        id='upInput'
+        type='file'
+        accept='.txt'
+        ref={myInput}
+        onChange={e =>
+          handeUpFile(e, ['txt'], 'cms/ai/upload', 'A2content1', data => upSuccFu(data), {
+            isDb: 'true'
+          })
+        }
+      />
+
+      <div className='A21left'>
+        <div className='A21l1'>
+          <div className='A21tit'>提示词</div>
+          {txtShow ? (
+            <div>
+              <MyPopconfirm
+                txtK='取消'
+                onConfirm={() => {
+                  setTxtShow(false)
+                  setValue(intro)
+                }}
+              />
+              &emsp;
+              <Button type='primary' onClick={txtBtnOk}>
+                确定
+              </Button>
+            </div>
+          ) : (
+            <Button type='primary' onClick={() => setTxtShow(true)}>
+              修改
+            </Button>
+          )}
+        </div>
+        <TextArea
+          placeholder={txtShow ? '请输入内容' : '(空)'}
+          readOnly={!txtShow}
+          value={value}
+          onChange={e => setValue(e.target.value)}
+        />
+
+        <div className='A21l1 A21l2'>
+          <div className='A21tit'>知识库</div>
+          <div className='A21lBtn'>
+            <span>仅支持txt格式</span>
+            <Button type='primary' onClick={() => myInput.current?.click()}>
+              上传
+            </Button>
+          </div>
+        </div>
+
+        <MyTable
+          yHeight={475}
+          list={file}
+          columnsTemp={[['txt', '文件名称', 'fileName']]}
+          lastBtn={tableLastBtn}
+          pagingInfo={false}
+        />
+      </div>
+
+      {/* 右侧--------------------- */}
+      <div className='A21right'>
+        <div className='A21r1'>
+          <div className='A21tit'>调试预览</div>
+          <MyPopconfirm
+            txtK='清空记录'
+            onConfirm={clearFu}
+            Dom={<Button type='primary'>清空记录</Button>}
+          />
+        </div>
+
+        <div className='A21r2'>
+          {rInfo.loding && rInfo.list.length > 0 ? (
+            <div className='A21r2Main' ref={A21r2MainRef}>
+              {rInfo.list.map((item, index) => (
+                <div
+                  hidden={item.name === 'system'}
+                  key={index}
+                  className={classNames('A21r2Row', item.name === 'assistant' ? '' : 'A21r2Row2')}
+                >
+                  <div>{item.name === 'assistant' ? '数字人' : '用户'}:</div>
+                  <p>{item.rtf}</p>
+                </div>
+              ))}
+            </div>
+          ) : (
+            <div className='A21r2Null'>暂无记录</div>
+          )}
+        </div>
+
+        <div className='A21r3'>
+          <TextArea
+            placeholder='请输入内容'
+            value={value2}
+            onChange={e => setValue2(e.target.value)}
+          />
+          <div>
+            <Button onClick={() => setValue2('')}>清空</Button>
+
+            <Button type='primary' onClick={sendMesFu}>
+              发送
+            </Button>
+          </div>
+        </div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA2tab1 = React.memo(A2tab1)
+
+export default MemoA2tab1

+ 4 - 0
后台管理/src/pages/A2content/A2tab2/index.module.scss

@@ -0,0 +1,4 @@
+// .A2tab2 {
+//   :global {
+//   }
+// }

+ 65 - 0
后台管理/src/pages/A2content/A2tab2/index.tsx

@@ -0,0 +1,65 @@
+import React, { useCallback, useEffect, useMemo } from 'react'
+import styles from './index.module.scss'
+import { useDispatch, useSelector } from 'react-redux'
+import { API_A22getList } from '@/store/action/A2content'
+import { RootState } from '@/store'
+import MyTable from '@/components/MyTable'
+import { Button } from 'antd'
+
+function A2tab2() {
+  const dispatch = useDispatch()
+
+  const getListFu = useCallback(() => {
+    dispatch(API_A22getList())
+  }, [dispatch])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  const A22List = useSelector((state: RootState) => state.A2content.A22List)
+  const { show, Ref } = useSelector((state: RootState) => state.socket)
+
+  // 待完善 其他字段
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '展示位置',
+        render: (item: any) => (
+          <>
+            {show.map(v => (
+              <Button
+                onClick={() => {
+                  Ref.emit('ChangeDeviceInfo', {
+                    type: 'show',
+                    data: { name: v.name, val: Number(item.UNum) }
+                  })
+                }}
+                key={v.name}
+                type={item.UNum === v.val + '' ? 'primary' : 'default'}
+              >
+                {v.name}
+              </Button>
+            ))}
+          </>
+        )
+      }
+    ]
+  }, [Ref, show])
+
+  return (
+    <div className={styles.A2tab2}>
+      <MyTable
+        yHeight={700}
+        list={A22List}
+        columnsTemp={[['txt', '名称', 'name']]}
+        lastBtn={tableLastBtn}
+        pagingInfo={false}
+      />
+    </div>
+  )
+}
+
+const MemoA2tab2 = React.memo(A2tab2)
+
+export default MemoA2tab2

+ 4 - 0
后台管理/src/pages/A2content/A2tab3/index.module.scss

@@ -0,0 +1,4 @@
+.A2tab3 {
+  :global {
+  }
+}

+ 13 - 0
后台管理/src/pages/A2content/A2tab3/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react'
+import styles from './index.module.scss'
+function A2tab3() {
+  return (
+    <div className={styles.A2tab3}>
+      <h1>A2tab3</h1>
+    </div>
+  )
+}
+
+const MemoA2tab3 = React.memo(A2tab3)
+
+export default MemoA2tab3

+ 4 - 0
后台管理/src/pages/A2content/A2tab4/index.module.scss

@@ -0,0 +1,4 @@
+.A2tab4 {
+  :global {
+  }
+}

+ 13 - 0
后台管理/src/pages/A2content/A2tab4/index.tsx

@@ -0,0 +1,13 @@
+import React from 'react'
+import styles from './index.module.scss'
+function A2tab4() {
+  return (
+    <div className={styles.A2tab4}>
+      <h1>A2tab4</h1>
+    </div>
+  )
+}
+
+const MemoA2tab4 = React.memo(A2tab4)
+
+export default MemoA2tab4

+ 4 - 0
后台管理/src/pages/A2content/data.ts

@@ -0,0 +1,4 @@
+export type A21InfoType = {
+  intro: string
+  file: { fileName: string; filePath: string; display: 1 | 0 | null }[]
+}

+ 14 - 0
后台管理/src/pages/A2content/index.module.scss

@@ -1,4 +1,18 @@
 .A2content {
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 15px;
   :global {
+    .A2top {
+      margin-bottom: 15px;
+    }
+    .A2main {
+      width: 100%;
+      height: calc(100% - 50px);
+      & > div {
+        width: 100%;
+        height: 100%;
+      }
+    }
   }
 }

+ 30 - 2
后台管理/src/pages/A2content/index.tsx

@@ -1,9 +1,37 @@
-import React from 'react'
+import React, { useState } from 'react'
 import styles from './index.module.scss'
+import { Button } from 'antd'
+import A2tab1 from './A2tab1'
+import A2tab2 from './A2tab2'
+import A2tab3 from './A2tab3'
+import A2tab4 from './A2tab4'
+
+const tabArr = ['AI数字人', '透明展示柜', '文物互动墙', '空中成像']
+
 function A2content() {
+  const [tabAc, setTabAc] = useState('AI数字人')
+
   return (
     <div className={styles.A2content}>
-      <h1>A2content</h1>
+      <div className='A2top'>
+        {tabArr.map(item => (
+          <Button
+            onClick={() => setTabAc(item)}
+            type={tabAc === item ? 'primary' : 'default'}
+            key={item}
+            className='A2topRow'
+          >
+            {item}
+          </Button>
+        ))}
+      </div>
+
+      <div className='A2main'>
+        {tabAc === 'AI数字人' ? <A2tab1 /> : null}
+        {tabAc === '透明展示柜' ? <A2tab2 /> : null}
+        {tabAc === '文物互动墙' ? <A2tab3 /> : null}
+        {tabAc === '空中成像' ? <A2tab4 /> : null}
+      </div>
     </div>
   )
 }

+ 2 - 1
后台管理/src/pages/Layout/index.module.scss

@@ -18,6 +18,7 @@
     .layoutRightTop {
       height: 60px;
       position: relative;
+      z-index: 10;
       background-color: #fff;
       padding-left: 26px;
       display: flex;
@@ -144,7 +145,6 @@
 
     .layoutRightMain {
       height: calc(100% - 60px);
-      padding: 15px;
       background-color: #ecedf1;
 
       .mainBoxR {
@@ -152,6 +152,7 @@
         height: 100%;
         // overflow: hidden;
         position: relative;
+        padding: 15px;
 
         & > div {
           width: 100%;

+ 118 - 0
后台管理/src/store/action/A2content.ts

@@ -0,0 +1,118 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+// -------------------AI数字人-----------------------------
+
+// 获取信息
+export const API_A21getInfo = (): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.get('cms/ai/getCue')
+    if (res.code === 0) {
+      dispatch({ type: 'A21/getInfo', payload: res.data })
+    }
+  }
+}
+
+// 保存信息
+export const API_A21setInfo = (data: any) => {
+  return http.post('cms/ai/setCue', data)
+}
+
+// 删除附件
+export const API_A21del = (ids: number[]) => {
+  return http.post(
+    'cms/file/removes',
+    ids.map(v => v + '')
+  )
+}
+
+// 启用
+export const API_A21switchFu = (id: number, usable: 1 | 0) => {
+  return http.get(`cms/ai/updateDisplay/${id}/${usable}`)
+}
+
+// 获取记录
+export const API_A21getLog = () => {
+  return http.get('cms/ai/getRecord')
+}
+
+// 保存记录
+export const API_A21setLog = (data: any) => {
+  return http.post('cms/ai/saveEntity', data)
+}
+
+// 清空记录
+export const API_A21delLog = () => {
+  return http.get('cms/ai/delByUser')
+}
+
+// 获取提示词
+export const API_A21getMes = () => {
+  return http.get('cms/ai/getContent')
+}
+
+// 发送消息
+export const API_A21send = async (msg: string, list: any[], titArr: any[]) => {
+  let tit = titArr.length ? '' : '你是一个乐于助人的助手'
+
+  titArr.forEach(v => {
+    if (v.content) tit += `${v.name}:${v.content}\n`
+  })
+
+  tit = tit ? tit : '你是一个乐于助人的助手'
+
+  let messages: any[] = []
+  if (list.length === 0) {
+    const content = `你是一个乐于助人的助手。
+使用以下内容作为你所学习的知识,放在<context></context>XML标签内。
+<context>
+${tit}
+</context>
+回答用户时:
+如果你不知道,就直说你不知道。如果你在不确定的时候不知道,就寻求澄清。
+避免提及你是从上下文中获取的信息。
+并根据用户问题的语言来回答。`
+
+    messages.push({
+      role: 'system',
+      content
+    })
+
+    // 加一条新纪录
+    await API_A21setLog({ name: 'system', rtf: content })
+  } else {
+    list.forEach(v => {
+      messages.push({
+        role: v.name,
+        content: v.rtf
+      })
+    })
+  }
+
+  messages.push({
+    role: 'user',
+    content: msg
+  })
+
+  const data = {
+    model: modelName,
+    messages,
+    think: false,
+    stream: false
+  }
+
+  return http.post(modelUrl, data, {
+    baseURL: '',
+    timeout: 5000000
+  })
+}
+
+// -------------------透明展示柜-----------------------------
+
+export const API_A22getList = (): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post('cms/exhibit/getList', { pageNum: 1, pageSize: 99999 })
+    if (res.code === 0) {
+      dispatch({ type: 'A22/getList', payload: res.data || [] })
+    }
+  }
+}

+ 23 - 0
后台管理/src/store/reducer/A2content.ts

@@ -0,0 +1,23 @@
+import { A21InfoType } from '@/pages/A2content/data'
+
+// 初始化状态
+const initState = {
+  A21info: {} as A21InfoType,
+  A22List: [] as any[]
+}
+
+// 定义 action 类型
+type Props = { type: 'A21/getInfo'; payload: A21InfoType } | { type: 'A22/getList'; payload: any[] }
+
+// reducer
+export default function userReducer(state = initState, action: Props) {
+  switch (action.type) {
+    case 'A21/getInfo':
+      return { ...state, A21info: action.payload }
+    case 'A22/getList':
+      return { ...state, A22List: action.payload }
+
+    default:
+      return state
+  }
+}

+ 2 - 0
后台管理/src/store/reducer/index.ts

@@ -4,6 +4,7 @@ import { combineReducers } from 'redux'
 // 导入 登录 模块的 reducer
 
 import A0Layout from './layout'
+import A2content from './A2content'
 import socket from './socket'
 import Z6user from './Z6user'
 import Z7log from './Z7log'
@@ -12,6 +13,7 @@ import Z7log from './Z7log'
 const rootReducer = combineReducers({
   socket,
   A0Layout,
+  A2content,
   Z6user,
   Z7log
 })

+ 1 - 0
后台管理/src/types/declaration.d.ts

@@ -11,3 +11,4 @@ declare const baseUrlTempOne: string
 declare const nodeSocketUrl: string
 declare const nodeSocketPath: string
 declare const modelUrl: string
+declare const modelName: string

+ 89 - 0
后台管理/src/utils/upFile.ts

@@ -0,0 +1,89 @@
+import { API_upFile } from '@/store/action/layout'
+import { MessageFu } from './message'
+import { fileDomInitialFu } from './domShow'
+// 上传文件自动归类
+type FileObjType = {
+  [key: string]: string[]
+}
+
+export const fileImgArr = [
+  'jpg',
+  'jpeg',
+  'png',
+  'gif',
+  'bmp',
+  'tiff',
+  'webp',
+  'svg',
+  'psd',
+  'ai',
+  'ico'
+]
+export const fileVideoArr = ['mp4', 'mov', 'avi', 'mkv', 'flv', 'wmv', 'mpeg', 'webm']
+
+const fileObj: FileObjType = {
+  img: fileImgArr,
+  video: fileVideoArr,
+  model: ['4dage', 'obj', 'stl', 'fbx', 'gltf', '3ds', 'blend', 'dae', 'step'],
+  audio: ['mp3', 'wav', 'flac', 'aac', 'ogg', 'm4a'],
+  doc: ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'txt', 'csv', 'md', 'html']
+  // 'other':[]
+}
+
+export const fileTypeRes = (fileName: string) => {
+  const txtArr = fileName.split('.')
+  const txt = txtArr[txtArr.length - 1]
+
+  let type = 'other'
+
+  for (const k in fileObj) {
+    if (fileObj[k].includes(txt.toLowerCase())) type = k
+  }
+  return type
+}
+
+export const handeUpFile = async (
+  e: React.ChangeEvent<HTMLInputElement>,
+  typeArr: string[],
+  url: string,
+  dirCode: string,
+  back: (data: any) => void,
+  fromData?: any
+) => {
+  if (e.target.files) {
+    // 拿到files信息
+    const filesInfo = e.target.files[0]
+    // console.log('-----', filesInfo)
+
+    const lastNameArr = filesInfo.name.split('.')
+    let lastName = lastNameArr[lastNameArr.length - 1]
+    lastName = lastName.toLocaleLowerCase()
+
+    if (!typeArr.includes(lastName))
+      return MessageFu.warning(`只支持 ${typeArr.join('/')} 格式文件`)
+
+    // 创建FormData对象
+    const fd = new FormData()
+    fd.append('type', fileTypeRes(filesInfo.name))
+    fd.append('dirCode', dirCode)
+    fd.append('file', filesInfo)
+
+    if (fromData) {
+      for (const k in fromData) {
+        if (fromData[k]) fd.append(k, fromData[k])
+      }
+    }
+
+    e.target.value = ''
+
+    try {
+      const res = await API_upFile(fd, url)
+      if (res.code === 0) {
+        back(res.data)
+      }
+      fileDomInitialFu()
+    } catch (error) {
+      fileDomInitialFu()
+    }
+  }
+}