shaogen1995 10 months ago
parent
commit
487ab0eee5

+ 244 - 298
src/components/ZupTypes/index.tsx

@@ -1,58 +1,52 @@
-import React, {
-  useCallback,
-  useEffect,
-  useMemo,
-  useRef,
-  useState,
-} from "react";
-import styles from "./index.module.scss";
-import { Button, Checkbox, Input, Modal } from "antd";
-import { forwardRef, useImperativeHandle } from "react";
-import { baseURL } from "@/utils/http";
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { Button, Checkbox, Input, Modal } from 'antd'
+import { forwardRef, useImperativeHandle } from 'react'
+import { baseURL } from '@/utils/http'
 import {
   PlusOutlined,
   CloseCircleOutlined,
   UploadOutlined,
   CloseOutlined,
   DownloadOutlined,
-  EyeOutlined,
-} from "@ant-design/icons";
-import { MessageFu } from "@/utils/message";
-import { API_upFile } from "@/store/action/layout";
-import { fileDomInitialFu } from "@/utils/domShow";
-import store from "@/store";
-import ImageLazy from "../ImageLazy";
-import classNames from "classnames";
-import MyPopconfirm from "../MyPopconfirm";
+  EyeOutlined
+} from '@ant-design/icons'
+import { MessageFu } from '@/utils/message'
+import { API_upFile } from '@/store/action/layout'
+import { fileDomInitialFu } from '@/utils/domShow'
+import store from '@/store'
+import ImageLazy from '../ImageLazy'
+import classNames from 'classnames'
+import MyPopconfirm from '../MyPopconfirm'
 // import { A2_APIchangeImgName } from "@/store/action/A2exhibition";
 
 export type FileListType = {
-  fileName: string;
-  thumb?: string;
-  filePath: string;
-  id: number;
-  type: "model" | "img" | "audio" | "video";
-  imgName: string;
-};
+  fileName: string
+  thumb?: string
+  filePath: string
+  id: number
+  type: 'model' | 'img' | 'audio' | 'video'
+  imgName: string
+}
 
 type Props = {
-  ref: any; //当前自己的ref,给父组件调用
-  selecFlag: string; //筛选的字符串 模型/图片/音频/视频
-  fileCheck: boolean; //有没有点击过确定
-  dirCode: string; //文件的code码
-  myUrl: string; //请求地址
-  isLook?: boolean; //是不是查看
-  modelSize?: number; //模型文件大小限制
-  imgSize?: number; //图片大小限制
-  imgLength?: number; //图片数量限制
-  audioSize?: number; //音频大小限制
-  videoSize?: number; //视频大小限制
-  videoTit?: string; //视频上传的提示语
-  isTypeShow?: boolean; //默认就选中(只有一个类型的时候)
-  isUpName?: boolean; //是否能修改图片名字
-  lastImgTxt?: string; //加载最后面的上传提示
-  oneIsCover?: boolean; //是否将第一张作为封面
-};
+  ref: any //当前自己的ref,给父组件调用
+  selecFlag: string //筛选的字符串 模型/图片/音频/视频
+  fileCheck: boolean //有没有点击过确定
+  dirCode: string //文件的code码
+  myUrl: string //请求地址
+  isLook?: boolean //是不是查看
+  modelSize?: number //模型文件大小限制
+  imgSize?: number //图片大小限制
+  imgLength?: number //图片数量限制
+  audioSize?: number //音频大小限制
+  videoSize?: number //视频大小限制
+  videoTit?: string //视频上传的提示语
+  isTypeShow?: boolean //默认就选中(只有一个类型的时候)
+  isUpName?: boolean //是否能修改图片名字
+  lastImgTxt?: string //加载最后面的上传提示
+  oneIsCover?: boolean //是否将第一张作为封面
+}
 
 function ZupTypes(
   {
@@ -66,223 +60,214 @@ function ZupTypes(
     imgLength = 9,
     audioSize = 10,
     videoSize = 500,
-    videoTit = "",
+    videoTit = '',
     isTypeShow = false,
     isUpName = false,
-    lastImgTxt = "",
-    oneIsCover = false,
+    lastImgTxt = '',
+    oneIsCover = false
   }: Props,
   ref: any
 ) {
   // 筛选
-  const [typeCheck, setTypeCheck] = useState<string[]>([]);
+  const [typeCheck, setTypeCheck] = useState<string[]>([])
 
   // 筛选数组
   const typeCheckArr = useMemo(() => {
     const arr = [
-      { label: "模型", value: "model" },
-      { label: "图片", value: "img" },
-      { label: "音频", value: "audio" },
-      { label: "视频", value: "video" },
-    ];
+      { label: '模型', value: 'model' },
+      { label: '图片', value: 'img' },
+      { label: '音频', value: 'audio' },
+      { label: '视频', value: 'video' }
+    ]
+
+    const arrRes = arr.filter(v => selecFlag.includes(v.label))
 
-    const arrRes = arr.filter((v) => selecFlag.includes(v.label));
     if (arrRes.length <= 1 && isTypeShow) {
-      setTypeCheck([arrRes[0].value]);
+      setTypeCheck([arrRes[0].value])
       // 默认就选中(只有一个类型的时候)
     }
 
-    return arrRes;
-  }, [isTypeShow, selecFlag]);
+    return arrRes
+  }, [isTypeShow, selecFlag])
 
   // 上传附件的信息
   const [fileList, setFileList] = useState({
     model: {} as FileListType,
     img: [] as FileListType[],
     audio: {} as FileListType,
-    video: {} as FileListType,
-  });
+    video: {} as FileListType
+  })
 
   // 附件信息的校验,不满足返回 true
   const fileCheckFu = useMemo(() => {
-    let flag = false;
-    if (typeCheck.length === 0) flag = true;
-    if (typeCheck.includes("model") && !fileList.model.id) flag = true;
-    if (typeCheck.includes("img") && fileList.img.length === 0) flag = true;
-    if (typeCheck.includes("audio") && !fileList.audio.id) flag = true;
-    if (typeCheck.includes("video") && !fileList.video.id) flag = true;
-    return flag;
-  }, [fileList, typeCheck]);
+    let flag = false
+    if (typeCheck.length === 0) flag = true
+    if (typeCheck.includes('model') && !fileList.model.id) flag = true
+    if (typeCheck.includes('img') && fileList.img.length === 0) flag = true
+    if (typeCheck.includes('audio') && !fileList.audio.id) flag = true
+    if (typeCheck.includes('video') && !fileList.video.id) flag = true
+    return flag
+  }, [fileList, typeCheck])
 
   // 点击上传附件按钮
-  const myInput = useRef<HTMLInputElement>(null);
+  const myInput = useRef<HTMLInputElement>(null)
 
