lanxin 1 ヶ月 前
コミット
bc55f203e0
53 ファイル変更1154 行追加417 行削除
  1. 1 1
      package-lock.json
  2. 4 1
      package.json
  3. 0 5
      public/index.html
  4. 3 2
      src/components/MyTable/index.tsx
  5. 95 3
      src/components/PreviewOperate/index.tsx
  6. 26 0
      src/components/ZRichTexts/index.module.scss
  7. 63 36
      src/components/ZRichTexts/index.tsx
  8. 10 8
      src/components/ZupOne/index.tsx
  9. 38 12
      src/components/ZupTypes/index.tsx
  10. 18 6
      src/pages/A1banner/A1add/index.tsx
  11. 2 1
      src/pages/A1banner/index.tsx
  12. 14 5
      src/pages/A2introduction/index.module.scss
  13. 70 17
      src/pages/A2introduction/index.tsx
  14. 14 5
      src/pages/A3map/index.module.scss
  15. 70 17
      src/pages/A3map/index.tsx
  16. 21 8
      src/pages/A4news/A4add/index.tsx
  17. 22 4
      src/pages/A4news/index.tsx
  18. 43 20
      src/pages/A5activity/A5add/index.tsx
  19. 22 4
      src/pages/A5activity/index.tsx
  20. 55 13
      src/pages/A6exhibition/A6add/index.tsx
  21. 23 3
      src/pages/A6exhibition/index.tsx
  22. 35 14
      src/pages/A7collection/A7add/index.tsx
  23. 27 3
      src/pages/A7collection/index.tsx
  24. 31 3
      src/pages/B1reserve1/index.tsx
  25. 32 4
      src/pages/B2reserve2/index.tsx
  26. 0 3
      src/pages/C1reserveOpt/C1NoTime.tsx
  27. 4 4
      src/pages/C1reserveOpt/C1edit/index.module.scss
  28. 97 14
      src/pages/C1reserveOpt/C1edit/index.tsx
  29. 17 21
      src/pages/C1reserveOpt/C1xuZhi.tsx
  30. 3 0
      src/pages/C1reserveOpt/index.module.scss
  31. 1 2
      src/pages/C1reserveOpt/index.tsx
  32. 0 0
      src/pages/D1feedback/D1look/index.module.scss
  33. 0 0
      src/pages/D1feedback/D1look/index.tsx
  34. 25 5
      src/pages/D1feedback/index.tsx
  35. 21 7
      src/pages/Layout/index.tsx
  36. 2 2
      src/pages/Login/index.tsx
  37. 26 7
      src/pages/Z1user/UserAdd/index.tsx
  38. 0 1
      src/pages/Z1user/UserAuth/index.tsx
  39. 16 7
      src/pages/Z1user/index.tsx
  40. 3 58
      src/store/action/A2introduction.ts
  41. 7 0
      src/store/action/Breserve.ts
  42. 7 0
      src/store/action/D1feedback.ts
  43. 8 2
      src/store/reducer/A1banner.ts
  44. 0 44
      src/store/reducer/A2introduction.ts
  45. 0 2
      src/store/reducer/index.ts
  46. 5 26
      src/types/api/A2introduction.ts
  47. 2 0
      src/types/api/A6exhibition.ts
  48. 4 1
      src/types/declaration.d.ts
  49. 0 1
      src/utils/copyTxt.ts
  50. 4 0
      src/utils/http.ts
  51. 13 12
      src/utils/tableData.ts
  52. 23 0
      src/utils/xlsxExport.ts
  53. 127 3
      yarn.lock

+ 1 - 1
package-lock.json

@@ -31290,4 +31290,4 @@
       }
     }
   }
-}
+}

+ 4 - 1
package.json

@@ -11,12 +11,14 @@
     "@types/node": "^16.18.3",
     "@types/react": "^18.0.24",
     "@types/react-dom": "^18.0.8",
+    "@types/xlsx": "^0.0.36",
     "antd": "^5.8.3",
     "antd-mobile": "^5.30.0",
     "axios": "^1.1.3",
     "braft-editor": "^2.3.9",
     "braft-utils": "^3.0.12",
     "dayjs": "^1.11.10",
+    "html2canvas": "^1.4.1",
     "js-base64": "^3.7.3",
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
@@ -29,7 +31,8 @@
     "redux-thunk": "^2.4.1",
     "sass": "^1.55.0",
     "typescript": "^4.8.4",
