lanxin 1 周之前
父节点
当前提交
db9be2ebc6

+ 15 - 13
front/src/App.tsx

@@ -59,19 +59,21 @@ export default function App() {
       <AsyncSpinLoding />
 
       {/* 所有图片点击预览查看大图 */}
-      <Image
-        preview={{
-          visible: lookBigImg.show,
-          src: lookBigImg.url,
-          onVisibleChange: value => {
-            // 清除仓库信息
-            store.dispatch({
-              type: 'layout/lookBigImg',
-              payload: { url: '', show: false }
-            })
-          }
-        }}
-      />
+      {lookBigImg.show ? (
+        <Image
+          preview={{
+            visible: lookBigImg.show,
+            src: lookBigImg.url,
+            onVisibleChange: value => {
+              // 清除仓库信息
+              store.dispatch({
+                type: 'layout/lookBigImg',
+                payload: { url: '', show: false }
+              })
+            }
+          }}
+        />
+      ) : null}
 
       {/* 上传附件的进度条元素 */}
       <UpAsyncLoding />

二进制
front/src/assets/img/jianjie.png


二进制
front/src/assets/img/left.png


二进制
front/src/assets/img/muqu.png


二进制
front/src/assets/img/play.png


二进制
front/src/assets/img/right.png


二进制
front/src/assets/img/ziliao.png


+ 8 - 4
front/src/assets/styles/base.css

@@ -9,7 +9,8 @@ html {
   user-select: none;
 }
 body {
-  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB', 'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
+  font: 1em/1.4 'Microsoft Yahei', 'PingFang SC', 'Avenir', 'Segoe UI', 'Hiragino Sans GB',
+    'STHeiti', 'Microsoft Sans Serif', 'WenQuanYi Micro Hei', sans-serif;
   height: 100%;
   color: black;
 }
@@ -104,9 +105,9 @@ textarea {
   display: flex;
   justify-content: center;
 }
-#root .ant-image {
+/* #root .ant-image {
   display: none;
-}
+} */
 #root .ant-table-cell {
   text-align: center !important;
 }
@@ -179,6 +180,9 @@ textarea {
 .ant-image-preview-operations-wrapper {
   z-index: 9999 !important;
 }
+.anticon {
+  color: #f4f3d0;
+}
 .ant-notification-notice {
   max-height: 500px !important;
   overflow-y: auto !important;
@@ -220,4 +224,4 @@ textarea {
     opacity: 1;
     pointer-events: auto;
   }
-}
+}

+ 51 - 0
front/src/components/ImageLazyList/index.module.scss