-  const [fileOneType, setFileOneType] = useState("");
+  const [fileOneType, setFileOneType] = useState('')
 
   useEffect(() => {
-    if (fileOneType) myInput.current?.click();
-  }, [fileOneType]);
+    if (fileOneType) myInput.current?.click()
+  }, [fileOneType])
 
   const upFileFu = useCallback((type: string) => {
-    setFileOneType("");
+    setFileOneType('')
     window.setTimeout(() => {
-      setFileOneType(type);
-    }, 100);
-  }, []);
+      setFileOneType(type)
+    }, 100)
+  }, [])
 
   // 上传附件的处理函数
   const handeUpPhoto2 = useCallback(
     async (e: React.ChangeEvent<HTMLInputElement>) => {
       if (e.target.files) {
         // 拿到files信息
-        const filesInfo = e.target.files[0];
-
-        let anType = ["image/jpeg", "image/png"];
-        let anTit1 = "只支持png、jpg格式!";
-        let anTit2 = `最大支持${imgSize}M!`;
-        let anSize = imgSize * 1024 * 1024;
-
-        if (fileOneType === "audio") {
-          anType = ["audio/mpeg"];
-          anTit1 = "只支持mp3格式!";
-          anTit2 = `最大支持${audioSize}M!`;
-          anSize = audioSize * 1024 * 1024;
-        } else if (fileOneType === "video") {
-          anType = ["video/mp4"];
-          anTit1 = "只支持mp4格式!";
-          anTit2 = `最大支持${videoSize}M!`;
-          anSize = videoSize * 1024 * 1024;
-        } else if (fileOneType === "model") {
-          anType = [""];
-          anTit1 = "只支持4dage格式!";
-          anTit2 = `最大支持${modelSize}M!`;
-          anSize = modelSize * 1024 * 1024;
+        const filesInfo = e.target.files[0]
+
+        let anType = ['image/jpeg', 'image/png']
+        let anTit1 = '只支持png、jpg格式!'
+        let anTit2 = `最大支持${imgSize}M!`
+        let anSize = imgSize * 1024 * 1024
+
+        if (fileOneType === 'audio') {
+          anType = ['audio/mpeg']
+          anTit1 = '只支持mp3格式!'
+          anTit2 = `最大支持${audioSize}M!`
+          anSize = audioSize * 1024 * 1024
+        } else if (fileOneType === 'video') {
+          anType = ['video/mp4']
+          anTit1 = '只支持mp4格式!'
+          anTit2 = `最大支持${videoSize}M!`
+          anSize = videoSize * 1024 * 1024
+        } else if (fileOneType === 'model') {
+          anType = ['']
+          anTit1 = '只支持4dage格式!'
+          anTit2 = `最大支持${modelSize}M!`
+          anSize = modelSize * 1024 * 1024
         }
 
         // 校验格式
-        if (fileOneType !== "model") {
+        if (fileOneType !== 'model') {
           if (!anType.includes(filesInfo.type)) {
-            e.target.value = "";
-            return MessageFu.warning(anTit1);
+            e.target.value = ''
+            return MessageFu.warning(anTit1)
           }
         } else {
-          if (!filesInfo.name.includes(".4dage")) {
-            e.target.value = "";
-            return MessageFu.warning(anTit1);
+          if (!filesInfo.name.includes('.4dage')) {
+            e.target.value = ''
+            return MessageFu.warning(anTit1)
           }
         }
 
         // 校验大小
         if (filesInfo.size > anSize) {
-          e.target.value = "";
-          return MessageFu.warning(anTit2);
+          e.target.value = ''
+          return MessageFu.warning(anTit2)
         }
         // 创建FormData对象
-        const fd = new FormData();
+        const fd = new FormData()
         // 把files添加进FormData对象(‘photo’为后端需要的字段)
-        fd.append("type", fileOneType);
-        fd.append("dirCode", dirCode);
-        fd.append("isDb", "true");
+        fd.append('type', fileOneType)
+        fd.append('dirCode', dirCode)
+        fd.append('isDb', 'true')
 
         //初始图片 fileName为:未命名
         if (isUpName) {
-          fd.append("isDefaultName", "false");
+          fd.append('isDefaultName', 'false')
         }
 
-        fd.append("file", filesInfo);
+        fd.append('file', filesInfo)
 
-        if (fileOneType === "img" && filesInfo.size > 1 * 1024 * 1024) {
+        if (fileOneType === 'img' && filesInfo.size > 1 * 1024 * 1024) {
           // 开启压缩图片
-          fd.append("isCompress", "true");
+          fd.append('isCompress', 'true')
         }
 
-        e.target.value = "";
+        e.target.value = ''
 
-        const res = await API_upFile(fd, myUrl);
+        const res = await API_upFile(fd, myUrl)
 
         try {
           if (res.code === 0) {
-            MessageFu.success("上传成功!");
-            if (fileOneType === "img")
+            MessageFu.success('上传成功!')
+            if (fileOneType === 'img')
               setFileList({
                 ...fileList,
-                img: [{ ...res.data, imgName: "未命名" }, ...fileList.img],
-              });
-            else setFileList({ ...fileList, [fileOneType]: res.data });
+                img: [{ ...res.data, imgName: '未命名' }, ...fileList.img]
+              })
+            else setFileList({ ...fileList, [fileOneType]: res.data })
           }
-          fileDomInitialFu();
+          fileDomInitialFu()
         } catch (error) {
-          fileDomInitialFu();
+          fileDomInitialFu()
         }
       }
     },
-    [
-      audioSize,
-      dirCode,
-      fileList,
-      fileOneType,
-      imgSize,
-      isUpName,
-      modelSize,
-      myUrl,
-      videoSize,
-    ]
-  );
+    [audioSize, dirCode, fileList, fileOneType, imgSize, isUpName, modelSize, myUrl, videoSize]
+  )
 
   // 附件图片的拖动
-  const [dragImg, setDragImg] = useState<any>(null);
+  const [dragImg, setDragImg] = useState<any>(null)
 
   const handleDragOver = useCallback(
     (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
-      if (isLook) return;
-      e.dataTransfer.dropEffect = "move";
+      if (isLook) return
+      e.dataTransfer.dropEffect = 'move'
     },
     [isLook]
-  );
+  )
 
   const handleDragEnter = useCallback(
     (e: React.DragEvent<HTMLDivElement>, item: FileListType) => {
-      if (isLook) return;
-
-      e.dataTransfer.effectAllowed = "move";
-      if (item === dragImg) return;
-      const newItems = [...fileList.img]; //拷贝一份数据进行交换操作。
-      const src = newItems.indexOf(dragImg); //获取数组下标
-      const dst = newItems.indexOf(item);
-      newItems.splice(dst, 0, ...newItems.splice(src, 1)); //交换位置
-      setFileList({ ...fileList, img: newItems });
+      if (isLook) return
+
+      e.dataTransfer.effectAllowed = 'move'
+      if (item === dragImg) return
+      const newItems = [...fileList.img] //拷贝一份数据进行交换操作。
+      const src = newItems.indexOf(dragImg) //获取数组下标
+      const dst = newItems.indexOf(item)
+      newItems.splice(dst, 0, ...newItems.splice(src, 1)) //交换位置
+      setFileList({ ...fileList, img: newItems })
     },
     [dragImg, fileList, isLook]
-  );
+  )
 
   // 删除某一张图片
   const delImgListFu = useCallback(
     (id: number) => {
-      const newItems = fileList.img.filter((v) => v.id !== id);
-      setFileList({ ...fileList, img: newItems });
+      const newItems = fileList.img.filter(v => v.id !== id)
+      setFileList({ ...fileList, img: newItems })
     },
     [fileList]
-  );
+  )
 
   // 模型 音频 视频 的 dom
   const resOneDivDom = useCallback(
-    (type: "model" | "audio" | "video") => {
+    (type: 'model' | 'audio' | 'video') => {
       const dom = (
-        <div className="ZTbox" hidden={!typeCheck.includes(type)}>
-          <div className="ZTbox1">
+        <div className='ZTbox' hidden={!typeCheck.includes(type)}>
+          <div className='ZTbox1'>
             <span> </span>
-            {type === "model" ? "模型" : type === "audio" ? "音频" : "视频"}:
+            {type === 'model' ? '模型' : type === 'audio' ? '音频' : '视频'}:
           </div>
           {fileList[type].id ? (
-            <div className="ZTbox2">
-              <div className="ZTbox2Name">{fileList[type].fileName}</div>
+            <div className='ZTbox2'>
+              <div className='ZTbox2Name'>{fileList[type].fileName}</div>
 
               <div
-                className="ZTbox2Look"
+                className='ZTbox2Look'
                 onClick={() =>
                   store.dispatch({
-                    type: "layout/lookDom",
-                    payload: { src: fileList[type].filePath, type },
+                    type: 'layout/lookDom',
+                    payload: { src: fileList[type].filePath, type }
                   })
                 }
               >
@@ -292,97 +277,87 @@ function ZupTypes(
               <a
                 href={baseURL + fileList[type].filePath}
                 download
-                target="_blank"
-                className="ZTbox2Down"
-                rel="noreferrer"
+                target='_blank'
+                className='ZTbox2Down'
+                rel='noreferrer'
               >
                 <DownloadOutlined rev={undefined} />
               </a>
 
               <MyPopconfirm
-                txtK="删除"
-                onConfirm={() =>
-                  setFileList({ ...fileList, [type]: {} as FileListType })
-                }
-                Dom={
-                  <CloseCircleOutlined className="ZTbox2X" rev={undefined} />
-                }
+                txtK='删除'
+                onConfirm={() => setFileList({ ...fileList, [type]: {} as FileListType })}
+                Dom={<CloseCircleOutlined className='ZTbox2X' rev={undefined} />}
               />
             </div>
           ) : (
             <>
-              <Button
-                onClick={() => upFileFu(type)}
-                icon={<UploadOutlined rev={undefined} />}
-              >
+              <Button onClick={() => upFileFu(type)} icon={<UploadOutlined rev={undefined} />}>
                 上传
               </Button>
 
-              <div className="ZTboxTit">
-                {type === "model"
+              <div className='ZTboxTit'>
+                {type === 'model'
                   ? `仅支持4dage格式的模型文件,大小不能超过${modelSize}M。`
-                  : type === "audio"
+                  : type === 'audio'
                   ? `仅支持mp3格式的音频文件,大小不得超过${audioSize}MB。`
                   : `仅支持mp4格式的视频文件,大小不得超过${videoSize}MB。${videoTit}`}
               </div>
             </>
           )}
         </div>
-      );
-      return dom;
+      )
+      return dom
     },
     [audioSize, fileList, modelSize, typeCheck, upFileFu, videoSize, videoTit]
-  );
+  )
 
   // ------------让父组件调用的 回显
   const setFileComFileFu = useCallback((info: any) => {
-    if (info.type) setTypeCheck(info.type.split(","));
+    if (info.type) setTypeCheck(info.type.split(','))
 
     if (info.fileList && info.fileList.length) {
-      const data: FileListType[] = info.fileList;
+      const data: FileListType[] = info.fileList
       const obj = {
         model: {} as FileListType,
         img: [] as FileListType[],
         audio: {} as FileListType,
-        video: {} as FileListType,
-      };
-
-      data.forEach((v) => {
-        if (v.type === "img") {
-          obj.img.push({ ...v, imgName: v.fileName });
-        } else obj[v.type!] = v;
-      });
-      setFileList(obj);
+        video: {} as FileListType
+      }
+
+      data.forEach(v => {
+        if (v.type === 'img') {
+          obj.img.push({ ...v, imgName: v.fileName })
+        } else obj[v.type!] = v
+      })
+      setFileList(obj)
     }
-  }, []);
+  }, [])
 
   // --------------让父组件调用的返回 附件 信息
   const fileComFileResFu = useCallback(() => {
-    let coverUrl = "";
-    const fileIds = [];
-    if (fileList.model.id && typeCheck.includes("model"))
-      fileIds.push(fileList.model.id);
-    if (fileList.audio.id && typeCheck.includes("audio"))
-      fileIds.push(fileList.audio.id);
-    if (fileList.video.id && typeCheck.includes("video"))
-      fileIds.push(fileList.video.id);
-    if (typeCheck.includes("img")) {
+    let coverUrl = ''
+    const fileIds = []
+    if (fileList.model.id && typeCheck.includes('model')) fileIds.push(fileList.model.id)
+    if (fileList.audio.id && typeCheck.includes('audio')) fileIds.push(fileList.audio.id)
+    if (fileList.video.id && typeCheck.includes('video')) fileIds.push(fileList.video.id)
+    if (typeCheck.includes('img')) {
       fileList.img.forEach((v, i) => {
         if (v.id) {
-          fileIds.push(v.id);
+          fileIds.push(v.id)
           if (oneIsCover && i === 0) {
             // 返回 第一张图的url 作为封面
-            coverUrl = v.thumb || v.filePath;
+            coverUrl = v.thumb || v.filePath
           }
         }
-      });
+      })
     }
     return {
       sonType: typeCheck,
       sonFileIds: fileIds,
       sonIsOk: fileCheckFu,
-      coverUrl,
-    };
+      coverUrl
+    }
   }, [
     fileCheckFu,
     fileList.audio.id,
@@ -390,45 +365,43 @@ function ZupTypes(
     fileList.model.id,
     fileList.video.id,
     oneIsCover,
-    typeCheck,
-  ]);
+    typeCheck
+  ])
 
   // 可以让父组件调用子组件的方法
   useImperativeHandle(ref, () => ({
     setFileComFileFu,
-    fileComFileResFu,
-  }));
+    fileComFileResFu
+  }))
 
   // 修改图片名称
   const [isNameChange, setIsNameChange] = useState({
     id: 0,
-    oldName: "",
-    newName: "",
-  });
+    oldName: '',
+    newName: ''
+  })
 
   // 关闭弹窗
   const isNameChangeXFu = useCallback(() => {
-    setIsNameChange({ id: 0, oldName: "", newName: "" });
-  }, []);
+    setIsNameChange({ id: 0, oldName: '', newName: '' })
+  }, [])
 
   // 点击图片名字-出来弹窗
   const isNameChangeFu = useCallback(
     (item: FileListType) => {
-      if (isLook) return;
-      setIsNameChange({ id: item.id, oldName: item.imgName, newName: "" });
+      if (isLook) return
+      setIsNameChange({ id: item.id, oldName: item.imgName, newName: '' })
     },
     [isLook]
-  );
+  )
 
   // 修改完这点击 确定修改
   const isNameChangeOkFu = useCallback(async () => {
     // if (!isNameChange.newName) return MessageFu.warning("图片名不能为空!");
-
     // const res = await A2_APIchangeImgName({
     //   id: isNameChange.id,
     //   fileName: isNameChange.newName,
     // });
-
     // if (res.code === 0) {
     //   MessageFu.success("修改图片名成功!");
     //   setFileList({
@@ -440,154 +413,127 @@ function ZupTypes(
     //   });
     //   isNameChangeXFu();
     // }
-  }, []);
+  }, [])
   //
 
   return (
-    <div
-      className={classNames(styles.ZupTypes, isLook ? styles.ZupTypesLook : "")}
-    >
+    <div className={classNames(styles.ZupTypes, isLook ? styles.ZupTypesLook : '')}>
       <input
-        id="upInput"
-        type="file"
+        id='upInput'
+        type='file'
         accept={
-          fileOneType === "img"
-            ? ".png,.jpg,.jpeg"
-            : fileOneType === "audio"
-            ? ".mp3"
-            : fileOneType === "model"
-            ? ".4dage"
-            : ".mp4"
+          fileOneType === 'img'
+            ? '.png,.jpg,.jpeg'
+            : fileOneType === 'audio'
+            ? '.mp3'
+            : fileOneType === 'model'
+            ? '.4dage'
+            : '.mp4'
         }
         ref={myInput}
-        onChange={(e) => handeUpPhoto2(e)}
+        onChange={e => handeUpPhoto2(e)}
       />
       <div hidden={isTypeShow}>
         <Checkbox.Group
           options={typeCheckArr}
           value={typeCheck}
-          onChange={(e) => setTypeCheck(e as string[])}
+          onChange={e => setTypeCheck(e as string[])}
         />
       </div>
 
       {/* -----------模型 */}
-      {resOneDivDom("model")}
+      {resOneDivDom('model')}
 
       {/* -----------图片 */}
-      <div className="ZTboxImgMain" hidden={!typeCheck.includes("img")}>
-        <div className="ZTboxImgBox">
-          <div className="ZTbox1" hidden={isTypeShow}>
+      <div className='ZTboxImgMain' hidden={!typeCheck.includes('img')}>
+        <div className='ZTboxImgBox'>
+          <div className='ZTbox1' hidden={isTypeShow}>
             <span> </span> 图片:
           </div>
 
-          <div
-            className="ZTbox1Img"
-            style={{ width: isTypeShow ? "100%" : "" }}
-          >
+          <div className='ZTbox1Img' style={{ width: isTypeShow ? '100%' : '' }}>
             <div
-              hidden={
-                (!!fileList.img.length && fileList.img.length >= imgLength) ||
-                isLook
-              }
-              className="ZTbox1ImgIcon"
-              onClick={() => upFileFu("img")}
+              hidden={(!!fileList.img.length && fileList.img.length >= imgLength) || isLook}
+              className='ZTbox1ImgIcon'
+              onClick={() => upFileFu('img')}
             >
               <PlusOutlined rev={undefined} />
             </div>
             {fileList.img.map((v, i) => (
               <div
-                className="ZTbox1ImgRow"
+                className='ZTbox1ImgRow'
                 key={v.id}
-                draggable="true"
+                draggable='true'
                 onDragStart={() => setDragImg(v)}
-                onDragOver={(e) => handleDragOver(e, v)}
-                onDragEnter={(e) => handleDragEnter(e, v)}
+                onDragOver={e => handleDragOver(e, v)}
+                onDragEnter={e => handleDragEnter(e, v)}
                 onDragEnd={() => setDragImg(null)}
               >
                 {v.thumb || v.filePath ? (
-                  <ImageLazy
-                    noLook={true}
-                    width={100}
-                    height={100}
-                    src={v.thumb || v.filePath}
-                  />
+                  <ImageLazy noLook={true} width={100} height={100} src={v.thumb || v.filePath} />
                 ) : null}
 
-                {oneIsCover && i === 0 ? (
-                  <div className="ZTbox1ImgRowCover">封面</div>
-                ) : null}
+                {oneIsCover && i === 0 ? <div className='ZTbox1ImgRowCover'>封面</div> : null}
 
                 {/* 修改图片名字 */}
                 {isUpName ? (
                   <div
                     title={v.imgName}
-                    className="ZTbox1ImgRowName"
+                    className='ZTbox1ImgRowName'
                     onClick={() => isNameChangeFu(v)}
                   >
                     {v.imgName}
                   </div>
                 ) : null}
 
-                <div className="ZTbox1ImgRowIcon">
+                <div className='ZTbox1ImgRowIcon'>
                   <EyeOutlined
                     onClick={() =>
                       store.dispatch({
-                        type: "layout/lookBigImg",
+                        type: 'layout/lookBigImg',
                         payload: {
                           url: baseURL + v.filePath,
-                          show: true,
-                        },
+                          show: true
+                        }
                       })
                     }
                     rev={undefined}
                   />
-                  <a
-                    href={baseURL + v.filePath}
-                    download
-                    target="_blank"
-                    rel="noreferrer"
-                  >
+                  <a href={baseURL + v.filePath} download target='_blank' rel='noreferrer'>
                     <DownloadOutlined rev={undefined} />
                   </a>
                 </div>
 
                 <MyPopconfirm
-                  txtK="删除"
+                  txtK='删除'
                   onConfirm={() => delImgListFu(v.id!)}
-                  Dom={
-                    <CloseOutlined className="ZTbox1ImgRowX" rev={undefined} />
-                  }
+                  Dom={<CloseOutlined className='ZTbox1ImgRowX' rev={undefined} />}
                 />
               </div>
             ))}
           </div>
         </div>
 
