Browse Source

附件管理

shaogen1995 1 day ago
parent
commit
5823088bfb

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

@@ -12,6 +12,7 @@ type Props = {
   pageSize?: number
   pagingInfo?: any | boolean
   onChange?: (pageNum: number, pageSize: number) => void
+  staBtn?: any
   lastBtn?: any
   classKey?: string //一个组件多次使用的时候要传递,分别设置style
   // 表格简单的合并
@@ -48,6 +49,7 @@ function MyTable({
   },
   onChange,
   lastBtn = [],
+  staBtn = [],
   classKey = '',
   merge,
   myTitle,
@@ -149,7 +151,7 @@ function MyTable({
       className={`${styles.MyTable} MyTable${classKey}`}
       scroll={{ y: yHeight ? yHeight : '' }}
       dataSource={list}
-      columns={[...columns, ...lastBtn]}
+      columns={[...staBtn, ...columns, ...lastBtn]}
       rowKey='id'
       pagination={
         pagingInfo

+ 41 - 0
后台管理/src/components/YtableVideo/index.module.scss

@@ -0,0 +1,41 @@
+.YtableVideo {
+  display: flex;
+  justify-content: center;
+  :global {
+    .TvideoBox {
+      cursor: pointer;
+      width: 60px;
+      height: 60px;
+      position: relative;
+      .TvideoBoxLook {
+        position: absolute;
+        z-index: 10;
+        opacity: 0;
+        transition: opacity 0.3s;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 14px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, 0.6);
+        .anticon-eye {
+          font-size: 18px;
+        }
+      }
+      video {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+      }
+      &:hover {
+        .TvideoBoxLook {
+          opacity: 1;
+        }
+      }
+    }
+  }
+}

+ 36 - 0
后台管理/src/components/YtableVideo/index.tsx

@@ -0,0 +1,36 @@
+import React from 'react'
+import styles from './index.module.scss'
+import store from '@/store'
+import { EyeOutlined } from '@ant-design/icons'
+import { baseURL } from '@/utils/http'
+
+type Props = {
+  src: string
+}
+
+function YtableVideo({ src }: Props) {
+  return (
+    <div className={styles.YtableVideo}>
+      <div className='TvideoBox'>
+        <div
+          className='TvideoBoxLook'
+          onClick={() =>
+            store.dispatch({
+              type: 'layout/lookDom',
+              payload: { src: baseURL + src, type: 'video', flag: true }
+            })
+          }
+        >
+          <EyeOutlined />
+          &nbsp;
+          <div>预览</div>
+        </div>
+        <video src={baseURL + src}></video>
+      </div>
+    </div>
+  )
+}
+
+const MemoYtableVideo = React.memo(YtableVideo)
+
+export default MemoYtableVideo

+ 55 - 73
后台管理/src/components/ZupAudio/index.tsx

@@ -1,106 +1,94 @@
-import React, { useCallback, useRef } from "react";
-import styles from "./index.module.scss";
-import { MessageFu } from "@/utils/message";
-import { API_upFile } from "@/store/action/layout";
-import { fileDomInitialFu } from "@/utils/domShow";
-import { UploadOutlined, DeleteOutlined, EyeOutlined } from "@ant-design/icons";
-import store from "@/store";
-import MyPopconfirm from "../MyPopconfirm";
+import React, { useCallback, useRef } from 'react'
+import styles from './index.module.scss'
+import { MessageFu } from '@/utils/message'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import { UploadOutlined, DeleteOutlined, EyeOutlined } from '@ant-design/icons'
+import store from '@/store'
+import MyPopconfirm from '../MyPopconfirm'
 
 export type ZupAudioType = {
-  fileName: string;
-  filePath: string;
-};
+  fileName: string
+  filePath: string
+}
 
 type Props = {
-  fileInfo: ZupAudioType;
-  upDataFu: (info: ZupAudioType) => void;
-  delFu: () => void;
-  dirCode: string;
-  myUrl: string;
-  size?: number;
-  isLook?: boolean;
-};
+  fileInfo: ZupAudioType
+  upDataFu: (info: ZupAudioType) => void
+  delFu: () => void
+  dirCode: string
+  myUrl: string
+  size?: number
+  isLook?: boolean
+}
 
-function ZupAudio({
-  size = 10,
-  isLook = false,
-  fileInfo,
-  upDataFu,
-  delFu,
-  dirCode,
-  myUrl,
-}: Props) {
+function ZupAudio({ size = 10, isLook = false, fileInfo, upDataFu, delFu, dirCode, myUrl }: Props) {
   // 上传 无障碍音频的 点击
   const handeUpAudio = useCallback(
     async (e: React.ChangeEvent<HTMLInputElement>) => {
       if (e.target.files) {
         // 拿到files信息
-        const filesInfo = e.target.files[0];
+        const filesInfo = e.target.files[0]
         // console.log("-----", filesInfo);
 
         // 校验格式
-        const type = ["audio/mpeg"];
+        const type = ['audio/mpeg']
         if (!type.includes(filesInfo.type)) {
-          e.target.value = "";
-          return MessageFu.warning(`只支持.mp3格式!`);
+          e.target.value = ''
+          return MessageFu.warning(`只支持.mp3格式!`)
         }
 
         // 校验大小
         if (filesInfo.size > size * 1024 * 1024) {
-          e.target.value = "";
-          return MessageFu.warning(`最大支持${size}M!`);
+          e.target.value = ''
+          return MessageFu.warning(`最大支持${size}M!`)
         }
         // 创建FormData对象
-        const fd = new FormData();
+        const fd = new FormData()
 
-        fd.append("type", "audio");
-        fd.append("dirCode", dirCode);
-        fd.append("file", filesInfo);
-        e.target.value = "";
+        fd.append('type', 'audio')
+        fd.append('dirCode', dirCode)
+        fd.append('file', filesInfo)
+        e.target.value = ''
 
         try {
-          const res = await API_upFile(fd, myUrl);
+          const res = await API_upFile(fd, myUrl)
           if (res.code === 0) {
-            MessageFu.success("上传成功!");
+            MessageFu.success('上传成功!')
             // console.log(res);
-            upDataFu(res.data);
+            upDataFu(res.data)
           }
-          fileDomInitialFu();
+          fileDomInitialFu()
         } catch (error) {
-          fileDomInitialFu();
+          fileDomInitialFu()
         }
       }
     },
     [dirCode, myUrl, size, upDataFu]
-  );
+  )
 
-  // 上传附的ref
-  const myInput = useRef<HTMLInputElement>(null);
+  // 上传附的ref
+  const myInput = useRef<HTMLInputElement>(null)
 
   return (
-    <div
-      className={styles.ZupAudio}
-      id="upInputAudioBox"
-      hidden={isLook && !fileInfo.filePath}
-    >
+    <div className={styles.ZupAudio} id='upInputAudioBox' hidden={isLook && !fileInfo.filePath}>
       {/* 上传无障碍音频 */}
       <input
-        id="upInputAudio"
-        type="file"
-        accept=".mp3"
-        onChange={(e) => handeUpAudio(e)}
+        id='upInputAudio'
+        type='file'
+        accept='.mp3'
+        onChange={e => handeUpAudio(e)}
         ref={myInput}
       />
       {fileInfo.filePath ? (
-        <div className="ZupAudio2">
+        <div className='ZupAudio2'>
           <div title={fileInfo.fileName}>{fileInfo.fileName}</div>
           <EyeOutlined
-            title="预览"
+            title='预览'
             onClick={() =>
               store.dispatch({
-                type: "layout/lookDom",
-                payload: { src: fileInfo.filePath, type: "audio" },
+                type: 'layout/lookDom',
+                payload: { src: fileInfo.filePath, type: 'audio' }
               })
             }
           />
@@ -109,32 +97,26 @@ function ZupAudio({
             <>
               &nbsp;
               <MyPopconfirm
-                txtK="删除"
+                txtK='删除'
                 onConfirm={delFu}
-                Dom={
-                  <DeleteOutlined
-                    title="删除"
-                    className="ZTbox2X"
-                    rev={undefined}
-                  />
-                }
+                Dom={<DeleteOutlined title='删除' className='ZTbox2X' rev={undefined} />}
               />
             </>
           )}
         </div>
       ) : (
-        <div className="ZupAudio1" onClick={() => myInput.current?.click()}>
+        <div className='ZupAudio1' onClick={() => myInput.current?.click()}>
           <UploadOutlined />
-          <div className="ZupAudio1_1">
+          <div className='ZupAudio1_1'>
             <p>上传无障碍音频</p>
             <p>支持{size}MB以下mp3格式</p>
           </div>
         </div>
       )}
     </div>
-  );
+  )
 }
 
-const MemoZupAudio = React.memo(ZupAudio);
+const MemoZupAudio = React.memo(ZupAudio)
 
-export default MemoZupAudio;
+export default MemoZupAudio

+ 4 - 1
后台管理/src/pages/A1record/A1look/index.tsx

@@ -6,6 +6,7 @@ import { A1ListType, cityResFu } from '../data'
 import ImageLazy from '@/components/ImageLazy'
 import A1edit from '../A1edit'
 import A1tab1introduce from '../A1tab1introduce'
+import A1tab2file from '../A1tab2file'
 
 type Props = {
   sId: number
@@ -65,7 +66,8 @@ function A1look({ sId, closeFu }: Props) {
                 <div className='A1Lrow A1LrowAll'>
                   <div>所在位置:</div>
                   <div>
-                    {cityResFu(info)}&emsp;{info.lat || '(空)'}° ,{info.lng || '(空)'}°&emsp;
+                    {cityResFu(info)}&emsp;&emsp;{info.lat || '(空)'}° ,{info.lng || '(空)'}
+                    °&emsp;&emsp;
                     {info.address}
                   </div>
                 </div>
@@ -136,6 +138,7 @@ function A1look({ sId, closeFu }: Props) {
 
           <div className='A1Lbtn2'>
             {btnAc === '古桥介绍' ? <A1tab1introduce sInfo={info} upInfoFu={getInfoFu} /> : null}
+            {btnAc === '附件管理' ? <A1tab2file sId={sId} /> : null}
           </div>
         </div>
       </div>

+ 0 - 1
后台管理/src/pages/A1record/A1tab1introduce/index.tsx

@@ -70,7 +70,6 @@ function A1tab1introduce({ sInfo, upInfoFu }: Props) {
           <div className='A1T1box'>
             <div className='A1T1box1'>视频:</div>
             <div className='A1T1box2'>
-              {' '}
               <ZupVideos
                 isLook={true}
                 size={500}

+ 18 - 0
后台管理/src/pages/A1record/A1tab2file/index.module.scss

@@ -0,0 +1,18 @@
+.A1tab2file {
+  :global {
+    .A1T2top {
+      display: flex;
+      justify-content: space-between;
+      .A1T2top1 {
+        display: flex;
+        & > div {
+          margin-right: 20px;
+        }
+      }
+      margin-bottom: 15px;
+    }
+    .ant-table-cell {
+      padding: 8px !important;
+    }
+  }
+}

+ 282 - 0
后台管理/src/pages/A1record/A1tab2file/index.tsx

@@ -0,0 +1,282 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { A1_APIfileDel, A1_APIfileGetList, A1_APIfileSave } from '@/store/action/A1record'
+import { Button, Input, Select } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { MessageFu } from '@/utils/message'
+import { A1fileArr, fileImgArr, fileTypeRes, fileVideoArr } from '../data'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import MyTable from '@/components/MyTable'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import ImageLazy from '@/components/ImageLazy'
+import YtableVideo from '@/components/YtableVideo'
+import { baseURL } from '@/utils/http'
+
+type ListType = {
+  id: number
+  fileName: string
+  filePath: string
+  thumb: string
+  type: string
+  effectDictId: number
+}
+
+type FromDataType = {
+  effectDictId: number | null
+  searchKey: string
+  type: string | null
+}
+
+const baseFromData: FromDataType = {
+  effectDictId: null,
+  searchKey: '',
+  type: null
+}
+
+type Props = {
+  sId: number
+}
+
+function A1tab2file({ sId }: Props) {
+  const listObj = useSelector((state: RootState) => state.A3dict.listObj)
+
+  const [fromData, setFromData] = useState(baseFromData)
+
+  const [list, setList] = useState<ListType[]>([])
+
+  const getListFu = useCallback(async () => {
+    const res = await A1_APIfileGetList({ ...fromData, moduleId: sId })
+
+    if (res.code === 0) {
+      setList(res.data || [])
+    }
+  }, [fromData, sId])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  // 作品名称的输入
+  const timeRef = useRef(-1)
+  const [inputKey, setInputKey] = useState(0)
+  const fromKeyChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 下拉框的改变
+  const selectChangeFu = useCallback(
+    (value: any, key: 'type' | 'effectDictId') => {
+      setFromData({ ...fromData, [key]: value })
+    },
+    [fromData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...baseFromData })
+  }, [])
+
+  // 点击删除
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A1_APIfileDel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  // 下拉框改变
+  const effectDictIdChange = useCallback(
+    async (id: number, effectDictId: number) => {
+      const res = await A1_APIfileSave({ id, effectDictId })
+      if (res.code === 0) {
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  const staBtn = useMemo(() => {
+    return [
+      {
+        title: '附件名称',
+        render: (item: ListType) => item.fileName || '(空)'
+      },
+      {
+        title: '附件类型',
+        render: (item: ListType) => A1fileArr.find(v => v.value === item.type)?.label
+      },
+      {
+        title: '附件用途',
+        render: (item: ListType) => (
+          <Select
+            style={{ width: 200 }}
+            value={item.effectDictId}
+            onChange={e => effectDictIdChange(item.id, e)}
+            options={listObj.effect.map(v => ({ value: v.id, label: v.name }))}
+          />
+        )
+      },
+      {
+        title: '缩略图/视频',
+        render: (item: ListType) => {
+          const fileNameArr = item.fileName.split('.')
+          const fileNameLast = fileNameArr[fileNameArr.length - 1]
+
+          return fileImgArr.includes(fileNameLast.toLowerCase()) ? (
+            <div className='tableImgAuto'>
+              <ImageLazy width={60} height={60} srcBig={item.filePath} src={item.thumb} />
+            </div>
+          ) : fileVideoArr.includes(fileNameLast.toLowerCase()) ? (
+            <YtableVideo src={item.filePath} />
+          ) : (
+            ' - '
+          )
+        }
+      }
+    ]
+  }, [effectDictIdChange, listObj.effect])
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: ListType) => {
+          return (
+            <>
+              <Button size='small' type='text'>
+                <a
+                  href={baseURL + item.filePath}
+                  download
+                  target='_blank'
+                  className='ZTbox2Down'
+                  rel='noreferrer'
+                >
+                  下载
+                </a>
+              </Button>
+              <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+            </>
+          )
+        }
+      }
+    ]
+  }, [delTableFu])
+
+  const myInput = useRef<HTMLInputElement>(null)
+
+  // 上传附件
+  const handeUpPhoto = useCallback(
+    async (e: React.ChangeEvent<HTMLInputElement>) => {
+      if (e.target.files) {
+        // 拿到files信息
+        const filesInfo = e.target.files[0]
+        // console.log("-----", filesInfo.type);
+        // 创建FormData对象
+        const fd = new FormData()
+        // 把files添加进FormData对象(‘photo’为后端需要的字段)
+        const typeRes = fileTypeRes(filesInfo.name)
+        fd.append('type', typeRes)
+        fd.append('dirCode', 'A1recordFile')
+        fd.append('moduleId', sId + '')
+        fd.append('isDb', 'true')
+        fd.append('file', filesInfo)
+
+        // 开启压缩图片
+        fd.append('isCompress', 'true')
+
+        e.target.value = ''
+
+        try {
+          const res = await API_upFile(fd, 'cms/bridgeFile/upload')
+          if (res.code === 0) {
+            MessageFu.success('上传成功!')
+            getListFu()
+          }
+          fileDomInitialFu()
+        } catch (error) {
+          fileDomInitialFu()
+        }
+      }
+    },
+    [getListFu, sId]
+  )
+
+  return (
+    <div className={styles.A1tab2file}>
+      <input id='upInput' type='file' ref={myInput} onChange={e => handeUpPhoto(e)} />
+
+      <div className='A1T2top'>
+        <div className='A1T2top1'>
+          <div>
+            <Input
+              key={inputKey}
+              maxLength={30}
+              showCount
+              style={{ width: 300 }}
+              placeholder='请输入附件名称'
+              allowClear
+              onChange={e => fromKeyChangeFu(e, 'searchKey')}
+            />
+          </div>
+
+          <div>
+            <Select
+              allowClear
+              style={{ width: 200 }}
+              placeholder='附件类型'
+              value={fromData.type || null}
+              onChange={e => selectChangeFu(e, 'type')}
+              options={A1fileArr}
+            />
+          </div>
+
+          <div>
+            <Select
+              allowClear
+              style={{ width: 200 }}
+              placeholder='附件用途'
+              value={fromData.effectDictId || null}
+              onChange={e => selectChangeFu(e, 'effectDictId')}
+              options={listObj.effect.map(v => ({ value: v.id, label: v.name }))}
+            />
+          </div>
+        </div>
+        <div className='A1T2top2'>
+          <Button onClick={resetSelectFu}>重置</Button>&emsp;
+          <Button type='primary' onClick={() => myInput.current?.click()}>
+            上传附件
+          </Button>
+        </div>
+      </div>
+
+      <MyTable
+        classKey='A1tab2file'
+        list={list}
+        columnsTemp={[
+          ['txt', '上传人', 'creatorName'],
+          ['txt', '上传时间', 'createTime']
+        ]}
+        staBtn={staBtn}
+        lastBtn={tableLastBtn}
+        pagingInfo={false}
+      />
+    </div>
+  )
+}
+
+const MemoA1tab2file = React.memo(A1tab2file)
+
+export default MemoA1tab2file

+ 56 - 0
后台管理/src/pages/A1record/data.ts

@@ -68,3 +68,59 @@ export const cityResFu = (item: A1ListType) => {
 
   return res
 }
+
+// 附件类型
+export const A1fileArr = [
+  {
+    value: 'img',
+    label: '图片'
+  },
+  {
+    value: 'video',
+    label: '视频'
+  },
+  {
+    value: 'model',
+    label: '三维'
+  },
+  {
+    value: 'audio',
+    label: '音频'
+  },
+  {
+    value: 'doc',
+    label: '文档'
+  },
+  {
+    value: 'other',
+    label: '其他'
+  }
+]
+
+// 上传文件自动归类
+type FileObjType = {
+  [key: string]: string[]
+}
+export const fileImgArr = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tiff', 'webp', 'svg', 'psd', 'ai']
+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
+}

+ 22 - 0
后台管理/src/store/action/A1record.ts

@@ -40,3 +40,25 @@ export const A1_APIsave = (data: any) => {
 export const A1_APIgetInfo = (id: number) => {
   return http.get(`cms/bridge/detail/${id}`)
 }
+
+// ----------------附件管理---------------------
+/**
+ * 古桥档案-附件管理-获取列表
+ */
+export const A1_APIfileGetList = (data: any) => {
+  return http.post('cms/bridgeFile/getList', data)
+}
+
+/**
+ * 古桥档案--附件管理-删除
+ */
+export const A1_APIfileDel = (id: number) => {
+  return http.get(`cms/bridgeFile/remove/${id}`)
+}
+
+/**
+ * 古桥档案--附件管理-编辑用途
+ */
+export const A1_APIfileSave = (data: any) => {
+  return http.post('cms/bridgeFile/save', data)
+}