@@ -0,0 +1,51 @@
+.ImageLazy {
+  position: relative;
+
+  :global {
+    .lazyBox {
+      width: 100%;
+      height: 100%;
+      position: relative;
+
+      .adm-image {
+        width: 100%;
+        height: 100%;
+
+        img {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .lookImg {
+        cursor: pointer;
+        transition: opacity .3s;
+        opacity: 0;
+        pointer-events: none;
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        font-size: 18px;
+        color: #fff;
+        background-color: rgba(0, 0, 0, .6);
+
+        &>div {
+          font-size: 14px;
+        }
+      }
+
+      &:hover {
+        .lookImg {
+          opacity: 1;
+          pointer-events: auto;
+        }
+      }
+    }
+  }
+
+}

+ 85 - 0
front/src/components/ImageLazyList/index.tsx

@@ -0,0 +1,85 @@
+import React, { useCallback, useState } from 'react'
+import styles from './index.module.scss'
+import { baseURL } from '@/utils/http'
+import imgLoding from '@/assets/img/loading.gif'
+import imgErr from '@/assets/img/IMGerror.png'
+import { EyeOutlined } from '@ant-design/icons'
+import store from '@/store'
+import { Image } from 'antd-mobile'
+import { Image as ImagePc } from 'antd'
+
+type Props = {
+  width?: number | string
+  height?: number | string
+  imgList: any
+  noLook?: boolean
+  offline?: boolean
+}
+
+function ImageLazy({
+  width = 100,
+  height = 100,
+  imgList,
+  noLook,
+  offline = false
+}: Props) {
+  const PreviewGroup = ImagePc.PreviewGroup
+
+  // 默认不能预览图片,加载成功之后能预览
+  const [lookImg, setLookImg] = useState(false)
+
+  // 图片加载完成
+  const onLoad = useCallback(() => {
+    setLookImg(true)
+  }, [])
+
+  // 点击预览图片
+  const lookBigImg = useCallback(
+    (src?: string, srcBig?: string) => {
+      store.dispatch({
+        type: 'layout/lookBigImg',
+        payload: {
+          url: (offline ? src : srcBig ? baseURL + srcBig : baseURL + src) as string,
+          show: true
+        }
+      })
+    },
+    [offline]
+  )
+
+  return (
+    <div className={styles.ImageLazy} style={{ width: width, height: height }}>
+      <div className='lazyBox'>
+        {imgList.map((item: any, index: number) => {
+          return (
+            <PreviewGroup key={index}>
+              <Image
+                lazy
+                onLoad={onLoad}
+                src={item.thumb ? (offline ? item.thumb : baseURL + item.thumb) : ''}
+                placeholder={<img src={imgLoding} alt='' />}
+                fallback={<img src={imgErr} alt='' />}
+                fit='cover'
+              />
+              {/* 图片预览 */}
+              {noLook || !lookImg ? null : (
+                <div
+                  className='lookImg'
+                  onClick={() => lookBigImg(item.thumb, item.filePath)}
+                >
+                  <EyeOutlined rev={undefined} />
+                  &nbsp;
+                  <div>预览</div>
+                </div>
+              )}
+            </PreviewGroup>
+          )
+        })}
+      </div>
+    </div>
+  )
+}
+
+const MemoImageLazy = React.memo(ImageLazy)
+
+export default MemoImageLazy

+ 1 - 1
front/src/pages/A1home/index.module.scss

@@ -21,7 +21,7 @@
               transition: all 0.3s ease-in-out;
             }
           }
-          [id^="tap-"] {
+          [id^='tap-'] {
             pointer-events: none;
           }
         }

+ 21 - 19
front/src/pages/components/BottomSearch/index.module.scss

@@ -6,24 +6,26 @@
   width: 35%;
   padding: 5px 10px;
   background: url('../../../assets/img/bottomSearchBg.png') no-repeat 100% 100%;
+  background-size: 100% 100%;
   border-radius: 10px;
   margin: 0 auto;
   display: flex;
   align-items: center;
   justify-content: center;