-        <div className="ZTboxTit" hidden={isLook}>
+        <div className='ZTboxTit' hidden={isLook}>
           {fileList.img.length && fileList.img.length >= 2 ? (
             <>
               按住鼠标可拖动图片调整顺序。
               <br />
             </>
           ) : null}
-          支持png、jpg的图片格式;最大支持5M;最多支持{imgLength}张。
+          支持png、jpg的图片格式;最大支持{imgSize}M;最多支持{imgLength}张。
           {lastImgTxt}
         </div>
       </div>
 
       {/* -----------音频 */}
-      {resOneDivDom("audio")}
+      {resOneDivDom('audio')}
 
       {/* -----------视频 */}
-      {resOneDivDom("video")}
+      {resOneDivDom('video')}
 
       {/* 最后的提示 */}
-      <div
-        className={classNames(
-          "ZcheckTxt",
-          fileCheck && fileCheckFu ? "ZcheckTxtAc" : ""
-        )}
-      >
+      <div className={classNames('ZcheckTxt', fileCheck && fileCheckFu ? 'ZcheckTxtAc' : '')}>
         请最少上传一张图片!
       </div>
 
@@ -596,45 +542,45 @@ function ZupTypes(
         <Modal
           wrapClassName={styles.ZupTypesMo}
           open={true}
-          title="修改展品图片名称"
+          title='修改展品图片名称'
           footer={
             [] // 设置footer为空,去掉 取消 确定默认按钮
           }
         >
           <br />
-          <div className="ZupTypesMoRow">
+          <div className='ZupTypesMoRow'>
             <strong>当前名:</strong>
             {isNameChange.oldName}
           </div>
-          <div className="ZupTypesMoRow">
+          <div className='ZupTypesMoRow'>
             <br />
             <strong>修改为:</strong>
             <Input
               style={{ width: 400 }}
-              placeholder="请输入图片名"
+              placeholder='请输入图片名'
               maxLength={50}
               showCount
               value={isNameChange.newName}
-              onChange={(e) => {
+              onChange={e => {
                 setIsNameChange({
                   ...isNameChange,
-                  newName: e.target.value.replace(/\s+/g, ""),
-                });
+                  newName: e.target.value.replace(/\s+/g, '')
+                })
               }}
             />
           </div>
 
-          <div className="ZupTypesMoBtn">
+          <div className='ZupTypesMoBtn'>
             <Button onClick={isNameChangeXFu}>取消</Button>
             &emsp;
-            <Button type="primary" onClick={isNameChangeOkFu}>
+            <Button type='primary' onClick={isNameChangeOkFu}>
               修改
             </Button>
           </div>
         </Modal>
       ) : null}
     </div>