-    "web-vitals": "^2.1.4"
+    "web-vitals": "^2.1.4",
+    "xlsx": "^0.18.5"
   },
   "scripts": {
     "dev": "react-app-rewired start",

+ 0 - 5
public/index.html

@@ -37,9 +37,4 @@
       To create a production bundle, use `npm run build` or `yarn build`.
     -->
   </body>
-
-  <script>
-    // 图书影印模块-显示bookId
-    const isBookIdShow = false
-  </script>
 </html>

+ 3 - 2
src/components/MyTable/index.tsx

@@ -92,7 +92,6 @@ function MyTable({
           return item.status === 0 ? item[v[3]] || isNull : item[v[2]] || isNull
         },
         img: (item: any) => {
-          console.log(item.status, item[v[2]], v[2], v, 'txt')
           return (
             <div className='tableImgAuto'>
               <ImageLazy
@@ -151,7 +150,9 @@ function MyTable({
               </Button>
             </div>
           )
-        }
+        },
+        // 预约设置 人数+‘人’
+        reserveOpt: (item: any) => (item[v[2]] === -1 ? isNull : item[v[2]] + '人')
       }
 
       return Reflect.get(obj, v[0])

+ 95 - 3
src/components/PreviewOperate/index.tsx

@@ -1,29 +1,121 @@
-import React, { useCallback } from 'react'
+import React, { useCallback, useRef, useEffect, useState } from 'react'
 import styles from './index.module.scss'
 import { Button } from 'antd'
 import { useDispatch } from 'react-redux'
 import { editPreview } from '@/store/action/layout'
 import { handleCopyClick } from '@/utils/copyTxt'
 import { MessageFu } from '@/utils/message'
+import html2canvas from 'html2canvas'
 
 function PreviewOperate({ src }: { src?: string }) {
   const dispatch = useDispatch()
+  const iframeRef = useRef<HTMLIFrameElement>(null)
+  // 用于标记 iframe 是否加载完成且文档就绪
+  const [isIframeReady, setIsIframeReady] = useState(false)
+  const cachedImageUrl = useRef<string | null>(null)
 
   const copyLink = useCallback(() => {
     if (src) {
-      handleCopyClick(src)
+      handleCopyClick(`https://sit-kelamayi.4dage.com/mini/#${src}`)
     } else {
       MessageFu.error('暂无数据')
     }
   }, [src])
 
+  const downloadImg = useCallback(async () => {
+    if (iframeRef.current && isIframeReady) {
+      try {
+        const iframeDocument = iframeRef.current.contentDocument!
+        const elInput: HTMLElement | null = iframeDocument.querySelector('.el-input')
+        const virInput: HTMLElement | null = iframeDocument.querySelector('#vir-input')
+        if (iframeDocument) {
+          if (elInput && virInput) {
+            elInput.style.display = 'none'
+            virInput.style.display = 'flex'
+          }
+          const canvas = await html2canvas(iframeDocument.body)
+          cachedImageUrl.current = canvas.toDataURL('image/png')
+          // const imageDataUrl = canvas.toDataURL('image/png')
+          // const link = document.createElement('a')
+          // link.download = 'preview.png'
+          // link.href = imageDataUrl
+          // link.click()
+          if (elInput && virInput) {
+            elInput.style.display = 'flex'
+            virInput.style.display = 'none'
+          }
+        }
+      } catch (error) {
+        MessageFu.error('截图下载失败,请稍后重试')
+      }
+    }
+  }, [isIframeReady])
+
+  const download = useCallback(async () => {
+    if (!cachedImageUrl.current) {
+      MessageFu.error('正在加载...')
+      await downloadImg()
+      return
+    }
+
+    const link = document.createElement('a')
+    link.download = 'preview.png'
+    link.href = cachedImageUrl.current
+    link.click()
+  }, [downloadImg])
+
+  useEffect(() => {
+    const iframe = iframeRef.current
+    if (iframe) {
+      const handleLoad = () => {
+        const iframeWindow = iframe.contentWindow
+        if (iframeWindow) {
+          // 检查 iframe 文档是否就绪
+          const checkDocumentReady = () => {
+            if (iframeWindow.document.readyState === 'complete') {
+              const iframeDocument = iframeWindow.document
+              // 动态修改 CSS 路径
+              const linkElements = iframeDocument.querySelectorAll('link[rel="stylesheet"]')
+              linkElements.forEach((link: any) => {
+                if (link.href.includes('index-BNWQR98R.css')) {
+                  // 替换为正确的 CSS 路径,请根据实际情况修改
+                  link.href = 'https://sit-kelamayi.4dage.com/mini/assets/index-BNWQR98R.css'
+                }
+              })
+              setIsIframeReady(true)
+            } else {
+              // 若未就绪,100 毫秒后再次检查
+              setTimeout(checkDocumentReady, 100)
+            }
+          }
+          checkDocumentReady()
+        }
+      }
+      iframe.addEventListener('load', handleLoad)
+      return () => {
+        iframe.removeEventListener('load', handleLoad)
+      }
+    }
+  }, [])
+
   return (
     <div className={styles.PreviewOperate}>
       <div className='PreviewOperateMain'>
         <div className='pageBox'>
-          {src ? <iframe src={src} title='preview' /> : <div className='noData'>暂无数据</div>}
+          {src ? (
+            <iframe
+              ref={iframeRef}
+              src={`https://sit-kelamayi.4dage.com/mini/#${src}`}
+              title='preview'
+            />
+          ) : (
+            <div className='noData'>暂无数据</div>
+          )}
           <div className='copyLink'>
             <Button onClick={copyLink}>复制链接</Button>
+            <Button onClick={download} disabled={!isIframeReady}>
+              下载图片
+            </Button>
             <Button onClick={() => dispatch(editPreview({ isOpenPreview: false }))}>关闭</Button>
           </div>
         </div>

+ 26 - 0
src/components/ZRichTexts/index.module.scss

@@ -44,12 +44,28 @@
         &:nth-of-type(8) {
           display: none !important;
         }
+        &:nth-of-type(16) {
+          display: none !important;
+        }
+        &:nth-of-type(17) {
+          display: none !important;
+        }
         &:nth-of-type(18) {
           display: none !important;
         }
         &:nth-of-type(19) {
           display: none !important;
         }
+        &:nth-of-type(21) {
+          display: none !important;
+        }
+      }
+
+      .bf-emoji-dropdown {
+        display: none !important;
+      }
+      .link-editor-dropdown {
+        display: none !important;
       }
 
       .bf-container {
@@ -76,6 +92,10 @@
         .upImgBoxNo {
           display: none;
         }
+        .my-button {
+          float: right;
+          margin-right: 24px;
+        }
       }
 
       .zztxtRow {
@@ -158,6 +178,12 @@
         }
       }
     }
+    .tip {
+      font-size: 14px;
+      color: rgb(126, 124, 124);
+      height: 32px;
+      margin-top: -16px;
+    }
 
     .noUpThumb {
       position: relative;

+ 63 - 36
src/components/ZRichTexts/index.tsx

@@ -6,7 +6,8 @@ import styles from './index.module.scss'
 // 安装---npm install braft-editor --save --force
 // npm install braft-utils --save --force
 import { ContentUtils } from 'braft-utils'
-import BraftEditor from 'braft-editor'
+import BraftEditor, { ExtendControlType } from 'braft-editor'
+
 // 引入编辑器样式
 import 'braft-editor/dist/index.css'
 
@@ -39,7 +40,7 @@ type Props = {
 }
 
 function ZRichTexts(
-  { check, dirCode, isLook, myUrl, isOne = false, upAudioBtnNone = false, video = false }: Props,
+  { check, dirCode, isLook, myUrl, isOne = false, upAudioBtnNone = false, video = true }: Props,
   ref: any
 ) {
   const [sectionArr, setSectionArr] = useState<SectionArrType[]>([
@@ -82,15 +83,23 @@ function ZRichTexts(
   }, [isSection, sectionArr])
 
   const myInput = useRef<HTMLInputElement>(null)
+  const myInput1 = useRef<HTMLInputElement>(null)
 
-  // 上传图片
+  // 上传
   const handeUpPhoto = useCallback(
-    async (e: React.ChangeEvent<HTMLInputElement>) => {
+    async (e: React.ChangeEvent<HTMLInputElement>, type: 'img' | 'video') => {
       if (e.target.files) {
         // 拿到files信息
         const filesInfo = e.target.files[0]
 
-        const isVideo = filesInfo.name.endsWith('.mp4')
+        const isVideo = filesInfo?.name.endsWith('.mp4')
+        if (isVideo && type === 'img') {
+          return MessageFu.warning('图片只支持png、jpg和jpeg格式!')
+        } else if (!isVideo && type === 'video') {
+          return MessageFu.warning('视频只支持mp4格式!')
+        }
+
+        if (!video && isVideo) return MessageFu.warning('不支持上传视频!')
 
         const mediaConfig = isVideo
           ? {
@@ -102,9 +111,9 @@ function ZRichTexts(
             }
           : {
               types: ['image/jpeg', 'image/png'],
-              maxSize: 5,
+              maxSize: 999,
               errorText: '图片只支持png、jpg和jpeg格式!',
-              errorSizeText: '图片最大支持5M!',
+              errorSizeText: '图片最大支持999M!',
               mediaType: 'IMAGE'
             }
 
@@ -150,7 +159,7 @@ function ZRichTexts(
         }
       }
     },
-    [dirCode, myUrl, sectionArr]
+    [dirCode, myUrl, sectionArr, video]
   )
 
   // 让父组件调用的 回显 富文本
@@ -262,17 +271,55 @@ function ZRichTexts(
     [isSection]
   )
 
+  const extendControls: ExtendControlType[] = [
+    'separator',
+    {
+      key: 'my-button',
+      type: 'button',
+      title: `上传图片`,
+      className: 'my-button', // 指定按钮的样式名
+      html: null, // 指定在按钮中渲染的html字符串
+      text: `上传图片`,
+      onClick: () => {
+        nowIndexRef.current = 0
+        myInput.current?.click()
+      }
+    }
+  ]
+  const videoControlBtn: ExtendControlType = {
+    key: 'my-button1',
+    type: 'button',
+    title: `上传视频`,
+    className: 'my-button', // 指定按钮的样式名
+    html: null, // 指定在按钮中渲染的html字符串
+    text: `上传视频`,
+    onClick: () => {
+      nowIndexRef.current = 0
+      myInput1.current?.click()
+    }
+  }
+
   return (
     <div className={styles.ZRichTexts}>
       <input
         id='upInput'
         type='file'
-        accept='.png,.jpg,.jpeg,.mp4'
+        accept={`.png,.jpg,.jpeg`}
         ref={myInput}
-        onChange={e => handeUpPhoto(e)}
+        onChange={e => handeUpPhoto(e, 'img')}
+      />
+      <input
+        id='upInput'
+        type='file'
+        accept={`.mp4`}
+        ref={myInput1}
+        onChange={e => handeUpPhoto(e, 'video')}
       />
 
-      <div className={classNames('formRightZW', isLook ? 'formRightZWLook' : '')}>
+      <div
+        className={classNames('formRightZW', isLook ? 'formRightZWLook' : '')}
+        style={{ display: isSection ? 'block' : 'none' }}
+      >
         {isOne ? (
           <div></div>
         ) : (
@@ -281,34 +328,10 @@ function ZRichTexts(
           </Checkbox>
         )}
 
-        {isSection ? (
+        {isSection && (
           <Button hidden={isLook} type='primary' onClick={addSectionFu}>
             新增章节
           </Button>
-        ) : (
-          <div className='formRightZWRR'>
-            {upAudioBtnNone ? null : (
-              <ZupAudio
-                fileInfo={sectionArr[0].fileInfo}
-                upDataFu={info => upSectionFu(info, 0)}
-                delFu={() => delSectionFu(0)}
-                dirCode={dirCode}
-                myUrl={myUrl}
-                isLook={isLook}
-              />
-            )}
-
-            <div hidden={isLook}>
-              <Button
-                onClick={() => {
-                  nowIndexRef.current = 0
-                  myInput.current?.click()
-                }}
-              >
-                上传图片/视频
-              </Button>
-            </div>
-          </div>
         )}
       </div>
 
@@ -402,10 +425,14 @@ function ZRichTexts(
                 setSectionArr(arr)
               }}
               imageControls={['remove']}
+              extendControls={
+                isLook ? [] : video ? [...extendControls, videoControlBtn] : extendControls
+              }
             />
           </div>
         ))}
       </div>
+      {video && <div className='tip'>视频格式要求,仅支持MP4格式的视频文件,大小不得超过500M</div>}
       <div className={classNames('noUpThumb', check && isTxtFlag ? 'noUpThumbAc' : '')}>
         {`请完整输入${isSection ? '标题/' : ''}正文!`}
       </div>

+ 10 - 8
src/components/ZupOne/index.tsx

@@ -27,6 +27,7 @@ type Props = {
   dirCode: string //文件的code码
   myUrl: string //请求地址
   format: string[] //上传格式 ["image/jpeg", "image/png"] ["video/mp4"] ,application/pdf
+  inchTxt: string //上传图片尺寸提示
   formatTxt: string //上传图片提示
   checkTxt: string
   upTxt: string
@@ -43,6 +44,7 @@ function ZupOne(
     dirCode,
     myUrl,
     format,
+    inchTxt,
     formatTxt,
     checkTxt,
     upTxt,
@@ -66,7 +68,6 @@ function ZupOne(
       if (e.target.files) {
         // 拿到files信息
         const filesInfo = e.target.files[0]
-        // console.log("-----", filesInfo.type);
 
         // 校验格式
         const type = format
@@ -83,8 +84,10 @@ function ZupOne(
           }
         } else {
           if (!type.includes(filesInfo.type)) {
-            e.target.value = ''
-            return MessageFu.warning(`只支持${formatTxt}格式!`)
+            if (!filesInfo.name.endsWith('.jpg')) {
+              e.target.value = ''
+              return MessageFu.warning(`只支持${formatTxt}格式!`)
+            }
           }
         }
 
@@ -150,16 +153,15 @@ function ZupOne(
   }))
 
   const acceptRes = useMemo(() => {
-    let accept = '.png,.jpg,.jpeg'
+    let accept = format.map(item => item.replace('image/', '.')).join(',')
+    console.log(accept)
     if (myType === 'video') accept = '.mp4'
     else if (myType === 'audio') accept = '.mp3'
     else if (myType === 'model') accept = '.4dage'
     else if (myType === 'pdf') accept = '.pdf'
     else if (myType === 'epub') accept = '.epub'
     return accept
-  }, [myType])
-
-  console.log(fileUrl, 'fileUrl')
+  }, [format, myType])
 
   // 点击 预览(除了图片)
   const lookFileNoImgFu = useCallback(
@@ -268,7 +270,7 @@ function ZupOne(
       ) : null}
 
       <div className='fileBoxRow_r_tit' hidden={isLook}>
-        格式要求:支持{formatTxt}格式;最大支持{size}M。{upTxt}
+        格式要求:推荐尺寸{inchTxt};支持{formatTxt}格式;最大支持{size}M。{upTxt}
         <br />
         <div className={classNames('noUpThumb', !fileUrl.url && fileCheck ? 'noUpThumbAc' : '')}>
           {checkTxt}

+ 38 - 12
src/components/ZupTypes/index.tsx

@@ -75,7 +75,7 @@ function ZupTypes(
     const arr = [
       { label: '模型', value: 'model' },
       { label: '图片', value: 'img' },
-      { label: '音频', value: 'audio' },
+      // { label: '音频', value: 'audio' },
       { label: '视频', value: 'video' }
     ]
 
@@ -223,7 +223,6 @@ function ZupTypes(
                 }
               })
           }
-          console.log(fileList)
           fileDomInitialFu()
         } catch (error) {
           fileDomInitialFu()
@@ -268,6 +267,23 @@ function ZupTypes(
     [fileList]
   )
 
+  // 对模型的单独处理,改为输入框输入链接即可
+  const modelInputChange = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>) => {
+      setFileList({
+        ...fileList,
+        model: {
+          fileName: e.target.value,
+          filePath: e.target.value,
+          id: 1,
+          type: 'model',
+          imgName: '未命名'
+        }
+      })
+    },
+    [fileList]
+  )
+
   // 模型 音频 视频 的 dom
   const resOneDivDom = useCallback(
     (type: 'model' | 'audio' | 'video') => {
@@ -275,9 +291,10 @@ function ZupTypes(
         <div className='ZTbox' hidden={!typeCheck.includes(type)}>
           <div className='ZTbox1'>
             <span> </span>
+            <span style={{ color: 'red' }}>*</span>
             {type === 'model' ? '模型' : type === 'audio' ? '音频' : '视频'}:
           </div>
-          {fileList[type].id ? (
+          {fileList[type].id && type !== 'model' ? (
             <div className='ZTbox2'>
               <div className='ZTbox2Name'>{fileList[type].fileName}</div>
 
@@ -311,16 +328,26 @@ function ZupTypes(
             </div>
           ) : (
             <>
-              <Button onClick={() => upFileFu(type)} icon={<UploadOutlined rev={undefined} />}>
-                上传
-              </Button>
+              {/* 模型改为输入框 */}
+              {type === 'model' ? (
+                <Input
+                  style={{ width: '300px' }}
+                  value={fileList.model.fileName}
+                  placeholder='请输入模型链接'
+                  onChange={modelInputChange}
+                />
+              ) : (
+                <Button onClick={() => upFileFu(type)} icon={<UploadOutlined rev={undefined} />}>
+                  上传
+                </Button>
+              )}
 
               <div className='ZTboxTit'>
                 {type === 'model'
                   ? `仅支持4dage格式的模型文件,大小不能超过${modelSize}M。`
                   : type === 'audio'
                   ? `仅支持mp3格式的音频文件,大小不得超过${audioSize}MB。`
-                  : `仅支持mp4格式的视频文件,大小不得超过${videoSize}MB。${videoTit}`}
+                  : `仅支持MP4格式的视频文件,大小不得超过${videoSize}MB。${videoTit}`}
               </div>
             </>
           )}
@@ -328,7 +355,7 @@ function ZupTypes(
       )
       return dom
     },
-    [audioSize, fileList, modelSize, typeCheck, upFileFu, videoSize, videoTit]
+    [audioSize, fileList, modelInputChange, modelSize, typeCheck, upFileFu, videoSize, videoTit]
   )
 
   // ------------让父组件调用的 回显
@@ -337,7 +364,6 @@ function ZupTypes(
 
     if (info.fileList && info.fileList.length) {
       const data: FileListType[] = info.fileList
-      console.log('-xsxxas', data)
       const obj = {
         model: {} as FileListType,
         img: [] as FileListType[],
@@ -461,7 +487,7 @@ function ZupTypes(
       <div className='ZTboxImgMain' hidden={!typeCheck.includes('img')}>
         <div className='ZTboxImgBox'>
           <div className='ZTbox1' hidden={isTypeShow}>
-            <span> </span> 图片:
+            <span> </span> <span style={{ color: 'red' }}>*</span>图片:
           </div>
 
           <div className='ZTbox1Img' style={{ width: isTypeShow ? '100%' : '' }}>
@@ -534,7 +560,7 @@ function ZupTypes(
               <br />
             </>
           ) : null}
-          支持png、jpg的图片格式;最大支持5M;最多支持{imgLength}张。
+          支持png、jpg和jpeg的图片格式;最大支持{imgSize}M;最多支持{imgLength}张。
           {lastImgTxt}
         </div>
       </div>
@@ -547,7 +573,7 @@ function ZupTypes(
 
       {/* 最后的提示 */}
       <div className={classNames('ZcheckTxt', fileCheck && fileCheckFu ? 'ZcheckTxtAc' : '')}>
-        请最少上传一张图片!
+        至少上传一个类型
       </div>
 
       {/* 点击修改名字出来的弹窗 */}

+ 18 - 6
src/pages/A1banner/A1add/index.tsx

@@ -94,6 +94,7 @@ function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         imgB: '',
         imgTh: coverUrl.thUrl,
         imgThB: '',
+        publish: values.publish?.format('YYYY-MM-DD') || '',
         publishB: '',
         status: 1,
         titleB: '',
@@ -107,7 +108,7 @@ function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         imgTh: '',
         imgThB: coverUrl.thUrl,
         publish: '',
-        publishB: values.publish,
+        publishB: values.publish?.format('YYYY-MM-DD') || '',
         status: 0,
         title: '',
         titleB: values.title,
@@ -161,8 +162,18 @@ function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             <Input placeholder='请输入内容,不超过30个字' maxLength={30} showCount />
           </Form.Item>
 
-          <Form.Item label='发布日期' name='publish'>
-            <DatePicker allowClear placeholder='请选择日期' style={{ width: 200 }} />
+          <Form.Item
+            label='发布日期'
+            name='publish'
+            rules={[{ required: true, message: '请选择日期!' }]}
+            initialValue={dayjs()}
+          >
+            <DatePicker
+              defaultValue={dayjs()}
+              allowClear
+              placeholder='请选择日期'
+              style={{ width: 200 }}
+            />
           </Form.Item>
 
           {/* 封面 */}
@@ -178,8 +189,9 @@ function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
                 size={5}
                 dirCode={'A1banner'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
-                formatTxt='png、jpg和jpeg'
+                format={['image/jpg', 'image/png']}
+                inchTxt='3:2'
+                formatTxt='png、jpg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
                 myType='thumb'
@@ -188,7 +200,7 @@ function A1add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
           </div>
 
           <div className='A1fromRow'>
-            <Form.Item label='链接' name='url' rules={[{ required: true, message: '请输入链接!' }]}>
+            <Form.Item label='链接' name='url'>
               <Input placeholder='请输入' />
             </Form.Item>
           </div>

+ 2 - 1
src/pages/A1banner/index.tsx

@@ -38,6 +38,7 @@ function A1banner() {
   }, [])
 
   const tableInfo = useSelector((state: RootState) => state.A1banner.tableInfo)
+  console.log(tableInfo)
 
   const delTableFu = useCallback(
     async (id: number) => {
@@ -75,7 +76,7 @@ function A1banner() {
                   dispatch(
                     editPreview({
                       isOpenPreview: true,
-                      src: 'https://sit-kelamayi.4dage.com/mini/#/indexPage?preview=1'
+                      src: '/indexPage?preview=1'
                     })
                   )
                 }}

+ 14 - 5
src/pages/A2introduction/index.module.scss

@@ -13,10 +13,20 @@
       width: 100%;
       background-color: #fff;
       .introduction {
-        font-size: 20px;
-        font-weight: bold;
-        padding: 20px 45px;
-        color: var(--themeColor);
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .introTitle {
+          font-size: 20px;
+          font-weight: bold;
+          padding: 20px 45px;
+          color: var(--themeColor);
+        }
+        .introButton {
+          display: flex;
+          gap: 20px;
+          padding: 0 30px;
+        }
       }
       .introBox {
         display: flex;
@@ -41,7 +51,6 @@
             display: none;
           }
           font-size: 16px;
-          line-height: 24px;
           & > img {
             width: 100%;
             padding: 10px 0;

+ 70 - 17
src/pages/A2introduction/index.tsx

@@ -1,23 +1,28 @@
 import React, { useEffect, useRef, useState } from 'react'
 import { Button, message } from 'antd'
+import { useDispatch } from 'react-redux'
 import styles from './index.module.scss'
 import ZRichTexts from '@/components/ZRichTexts'
-import { A2_APIaddOrEdit, A2_APIgetInfo } from '@/store/action/A2introduction'
+import { A2_APIaddOrEdit, A2_APIgetInfo, A2_APIpublish } from '@/store/action/A2introduction'
 import { A2AddType, A2editType } from '@/types/api/A2introduction'
 import { SectionArrType } from '@/components/ZRichTexts'
+import { editPreview } from '@/store/action/layout'
+
 const A2introduction = () => {
   const [isEdit, setIsEdit] = useState(false)
   const [data, setData] = useState<any>({})
   const ZRichTextRef = useRef<any>(null)
 
+  const dispatch = useDispatch()
+
   const getInfo = async () => {
     const res = await A2_APIgetInfo(1)
     if (res.data) {
       const arr: SectionArrType[] = [
         {
-          id: res.data.carouselId,
+          id: res.data.contextId,
           name: '克博简介',
-          txt: res.data.context,
+          txt: res.data.status === 1 ? res.data.context : res.data.contextB,
           fileInfo: { fileName: '', filePath: '' }
         }
       ]
@@ -35,19 +40,29 @@ const A2introduction = () => {
     getInfo()
   }
 
-  const publishFu = () => {
+  const submitFu = (isPre: boolean) => {
     const obj = ZRichTextRef.current?.fatherBtnOkFu()
-    console.log(obj, 'obj')
+    const contentParams = isPre
+      ? {
+          context: '',
+          contextB: obj.val.txtArr[0].txt as string,
+          status: 0
+        }
+      : {
+          context: obj.val.txtArr[0].txt as string,
+          contextB: '',
+          status: 1
+        }
     const addObj: A2AddType = {
-      context: obj.val.txtArr[0].txt,
+      ...contentParams,
       type: 1
     }
     const editObj: A2editType = {
-      carouselId: data.carouselId,
-      context: obj.val.txtArr[0].txt,
+      contextId: data.contextId,
+      ...contentParams,
       type: 1
     }
-    const params = data.carouselId ? editObj : addObj
+    const params = data.contextId ? editObj : addObj
     // 添加与修改是同一个接口,区别在于是否传入carouselId
     if (!obj.flag) {
       A2_APIaddOrEdit(params).then((res: any) => {
@@ -60,29 +75,63 @@ const A2introduction = () => {
     }
   }
 
+  const publishFu = (contextId: number) => {
+    A2_APIpublish(contextId).then((res: any) => {
+      if (res.code === 0) {
+        message.success('发布成功')
+        getInfo()
+      }
+    })
+  }
+
   return (
     <div className={styles.A2introduction}>
       <div className='pageTitle'>克博简介</div>
       <div className='A2containerBox'>
-        <div className='introduction'>克博简介</div>
-        {!isEdit ? (
-          <div className='introBox'>
-            <div className='introLeft'>
-              <div>简介:</div>
+        <div className='introduction'>
+          <div className='introTitle'>克博简介</div>
+          {!isEdit && (
+            <div className='introButton'>
               <Button type='primary' onClick={editFu}>
                 修改
               </Button>
+              <Button
+                type='primary'
+                disabled={data.status === 1}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/allDetailsShow?id=${data.contextId}&type=museum&preview=1`
+                    })
+                  )
+                }
+              >
+                预览
+              </Button>
+              <Button type='primary' onClick={() => publishFu(data.contextId)}>
+                发布
+              </Button>
+            </div>
+          )}
+        </div>
+        {!isEdit ? (
+          <div className='introBox'>
+            <div className='introLeft'>
+              <div>内容:</div>
             </div>
             <div
               className='introRight'
-              dangerouslySetInnerHTML={{ __html: data.context || '暂无内容' }}
+              dangerouslySetInnerHTML={{
+                __html: data.status === 1 ? data.context : data.contextB || '暂无内容'
+              }}
             ></div>
           </div>
         ) : (
           <>
             <div className='introBox'>
               <div className='introLeft'>
-                <div>简介:</div>
+                <div>内容:</div>
               </div>
               <div className='introRight'>
                 <ZRichTexts
@@ -93,13 +142,17 @@ const A2introduction = () => {
                   myUrl='museum/upload/upload'
                   isOne={true}
                   upAudioBtnNone={true}
+                  video={false}
                 />
               </div>
             </div>
             <div className='btnBox'>
-              <Button type='primary' onClick={publishFu}>
+              <Button type='primary' onClick={() => submitFu(false)}>
                 发布
               </Button>
+              <Button type='primary' onClick={() => submitFu(true)}>
+                预发布
+              </Button>
               <Button onClick={() => setIsEdit(false)}>取消</Button>
             </div>
           </>

+ 14 - 5
src/pages/A3map/index.module.scss

@@ -13,10 +13,20 @@
       width: 100%;
       background-color: #fff;
       .introduction {
-        font-size: 20px;
-        font-weight: bold;
-        padding: 20px 45px;
-        color: var(--themeColor);
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        .introTitle {
+          font-size: 20px;
+          font-weight: bold;
+          padding: 20px 45px;
+          color: var(--themeColor);
+        }
+        .introButton {
+          display: flex;
+          gap: 20px;
+          padding: 0 30px;
+        }
       }
       .introBox {
         display: flex;
@@ -41,7 +51,6 @@
             display: none;
           }
           font-size: 16px;
-          line-height: 24px;
           & > img {
             width: 100%;
             padding: 10px 0;

+ 70 - 17
src/pages/A3map/index.tsx

@@ -1,23 +1,28 @@
 import React, { useEffect, useRef, useState } from 'react'
 import { Button, message } from 'antd'
 import styles from './index.module.scss'
+import { useDispatch } from 'react-redux'
 import ZRichTexts from '@/components/ZRichTexts'
-import { A2_APIaddOrEdit, A2_APIgetInfo } from '@/store/action/A2introduction'
+import { A2_APIaddOrEdit, A2_APIgetInfo, A2_APIpublish } from '@/store/action/A2introduction'
 import { A2AddType, A2editType } from '@/types/api/A2introduction'
 import { SectionArrType } from '@/components/ZRichTexts'
+import { editPreview } from '@/store/action/layout'
+
 const A3map = () => {
   const [isEdit, setIsEdit] = useState(false)
   const [data, setData] = useState<any>({})
   const ZRichTextRef = useRef<any>(null)
 
+  const dispatch = useDispatch()
+
   const getInfo = async () => {
     const res = await A2_APIgetInfo(2)
     if (res.data) {
       const arr: SectionArrType[] = [
         {
-          id: res.data.carouselId,
+          id: res.data.contextId,
           name: '克博地图',
-          txt: res.data.context,
+          txt: res.data.status === 1 ? res.data.context : res.data.contextB,
           fileInfo: { fileName: '', filePath: '' }
         }
       ]
@@ -35,19 +40,29 @@ const A3map = () => {
     getInfo()
   }
 
-  const publishFu = () => {
+  const submitFu = (isPre: boolean) => {
     const obj = ZRichTextRef.current?.fatherBtnOkFu()
-    console.log(obj, 'obj')
+    const contentParams = isPre
+      ? {
+          context: '',
+          contextB: obj.val.txtArr[0].txt as string,
+          status: 0
+        }
+      : {
+          context: obj.val.txtArr[0].txt as string,
+          contextB: '',
+          status: 1
+        }
     const addObj: A2AddType = {
-      context: obj.val.txtArr[0].txt,
+      ...contentParams,
       type: 2
     }
     const editObj: A2editType = {
-      carouselId: data.carouselId,
-      context: obj.val.txtArr[0].txt,
+      contextId: data.contextId,
+      ...contentParams,
       type: 2
     }
-    const params = data.carouselId ? editObj : addObj
+    const params = data.contextId ? editObj : addObj
     // 添加与修改是同一个接口,区别在于是否传入carouselId
     if (!obj.flag) {
       A2_APIaddOrEdit(params).then((res: any) => {
@@ -60,29 +75,63 @@ const A3map = () => {
     }
   }
 
+  const publishFu = (contextId: number) => {
+    A2_APIpublish(contextId).then((res: any) => {
+      if (res.code === 0) {
+        message.success('发布成功')
+        getInfo()
+      }
+    })
+  }
+
   return (
     <div className={styles.A3map}>
       <div className='pageTitle'>克博地图</div>
       <div className='A3containerBox'>
-        <div className='introduction'>克博地图</div>
-        {!isEdit ? (
-          <div className='introBox'>
-            <div className='introLeft'>
-              <div>简介:</div>
+        <div className='introduction'>
+          <div className='introTitle'>克博地图</div>
+          {!isEdit && (
+            <div className='introButton'>
               <Button type='primary' onClick={editFu}>
                 修改
               </Button>
+              <Button
+                type='primary'
+                disabled={data.status === 1}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/indexPage/map?preview=1`
+                    })
+                  )
+                }
+              >
+                预览
+              </Button>
+              <Button type='primary' onClick={() => publishFu(data.contextId)}>
+                发布
+              </Button>
+            </div>
+          )}
+        </div>
+        {!isEdit ? (
+          <div className='introBox'>
+            <div className='introLeft'>
+              <div>内容:</div>
             </div>
             <div
               className='introRight'
-              dangerouslySetInnerHTML={{ __html: data.context || '暂无内容' }}
+              dangerouslySetInnerHTML={{
+                __html: data.status === 1 ? data.context : data.contextB || '暂无内容'
+              }}
             ></div>
           </div>
         ) : (
           <>
             <div className='introBox'>
               <div className='introLeft'>
-                <div>简介:</div>
+                <div>内容:</div>
               </div>
               <div className='introRight'>
                 <ZRichTexts
@@ -93,13 +142,17 @@ const A3map = () => {
                   myUrl='museum/upload/upload'
                   isOne={true}
                   upAudioBtnNone={true}
+                  video={false}
                 />
               </div>
             </div>
             <div className='btnBox'>
-              <Button type='primary' onClick={publishFu}>
+              <Button type='primary' onClick={() => submitFu(false)}>
                 发布
               </Button>
+              <Button type='primary' onClick={() => submitFu(true)}>
+                预发布
+              </Button>
               <Button onClick={() => setIsEdit(false)}>取消</Button>
             </div>
           </>

+ 21 - 8
src/pages/A4news/A4add/index.tsx

@@ -47,7 +47,6 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
     } else {
       editObj = { ...info }
     }
-    console.log('-xsxxas', editObj)
     FormBoxRef.current?.setFieldsValue({ ...editObj, publish: dayjs(editObj.publish) })
 
     // 设置封面图
@@ -125,6 +124,7 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         infoImg: coverUrl2.url,
         infoImgTh: coverUrl2.thUrl,
         context: context.val.txtArr[0].txt,
+        publish: values.publish?.format('YYYY-MM-DD') || '',
         publishB: '',
         status: 1,
         titleB: '',
@@ -142,7 +142,7 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         infoImgTh: '',
         infoImgThB: coverUrl2.thUrl,
         publish: '',
-        publishB: values.publish,
+        publishB: values.publish?.format('YYYY-MM-DD') || '',
         status: 0,
         title: '',
         titleB: values.title,
@@ -198,8 +198,18 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             <Input placeholder='请输入内容,不超过30个字' maxLength={30} showCount />
           </Form.Item>
 
-          <Form.Item label='发表日期' name='publish'>
-            <DatePicker allowClear placeholder='请选择日期' style={{ width: 200 }} />
+          <Form.Item
+            label='发布日期'
+            name='publish'
+            rules={[{ required: true, message: '请选择日期!' }]}
+            initialValue={dayjs()}
+          >
+            <DatePicker
+              defaultValue={dayjs()}
+              allowClear
+              placeholder='请选择日期'
+              style={{ width: 200 }}
+            />
           </Form.Item>
 
           <Form.Item label='摘要' name='remark'>
@@ -215,6 +225,7 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
               myUrl='museum/upload/upload'
               isOne={true}
               upAudioBtnNone={true}
+              video={false}
             />
           </Form.Item>
 
@@ -231,8 +242,9 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
                 size={5}
                 dirCode={'A1banner'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
-                formatTxt='png、jpg和jpeg'
+                format={['image/jpg', 'image/png']}
+                inchTxt='7:2'
+                formatTxt='png、jpg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
                 myType='thumb'
@@ -252,8 +264,9 @@ function A4add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
                 size={5}
                 dirCode={'A1banner'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
-                formatTxt='png、jpg和jpeg'
+                format={['image/jpg', 'image/png']}
+                inchTxt='5:7'
+                formatTxt='png、jpg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
                 myType='thumb'

+ 22 - 4
src/pages/A4news/index.tsx

@@ -76,7 +76,9 @@ function A4news() {
               type='text'
               onClick={() =>
                 handleCopyClick(
-                  `/allDetailsShow?id=${item.informationId}&type=information&isFromPage=indexPage/news`
+                  item.status === 1
+                    ? `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.informationId}&type=information&isFrom=weixin`
+                    : `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.informationId}&type=information&preview=1&isFromPage=indexPage/news`
                 )
               }
             >
@@ -86,7 +88,14 @@ function A4news() {
               <Button
                 size='small'
                 type='text'
-                onClick={() => dispatch(editPreview({ isOpenPreview: true, src: '' }))}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/allDetailsShow?id=${item.informationId}&type=information&preview=1&isFromPage=indexPage/news`
+                    })
+                  )
+                }
               >
                 预览
               </Button>
@@ -123,10 +132,19 @@ function A4news() {
       {/* 顶部筛选 */}
       <div className='A4top'>
         <div className='A4topLeft'>
-          <Input placeholder='请输入标题' onChange={e => setTitle(e.target.value)} />
+          <Input placeholder='请输入标题' onChange={e => setTitle(e.target.value)} value={title} />
           <Button type='primary' onClick={() => getListFu(title)}>
             搜索
           </Button>
+          <Button
+            type='primary'
+            onClick={() => {
+              setTitle('')
+              getListFu()
+            }}
+          >
+            重置
+          </Button>
         </div>
         <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '新增' })}>
           新增
@@ -136,7 +154,7 @@ function A4news() {
       {/* 表格主体 */}
       <div className='A4tableBox'>
         <MyTable
-          myKey='carouselId'
+          myKey='informationId'
           yHeight={625}
           list={tableInfo.list}
           columnsTemp={A4tableC}

+ 43 - 20
src/pages/A5activity/A5add/index.tsx

@@ -56,7 +56,6 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
     } else {
       editObj = { ...info }
     }
-    console.log('-xsxxas', editObj)
     FormBoxRef.current?.setFieldsValue({
       ...editObj,
       publish: dayjs(editObj.publish),
@@ -143,13 +142,14 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         infoImg: coverUrl2.url,
         infoImgTh: coverUrl2.thUrl,
         context: context.val.txtArr[0].txt,
+        publish: values.publish?.format('YYYY-MM-DD') || '',
         publishB: '',
         status: 1,
         titleB: '',
         urlB: '',
         startTime: values?.date?.[0]?.format('YYYY-MM-DD') || '',
         endTime: values?.date?.[1]?.format('YYYY-MM-DD') || '',
-        personCount: values?.personCount || 0,
+        personCount: values?.personCount || 1,
         time: values?.time?.map((item: any) => item.format('HH:mm')).join('-') || '',
         guide: values?.guide || ''
       }
@@ -165,7 +165,7 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         infoImgTh: '',
         infoImgThB: coverUrl2.thUrl,
         publish: '',
-        publishB: values.publish,
+        publishB: values.publish?.format('YYYY-MM-DD') || '',
         status: 0,
         subscribe: values.subscribe,
         title: '',
@@ -176,7 +176,7 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         contextB: context.val.txtArr[0].txt,
         startTime: values?.date?.[0]?.format('YYYY-MM-DD') || '',
         endTime: values?.date?.[1]?.format('YYYY-MM-DD') || '',
-        personCount: values?.personCount || 0,
+        personCount: values?.personCount || 1,
         time: values?.time?.map((item: any) => item.format('HH:mm')).join('-') || '',
         guide: values?.guide || ''
       }
@@ -229,8 +229,18 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             <Input placeholder='请输入内容,不超过30个字' maxLength={30} showCount />
           </Form.Item>
 
-          <Form.Item label='发表日期' name='publish'>
-            <DatePicker allowClear placeholder='请选择日期' style={{ width: 200 }} />
+          <Form.Item
+            label='发布日期'
+            name='publish'
+            initialValue={dayjs()}
+            rules={[{ required: true, message: '请选择日期!' }]}
+          >
+            <DatePicker
+              allowClear
+              defaultValue={dayjs()}
+              placeholder='请选择日期'
+              style={{ width: 200 }}
+            />
           </Form.Item>
 
           <Form.Item label='摘要' name='remark'>
@@ -262,8 +272,9 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
                 size={5}
                 dirCode={'A5'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
-                formatTxt='png、jpg和jpeg'
+                format={['image/jpg', 'image/png']}
+                inchTxt='5:6'
+                formatTxt='png、jpg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
                 myType='thumb'
@@ -283,8 +294,9 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
                 size={5}
                 dirCode={'A5'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
-                formatTxt='png、jpg和jpeg'
+                format={['image/jpg', 'image/png']}
+                inchTxt='3:2'
+                formatTxt='png、jpg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
                 myType='thumb'
@@ -292,8 +304,14 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             </div>
           </div>
 
-          <Form.Item label='需要预约' name='subscribe'>
+          <Form.Item
+            label='需要预约'
+            name='subscribe'
+            initialValue={0}
+            rules={[{ required: true, message: '请选择预约类型!' }]}
+          >
             <Select
+              defaultValue={0}
               onChange={val => {
                 setIsReserve(val)
               }}
@@ -316,23 +334,28 @@ function A5add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
               <Form.Item
                 label='可预约人数'
                 name='personCount'
+                initialValue={1}
                 rules={[{ required: !!isReserve, message: '请输入可预约人数' }]}
               >
-                <InputNumber min={0} max={1000000} />
+                <InputNumber defaultValue={1} min={1} max={9999} />
               </Form.Item>
               <Form.Item
                 label='活动时间段'
                 name='time'
                 rules={[{ required: !!isReserve, message: '请选择活动时间段' }]}
               >
-                <TimePicker.RangePicker />
-              </Form.Item>
-              <Form.Item
-                label='活动须知'
-                name='guide'
-                rules={[{ required: !!isReserve, message: '请输入活动须知' }]}
-              >
-                <TextArea maxLength={200} showCount placeholder='请输入内容,不超过200个字' />
+                <TimePicker.RangePicker
+                  format='HH:mm'
+                  disabledTime={() => ({
+                    disabledHours: () => [0, 1, 2, 3, 4, 5, 6, 7, 21, 22, 23],
+                    disabledMinutes: currentHour => {
+                      if (currentHour === 20) {
+                        return Array.from({ length: 59 }, (_, i) => i + 1)
+                      }
+                      return []
+                    }
+                  })}
+                />
               </Form.Item>
             </div>
           )}

+ 22 - 4
src/pages/A5activity/index.tsx

@@ -76,7 +76,9 @@ function A5activity() {
               type='text'
               onClick={() =>
                 handleCopyClick(
-                  `/allDetailsShow?id=${item.activityId}&type=activity&isFromPage=indexPage/activity`
+                  item.status === 1
+                    ? `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.activityId}&type=activity&isFrom=weixin`
+                    : `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.activityId}&type=activity&preview=1&isFromPage=indexPage/activity`
                 )
               }
             >
@@ -86,7 +88,14 @@ function A5activity() {
               <Button
                 size='small'
                 type='text'
-                onClick={() => dispatch(editPreview({ isOpenPreview: true, src: '' }))}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/allDetailsShow?id=${item.activityId}&type=activity&preview=1&isFromPage=indexPage/activity`
+                    })
+                  )
+                }
               >
                 预览
               </Button>
@@ -118,15 +127,24 @@ function A5activity() {
 
   return (
     <div className={styles.A5activity}>
-      <div className='pageTitle'>活动列表 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
+      <div className='pageTitle'>克博活动 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
 
       {/* 顶部筛选 */}
       <div className='A5top'>
         <div className='A5topLeft'>
-          <Input placeholder='请输入标题' onChange={e => setTitle(e.target.value)} />
+          <Input placeholder='请输入标题' onChange={e => setTitle(e.target.value)} value={title} />
           <Button type='primary' onClick={() => getListFu(title)}>
             搜索
           </Button>
+          <Button
+            type='primary'
+            onClick={() => {
+              resetSelectFu()
+              setTitle('')
+            }}
+          >
+            重置
+          </Button>
         </div>
         <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '新增' })}>
           新增

+ 55 - 13
src/pages/A6exhibition/A6add/index.tsx

@@ -45,7 +45,6 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
     } else {
       editObj = { ...info }
     }
-    console.log('-xsxxas', editObj)
     FormBoxRef.current?.setFieldsValue({ ...editObj, publish: dayjs(editObj.publish) })
 
     // 设置封面图
@@ -106,7 +105,8 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
       const context = ZRichTextRef.current?.fatherBtnOkFu()
       // 没有传 封面图
       if (!coverUrl1.url) return MessageFu.warning('请上传首页封面图!')
-      if (!context.val.txtArr[0].txt) return MessageFu.warning('请输入正文!')
+      console.log(context)
+      if (context.flag) return MessageFu.warning('请输入正文!')
 
       // 发布
       const obj1: A6AddType = {
@@ -114,6 +114,7 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         img: coverUrl1.url,
         imgTh: coverUrl1.thUrl,
         context: context.val.txtArr[0].txt,
+        publish: values.publish?.format('YYYY-MM-DD') || '',
         publishB: '',
         status: 1,
         titleB: '',
@@ -127,7 +128,7 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         imgTh: '',
         imgThB: coverUrl1.thUrl,
         publish: '',
-        publishB: values.publish,
+        publishB: values.publish?.format('YYYY-MM-DD') || '',
         status: 0,
         type: values.type,
         title: '',
@@ -137,7 +138,8 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         context: '',
         contextB: context.val.txtArr[0].txt,
         webSiteB: values.webSite,
-        webSite: ''
+        webSite: '',
+        recommend: values.recommend
       }
 
       let res: any
@@ -183,11 +185,17 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             name='title'
             rules={[{ required: true, message: '请输入标题!' }]}
           >
-            <Input placeholder='请输入内容,不超过30个字' maxLength={30} showCount />
+            <Input placeholder='请输入内容,最多20个字' maxLength={20} showCount />
           </Form.Item>
 
-          <Form.Item label='展览类型' name='type'>
+          <Form.Item
+            label='类型'
+            name='type'
+            initialValue={1}
+            rules={[{ required: true, message: '请选择展览类型!' }]}
+          >
             <Select
+              defaultValue={1}
               placeholder='请选择展览类型'
               options={[
                 { label: '室内展览', value: 1 },
@@ -196,15 +204,48 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             />
           </Form.Item>
 
-          <Form.Item label='发表日期' name='publish'>
-            <DatePicker allowClear placeholder='请选择日期' style={{ width: 200 }} />
+          <Form.Item
+            label='发布日期'
+            name='publish'
+            rules={[{ required: true, message: '请选择日期!' }]}
+            initialValue={dayjs()}
+          >
+            <DatePicker
+              defaultValue={dayjs()}
+              allowClear
+              placeholder='请选择日期'
+              style={{ width: 200 }}
+            />
           </Form.Item>
 
           <Form.Item label='摘要' name='remark'>
-            <TextArea maxLength={200} showCount placeholder='请输入内容,不超过200个字' />
+            <TextArea maxLength={100} showCount placeholder='请输入内容,最多100字' />
+          </Form.Item>
+
+          <Form.Item
+            label='推荐展览'
+            name='recommend'
+            initialValue={1}
+            rules={[{ required: true, message: '请选择展览类型!' }]}
+          >
+            <Select
+              defaultValue={1}
+              placeholder='请选择展览类型'
+              options={[
+                { label: '是', value: 1 },
+                { label: '否', value: 0 }
+              ]}
+            />
           </Form.Item>
 
-          <Form.Item label='正文' name='context'>
+          <Form.Item
+            label={
+              <>
+                <span style={{ color: 'red' }}>*</span> 正文
+              </>
+            }
+            name='context'
+          >
             <ZRichTexts
               check={true}
               dirCode={'A4news'}
@@ -224,16 +265,17 @@ function A6add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
           <div className='formRow'>
             <div className='formLeft'>
               <span>* </span>
-              封面:
+              封面
             </div>
             <div className='formRight'>
               <ZupOne
                 ref={ZupThumbRef1}
                 fileCheck={fileCheck}
-                size={5}
+                size={20}
                 dirCode={'A1banner'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
+                format={['image/jpeg', 'image/jpg', 'image/png']}
+                inchTxt='2:1'
                 formatTxt='png、jpg和jpeg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'

+ 23 - 3
src/pages/A6exhibition/index.tsx

@@ -77,7 +77,9 @@ function A6exhibition() {
               type='text'
               onClick={() =>
                 handleCopyClick(
-                  `/allDetailsShow?id=${item.exhibitId}&type=exhibition&isFromPage=exhibition`
+                  item.status === 1
+                    ? `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.exhibitId}&type=exhibition&isFrom=weixin`
+                    : `https://sit-kelamayi.4dage.com/mini/#/allDetailsShow?id=${item.exhibitId}&type=exhibition&preview=1&isFromPage=indexPage/exhibition`
                 )
               }
             >
@@ -87,7 +89,14 @@ function A6exhibition() {
               <Button
                 size='small'
                 type='text'
-                onClick={() => dispatch(editPreview({ isOpenPreview: true, src: '' }))}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/allDetailsShow?id=${item.exhibitId}&type=exhibition&preview=1&isFromPage=exhibition`
+                    })
+                  )
+                }
               >
                 预览
               </Button>
@@ -119,7 +128,7 @@ function A6exhibition() {
 
   return (
     <div className={styles.A6exhibition}>
-      <div className='pageTitle'>展览列表 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
+      <div className='pageTitle'>克博展览 {editInfo.id ? ` - ${editInfo.txt}` : ''}</div>
 
       {/* 顶部筛选 */}
       <div className='A6top'>
@@ -128,11 +137,22 @@ function A6exhibition() {
             style={{ width: 200 }}
             placeholder='请输入标题'
             onChange={e => setTitle(e.target.value)}
+            value={title}
+            maxLength={10}
           />
 
           <Button type='primary' onClick={() => getListFu(title)}>
             搜索
           </Button>
+          <Button
+            type='primary'
+            onClick={() => {
+              resetSelectFu()
+              setTitle('')
+            }}
+          >
+            重置
+          </Button>
           <div className='rowItem'>
             展览类型:
             <Select

+ 35 - 14
src/pages/A7collection/A7add/index.tsx

@@ -58,7 +58,6 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
     } else {
       editObj = { ...info }
     }
-    console.log('-xsxxas', editObj)
     FormBoxRef.current?.setFieldsValue({ ...editObj, publish: dayjs(editObj.publish) })
 
     // 设置封面图
@@ -143,15 +142,15 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
       setFileCheck(true)
 
       const coverUrl1 = ZupThumbRef1.current?.fileComFileResFu()
-      const { fileList, sonType } = ZupTypesRef.current?.fileComFileResFu()
-      console.log('-xsxxas', fileList)
+      const { fileList, sonType, sonIsOk } = ZupTypesRef.current?.fileComFileResFu()
       // 没有传 封面图
       if (!coverUrl1.url) return MessageFu.warning('请上传首页封面图!')
-      if (sonType.length === 0) return MessageFu.warning('请上传附件!')
+      if (sonIsOk) return MessageFu.warning('请上传附件!')
 
       // 发布
       const obj1: A7AddType = {
         ...values,
+        publish: values.publish?.format('YYYY-MM-DD') || '',
         img: coverUrl1.url,
         imgTh: coverUrl1.thUrl,
         status: 1,
@@ -174,7 +173,7 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
         imgB: coverUrl1.url,
         imgThB: coverUrl1.thUrl,
         modelFileB: fileList.model.filePath || '',
-        publishB: values.publish,
+        publishB: values.publish?.format('YYYY-MM-DD') || '',
         remarkB: values.remark,
         sizeB: values.size,
         status: 0,
@@ -233,15 +232,18 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             name='title'
             rules={[{ required: true, message: '请输入标题!' }]}
           >
-            <Input placeholder='请输入内容,不超过30个字' maxLength={30} showCount />
+            <Input placeholder='请输入内容,最多20字' maxLength={20} showCount />
           </Form.Item>
 
-          <Form.Item label='类别' name='type'>
+          <Form.Item label='类别' name='type' initialValue={4}>
             <Select
+              defaultValue={0}
+              placeholder='请选择类别'
               options={[
                 { label: '平面纸质类', value: 1 },
                 { label: '棉麻丝绸类', value: 2 },
-                { label: '专业器物类', value: 3 }
+                { label: '专业器物类', value: 3 },
+                { label: '其他', value: 4 }
               ]}
             />
           </Form.Item>
@@ -259,7 +261,7 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
           </Form.Item>
 
           <Form.Item label='尺寸' name='size'>
-            <Input placeholder='请输入内容,最多10字' maxLength={10} showCount />
+            <Input placeholder='请输入内容,最多30字' maxLength={30} showCount />
           </Form.Item>
 
           <Form.Item label='简介' name='remark'>
@@ -276,10 +278,11 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
               <ZupOne
                 ref={ZupThumbRef1}
                 fileCheck={fileCheck}
-                size={5}
+                size={20}
                 dirCode={'A7collection'}
                 myUrl='museum/upload/uploadImg'
-                format={['image/jpeg', 'image/png']}
+                format={['image/jpeg', 'image/jpg', 'image/png']}
+                inchTxt='5:4'
                 formatTxt='png、jpg和jpeg'
                 checkTxt='请上传封面图!'
                 upTxt='最多1张'
@@ -288,18 +291,36 @@ function A7add({ editInfo, closeFu, addTableFu, editTableFu }: Props) {
             </div>
           </div>
 
-          <Form.Item label='* 文件类型' name='file'>
+          <Form.Item
+            label={
+              <>
+                <span style={{ color: 'red' }}>*</span> 文件类型
+              </>
+            }
+            name='file'
+          >
             <ZupTypes
               ref={ZupTypesRef}
               selecFlag='模型/图片/音频/视频'
               fileCheck={fileCheck}
               dirCode='A7collection'
               myUrl='museum/upload/upload'
+              imgSize={20}
             />
           </Form.Item>
 
-          <Form.Item label='* 发表日期' name='publish'>
-            <DatePicker allowClear placeholder='请选择日期' style={{ width: 200 }} />
+          <Form.Item
+            label='发布日期'
+            name='publish'
+            initialValue={dayjs()}
+            rules={[{ required: true, message: '请选择日期!' }]}
+          >
+            <DatePicker
+              defaultValue={dayjs()}
+              allowClear
+              placeholder='请选择日期'
+              style={{ width: 200 }}
+            />
           </Form.Item>
 
           {/* 确定和取消按钮 */}

+ 27 - 3
src/pages/A7collection/index.tsx

@@ -75,7 +75,13 @@ function A7collection() {
             <Button
               size='small'
               type='text'
-              onClick={() => handleCopyClick(`/collectDetail?id=${item.artifactId}&preview=1`)}
+              onClick={() =>
+                handleCopyClick(
+                  item.status === 1
+                    ? `https://sit-kelamayi.4dage.com/mini/#/collectDetail?id=${item.artifactId}&isFrom=weixin`
+                    : `https://sit-kelamayi.4dage.com/mini/#/collectDetail?id=${item.artifactId}&preview=1`
+                )
+              }
             >
               复制地址
             </Button>
@@ -83,7 +89,14 @@ function A7collection() {
               <Button
                 size='small'
                 type='text'
-                onClick={() => dispatch(editPreview({ isOpenPreview: true, src: '' }))}
+                onClick={() =>
+                  dispatch(
+                    editPreview({
+                      isOpenPreview: true,
+                      src: `/collectDetail?id=${item.artifactId}&preview=1`
+                    })
+                  )
+                }
               >
                 预览
               </Button>
@@ -124,10 +137,20 @@ function A7collection() {
             style={{ width: 200 }}
             placeholder='请输入标题'
             onChange={e => setTitle(e.target.value)}
+            value={title}
           />
           <Button type='primary' onClick={() => getListFu(title)}>
             搜索
           </Button>
+          <Button
+            type='primary'
+            onClick={() => {
+              resetSelectFu()
+              setTitle('')
+            }}
+          >
+            重置
+          </Button>
           <div className='rowItem'>
             类别:
             <Select
@@ -137,7 +160,8 @@ function A7collection() {
                 { label: '全部', value: 0 },
                 { label: '平面纸质类', value: 1 },
                 { label: '棉麻丝绸类', value: 2 },
-                { label: '专业器物类', value: 3 }
+                { label: '专业器物类', value: 3 },
+                { label: '其他', value: 4 }
               ]}
               onChange={value => setType(value)}
             />

+ 31 - 3
src/pages/B1reserve1/index.tsx

@@ -3,7 +3,7 @@ import styles from './index.module.scss'
 import { Button } from 'antd'
 import { useDispatch, useSelector } from 'react-redux'
 import { B1EditInfoType } from './data'
-import { B_APIgetList, B_APIdel } from '@/store/action/Breserve'
+import { B_APIgetList, B_APIdel, B_APIexportExcel } from '@/store/action/Breserve'
 import { RootState } from '@/store'
 import { B1tableType } from '@/types'
 import MyPopconfirm from '@/components/MyPopconfirm'
@@ -11,6 +11,7 @@ import MyTable from '@/components/MyTable'
 import { B1tableC } from '@/utils/tableData'
 import B1look from './B1look'
 import { MessageFu } from '@/utils/message'
+// import { exportExcelFile } from '@/utils/xlsxExport'
 
 const pageDataBase = {
   pageNum: 1,
@@ -49,6 +50,33 @@ function B1reserve1() {
     [getListFu]
   )
 
+  // 导出excel前端实现
+  // const exportExcelFu = useCallback(() => {
+  //   const listData = tableInfo.list.map(item => {
+  //     const list = item.visitors.map(visitor => {
+  //       return {
+  //         申请时间: item.createTime,
+  //         申请人姓名: item.userName,
+  //         联系电话: item.phone,
+  //         人数: String(item.pcs),
+  //         预约日期: item.appointmentTime,
+  //         预约时段: item.time,
+  //         参观者姓名: visitor.name,
+  //         参观者身份证号: visitor.idCard,
+  //         参观者联系电话: visitor.phone
+  //       }
+  //     })
+  //     return list
+  //   })
+  //   console.log(listData.flat(), 'listData')
+  //   exportExcelFile(listData.flat(), [150, 50, 100, 100, 100, 140, 50, 140, 130], '展馆预约记录')
+  // }, [tableInfo.list])
+
+  // 导出excel后端实现
+  const exportExcelFu2 = useCallback(async () => {
+    window.open(B_APIexportExcel(1), '_blank')
+  }, [])
+
   const tableLastBtn = useMemo(() => {
     return [
       {
@@ -81,7 +109,7 @@ function B1reserve1() {
 
       {/* 顶部筛选 */}
       <div className='B1top'>
-        <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '导出' })}>
+        <Button type='primary' onClick={exportExcelFu2}>
           导出
         </Button>
       </div>
@@ -101,7 +129,7 @@ function B1reserve1() {
         />
       </div>
 
-      {/* 导出 */}
+      {/* 查看 */}
       {editInfo.id ? (
         <B1look
           editInfo={editInfo}

+ 32 - 4
src/pages/B2reserve2/index.tsx

@@ -3,7 +3,7 @@ import styles from './index.module.scss'
 import { Button } from 'antd'
 import { useDispatch, useSelector } from 'react-redux'
 import { B2EditInfoType } from './data'
-import { B_APIgetList, B_APIdel } from '@/store/action/Breserve'
+import { B_APIgetList, B_APIdel, B_APIexportExcel } from '@/store/action/Breserve'
 import { RootState } from '@/store'
 import { B2tableType } from '@/types'
 import MyPopconfirm from '@/components/MyPopconfirm'
@@ -11,6 +11,7 @@ import MyTable from '@/components/MyTable'
 import { B2tableC } from '@/utils/tableData'
 import B2look from './B2look'
 import { MessageFu } from '@/utils/message'
+// import { exportExcelFile } from '@/utils/xlsxExport'
 
 const pageDataBase = {
   pageNum: 1,
@@ -49,6 +50,33 @@ function B2reserve2() {
     [getListFu]
   )
 
+  // 导出excel前端实现
+  // const exportExcelFu = useCallback(() => {
+  //   const listData = tableInfo.list.map(item => {
+  //     const list = item.visitors.map(visitor => {
+  //       return {
+  //         申请时间: item.createTime,
+  //         申请人姓名: item.userName,
+  //         联系电话: item.phone,
+  //         活动名称: item.activityTitle,
+  //         预约日期: item.appointmentTime,
+  //         身份证: item.idCard,
+  //         参观者姓名: visitor.name,
+  //         参观者身份证号: visitor.idCard,
+  //         参观者联系电话: visitor.phone
+  //       }
+  //     })
+  //     return list
+  //   })
+  //   console.log(listData.flat(), 'listData')
+  //   exportExcelFile(listData.flat(), [150, 80, 100, 100, 100, 140, 80, 140, 130], '活动预约记录')
+  // }, [tableInfo.list])
+
+  // 导出excel后端实现
+  const exportExcelFu2 = useCallback(async () => {
+    window.open(B_APIexportExcel(2), '_blank')
+  }, [])
+
   const tableLastBtn = useMemo(() => {
     return [
       {
@@ -77,11 +105,11 @@ function B2reserve2() {
 
   return (
     <div className={styles.B2reserve2}>
-      <div className='pageTitle'>展馆预约记录</div>
+      <div className='pageTitle'>活动预约记录</div>
 
       {/* 顶部筛选 */}
       <div className='B2top'>
-        <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '导出' })}>
+        <Button type='primary' onClick={exportExcelFu2}>
           导出
         </Button>
       </div>
@@ -101,7 +129,7 @@ function B2reserve2() {
         />
       </div>
 
-      {/* 导出 */}
+      {/* 查看 */}
       {editInfo.id ? (
         <B2look
           editInfo={editInfo}

+ 0 - 3
src/pages/C1reserveOpt/C1NoTime.tsx

@@ -13,7 +13,6 @@ type Props = {
 }
 
 function C1NoTime({ baseTime, closeFu, editFu, num }: Props) {
-  console.log(baseTime)
   const baseTimeArr = baseTime
     ? baseTime
         .split(',')
@@ -24,13 +23,11 @@ function C1NoTime({ baseTime, closeFu, editFu, num }: Props) {
   const onChange = useCallback(
     (date: any, dateString: string[] | string) => {
       if (dateString.length) {
-        console.log(dateString)
         let arr = dateString as string[]
         if (arr.length > num) {
           arr = arr.filter((c, i) => i < num)
           MessageFu.warning(`最多${num}个日期,已过滤超出日期`)
         }
-        console.log(arr)
         setValue(arr.map(v => v.replaceAll('年', '-').replaceAll('月', '-').replaceAll('日', '')))
       } else setValue([])
     },

+ 4 - 4
src/pages/C1reserveOpt/C1edit/index.module.scss

@@ -82,10 +82,10 @@
 
     .C1eMmain {
       padding-top: 15px;
-      text-align: center;
-      .C1eMmainTit {
-        margin-top: 10px;
-      }
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      gap: 10px;
     }
 
     .C1eMbtn {

+ 97 - 14
src/pages/C1reserveOpt/C1edit/index.tsx

@@ -1,6 +1,6 @@
 import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
-import { Button, Input, Modal, Table, TimePicker } from 'antd'
+import { Button, Input, Modal, Table, TimePicker, Select } from 'antd'
 import dayjs from 'dayjs'
 import { DeleteOutlined } from '@ant-design/icons'
 import MyPopconfirm from '@/components/MyPopconfirm'
@@ -33,6 +33,8 @@ type Props = {
 function C1edit({ list, closeFu, editTableFu }: Props) {
   // 表格数据
   const [listRes, setListRes] = useState<C1tableType[]>([])
+  // 当前时段,1是上午,2是下午
+  const [currentTime, setCurrentTime] = useState(1)
 
   useEffect(() => {
     setListRes(
@@ -43,11 +45,52 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
     )
   }, [list])
 
-  // 1---------时段
+  // 时段
   const [time, setTime] = useState({} as TimeType)
 
-  // 1---------时段点击确定
+  // 时段点击确定
   const timeBtnOk = useCallback(async () => {
+    // 冲突校验逻辑
+    if (time.value) {
+      // 解析新时段时间
+      const [newStart, newEnd] = time.value
+      const newStartMin = parseInt(newStart.split(':')[0]) * 60 + parseInt(newStart.split(':')[1])
+      const newEndMin = parseInt(newEnd.split(':')[0]) * 60 + parseInt(newEnd.split(':')[1])
+
+      // 检查与现有时段的冲突(排除自己)
+      const hasTimeConflict = listRes.some(item => {
+        if (time.txt === '编辑' && item.id === time.id) return false
+        const [existStart, existEnd] = item.time.split('-')
+        const existStartMin =
+          parseInt(existStart.split(':')[0]) * 60 + parseInt(existStart.split(':')[1])
+        const existEndMin = parseInt(existEnd.split(':')[0]) * 60 + parseInt(existEnd.split(':')[1])
+        return newStartMin < existEndMin && newEndMin > existStartMin
+      })
+      if (hasTimeConflict) {
+        return MessageFu.warning('时间段与其他时段存在时间冲突,请重新设置!')
+      }
+
+      // 类型互斥校验
+      const getTimeType = (timeStr: string) => {
+        // 判断时段类型(1=上午,2=下午)
+        const startHour = parseInt(timeStr.split('-')[0].split(':')[0])
+        return startHour < 15 ? 1 : 2
+      }
+
+      const currentType = getTimeType(time.value.join('-')) // 当前操作的时段类型
+      const otherTypes = listRes
+        .filter(item => (time.txt === '编辑' ? item.id !== time.id : true)) // 编辑时排除自己
+        .map(item => getTimeType(item.time))
+
+      if (otherTypes.includes(currentType)) {
+        return MessageFu.warning(
+          `已有${currentType === 1 ? '上午' : '下午'}时段,只能设置${
+            currentType === 1 ? '下午' : '上午'
+          }时段!`
+        )
+      }
+    }
+
     if (time.txt === '新增') {
       setListRes([
         ...listRes,
@@ -72,12 +115,10 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
       )
     }
 
-    MessageFu.success(time.txt + '时段成功!')
-
     setTime({} as TimeType)
   }, [listRes, time])
 
-  // 1---------时间段改变
+  // 时间段改变
   const timeChangeFu = useCallback(
     (value: string[]) => {
       const valueRes = value[0] && value[1] ? value : null
@@ -107,17 +148,18 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
               }}
               Dom={<DeleteOutlined />}
             />
-
             <span
               className='C1ett'
-              onClick={() =>
+              onClick={() => {
+                const startHour = parseInt(item.time.split('-')[0].split(':')[0])
+                setCurrentTime(startHour < 15 ? 1 : 2)
                 setTime({
                   show: true,
                   value: item.time.split('-'),
                   txt: '编辑',
                   id: item.id
                 })
-              }
+              }}
             >
               {item.time}
             </span>
@@ -142,7 +184,7 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
               })
             }
           >
-            {Reflect.get(item, v.key)}
+            {Reflect.get(item, v.key) === -1 ? '闭馆' : Reflect.get(item, v.key)}
           </span>
         )
       })
@@ -150,20 +192,21 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
     return arr
   }, [listRes])
 
-  // 2---------星期的数字
+  // 星期的数字
   const [num, setNum] = useState({} as NumType)
 
   const inputChange = useCallback(
     (val: React.ChangeEvent<HTMLInputElement>) => {
       let txt = val.target.value.replace(/^(0+)|[^\d]+/g, '')
       let txtNum = Number(txt)
+      txtNum = txtNum < 1 ? 1 : txtNum
       txtNum = txtNum > 9999 ? 9999 : txtNum
       setNum({ ...num, value: txtNum })
     },
     [num]
   )
 
-  // 2---------提交
+  // 提交
   const numBtnOk = useCallback(() => {
     setListRes(
       listRes.map(v => ({
@@ -245,11 +288,47 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
         }
       >
         <div className='C1eMmain'>
+          <Select
+            value={currentTime}
+            options={[
+              {
+                label: '上午',
+                value: 1
+              },
+              {
+                label: '下午',
+                value: 2
+              }
+            ]}
+            onChange={v => {
+              setCurrentTime(v)
+              time.value = null
+            }}
+          />
           <TimePicker.RangePicker
             format='HH:mm'
             value={
               time.value ? [dayjs(time.value[0], 'HH:mm'), dayjs(time.value[1], 'HH:mm')] : null
             }
+            disabledTime={() => {
+              return {
+                disabledHours: () => {
+                  if (currentTime === 1) {
+                    // 第一个时段限制8-14点
+                    return [0, 1, 2, 3, 4, 5, 6, 7, 15, 16, 17, 18, 19, 20, 21, 22, 23]
+                  } else {
+                    // 第二个时段限制15-20点
+                    return [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21, 22, 23]
+                  }
+                },
+                disabledMinutes: currentHour => {
+                  if (currentHour === 20 || currentHour === 14) {
+                    return Array.from({ length: 59 }, (_, i) => i + 1) // 禁用0-59分钟(即无法选20:01及之后)
+                  }
+                  return []
+                }
+              }
+            }}
             onChange={(_, value) => timeChangeFu(value)}
           />
         </div>
@@ -272,18 +351,22 @@ function C1edit({ list, closeFu, editTableFu }: Props) {
       >
         <div className='C1eMmain'>
           <Input
+            size='large'
             style={{ width: 208 }}
             // placeholder="请输入0~9999整数"
-            value={num.value}
+            value={num.value === -1 ? '闭馆' : num.value}
             onChange={e => inputChange(e)}
+            min={1}
+            max={9999}
           />
-          <div className='C1eMmainTit'>&nbsp;&nbsp;为0时,则该时段不可预约(闭馆)</div>
         </div>
         <div className='C1eMbtn'>
           <Button onClick={() => setNum({} as NumType)}>取消</Button>&emsp;
           <Button type='primary' onClick={numBtnOk}>
             提交
           </Button>
+          &emsp;
+          <Button onClick={() => setNum({ ...num, value: -1 })}>闭馆</Button>
         </div>
       </Modal>
     </div>

+ 17 - 21
src/pages/C1reserveOpt/C1xuZhi.tsx

@@ -1,8 +1,8 @@
-import React, { useCallback, useEffect, useRef } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
 import styles from './index.module.scss'
-import { Button, Modal } from 'antd'
+import { Button, Modal, Input } from 'antd'
 import MyPopconfirm from '@/components/MyPopconfirm'
-import ZRichTexts from '@/components/ZRichTexts'
+// import ZRichTexts from '@/components/ZRichTexts'
 import { C1_APIgetbk, C1_APIsavebk } from '@/store/action/C1reserveOpt'
 import { MessageFu } from '@/utils/message'
 
@@ -11,24 +11,13 @@ type Props = {
 }
 
 function C1xuZhi({ closeFu }: Props) {
-  // 富文本的ref
-  const ZRichTextRef = useRef<any>(null)
+  const [val, setVal] = useState('')
 
   // 获取设置
   const getEMfu = useCallback(async () => {
     const res = await C1_APIgetbk({ type: 'notice' })
     if (res.code === 0) {
-      // 设置富文本
-      ZRichTextRef.current?.ritxtShowFu({
-        txtArr: [
-          {
-            id: 0,
-            name: '正文',
-            txt: res.data?.rtf || '',
-            fileInfo: { fileName: '', filePath: '' }
-          }
-        ]
-      })
+      setVal(res.data?.rtf || '')
     }
   }, [])
 
@@ -39,15 +28,15 @@ function C1xuZhi({ closeFu }: Props) {
   // 点击提交
   const btnOk = useCallback(async () => {
     // 富文本校验不通过
-    const rtf = ZRichTextRef.current?.fatherBtnOkFu() || { flag: true }
-
-    const res = await C1_APIsavebk({ type: 'notice', rtf: rtf.val.txtArr[0].txt || '' })
+    // const rtf = ZRichTextRef.current?.fatherBtnOkFu() || { flag: true }
 
+    // const res = await C1_APIsavebk({ type: 'notice', rtf: rtf.val.txtArr[0].txt || '' })
+    const res = await C1_APIsavebk({ type: 'notice', rtf: val || '' })
     if (res.code === 0) {
       MessageFu.success(`设置预约须知成功!`)
       closeFu()
     }
-  }, [closeFu])
+  }, [closeFu, val])
 
   return (
     <Modal
@@ -59,7 +48,7 @@ function C1xuZhi({ closeFu }: Props) {
       }
     >
       <div className='formRow'>
-        <ZRichTexts
+        {/* <ZRichTexts
           check={false}
           dirCode={'C1xuZhiText'}
           isLook={false}
@@ -67,6 +56,13 @@ function C1xuZhi({ closeFu }: Props) {
           myUrl='museum/upload/upload'
           isOne={true}
           upAudioBtnNone={true}
+        /> */}
+        <Input
+          value={val}
+          maxLength={3000}
+          showCount
+          onChange={(e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value)}
+          placeholder='请输入内容,最多3000字'
         />
       </div>
 

+ 3 - 0
src/pages/C1reserveOpt/index.module.scss

@@ -68,6 +68,9 @@
       padding-top: 15px !important;
     }
 
+    .formRow {
+      padding-bottom: 50px;
+    }
     .C1Xbtn {
       text-align: center;
       margin-top: 15px;

+ 1 - 2
src/pages/C1reserveOpt/index.tsx

@@ -1,4 +1,4 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react'
+import React, { useCallback, useEffect, useState } from 'react'
 import styles from './index.module.scss'
 import { Button } from 'antd'
 import MyTable from '@/components/MyTable'
@@ -43,7 +43,6 @@ function C1reserveOpt() {
   // 点击提交或者取消
   const A2NoBtn = useCallback(
     async (val: string[]) => {
-      console.log(val)
       let str = ''
 
       if (val && val[0] !== '') {

src/pages/D1feedback/D1Export/index.module.scss → src/pages/D1feedback/D1look/index.module.scss


src/pages/D1feedback/D1Export/index.tsx → src/pages/D1feedback/D1look/index.tsx


+ 25 - 5
src/pages/D1feedback/index.tsx

@@ -3,14 +3,15 @@ import styles from './index.module.scss'
 import { Button } from 'antd'
 import { useDispatch, useSelector } from 'react-redux'
 import { D1EditInfoType } from './data'
-import { D1_APIgetList, D1_APIdel } from '@/store/action/D1feedback'
+import { D1_APIgetList, D1_APIdel, D1_APIexportExcel } from '@/store/action/D1feedback'
 import { RootState } from '@/store'
 import { MessageFu } from '@/utils/message'
 import { D1tableType } from '@/types'
 import MyPopconfirm from '@/components/MyPopconfirm'
 import MyTable from '@/components/MyTable'
 import { D1tableC } from '@/utils/tableData'
-import D1Export from './D1Export'
+import D1look from './D1look'
+// import { exportExcelFile } from '@/utils/xlsxExport'
 
 const pageDataBase = {
   pageNum: 1,
@@ -68,6 +69,25 @@ function D1feedback() {
     ]
   }, [delTableFu])
 
+  // 导出excel前端实现
+  // const exportExcelFu = useCallback(() => {
+  //   const listData = tableInfo.list.map(item => {
+  //     return {
+  //       提交时间: item.createTime,
+  //       姓名: item.name,
+  //       邮箱: item.email,
+  //       内容: item.content
+  //     }
+  //   })
+  //   console.log(listData.flat(), 'listData')
+  //   exportExcelFile(listData.flat(), [150, 60, 150, 200], '意见反馈记录')
+  // }, [tableInfo.list])
+
+  // 导出excel后端实现
+  const exportExcelFu2 = useCallback(async () => {
+    window.open(D1_APIexportExcel(), '_blank')
+  }, [])
+
   //新增、编辑
   const [editInfo, setEditInfo] = useState<D1EditInfoType>({
     id: 0,
@@ -80,7 +100,7 @@ function D1feedback() {
 
       {/* 顶部筛选 */}
       <div className='D1top'>
-        <Button type='primary' onClick={() => setEditInfo({ id: -1, txt: '导出' })}>
+        <Button type='primary' onClick={exportExcelFu2}>
           导出
         </Button>
       </div>
@@ -100,9 +120,9 @@ function D1feedback() {
         />
       </div>
 
-      {/* 导出 */}
+      {/* 查看 */}
       {editInfo.id ? (
-        <D1Export
+        <D1look
           editInfo={editInfo}
           closeFu={() => setEditInfo({ id: 0, txt: '导出' })}
           addTableFu={resetSelectFu}

+ 21 - 7
src/pages/Layout/index.tsx

@@ -6,7 +6,7 @@ import { Route, Switch, useLocation } from 'react-router-dom'
 import AuthRoute from '@/components/AuthRoute'
 import classNames from 'classnames'
 import history from '@/utils/history'
-import { Button, Form, Input, Modal } from 'antd'
+import { Button, Form, FormInstance, Input, Modal } from 'antd'
 // import { Base64 } from 'js-base64'
 // import encodeStr from '@/utils/pass'
 import { passWordEditAPI, getLeftMenuAPI } from '@/store/action/layout'
@@ -57,7 +57,6 @@ function Layout() {
 
     if (res.code === 0) {
       const tempList: UserListType[] = res.data || []
-      console.log('tempList', tempList)
       tempList.forEach(v => {
         if (v.children) {
           v.children.forEach(c => {
@@ -124,6 +123,8 @@ function Layout() {
 
   // 拿到新密码的输入框的值
   const oldPasswordValue = useRef('')
+  // form表单的ref
+  const formRef = useRef<FormInstance>(null)
 
   const checkPassWord = (rule: any, value: any = '') => {
     if (value !== oldPasswordValue.current) return Promise.reject('新密码不一致!')
@@ -139,6 +140,14 @@ function Layout() {
       pwd: values.newPassword
     }
     const res: any = await passWordEditAPI(obj)
+    if (res.message === '密码错误') {
+      return formRef.current?.setFields([
+        {
+          name: 'oldPassword',
+          errors: ['密码错误']
+        }
+      ])
+    }
     if (res.code === 0) {
       MessageFu.success('修改成功!')
       loginExit()
@@ -165,7 +174,6 @@ function Layout() {
         <div className='layoutLeftMain'>
           {list.map(v => (
             <div className={classNames('layoutLRowBox')} key={v.id} hidden={!v.son.length}>
-              {/* 这个项目没有一级目录 */}
               <div className='layoutLRowBoxTxt'>{v.name}</div>
               {v.son.map(v2 => (
                 <div
@@ -232,6 +240,7 @@ function Layout() {
         }
       >
         <Form
+          ref={formRef}
           scrollToFirstError={true}
           name='basic'
           labelCol={{ span: 5 }}
@@ -245,7 +254,7 @@ function Layout() {
             rules={[{ required: true, message: '不能为空!' }]}
             getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
           >
-            <Input.Password maxLength={20} />
+            <Input.Password maxLength={20} placeholder='请输入内容,6-15个字' />
           </Form.Item>
 
           <Form.Item
@@ -253,12 +262,17 @@ function Layout() {
             name='newPassword'
             rules={[
               { required: true, message: '不能为空!' },
-              { min: 6, max: 15, message: '密码长度为6-15个字符!' }
+              { min: 6, max: 15, message: '密码长度需为6-15个字符!' },
+              {
+                pattern: /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{6,15}$/,
+                message: '密码必须包含数字和字母!'
+              }
             ]}
             getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
           >
             <Input.Password
               maxLength={15}
+              placeholder='请输入内容,6-15个字'
               onChange={e => (oldPasswordValue.current = e.target.value)}
             />
           </Form.Item>
@@ -266,10 +280,10 @@ function Layout() {
           <Form.Item
             label='确定新密码'
             name='checkPass'
-            rules={[{ validator: checkPassWord }]}
+            rules={[{ required: true, validator: checkPassWord }]}
             getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
           >
-            <Input.Password maxLength={15} />
+            <Input.Password maxLength={15} placeholder='请输入内容,6-15个字' />
           </Form.Item>
 
           <Form.Item wrapperCol={{ offset: 14, span: 16 }}>

+ 2 - 2
src/pages/Login/index.tsx

@@ -29,7 +29,7 @@ export default function Login() {
   // 点击登录
   const loginClickFu = useCallback(async () => {
     // 非空判断
-    if (userName === '') return MessageFu.warning('请输入账号!')
+    if (userName === '') return MessageFu.warning('请输入用户名!')
     else if (passWord === '') return MessageFu.warning('请输入密码!')
     const obj = {
       password: passWord,
@@ -74,7 +74,7 @@ export default function Login() {
               value={userName}
               onChange={e => setUserName(e.target.value.trim())}
               prefix={<img src={images.loginUser} alt='' />}
-              placeholder='请输入账号'
+              placeholder='请输入用户名'
               maxLength={15}
             />
           </div>

+ 26 - 7
src/pages/Z1user/UserAdd/index.tsx

@@ -1,8 +1,8 @@
 import { userSaveAPI } from '@/store/action/Z1user'
 import { SaveUserType } from '@/types'
 import { MessageFu } from '@/utils/message'
-import { Button, Form, Input, Modal } from 'antd'
-import React, { useCallback } from 'react'
+import { Button, Form, FormInstance, Input, Modal } from 'antd'
+import React, { useCallback, useRef } from 'react'
 import styles from './index.module.scss'
 import MyPopconfirm from '@/components/MyPopconfirm'
 
@@ -13,6 +13,7 @@ type Props = {
 }
 
 function UserAdd({ id, closePage, addTableList }: Props) {
+  const formRef = useRef<FormInstance>(null)
   // 通过校验点击确定
   const onFinish = useCallback(
     async (values: any) => {
@@ -21,10 +22,18 @@ function UserAdd({ id, closePage, addTableList }: Props) {
         status: 1,
         password: '123456' // 添加默认密码
       }
-      console.log(obj, 'obj')
 
       const res: any = await userSaveAPI(obj)
 
+      if (res.message === '用户已添加') {
+        return formRef.current?.setFields([
+          {
+            name: 'userName',
+            errors: ['已存在重复用户名']
+          }
+        ])
+      }
+
       if (res.code === 0) {
         MessageFu.success('新增成功!')
         addTableList()
@@ -47,6 +56,7 @@ function UserAdd({ id, closePage, addTableList }: Props) {
     >
       <div className='userAddMain'>
         <Form
+          ref={formRef}
           scrollToFirstError={true}
           name='basic'
           labelCol={{ span: 5 }}
@@ -54,15 +64,24 @@ function UserAdd({ id, closePage, addTableList }: Props) {
           autoComplete='off'
         >
           <Form.Item
-            label='账号名'
+            label='用户名'
             name='userName'
             rules={[
-              { required: true, message: '请输入账号名!' },
-              { min: 6, message: '最少6个字!' }
+              { required: true, message: '请输入用户名!' },
+              { min: 6, max: 15, message: '用户名需为6-15个字符!' },
+              {
+                pattern: /^[a-zA-Z0-9]+$/,
+                message: '用户名只能包含数字和字母!'
+              }
             ]}
             getValueFromEvent={e => e.target.value.replace(/\s+/g, '')}
           >
-            <Input disabled={id} maxLength={15} showCount placeholder='请输入内容' />
+            <Input
+              disabled={id}
+              maxLength={15}
+              showCount
+              placeholder='请输入内容,6-15字,不能重复'
+            />
           </Form.Item>
 
           <Form.Item label='初始密码' name='password'>

+ 0 - 1
src/pages/Z1user/UserAuth/index.tsx

@@ -19,7 +19,6 @@ function UserAuth({ authInfo, closeFu }: Props) {
   const [selectList, setSelectList] = useState<number[]>([])
 
   const checkboxChange = useCallback((value: any) => {
-    console.log(value)
     setSelectList(value)
   }, [])
 

+ 16 - 7
src/pages/Z1user/index.tsx

@@ -35,12 +35,12 @@ function Z1user() {
   const timeRef = useRef(-1)
   // 用户名
   const txtChangeFu = useCallback(
-    (e: React.ChangeEvent<HTMLInputElement>, key: 'userName') => {
+    (name: string, key: 'userName') => {
       clearTimeout(timeRef.current)
       timeRef.current = window.setTimeout(() => {
         setFromData({
           ...fromData,
-          [key]: e.target.value,
+          [key]: name,
           pageNum: 1
         })
       }, 500)
@@ -129,31 +129,40 @@ function Z1user() {
   // 授权管理
   const [authInfo, setAuthInfo] = useState({ id: 0, name: '' })
 
+  const [name, setName] = useState('')
+
   return (
     <div className={styles.Z1user}>
       <div className='pageTitle'>{authInfo.id ? `权限管理:${authInfo.name}` : '用户管理'}</div>
       <div className='userTop'>
         <div className='selectBox'>
           <div className='selectBoxRow'>
-            <span>搜索项:</span>
             <Input
               key={inputKey}
               maxLength={10}
               showCount
-              style={{ width: 300 }}
+              style={{ width: 200 }}
               placeholder='请输入用户名'
               allowClear
-              onChange={e => txtChangeFu(e, 'userName')}
+              value={name}
+              onChange={e => setName(e.target.value)}
             />
+            &emsp;
+            <Button type='primary' onClick={() => txtChangeFu(name, 'userName')}>
+              查询
+            </Button>
+            &emsp;&emsp;
+            <Button type='primary' onClick={resetSelectFu}>
+              重置
+            </Button>
           </div>
 
           <div className='selectBoxRow'>
-            &emsp;&emsp;<Button onClick={resetSelectFu}>重置</Button>
             &emsp;&emsp;
             <Button
               type='primary'
               onClick={() => {
-                if (tableInfo.total >= 30) return MessageFu.warning('最多30个账号!')
+                if (tableInfo.total >= 20) return MessageFu.warning('最多20个账号!')
                 openEditPageFu(0)
               }}
             >

+ 3 - 58
src/store/action/A2introduction.ts

@@ -1,22 +1,7 @@
 import http from '@/utils/http'
-import { AppDispatch } from '..'
 import { A2AddType, A2editType } from '@/types/api/A2introduction'
 
 /**
- *图书分类-树
- */
-
-export const A2_APIgetList1 = (): any => {
-  return async (dispatch: AppDispatch) => {
-    const res = await http.get('cms/storage/getTree')
-    if (res.code === 0) {
-      dispatch({ type: 'A2/getTree', payload: res.data })
-      dispatch({ type: 'A2/treeFlag', payload: true })
-      // console.log(123, res)
-    }
-  }
-}
-/**
  * 克博简介-添加、编辑
  */
 export const A2_APIaddOrEdit = (data: A2AddType | A2editType) => {
@@ -31,48 +16,8 @@ export const A2_APIgetInfo = (type: number) => {
 }
 
 /**
- * 克博简介-新增、编辑
- */
-export const A2_APIsave1 = (data: any) => {
-  return http.post('cms/storage/save', data)
-}
-
-// -------------------展示分类--------------------
-
-/**
- *展示分类-列表
- */
-
-export const A2_APIgetList2 = (data: any): any => {
-  return async (dispatch: AppDispatch) => {
-    const res = await http.post('cms/exhibitType/getList', data)
-    if (res.code === 0) {
-      const obj = {
-        list: res.data,
-        total: 50
-      }
-      dispatch({ type: 'A2/getList', payload: obj })
-    }
-  }
-}
-
-/**
- * 展示分类-删除
- */
-export const A2_APIdel2 = (id: number) => {
-  return http.get(`cms/exhibitType/remove/${id}`)
-}
-
-/**
- * 展示分类-获取详情
- */
-export const A2_APIgetInfo2 = (id: number) => {
-  return http.get(`cms/exhibitType/detail/${id}`)
-}
-
-/**
- * 展示分类-新增、编辑
+ * 克博简介-发布
  */
-export const A2_APIsave2 = (data: any) => {
-  return http.post('cms/exhibitType/save', data)
+export const A2_APIpublish = (contextId: number) => {
+  return http.post(`museum/introContext/publish`, { contextId })
 }

+ 7 - 0
src/store/action/Breserve.ts

@@ -30,3 +30,10 @@ export const B_APIgetInfo = (id: number) => {
 export const B_APIdel = (id: number) => {
   return http.post(`museum/appointment/del/${id}`)
 }
+
+/**
+ * 导出Excel Type 1-展馆预约记录 2-活动预约记录
+ */
+export const B_APIexportExcel = (type: number) => {
+  return `https://sit-kelamayi.4dage.com/api/museum/appointment/exportExcel?type=${type}`
+}

+ 7 - 0
src/store/action/D1feedback.ts

@@ -36,3 +36,10 @@ export const D1_APIadd = (data: any) => {
 export const D1_APIgetInfo = (id: number) => {
   return http.post(`museum/feedback/info/${id}`)
 }
+
+/**
+ * 意见反馈记录-导出
+ */
+export const D1_APIexportExcel = () => {
+  return `https://sit-kelamayi.4dage.com/api/museum/feedback/exportExcel`
+}

+ 8 - 2
src/store/reducer/A1banner.ts

@@ -49,11 +49,14 @@ export default function Reducer(state = initState, action: Props | sortProps) {
         ]
       }
       A1_APIsort(obj)
+      const newList = swapItems(state.tableInfo.list, indexSort, indexSort - 1)
+      newList[indexSort].sort = indexDown
+      newList[indexSort - 1].sort = indexUp
       return {
         ...state,
         tableInfo: {
           ...state.tableInfo,
-          list: swapItems(state.tableInfo.list, indexSort, indexSort - 1)
+          list: newList
         }
       }
     // 下移
@@ -77,11 +80,14 @@ export default function Reducer(state = initState, action: Props | sortProps) {
         ]
       }
       A1_APIsort(obj2)
+      const newList2 = swapItems(state.tableInfo.list, indexSort2, indexSort2 + 1)
+      newList2[indexSort2].sort = indexUp2
+      newList2[indexSort2 + 1].sort = indexDown2
       return {
         ...state,
         tableInfo: {
           ...state.tableInfo,
-          list: swapItems(state.tableInfo.list, indexSort2, indexSort2 + 1)
+          list: newList2
         }
       }
     default:

+ 0 - 44
src/store/reducer/A2introduction.ts

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

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

@@ -4,7 +4,6 @@ import { combineReducers } from 'redux'
 // 导入 登录 模块的 reducer
 import A0Layout from './layout'
 import A1banner from './A1banner'
-import A2introduction from './A2introduction'
 import A3map from './A3map'
 import A4news from './A4news'
 import A5activity from './A5activity'
@@ -19,7 +18,6 @@ import Breserve from './Breserve'
 const rootReducer = combineReducers({
   A0Layout,
   A1banner,
-  A2introduction,
   A3map,
   A4news,
   A5activity,

+ 5 - 26
src/types/api/A2introduction.ts

@@ -1,36 +1,15 @@
-export type A2TreeType = {
-  ancestor: string
-  children: A2TreeType[] | null
-  createTime: string
-  description: string
-  id: number
-  level: number
-  name: string
-  num: string
-  parentId: number | null
-  updateTime: string
-  sort: number
-  creatorName: string
-}
-
-export type A2tableType = {
-  createTime: string
-  creatorId: number
-  creatorName: string
-  id: number
-  name: string
-  sort: number
-  updateTime: string
-}
-
 // type 1:简介 2:地图
 export type A2AddType = {
   context: string
+  contextB: string
+  status: number
   type: number
 }
 
 export type A2editType = {
-  carouselId: number
+  contextId: number
   context: string
+  contextB: string
+  status: number
   type: number
 }

+ 2 - 0
src/types/api/A6exhibition.ts

@@ -21,6 +21,7 @@ export type A6tableType = {
   imgB: string | null
   imgThB: string | null
   status: 0 | 1
+  recommend: 0 | 1
 }
 
 export type A6AddType = {
@@ -41,4 +42,5 @@ export type A6AddType = {
   titleB: string
   webSite: string
   webSiteB: string
+  recommend: 0 | 1
 }

+ 4 - 1
src/types/declaration.d.ts

@@ -6,4 +6,7 @@ declare module '*.gif'
 declare module '*.svg'
 declare module 'js-export-excel'
 declare module 'braft-utils'
-declare const isBookIdShow: boolean
+declare module 'xlsx' {
+  const XLSX: any
+  export = XLSX
+}

+ 0 - 1
src/utils/copyTxt.ts

@@ -14,7 +14,6 @@ const copyWithTextarea = (text: string) => {
   return copyTXT
 }
 export const handleCopyClick = (text: string) => {
-  console.log(text, 'text')
   if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
     navigator.clipboard.writeText(text)
     message.success('链接已复制到剪贴板')

+ 4 - 0
src/utils/http.ts

@@ -69,6 +69,10 @@ http.interceptors.response.use(
       }, 200)
     } else if (response.data.code === 0) {
       // MessageFu.success(response.data.msg);
+    } else if (response.data.code === 4006) {
+      // 密码错误
+    } else if (response.data.code === 4004) {
+      // 用户名重复
     } else if (response.data.code !== 0) MessageFu.warning(response.data.message)
 
     return response.data

+ 13 - 12
src/utils/tableData.ts

@@ -19,7 +19,7 @@ export const A1tableC = [
   ['img', '轮播图封面', 'imgTh', 'imgThB'],
   ['txt', '链接', 'url', 'urlB'],
   ['txt', '发布日期', 'publish', 'publishB'],
-  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }],
+  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }],
   ['sort', '排序', 'sort']
 ]
 
@@ -28,7 +28,7 @@ export const A4tableC = [
   ['img', '封面', 'indexImgTh', 'indexImgThB'],
   ['txt', '摘要', 'remark', 'remarkB'],
   ['txt', '发布日期', 'publish', 'publishB'],
-  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }]
+  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }]
 ]
 
 export const A6tableC = [
@@ -37,19 +37,20 @@ export const A6tableC = [
   ['img', '展览封面', 'imgTh', 'imgThB'],
   ['txt', '摘要', 'remark', 'remarkB'],
   ['txt', '发布日期', 'publish', 'publishB'],
-  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }]
+  ['txtChange', '状态', 'status', { 0: '预发布', 1: '已发布' }],
+  ['txtChange', '设为推荐展览', 'recommend', { 0: '否', 1: '是' }]
 ]
 
 export const A7tableC = [
   ['txt', '标题', 'title', 'titleB'],
-  ['txtChange', '类别', 'type', { 1: '平面纸质类', 2: '棉麻丝绸类', 3: '专业器物类' }],
+  ['txtChange', '类别', 'type', { 1: '平面纸质类', 2: '棉麻丝绸类', 3: '专业器物类', 4: '其他' }],
   ['txt', '级别', 'level', 'levelB'],
   ['txt', '质地', 'texture', 'textureB'],
   ['txt', '年代', 'era', 'eraB'],
   ['img', '封面', 'imgTh', 'imgThB'],
   ['txt', '简介', 'remark', 'remarkB'],
   ['txt', '发布日期', 'publish', 'publishB'],
-  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }]
+  ['txtChange', '状态', 'status', { 0: '预发布', 1: '发布' }]
 ]
 
 export const B1tableC = [
@@ -72,13 +73,13 @@ export const B2tableC = [
 
 export const C1tableC = [
   ['txt', '时段', 'time'],
-  ['txt', '周一', 'monday'],
-  ['txt', '周二', 'tuesday'],
-  ['txt', '周三', 'wednesday'],
-  ['txt', '周四', 'thursday'],
-  ['txt', '周五', 'friday'],
-  ['txt', '周六', 'saturday'],
-  ['txt', '周日', 'sunday']
+  ['reserveOpt', '周一', 'monday'],
+  ['reserveOpt', '周二', 'tuesday'],
+  ['reserveOpt', '周三', 'wednesday'],
+  ['reserveOpt', '周四', 'thursday'],
+  ['reserveOpt', '周五', 'friday'],
+  ['reserveOpt', '周六', 'saturday'],
+  ['reserveOpt', '周日', 'sunday']
 ]
 
 export const D1tableC = [

+ 23 - 0
src/utils/xlsxExport.ts

@@ -0,0 +1,23 @@
+import * as xlsx from 'xlsx'
+import { WorkBook } from 'xlsx/types'
+
+export function exportExcelFile(
+  array: any[],
+  width?: number[],
+  fileName = '展馆预约记录',
+  sheetName = '表1'
+) {
+  // 设置列宽或默认150
+  const columnWidths = width
+    ? width.map((w, index) => ({ wpx: w }))
+    : Array(Object.keys(array[0]).length).fill({ wpx: 150 })
+  const jsonWorkSheet = xlsx.utils.json_to_sheet(array)
+  jsonWorkSheet['!cols'] = columnWidths
+  const workBook: WorkBook = {
+    SheetNames: [sheetName],
+    Sheets: {
+      [sheetName]: jsonWorkSheet
+    }
+  }
+  return xlsx.writeFile(workBook, `${fileName}.xlsx`)
+}

+ 127 - 3
yarn.lock

@@ -2482,6 +2482,13 @@
   dependencies:
     "@types/node" "*"
 
+"@types/xlsx@^0.0.36":
+  version "0.0.36"
+  resolved "https://registry.npmmirror.com/@types/xlsx/-/xlsx-0.0.36.tgz#b5062003e5c5374ab4f08fdd3bf69da4d4013af8"
+  integrity sha512-mvfrKiKKMErQzLMF8ElYEH21qxWCZtN59pHhWGmWCWFJStYdMWjkDSAy6mGowFxHXaXZWe5/TW7pBUiWclIVOw==
+  dependencies:
+    xlsx "*"
+
 "@types/yargs-parser@*":
   version "21.0.3"
   resolved "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15"
@@ -2799,6 +2806,11 @@ adjust-sourcemap-loader@^4.0.0:
     loader-utils "^2.0.0"
     regex-parser "^2.2.11"
 
+adler-32@~1.3.0:
+  version "1.3.1"
+  resolved "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2"
+  integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
+
 agent-base@6:
   version "6.0.2"
   resolved "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -3365,6 +3377,11 @@ balanced-match@^1.0.0:
   resolved "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
+base64-arraybuffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
+  integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
+
 batch@0.6.1:
   version "0.6.1"
   resolved "https://registry.npmmirror.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16"
@@ -3583,6 +3600,14 @@ case-sensitive-paths-webpack-plugin@^2.4.0:
   resolved "https://registry.npmmirror.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
   integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==
 
+cfb@~1.2.1:
+  version "1.2.2"
+  resolved "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44"
+  integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
+  dependencies:
+    adler-32 "~1.3.0"
+    crc-32 "~1.2.0"
+
 chalk@^2.4.1, chalk@^2.4.2:
   version "2.4.2"
   resolved "https://registry.npmmirror.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -3693,6 +3718,11 @@ coa@^2.0.2:
     chalk "^2.4.1"
     q "^1.1.2"
 
+codepage@~1.15.0:
+  version "1.15.0"
+  resolved "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
+  integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
+
 collect-v8-coverage@^1.0.0:
   version "1.0.2"
   resolved "https://registry.npmmirror.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9"
@@ -3902,6 +3932,11 @@ cosmiconfig@^7.0.0:
     path-type "^4.0.0"
     yaml "^1.10.0"
 
+crc-32@~1.2.0, crc-32@~1.2.1:
+  version "1.2.2"
+  resolved "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
+  integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
+
 cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3:
   version "7.0.3"
   resolved "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
@@ -3935,6 +3970,13 @@ css-has-pseudo@^3.0.4:
   dependencies:
     postcss-selector-parser "^6.0.9"
 
+css-line-break@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
+  integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
+  dependencies:
+    utrie "^1.0.2"
+
 css-loader@^6.5.1:
   version "6.10.0"
   resolved "https://registry.npmmirror.com/css-loader/-/css-loader-6.10.0.tgz#7c172b270ec7b833951b52c348861206b184a4b7"
@@ -5282,6 +5324,11 @@ forwarded@0.2.0:
   resolved "https://registry.npmmirror.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811"
   integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==
 
+frac@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
+  integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
+
 fraction.js@^4.3.7:
   version "4.3.7"
   resolved "https://registry.npmmirror.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
@@ -5647,6 +5694,14 @@ html-webpack-plugin@^5.5.0:
     pretty-error "^4.0.0"
     tapable "^2.0.0"
 
+html2canvas@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.npmmirror.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
+  integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
+  dependencies:
+    css-line-break "^2.1.0"
+    text-segmentation "^1.0.3"
+
 htmlparser2@^6.1.0:
   version "6.1.0"
   resolved "https://registry.npmmirror.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
@@ -9668,6 +9723,13 @@ sprintf-js@~1.0.2:
   resolved "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
 
+ssf@~0.11.2:
+  version "0.11.2"
+  resolved "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
+  integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
+  dependencies:
+    frac "~1.1.2"
+
 stable@^0.1.8:
   version "0.1.8"
   resolved "https://registry.npmmirror.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
@@ -9740,7 +9802,16 @@ string-natural-compare@^3.0.1:
   resolved "https://registry.npmmirror.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
   integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
 
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0:
+"string-width-cjs@npm:string-width@^4.2.0":
+  version "4.2.3"
+  resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0:
   version "4.2.3"
   resolved "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -9823,7 +9894,14 @@ stringify-object@^3.3.0:
     is-obj "^1.0.1"
     is-regexp "^1.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -10067,6 +10145,13 @@ test-exclude@^6.0.0:
     glob "^7.1.4"
     minimatch "^3.0.4"
 
+text-segmentation@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
+  integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
+  dependencies:
+    utrie "^1.0.2"
+
 text-table@^0.2.0:
   version "0.2.0"
   resolved "https://registry.npmmirror.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -10435,6 +10520,13 @@ utils-merge@1.0.1:
   resolved "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
   integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
 
+utrie@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
+  integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
+  dependencies:
+    base64-arraybuffer "^1.0.2"
+
 uuid@^8.3.2:
   version "8.3.2"
   resolved "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
@@ -10734,11 +10826,21 @@ which@^2.0.1:
   dependencies:
     isexe "^2.0.0"
 
+wmf@~1.0.1:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da"
+  integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
+
 word-wrap@~1.2.3:
   version "1.2.5"
   resolved "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34"
   integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
 
+word@~0.3.0:
+  version "0.3.0"
+  resolved "https://registry.npmmirror.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
+  integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
+
 workbox-background-sync@6.6.1:
   version "6.6.1"
   resolved "https://registry.npmmirror.com/workbox-background-sync/-/workbox-background-sync-6.6.1.tgz#08d603a33717ce663e718c30cc336f74909aff2f"
@@ -10908,7 +11010,16 @@ workbox-window@6.6.1:
     "@types/trusted-types" "^2.0.2"
     workbox-core "6.6.1"
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -10951,6 +11062,19 @@ ws@^8.13.0:
   resolved "https://registry.npmmirror.com/ws/-/ws-8.16.0.tgz#d1cd774f36fbc07165066a60e40323eab6446fd4"
   integrity sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==
 
+xlsx@*, xlsx@^0.18.5:
+  version "0.18.5"
+  resolved "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0"
+  integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
+  dependencies:
+    adler-32 "~1.3.0"
+    cfb "~1.2.1"
+    codepage "~1.15.0"
+    crc-32 "~1.2.1"
+    ssf "~0.11.2"
+    wmf "~1.0.1"
+    word "~0.3.0"
+
 xml-name-validator@^3.0.0:
   version "3.0.0"
   resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"