-transition: all 0.5s ease-in-out;
+  transition: all 0.5s ease-in-out;
   :global {
-    .ant-input-affix-wrapper ,.ant-input-affix-wrapper-focused {
+    .ant-input-affix-wrapper,
+    .ant-input-affix-wrapper-focused {
       width: 80%;
       margin-right: 20px;
-      background: transparent ! important;
+      background: transparent !important;
       border: none !important;
-      box-shadow:   none !important;
+      box-shadow: none !important;
     }
     .ant-input {
       background: transparent !important;
     }
-    .searchBtn{
+    .searchBtn {
       cursor: pointer;
     }
 
@@ -34,12 +36,12 @@ transition: all 0.5s ease-in-out;
       left: 50%;
       transform: translateX(-50%);
       bottom: 70px;
-      background: url('../../../assets/img/searchResultBg.png') ;
+      background: url('../../../assets/img/searchResultBg.png');
       background-size: 100% 100%;
       border-radius: 15px;
       padding: 0 20px;
-      .ant-tabs-extra-content{
-        &>img{
+      .ant-tabs-extra-content {
+        & > img {
           cursor: pointer;
         }
       }
@@ -49,22 +51,22 @@ transition: all 0.5s ease-in-out;
         &::-webkit-scrollbar {
           width: 0;
         }
-        .ant-tabs-tabpane{
-        &>div{
-          cursor: pointer;
+        .ant-tabs-tabpane {
+          & > div {
+            cursor: pointer;
+          }
         }
       }
-      }
     }
   }
 }
 
 .mood {
-      position: fixed;
-    top: 50%;
-    left: 50%;
-    transform: translate(-50%, -50%);
-    width: 100%;
-    height: 100%;
-    background: rgba(0, 0, 0, 0.5);
+  position: fixed;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.5);
 }

+ 42 - 11
front/src/pages/components/Detail/index.module.scss

@@ -3,7 +3,8 @@
   top: 0;
   right: 0;
   transform: translate(100%, 0);
-  width: 36%;
+  max-width: 960px;
+  width: fit-content;
   height: 100%;
   padding: 20px;
   display: flex;
@@ -14,7 +15,9 @@
       height: 100%;
       display: flex;
       .infoDetail {
-        width: 55%;
+        width: 0;
+        min-width: 620px;
+        flex: 1;
         height: 100%;
         background: url('../../../assets/img/infoDetailBox.png');
         background-size: 100% 100%;
@@ -31,6 +34,14 @@
           &::-webkit-scrollbar {
             width: 0;
           }
+          .title_img {
+            width: 50%;
+            height: 50px;
+            & > img {
+              height: 100%;
+              object-fit: contain;
+            }
+          }
           .mainInfo {
             display: flex;
             align-items: center;
@@ -69,19 +80,19 @@
               }
             }
             .left {
-              width: 33%;
+              width: 20%;
               height: 100%;
               & > img {
                 width: 100%;
                 height: 100%;
-                object-fit: cover;
+                object-fit: contain;
                 border-radius: 10px;
               }
             }
           }
           .vr {
             width: 100%;
-            height: 90px;
+            height: 150px;
             position: relative;
             cursor: pointer;
             & > img {
@@ -117,11 +128,31 @@
               align-items: center;
               flex-wrap: wrap;
               gap: 3px;
-              video {
-                width: 32%;
-                height: 70px;
-                object-fit: cover;
-                cursor: pointer;
+              .videoContainer {
+                width: 23%;
+                height: 140px;
+                position: relative;
+                & > video {
+                  width: 100%;
+                  height: 140px;
+                  object-fit: cover;
+                  cursor: pointer;
+                }
+                .videoMask {
+                  width: 100%;
+                  height: 100%;
+                  top: 0;
+                  pointer-events: none;
+                  position: absolute;
+                  & > img {
+                    width: 30px;
+                    height: 30px;
+                    position: absolute;
+                    top: 50%;
+                    left: 50%;
+                    transform: translate(-50%, -50%);
+                  }
+                }
               }
             }
           }
@@ -204,7 +235,7 @@
         }
       }
       .infoDetailAdd {
-        width: 45%;
+        width: 300px;
         height: 65%;
         display: flex;
         justify-content: center;

+ 87 - 42
front/src/pages/components/Detail/index.tsx

@@ -2,11 +2,13 @@ import React, { useState, useCallback, useMemo } from 'react'
 import styles from './index.module.scss'
 import classNames from 'classnames'
 import RelationEcharts from './RelationEcharts/index'
-import { Timeline } from 'antd'
+import { Timeline, Image } from 'antd'
 import store from '@/store'
 import { MartyrItem, RelationShipItem } from '@/types/api/martyr'
 import { ClueItem } from '@/types/api/clue'
 import ImageLazy from '@/components/ImageLazy/index'
+import ImageLazyList from '@/components/ImageLazyList/index'
+import imgErr from '@/assets/img/IMGerror.png'
 import { baseURL } from '@/utils/http'
 
 type DetailProps = {
@@ -35,80 +37,117 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
             1: '跟进中',
             2: '已找到',
             3: '未找到',
-            4: '作废',
-          }[item.status] as string,
+            4: '作废'
+          }[item.status] as string
         }
       })