-  );
+  )
 }
 
-export default forwardRef(ZupTypes);
+export default forwardRef(ZupTypes)

+ 102 - 0
src/pages/A1goods/A1add/index.module.scss

@@ -0,0 +1,102 @@
+.A1add {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 12;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 24px;
+
+  :global {
+    .A1aMain {
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      textarea {
+        min-height: 150px !important;
+      }
+      .A1Text {
+        textarea {
+          min-height: 75px !important;
+        }
+      }
+
+      .A1fromRow {
+        position: relative;
+        width: 800px;
+
+        .A1_6Frow {
+          position: absolute;
+          left: 200px;
+          top: 5px;
+          color: #999;
+          font-size: 12px;
+        }
+      }
+
+      .ant-form {
+        width: 800px;
+
+        // .ant-input-affix-wrapper{
+        //   width: 800px;
+        // }
+        .formRow {
+          display: flex;
+
+          .formLeft {
+            position: relative;
+            width: 100px;
+            text-align: right;
+
+            & > span {
+              color: #ff4d4f;
+            }
+          }
+
+          .formRight {
+            width: calc(100% - 100px);
+          }
+        }
+
+        .A1abtn {
+          position: absolute;
+          z-index: 10;
+          left: 1200px;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+      }
+    }
+
+    // 从查看进入
+    .A1aMainLook {
+      // 左边的 label 也不让选中
+      label {
+        pointer-events: none;
+      }
+
+      .ant-picker {
+        pointer-events: none;
+      }
+
+      .ant-checkbox-wrapper {
+        pointer-events: none;
+      }
+
+      .ant-input-number {
+        pointer-events: none;
+      }
+
+      .ant-select {
+        pointer-events: none;
+      }
+
+      .ant-radio-wrapper {
+        pointer-events: none;
+      }
+    }
+  }
+}

+ 260 - 0
src/pages/A1goods/A1add/index.tsx

@@ -0,0 +1,260 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { A1EditInfoType, A1levelArr, typeFileArr } from '../data'
+import classNames from 'classnames'
+import { Button, Form, FormInstance, Input, InputNumber, Radio, Select } from 'antd'
+import { A1_APIgetInfo, A1_APIsave } from '@/store/action/A1goods'
+import TextArea from 'antd/es/input/TextArea'
+import ZupOne from '@/components/ZupOne'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import ZupTypes from '@/components/ZupTypes'
+import { MessageFu } from '@/utils/message'
+
+type Props = {
+  editInfo: A1EditInfoType
+  closeFu: () => void
+  addTableFu: () => void
+  editTableFu: () => void
+}
+
+function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 封面图的ref
+  const ZupThumbRef = useRef<any>(null)
+
+  // 多张图片的ref
+  const ZupImgsRef = useRef<any>(null)
+
+  const [fileType, setFileType] = useState<'' | 'img' | 'model'>('')
+
+  // 编辑/查看 进入页面 获取信息
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await A1_APIgetInfo(id)
+    if (res.code === 0) {
+      const info = res.data.entity
+      const obj = {
+        ...info
+      }
+      setFileType(info.fileType)
+
+      FormBoxRef.current?.setFieldsValue(obj)
+
+      // 设置封面图
+      ZupThumbRef.current?.setFileComFileFu({
+        fileName: '',
+        filePath: info.thumb
+      })
+
+      const file = res.data.file || []
+
+      // 传给 附件 组件的
+      const sonInfo = {
+        type: 'img',
+        fileList: file
+      }
+      ZupImgsRef.current?.setFileComFileFu(sonInfo)
+    }
+  }, [])
+
+  // 附件 是否 已经点击过确定
+  const [fileCheck, setFileCheck] = useState(false)
+
+  useEffect(() => {
+    if (editInfo.id > 0) {
+      getInfoFu(editInfo.id)
+    } else {
+      FormBoxRef.current?.setFieldsValue({
+        sort: 999
+      })
+      setFileType('img')
+    }
+  }, [editInfo.id, getInfoFu])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    setFileCheck(true)
+  }, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      setFileCheck(true)
+      const coverUrl1 = ZupThumbRef.current?.fileComFileResFu()
+      // 没有传 封面图
+      if (!coverUrl1.filePath) return
+
+      // 附件组件的 type 数组 和 附件id数组
+      const { sonFileIds, sonIsOk } = ZupImgsRef.current?.fileComFileResFu()
+      if (fileType === 'img' && sonIsOk) return
+
+      const obj = {
+        ...values,
+        id: editInfo.id > 0 ? editInfo.id : null,
+        thumb: coverUrl1.filePath,
+        fileType,
+        fileIds: sonFileIds ? sonFileIds.join(',') : null
+      }
+
+      const res = await A1_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success(editInfo.txt + '成功!')
+        editInfo.id > 0 ? editTableFu() : addTableFu()
+        closeFu()
+      }
+    },
+    [addTableFu, closeFu, editInfo.id, editInfo.txt, editTableFu, fileType]
+  )
+
+  return (
+    <div className={styles.A1add}>
+      <div className={classNames('A1aMain', editInfo.txt === '查看' ? 'A1aMainLook' : '')}>
+        <Form
+          ref={FormBoxRef}
+          name='basic'
+          labelCol={{ span: 3 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete='off'
+          scrollToFirstError
+        >
+          <Form.Item label='名称' name='name' rules={[{ required: true, message: '请输入名称!' }]}>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+
+          {/* 封面 */}
+          <div className='formRow'>
+            <div className='formLeft'>
+              <span>* </span>
+              封面:
+            </div>
+            <div className='formRight'>
+              <ZupOne
+                ref={ZupThumbRef}
+                isLook={editInfo.txt === '查看'}
+                fileCheck={fileCheck}
+                size={5}
+                dirCode={'A1goods'}
+                myUrl='cms/goods/upload'
+                format={['image/jpeg', 'image/png']}
+                formatTxt='png、jpg和jpeg'
+                checkTxt='请上传封面图!'
+                upTxt='最多1张'
+                myType='thumb'
+              />
+            </div>
+          </div>
+          {editInfo.txt === '查看' ? <br /> : null}
+
+          <Form.Item label='类别' name='level' rules={[{ required: true, message: '请选择类别!' }]}>
+            <Select placeholder='请选择' style={{ width: 200 }} options={A1levelArr} />
+          </Form.Item>
+
+          {/* 文件类型 */}
+          <div className='formRow'>
+            <div className='formLeft'>
+              <span>* </span>
+              文件类型:
+            </div>
+            <div className='formRight'>
+              {typeFileArr.map(v => (
+                <Radio
+                  key={v.label}
+                  onClick={() => setFileType(v.value)}
+                  checked={v.value === fileType}
+                >
+                  {v.label}
+                </Radio>
+              ))}
+            </div>
+          </div>
+
+          <div className='formRow' hidden={fileType === 'model'}>
+            <div className='formLeft'></div>
+            <div className='formRight'>
+              <ZupTypes
+                ref={ZupImgsRef}
+                isLook={editInfo.txt === '查看'}
+                fileCheck={fileCheck}
+                selecFlag='图片'
+                imgSize={5}
+                dirCode={'A1goodsType'}
+                myUrl='cms/goods/upload'
+                isTypeShow={true}
+              />
+            </div>
+          </div>
+
+          {fileType === 'model' ? (
+            <>
+              <br />
+              <Form.Item
+                label='模型链接'
+                name='modelUrl'
+                rules={[{ required: fileType === 'model', message: '请输入模型链接!' }]}
+                getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
+              >
+                <TextArea
+                  className='A1Text'
+                  readOnly={editInfo.txt === '查看'}
+                  maxLength={200}
+                  showCount
+                  placeholder='请输入内容'
+                />
+              </Form.Item>
+            </>
+          ) : null}
+
+          <Form.Item label='简介' name='remark'>
+            <TextArea
+              readOnly={editInfo.txt === '查看'}
+              maxLength={200}
+              showCount
+              placeholder='请输入内容'
+            />
+          </Form.Item>
+
+          <div className='A1fromRow'>
+            <Form.Item
+              label='排序值'
+              name='sort'
+              rules={[{ required: true, message: '请输入排序值!' }]}
+            >
+              <InputNumber min={1} max={999} precision={0} placeholder='请输入' />
+            </Form.Item>
+            <div className='A1_6Frow' hidden={editInfo.txt === '查看'}>
+              请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <Form.Item className='A1abtn'>
+            {editInfo.txt === '查看' ? (
+              <Button onClick={closeFu}>返回</Button>
+            ) : (
+              <>
+                <Button type='primary' htmlType='submit'>
+                  提交
+                </Button>
+                <br />
+                <br />
+                <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+              </>
+            )}
+          </Form.Item>
+        </Form>
+      </div>
+    </div>
+  )
+}
+
+const MemoA1add = React.memo(A1add)
+
+export default MemoA1add

+ 23 - 0
src/pages/A1goods/data.ts

@@ -0,0 +1,23 @@
+export type A1FromDataType = {
+  searchKey: string
+  level: string | null
+
+  pageNum: number
+  pageSize: number
+}
+
+export type A1EditInfoType = {
+  id: number
+  txt: '新增' | '编辑' | '查看' | ''
+}
+
+export const A1levelArr = [
+  { value: '一级文物', label: '一级文物' },
+  { value: '二级文物', label: '二级文物' },
+  { value: '三级文物', label: '三级文物' }
+]
+
+export const typeFileArr: { value: 'img' | 'model'; label: '图片' | '三维模型' }[] = [
+  { value: 'img', label: '图片' },
+  { value: 'model', label: '三维模型' }
+]

+ 26 - 0
src/pages/A1goods/index.module.scss

@@ -1,4 +1,30 @@
 .A1goods {
+  position: relative;
   :global {
+    .A1top {
+      border-radius: 10px;
+      background-color: #fff;
+      padding: 15px 24px;
+      display: flex;
+      justify-content: space-between;
+      & > div {
+        display: flex;
+        .A1TopRow {
+          display: flex;
+          align-items: center;
+          margin-right: 20px;
+          .ant-select-selection-placeholder {
+            color: black;
+          }
+        }
+      }
+    }
+    .A1tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 77px);
+      background-color: #fff;
+    }
   }
 }