-      .filter(
-        (item: ClueItem) => item.status !== '待确定' && item.status !== '作废'
-      )
+      .filter((item: ClueItem) => item.status !== '待确定' && item.status !== '作废')
   }, [martyrDetail])
 
   return (
     <>
       <div className={classNames(styles.detail, classN)}>
-        <div className={classNames('infoDetailBox', (!martyrDetail?.life.length && !martyrDetail?.relic.length) && 'noInfoDetail')}>
+        <div
+          className={classNames(
+            'infoDetailBox',
+            !martyrDetail?.life.length && !martyrDetail?.relic.length && 'noInfoDetail'
+          )}
+        >
           <div className='infoDetail'>
             <div className='scrollContainner'>
+              <div className='title_img'>
+                <img src={require('@/assets/img/ziliao.png')} alt='' draggable='false' />
+              </div>
               <div className='mainInfo'>
                 <div className='left'>
-                  <img src={martyrDetail?.thumb} alt='' />
+                  <img
+                    src={martyrDetail?.thumb ? baseURL + martyrDetail?.thumb : imgErr}
+                    alt=''
+                  />
                 </div>
                 <div className='right'>
                   <div className='name'>
                     <div className='Name'>{martyrDetail?.name}</div>
                     <div className='other'>{martyrDetail?.dictPanName}</div>
                   </div>
-                  <div className='gender'>{martyrDetail?.gender === 1 ? '男' : '女' || '未知'}|{martyrDetail?.nation}</div>
-                  <div className='birth'>{martyrDetail?.dateStart}-{martyrDetail?.dateEnd}</div>
-                  <div className='home'>籍贯:{martyrDetail?.nativeProvince}-{martyrDetail?.nativeCity}-{martyrDetail?.nativeRegion},{martyrDetail?.nativeAddress}</div>
+                  <div className='gender'>
+                    {martyrDetail?.gender === 1 ? '男' : '女' || '未知'}|
+                    {martyrDetail?.nation}
+                  </div>
+                  <div className='birth'>
+                    {martyrDetail?.dateStart}-{martyrDetail?.dateEnd}
+                  </div>
+                  <div className='home'>
+                    籍贯:{martyrDetail?.nativeProvince}-{martyrDetail?.nativeCity}-
+                    {martyrDetail?.nativeRegion},{martyrDetail?.nativeAddress}
+                  </div>
                   <div className='sacrifice'>{`牺牲地:${martyrDetail?.lossProvince}-${martyrDetail?.lossCity}-${martyrDetail?.lossRegion},${martyrDetail?.lossAddress}`}</div>
                 </div>
               </div>
+              <div className='title_img'>
+                <img src={require('@/assets/img/muqu.png')} alt='' draggable='false' />
+              </div>
               <div className='vr' onClick={() => window.open(`${martyrDetail?.link}`)}>
                 <img src={require('@/assets/img/sceneThumb.png')} alt='' />
-                <img src={require('@/assets/img/vr.png')} alt="" className="botvr" />
+                <img src={require('@/assets/img/vr.png')} alt='' className='botvr' />
+              </div>
+              <div className='title_img'>
+                <img src={require('@/assets/img/jianjie.png')} alt='' draggable='false' />
               </div>
               <div className='content'>
                 <div className='text'>{martyrDetail?.intro}</div>
                 {(martyrDetail?.img || martyrDetail?.video) && (
                   <div className='media'>
                     <>
-                      {martyrDetail?.img?.map((item: any, index: number) => {
-                        return (
-                          <ImageLazy
-                            width={'32%'}
-                            height={'70px'}
-                            src={item.thumb}
-                            srcBig={item.filePath}
-                            key={index}
-                          />
-                        )
-                      })}
+                      {martyrDetail && (
+                        <Image.PreviewGroup>
+                          {martyrDetail.img?.map((item: any, index: number) => {
+                            return (
+                              <Image
+                                width={'23%'}
+                                height={'140px'}
+                                src={baseURL + item.thumb}
+                                preview={{
+                                  src: baseURL + item.filePath
+                                }}
+                                placeholder={
+                                  <img src={require('@/assets/img/loading.gif')} alt='' />
+                                }
+                                key={item.id}
+                              />
+                            )
+                          })}
+                        </Image.PreviewGroup>
+                      )}
                       {martyrDetail?.video?.map((item: any, index: number) => {
                         return (
-                          <video
-                            onClick={e => {
-                              e.stopPropagation()
-                              lookFileFu(item.filePath)
-                            }}
-                            src={baseURL + item.thumb}
-                            key={index}
-                          />
+                          <div className='videoContainer' key={index}>
+                            <video
+                              src={baseURL + item.thumb}
+                              onClick={e => {
+                                e.stopPropagation()
+                                lookFileFu(item.filePath)
+                              }}
+                            />
+                            <div className='videoMask'>
+                              <img src={require('@/assets/img/play.png')} alt='播放' />
+                            </div>
+                          </div>
                         )
                       })}
                     </>
                   </div>
                 )}
               </div>
-              <div className='relation'>
+              {/* 亲属关系图 */}
+              {/* <div className='relation'>
                 <div className='title'>
                   <img src={require('@/assets/img/relationTitle.png')} alt='' />
                 </div>
                 <div className='relationPic'>
                   <RelationEcharts martyrName={martyrDetail?.name} relationList={relationList} />
                 </div>
-              </div>
+              </div> */}
               {clueList?.map((item: any) => (
                 <div className='infoLabel' key={item.id}>
                   <div className='title'>
@@ -118,7 +157,9 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
                       <div>{item.status}</div>
                     </div>
                   </div>
-                  <div className='address'> {item.province + item.city + item.region + item.address}</div>
+                  <div className='address'>
+                    {item.province + item.city + item.region + item.address}
+                  </div>
                   <div className='labelContent'>{item.remark}</div>
                   <div className='labelContent'>{item.result}</div>
                   {(item.img || item.video) && (
@@ -151,8 +192,8 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
                     </div>
                   )}
                   <div className='updateTime'>{item.updateTime}</div>
-                </div>))}
-
+                </div>
+              ))}
             </div>
           </div>
 