+ 151 - 2
src/pages/A1goods/index.tsx

@@ -1,9 +1,158 @@
-import React from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
+import { A1EditInfoType, A1FromDataType, A1levelArr } from './data'
+import { useDispatch, useSelector } from 'react-redux'
+import { A1_APIdel, A1_APIgetList } from '@/store/action/A1goods'
+import { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import { A1tableType } from '@/types'
+import { Button, Input, Select } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import MyTable from '@/components/MyTable'
+import { A1tableC } from '@/utils/tableData'
+import A1add from './A1add'
+
+const fromDataBase: A1FromDataType = {
+  searchKey: '',
+  level: null,
+
+  pageNum: 1,
+  pageSize: 10
+}
+
 function A1goods() {
+  const dispatch = useDispatch()
+
+  const [fromData, setFromData] = useState(fromDataBase)
+
+  const getListFu = useCallback(() => {
+    dispatch(A1_APIgetList(fromData))
+  }, [dispatch, fromData])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  const [inputKey, setInputKey] = useState(1)
+
+  // 输入框的输入
+  const timeRef = useRef(-1)
+  const txtChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value.replaceAll("'", ''), pageNum: 1 })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...fromDataBase })
+  }, [])
+
+  const tableInfo = useSelector((state: RootState) => state.A1goods.tableInfo)
+
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A1_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: A1tableType) => (
+          <>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setEditInfo({ id: item.id, txt: '编辑' })}
+            >
+              编辑
+            </Button>
+
+            <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+          </>
+        )
+      }
+    ]
+  }, [delTableFu])
+
+  //查看、新增、编辑
+  const [editInfo, setEditInfo] = useState<A1EditInfoType>({
+    id: 0,
+    txt: ''
+  })
+
   return (
     <div className={styles.A1goods}>
-      <h1>A1goods</h1>
+      <div className='pageTitle'>馆藏文物 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
+
+      {/* 顶部筛选 */}
+      <div className='A1top'>
+        <div>
+          <div className='A1TopRow'>
+            <span>搜索:</span>
+            <Input
+              key={inputKey}
+              maxLength={20}
+              style={{ width: 200 }}
+              placeholder='请输入名称'
+              allowClear
+              onChange={e => txtChangeFu(e, 'searchKey')}
+            />
+          </div>
+          <div className='A1TopRow'>
+            <span>类别:</span>
+            <Select
+              allowClear
+              placeholder='全部'
+              style={{ width: 200 }}
+              value={fromData.level}
+              onChange={e => setFromData({ ...fromData, pageNum: 1, level: e })}
+              options={A1levelArr}
+            />
+          </div>
+        </div>
+        <div>
+          <Button onClick={resetSelectFu}>重置</Button>&emsp;
+          <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '新增' })}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className='A1tableBox'>
+        <MyTable
+          yHeight={625}
+          list={tableInfo.list}
+          columnsTemp={A1tableC}
+          lastBtn={tableLastBtn}
+          pageNum={fromData.pageNum}
+          pageSize={fromData.pageSize}
+          total={tableInfo.total}
+          onChange={(pageNum, pageSize) => setFromData({ ...fromData, pageNum, pageSize })}
+        />
+      </div>
+
+      {editInfo.id ? (
+        <A1add
+          editInfo={editInfo}
+          closeFu={() => setEditInfo({} as A1EditInfoType)}
+          addTableFu={resetSelectFu}
+          editTableFu={getListFu}
+        />
+      ) : null}
     </div>
   )
 }

+ 30 - 0
src/pages/A2panorama/A2Com/index.module.scss

@@ -0,0 +1,30 @@
+.A2Com {
+  position: relative;
+  :global {
+    .A2top {
+      border-radius: 10px;
+      background-color: #fff;
+      padding: 15px 24px;
+      display: flex;
+      justify-content: space-between;
+      & > div {
+        display: flex;
+        .A2TopRow {
+          display: flex;
+          align-items: center;
+          margin-right: 20px;
+          .ant-select-selection-placeholder {
+            color: black;
+          }
+        }
+      }
+    }
+    .A2tableBox {
+      border-radius: 10px;
+      overflow: hidden;
+      margin-top: 15px;
+      height: calc(100% - 77px);
+      background-color: #fff;
+    }
+  }
+}

+ 158 - 0
src/pages/A2panorama/A2Com/index.tsx

@@ -0,0 +1,158 @@
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { useDispatch, useSelector } from 'react-redux'
+import { A2FromDataType } from '../data'
+import { A2_APIdel, A2_APIgetList } from '@/store/action/A2panorama'
+import { RootState } from '@/store'
+import { MessageFu } from '@/utils/message'
+import { A2tableType } from '@/types'
+import { Button, Input } from 'antd'
+import MyPopconfirm from '@/components/MyPopconfirm'
+import { A1EditInfoType } from '@/pages/A1goods/data'
+import MyTable from '@/components/MyTable'
+import { A2tableC } from '@/utils/tableData'
+import A2add from '../A2add'
+
+const fromDataBase: A2FromDataType = {
+  searchKey: '',
+
+  pageNum: 1,
+  pageSize: 10
+}
+
+type Props = {
+  type: 'pano' | 'bird'
+}
+
+function A2Com({ type }: Props) {
+  const dispatch = useDispatch()
+
+  const [fromData, setFromData] = useState(fromDataBase)
+
+  const getListFu = useCallback(() => {
+    dispatch(A2_APIgetList(fromData, type))
+  }, [dispatch, fromData, type])
+
+  useEffect(() => {
+    getListFu()
+  }, [getListFu])
+
+  const [inputKey, setInputKey] = useState(1)
+
+  // 输入框的输入
+  const timeRef = useRef(-1)
+  const txtChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value.replaceAll("'", ''), pageNum: 1 })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 点击重置
+  const resetSelectFu = useCallback(() => {
+    setInputKey(Date.now())
+    setFromData({ ...fromDataBase })
+  }, [])
+
+  const { tableInfo1, tableInfo2 } = useSelector((state: RootState) => state.A2panorama)
+
+  const delTableFu = useCallback(
+    async (id: number) => {
+      const res = await A2_APIdel(id)
+      if (res.code === 0) {
+        MessageFu.success('删除成功!')
+        getListFu()
+      }
+    },
+    [getListFu]
+  )
+
+  const tableLastBtn = useMemo(() => {
+    return [
+      {
+        title: '操作',
+        render: (item: A2tableType) => (
+          <>
+            <Button
+              size='small'
+              type='text'
+              onClick={() => setEditInfo({ id: item.id, txt: '编辑' })}
+            >
+              编辑
+            </Button>
+
+            <MyPopconfirm txtK='删除' onConfirm={() => delTableFu(item.id)} />
+          </>
+        )
+      }
+    ]
+  }, [delTableFu])
+
+  //查看、新增、编辑
+  const [editInfo, setEditInfo] = useState<A1EditInfoType>({
+    id: 0,
+    txt: ''
+  })
+
+  return (
+    <div className={styles.A2Com}>
+      <div className='pageTitle'>
+        {type === 'pano' ? '全景景区' : '景点鸟瞰'} {editInfo.id ? ` - ${editInfo.txt}` : ''}
+      </div>
+
+      {/* 顶部筛选 */}
+      <div className='A2top'>
+        <div>
+          <div className='A2TopRow'>
+            <span>搜索:</span>
+            <Input
+              key={inputKey}
+              maxLength={20}
+              style={{ width: 200 }}
+              placeholder='请输入名称'
+              allowClear
+              onChange={e => txtChangeFu(e, 'searchKey')}
+            />
+          </div>
+        </div>
+        <div>
+          <Button onClick={resetSelectFu}>重置</Button>&emsp;
+          <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '新增' })}>
+            新增
+          </Button>
+        </div>
+      </div>
+
+      {/* 表格主体 */}
+      <div className='A2tableBox'>
+        <MyTable
+          yHeight={625}
+          list={type === 'pano' ? tableInfo1.list : tableInfo2.list}
+          columnsTemp={A2tableC}
+          lastBtn={tableLastBtn}
+          pageNum={fromData.pageNum}
+          pageSize={fromData.pageSize}
+          total={type === 'pano' ? tableInfo1.total : tableInfo2.total}
+          onChange={(pageNum, pageSize) => setFromData({ ...fromData, pageNum, pageSize })}
+        />
+      </div>
+
+      {editInfo.id ? (
+        <A2add
+          editInfo={editInfo}
+          closeFu={() => setEditInfo({} as A1EditInfoType)}
+          addTableFu={resetSelectFu}
+          editTableFu={getListFu}
+          type={type}
+        />
+      ) : null}
+    </div>
+  )
+}
+
+const MemoA2Com = React.memo(A2Com)
+
+export default MemoA2Com

+ 102 - 0
src/pages/A2panorama/A2add/index.module.scss

@@ -0,0 +1,102 @@
+.A2add {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 12;
+  background-color: #fff;
+  border-radius: 10px;
+  padding: 24px;
+
+  :global {
+    .A2aMain {
+      width: 100%;
+      height: 100%;
+      overflow-y: auto;
+
+      textarea {
+        min-height: 150px !important;
+      }
+      .A2Text {
+        textarea {
+          min-height: 75px !important;
+        }
+      }
+
+      .A2fromRow {
+        position: relative;
+        width: 800px;
+
+        .A2_6Frow {
+          position: absolute;
+          left: 200px;
+          top: 5px;
+          color: #999;
+          font-size: 12px;
+        }
+      }
+
+      .ant-form {
+        width: 800px;
+
+        // .ant-input-affix-wrapper{
+        //   width: 800px;
+        // }
+        .formRow {
+          display: flex;
+
+          .formLeft {
+            position: relative;
+            width: 100px;
+            text-align: right;
+
+            & > span {
+              color: #ff4d4f;
+            }
+          }
+
+          .formRight {
+            width: calc(100% - 100px);
+          }
+        }
+
+        .A2abtn {
+          position: absolute;
+          z-index: 10;
+          left: 1200px;
+          top: 50%;
+          transform: translateY(-50%);
+        }
+      }
+    }
+
+    // 从查看进入
+    .A2aMainLook {
+      // 左边的 label 也不让选中
+      label {
+        pointer-events: none;
+      }
+
+      .ant-picker {
+        pointer-events: none;
+      }
+
+      .ant-checkbox-wrapper {
+        pointer-events: none;
+      }
+
+      .ant-input-number {
+        pointer-events: none;
+      }
+
+      .ant-select {
+        pointer-events: none;
+      }
+
+      .ant-radio-wrapper {
+        pointer-events: none;
+      }
+    }
+  }
+}

+ 195 - 0
src/pages/A2panorama/A2add/index.tsx