@@ -161,7 +202,7 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
               <div className='title1'>
                 <img src={require('@/assets/img/modelPic.png')} alt='' />
               </div>
-              <div className='modelList' >
+              <div className='modelList'>
                 {martyrDetail?.relic.map((item: any) => (
                   <div className='model' key={item.id}>
                     <img src={item.thumb} alt='' onClick={() => setCurrentRelic(item)} />
@@ -171,7 +212,6 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
                     </div>
                   </div>
                 ))}
-
               </div>
               <div className='title2'>
                 <img src={require('@/assets/img/introPic.png')} alt='' />
@@ -179,19 +219,24 @@ function Detail({ classN, relationList, martyrDetail }: DetailProps) {
               <div className='introDetailBox'>
                 <Timeline
                   items={martyrDetail?.life?.map((item: any) => {
-                    const content = JSON.parse(item.rtf);
+                    const content = JSON.parse(item.rtf)
                     console.log(content, '123321')
                     return {
                       children: (
                         <div className='introDetail' key={item.id}>
                           <div className='year'>{item.date}</div>
-                          <div dangerouslySetInnerHTML={{ __html: content.txtArr[0].txt }} />
+                          <div
+                            dangerouslySetInnerHTML={{ __html: content.txtArr[0].txt }}
+                          />
                           <div>
-                            <img src={baseURL + content.txtArr[0].fileInfo.filePath} alt='' />
+                            <img
+                              src={baseURL + content.txtArr[0].fileInfo.filePath}
+                              alt=''
+                            />
                           </div>
                         </div>
                       )
-                    };
+                    }
                   })}
                 />
               </div>

+ 8 - 8
front/src/pages/components/Left/index.module.scss

@@ -2,10 +2,10 @@
   position: absolute;
   left: 0;
   top: 0;
-  width: 20%;
+  width: 384px;
   height: 100%;
   padding: 20px;
-transition: all 0.5s ease-in-out;
+  transition: all 0.5s ease-in-out;
   :global {
     .infoContainer {
       display: flex;
@@ -106,8 +106,8 @@ transition: all 0.5s ease-in-out;
               width: 100%;
               height: fit-content;
               padding: 16px;
-              border-radius: 5px;
-              background: url('../../../assets/img/mainInfoBg.png') repeat center;
+              border-radius: 10px;
+              background: #ecd5d1;
               display: flex;
               flex-direction: column;
               gap: 10px;
@@ -122,8 +122,8 @@ transition: all 0.5s ease-in-out;
                   display: flex;
                   align-items: center;
                   justify-content: center;
-                  background: url('../../../assets/img/label.png')  ;
-                      background-size: cover;
+                  background: url('../../../assets/img/label.png');
+                  background-size: cover;
                 }
               }
               .name {
@@ -150,8 +150,8 @@ transition: all 0.5s ease-in-out;
                 }
               }
             }