@@ -0,0 +1,195 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import { A1EditInfoType } from '@/pages/A1goods/data'
+import classNames from 'classnames'
+import { Button, Form, FormInstance, Input, InputNumber } from 'antd'
+import { A2_APIgetInfo, A2_APIsave } from '@/store/action/A2panorama'
+import { MessageFu } from '@/utils/message'
+import ZupOne from '@/components/ZupOne'
+import TextArea from 'antd/es/input/TextArea'
+import MyPopconfirm from '@/components/MyPopconfirm'
+
+type Props = {
+  editInfo: A1EditInfoType
+  closeFu: () => void
+  addTableFu: () => void
+  editTableFu: () => void
+  type: 'pano' | 'bird'
+}
+
+function A2add({ editInfo, closeFu, addTableFu, editTableFu, type }: Props) {
+  // 表单的ref
+  const FormBoxRef = useRef<FormInstance>(null)
+
+  // 封面图的ref
+  const ZupThumbRef = useRef<any>(null)
+
+  // 编辑/查看 进入页面 获取信息
+  const getInfoFu = useCallback(async (id: number) => {
+    const res = await A2_APIgetInfo(id)
+    if (res.code === 0) {
+      const info = res.data
+      const obj = {
+        ...info
+      }
+
+      FormBoxRef.current?.setFieldsValue(obj)
+
+      // 设置封面图
+      ZupThumbRef.current?.setFileComFileFu({
+        fileName: '',
+        filePath: info.thumb
+      })
+    }
+  }, [])
+
+  // 附件 是否 已经点击过确定
+  const [fileCheck, setFileCheck] = useState(false)
+
+  useEffect(() => {
+    if (editInfo.id > 0) {
+      getInfoFu(editInfo.id)
+    } else {
+      FormBoxRef.current?.setFieldsValue({
+        sort: 999
+      })
+    }
+  }, [editInfo.id, getInfoFu])
+
+  // 没有通过校验
+  const onFinishFailed = useCallback(() => {
+    setFileCheck(true)
+  }, [])
+
+  //  通过校验点击确定
+  const onFinish = useCallback(
+    async (values: any) => {
+      setFileCheck(true)
+      const coverUrl1 = ZupThumbRef.current?.fileComFileResFu()
+      // 没有传 封面图
+      if (!coverUrl1.filePath) return
+
+      const obj = {
+        ...values,
+        id: editInfo.id > 0 ? editInfo.id : null,
+        thumb: coverUrl1.filePath,
+        type
+      }
+
+      const res = await A2_APIsave(obj)
+
+      if (res.code === 0) {
+        MessageFu.success(editInfo.txt + '成功!')
+        editInfo.id > 0 ? editTableFu() : addTableFu()
+        closeFu()
+      }
+    },
+    [addTableFu, closeFu, editInfo.id, editInfo.txt, editTableFu, type]
+  )
+
+  return (
+    <div className={styles.A2add}>
+      <div className={classNames('A2aMain', editInfo.txt === '查看' ? 'A2aMainLook' : '')}>
+        <Form
+          ref={FormBoxRef}
+          name='basic'
+          labelCol={{ span: 3 }}
+          onFinish={onFinish}
+          onFinishFailed={onFinishFailed}
+          autoComplete='off'
+          scrollToFirstError
+        >
+          <Form.Item label='名称' name='name' rules={[{ required: true, message: '请输入名称!' }]}>
+            <Input
+              readOnly={editInfo.txt === '查看'}
+              placeholder='请输入内容'
+              maxLength={20}
+              showCount
+            />
+          </Form.Item>
+
+          {/* 封面 */}
+          <div className='formRow'>
+            <div className='formLeft'>
+              <span>* </span>
+              封面:
+            </div>
+            <div className='formRight'>
+              <ZupOne
+                ref={ZupThumbRef}
+                isLook={editInfo.txt === '查看'}
+                fileCheck={fileCheck}
+                size={5}
+                dirCode={'A2panorama'}
+                myUrl='cms/link/upload'
+                format={['image/jpeg', 'image/png']}
+                formatTxt='png、jpg和jpeg'
+                checkTxt='请上传封面图!'
+                upTxt='最多1张'
+                myType='thumb'
+              />
+            </div>
+          </div>
+          {editInfo.txt === '查看' ? <br /> : null}
+
+          <Form.Item label='简介' name='remark'>
+            <TextArea
+              readOnly={editInfo.txt === '查看'}
+              maxLength={200}
+              showCount
+              placeholder='请输入内容'
+            />
+          </Form.Item>
+
+          <Form.Item
+            label='场景链接'
+            name='link'
+            rules={[{ required: true, message: '请输入场景链接!' }]}
+            getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
+          >
+            <TextArea
+              className='A2Text'
+              readOnly={editInfo.txt === '查看'}
+              maxLength={200}
+              showCount
+              placeholder='请输入内容'
+            />
+          </Form.Item>
+
+          <div className='A2fromRow'>
+            <Form.Item
+              label='排序值'
+              name='sort'
+              rules={[{ required: true, message: '请输入排序值!' }]}
+            >
+              <InputNumber min={1} max={999} precision={0} placeholder='请输入' />
+            </Form.Item>
+            <div className='A2_6Frow' hidden={editInfo.txt === '查看'}>
+              请输入1~999的数字。数字越小,排序越靠前。数字相同时,更新发布的内容排在前面
+            </div>
+          </div>
+
+          {/* 确定和取消按钮 */}
+          <Form.Item className='A2abtn'>
+            {editInfo.txt === '查看' ? (
+              <Button onClick={closeFu}>返回</Button>
+            ) : (
+              <>
+                <Button type='primary' htmlType='submit'>
+                  提交
+                </Button>
+                <br />
+                <br />
+                <MyPopconfirm txtK='取消' onConfirm={closeFu} />
+              </>
+            )}
+          </Form.Item>
+        </Form>
+      </div>
+    </div>
+  )
+}
+
+const MemoA2add = React.memo(A2add)
+
+export default MemoA2add

+ 3 - 0
src/pages/A2panorama/data.ts

@@ -0,0 +1,3 @@
+import { A1FromDataType } from '../A1goods/data'
+
+export type A2FromDataType = Omit<A1FromDataType, 'level'>

+ 0 - 4
src/pages/A2panorama/index.module.scss

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

+ 2 - 6
src/pages/A2panorama/index.tsx

@@ -1,11 +1,7 @@
 import React from 'react'
-import styles from './index.module.scss'
+import A2Com from './A2Com'
 function A2panorama() {
-  return (
-    <div className={styles.A2panorama}>
-      <h1>A2panorama</h1>
-    </div>
-  )
+  return <A2Com type='pano' />
 }
 
 const MemoA2panorama = React.memo(A2panorama)

+ 0 - 4
src/pages/A3scenic/index.module.scss

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

+ 2 - 6
src/pages/A3scenic/index.tsx

@@ -1,11 +1,7 @@
 import React from 'react'
-import styles from './index.module.scss'
+import A2Com from '../A2panorama/A2Com'
 function A3scenic() {
-  return (
-    <div className={styles.A3scenic}>
-      <h1>A3scenic</h1>
-    </div>
-  )
+  return <A2Com type='bird' />
 }
 
 const MemoA3scenic = React.memo(A3scenic)

+ 39 - 0
src/store/action/A1goods.ts

@@ -0,0 +1,39 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+
+/**
+ *馆藏文物-列表
+ */
+
+export const A1_APIgetList = (data: any): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post('cms/goods/pageList', data)
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total
+      }
+      dispatch({ type: 'A1/getList', payload: obj })
+    }
+  }
+}
+/**
+ * 馆藏文物-删除
+ */
+export const A1_APIdel = (id: number) => {
+  return http.get(`cms/goods/remove/${id}`)
+}
+
+/**
+ * 馆藏文物-获取详情
+ */
+export const A1_APIgetInfo = (id: number) => {
+  return http.get(`cms/goods/detail/${id}`)
+}
+
+/**
+ * 馆藏文物-新增、编辑
+ */
+export const A1_APIsave = (data: any) => {
+  return http.post('cms/goods/save', data)
+}

+ 39 - 0
src/store/action/A2panorama.ts

@@ -0,0 +1,39 @@
+import http from '@/utils/http'
+import { AppDispatch } from '..'
+
+/**
+ *全景景区-景点鸟瞰-列表
+ */
+
+export const A2_APIgetList = (data: any, type: 'pano' | 'bird'): any => {
+  return async (dispatch: AppDispatch) => {
+    const res = await http.post('cms/link/pageList', { ...data, type })
+    if (res.code === 0) {
+      const obj = {
+        list: res.data.records,
+        total: res.data.total
+      }
+      dispatch({ type: type === 'pano' ? 'A2/getList1' : 'A2/getList2', payload: obj })
+    }
+  }
+}
+/**
+ * 全景景区-景点鸟瞰-删除
+ */
+export const A2_APIdel = (id: number) => {
+  return http.get(`cms/link/remove/${id}`)
+}
+
+/**
+ * 全景景区-景点鸟瞰-获取详情
+ */
+export const A2_APIgetInfo = (id: number) => {
+  return http.get(`cms/link/detail/${id}`)
+}
+
+/**
+ * 全景景区-景点鸟瞰-新增、编辑
+ */
+export const A2_APIsave = (data: any) => {
+  return http.post('cms/link/save', data)
+}

+ 28 - 0
src/store/reducer/A1goods.ts

@@ -0,0 +1,28 @@
+import { A1tableType } from '@/types'
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo: {
+    list: [] as A1tableType[],
+    total: 0
+  }
+}
+
+// 定义 action 类型
+type Props = {
+  type: 'A1/getList'
+  payload: { list: A1tableType[]; total: number }
+}
+
+// reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case 'A1/getList':
+      return { ...state, tableInfo: action.payload }
+
+    default:
+      return state
+  }
+}

+ 39 - 0
src/store/reducer/A2panorama.ts

@@ -0,0 +1,39 @@
+import { A2tableType } from '@/types'
+
+// 初始化状态
+const initState = {
+  // 列表数据
+  tableInfo1: {
+    list: [] as A2tableType[],
+    total: 0
+  },
+
+  tableInfo2: {
+    list: [] as A2tableType[],
+    total: 0
+  }
+}
+
+// 定义 action 类型
+type Props =
+  | {
+      type: 'A2/getList1'
+      payload: { list: A2tableType[]; total: number }
+    }
+  | {
+      type: 'A2/getList2'
+      payload: { list: A2tableType[]; total: number }
+    }
+
+// reducer
+export default function Reducer(state = initState, action: Props) {
+  switch (action.type) {
+    // 获取列表数据
+    case 'A2/getList1':
+      return { ...state, tableInfo1: action.payload }
+    case 'A2/getList2':
+      return { ...state, tableInfo2: action.payload }
+    default:
+      return state
+  }
+}

+ 4 - 0
src/store/reducer/index.ts

@@ -3,12 +3,16 @@ import { combineReducers } from 'redux'
 
 // 导入 登录 模块的 reducer
 import A0Layout from './layout'
+import A1goods from './A1goods'
+import A2panorama from './A2panorama'
 import Z1user from './Z1user'
 import Z2log from './Z2log'
 
 // 合并 reducer
 const rootReducer = combineReducers({
   A0Layout,
+  A1goods,
+  A2panorama,
   Z1user,
   Z2log
 })

+ 17 - 0
src/types/api/A1goods.d.ts

@@ -0,0 +1,17 @@
+export type A1tableType = {
+  createTime: string
+  creatorId: number
+  creatorName: string
+  dirCode: string
+  display: number
+  fileIds: string
+  fileType: string
+  id: number
+  level: string
+  modelUrl: string
+  name: string
+  remark: string
+  sort: number
+  thumb: string
+  updateTime: string
+}

+ 13 - 0
src/types/api/A2panorama.ts

@@ -0,0 +1,13 @@
+export type A2tableType = {
+  createTime: string
+  creatorId: number
+  creatorName: string
+  id: number
+  link: string
+  name: string
+  remark: string
+  sort: number
+  thumb: string
+  type: string
+  updateTime: string
+}

+ 2 - 7
src/types/index.d.ts

@@ -1,10 +1,5 @@
 export * from './api/layot'
-export * from './api/A1manage'
-export * from './api/A2classify'
-export * from './api/A3recommend'
-export * from './api/A4iosUser'
-export * from './api/A5bookAudit'
-export * from './api/A6remarkAudit'
-export * from './api/A7notice'
+export * from './api/A1goods'
+export * from './api/A2panorama'
 export * from './api/Z1user'
 export * from './api/Z2log'

+ 7 - 94
src/utils/tableData.ts

@@ -15,105 +15,18 @@
 //   ];
 
 export const A1tableC = [
-  ['txt', '书名', 'name'],
+  ['txt', '类别', 'level'],
+  ['txt', '名称', 'name'],
   ['img', '封面', 'thumb'],
-  ['txt', '作者', 'author'],
-  ['txt', '出版社', 'press'],
-  ['txt', '中图法分类', 'storageName'],
-  ['txt', '展示分类', 'exhibitTypeName'],
-  ['txt', 'ISBN编号', 'num'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName'],
-  ['txt', '编辑时间', 'updateTime']
-]
-
-export const A1tableCimp = [
-  ['txt', '书名', 'name'],
-  ['img', '封面(png、jpg)', 'thumb', `<span class='tabx'>(待填入)</span>`],
-  ['txt', '作者', 'author'],
-  ['txt', '出版社', 'press'],
-  ['txt', '出版年份', 'year'],
-  ['txt', '中图法分类', 'storageName', `<span class='tabx'>(待填入)</span>`],
-  ['txt', '展示分类', 'exhibitTypeName', `<span class='tabx'>(待填入)</span>`],
-  ['txt', 'ISBN编号', 'num'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '附件(equb格式)', 'fileName', `<span class='tabx'>(待填入)</span>`]
-]
-
-export const A2tableY1 = [
-  ['img', '封面', 'thumb'],
-  ['text', '正文', 'description', 50],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName'],
-  ['txt', '编辑时间', 'updateTime']
-]
-
-export const A2tableY2 = [
-  ['txt', '标题', 'name'],
-  ['img', '视频封面', 'thumb'],
-  ['text', '简介', 'description', 50],
-  ['txt', '附件', 'fileName'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName'],
-  ['txt', '编辑时间', 'updateTime']
+  ['text', '简介', 'remark', 50],
+  ['txt', '排序值', 'sort']
 ]
 
 export const A2tableC = [
-  ['txt', '分类名称', 'name'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName'],
-  ['txt', '编辑时间', 'updateTime']
-]
-
-export const A3tableC = [
-  ['txt', '书名', 'name'],
+  ['txt', '名称', 'name'],
   ['img', '封面', 'thumb'],
-  ['txt', '作者', 'author'],
-  ['txt', '出版社', 'press'],
-  ['txt', 'ISBN编号', 'num'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName'],
-  ['txt', '编辑时间', 'updateTime']
-]
-
-export const A4tableC = [
-  ['txt', '用户ID', 'openId'],
-  ['txt', '用户昵称', 'nickName'],
-  ['img', '用户头像', 'avatarUrl'],
-  ['txt', '注册时间', 'createTime'],
-  ['txt', '最近登录时间', 'lastLoginTime'],
-  ['txt', '最近登录IP', 'lastIp']
-]
-
-export const A5tableC = [
-  ['txt', '书名', 'name'],
-  ['img', '封面', 'thumb'],
-  ['txt', '作者', 'author'],
-  ['txt', '出版社', 'press'],
-  ['txt', 'ISBN编号', 'num'],
-  ['txt', '提交人', 'wxUserName'],
-  ['txt', '提交时间', 'createTime'],
-  ['txtChange', '审核状态', 'auditStatus', { 0: '待审核', 1: '审核通过', 2: '审核驳回' }],
-  ['text', '审核备注', 'auditDesc', 50]
-]
-
-export const A6tableC = [
-  ['txt', '书名', 'bookName'],
-  ['text', '评论内容', 'content', 50],
-  ['txtChange', '审核状态', 'auditStatus', { 0: '待审核', 1: '审核通过', 2: '审核驳回' }],
-  ['txt', '提交人', 'createBy'],
-  ['txt', '提交时间', 'createTime']
-]
-
-export const A7tableC = [
-  ['txt', '标题', 'name'],
-  ['txt', '公告类型', 'dictName'],
-  ['txt', '排序值', 'sort'],
-  ['txt', '编辑人', 'creatorName']
-]
-
-export const A7tableCtype = [
-  ['txt', '类型名称', 'name'],
+  ['text', '简介', 'remark', 50],
+  ['text', '场景链接', 'link', 50, 'A'],
   ['txt', '排序值', 'sort']
 ]