-            .ant-pagination{
-              margin:  0 auto;
+            .ant-pagination {
+              margin: 0 auto;
             }
           }
         }

+ 1 - 1
front/src/pages/components/Right/index.module.scss

@@ -2,7 +2,7 @@
   position: absolute;
   left: 0;
   top: 0;
-  width: 23%;
+  width: 480px;
   height: 100%;
   padding: 20px;
   left: auto;

+ 21 - 13
front/src/pages/components/Right/index.tsx

@@ -13,11 +13,9 @@ type RightProps = {
   handleItemClick: (cityId: string, name: string, martyrId: number) => void
 }
 function Right(props: RightProps) {
-
   const { list: listAll } = useSelector((state: RootState) => state.Martyr.tableInfo)
   console.log(listAll)
 
-
   // const handleItemClick = useCallback(
   //   (cityId: string, name: string) => {
   //     props.setIsAddClassName(true)
@@ -31,38 +29,48 @@ function Right(props: RightProps) {
   return (
     <div className={classNames(styles.Right, props.classN)}>
       <div className='infoContainner'>
-        <div
-          className='mainInfoList'
-
-        >
-
+        <div className='mainInfoList'>
           {listAll.map((item: MartyrItem, index: number) => {
             return (
-              <div className='infoItem' onClick={() => props.handleItemClick(item.cityId, item.name, item.id)} key={index}>
+              <div
+                className='infoItem'
+                onClick={() => props.handleItemClick(item.cityId, item.name, item.id)}
+                key={index}
+              >
                 <div className='mainInfo'>
                   <div className='left'>
                     <div className='name'>
                       <div className='Name'>{item.name}</div>
                       <div className='other'>{item.dictPanName}</div>
                     </div>
-                    <div className='gender'>{item.gender}|{item.nation}</div>
-                    <div className='birth'>{item.dateStart} - {item.dateEnd}</div>
+                    <div className='gender'>
+                      {item.gender} | {item.nation}
+                    </div>
+                    <div className='birth'>
+                      {item.dateStart} - {item.dateEnd}
+                    </div>
                     <div className='home'>{`籍贯:${item.nativeProvince}-${item.nativeCity}-${item.nativeRegion}`}</div>
                   </div>
 
                   <div className='right' onClick={e => e.stopPropagation()}>
-                    <ImageLazy width={'100%'} height={'100%'} src={item.thumb} srcBig={item.thumbPc} />
+                    <ImageLazy
+                      width={'100%'}
+                      height={'100%'}
+                      src={item.thumb}
+                      srcBig={item.thumbPc}
+                    />
                   </div>
                 </div>
                 <div className='tag'>
-                  <div className='label'>{item.clueType}</div>
+                  <div className='label'>{item?.clueType || '寻亲线索'}</div>
                   <div>{item.clueRemark}</div>
                 </div>
                 {/* <div className='tag'>
                   <div className='label'>线索类型名称</div>
                   <div>这是一段线索说明</div>
                 </div> */}
-              </div>)
+              </div>
+            )
           })}
         </div>
       </div>

+ 1 - 1
front/src/utils/http.ts

@@ -7,7 +7,7 @@ import { domShowFu } from './domShow'
 const envFlag = process.env.NODE_ENV === 'development'
 
 // const baseUrlTemp = 'https://sit-jinanlsly.4dage.com/' // 测试环境
-const baseUrlTemp = envFlag ? 'http://192.168.20.61:8104/' : 'https://sit-jinanlsly.4dage.com/' // 线下环境
+const baseUrlTemp = 'https://sit-jinanlsly.4dage.com/' // 线下环境
 
 // const baseFlag = baseUrlTemp.includes('https://')