shaogen1995 9 månader sedan
förälder
incheckning
2698941e69

+ 10 - 3
web/README.md

@@ -1,5 +1,12 @@
-一开始说的没有后端,所以做的数据和代码分离的模式来开发的。
+1.用 yarn
 
-本地运行:静态资源里面先运行一个服务(使用 http-server 或者 其它)
+2.测试堡垒机存放目录
+227/data/data/guangdong_guangzhou_zhongda_zhongliuyiyuan_data
 
-修改 baseUrlLoc(本地 dev) 和 myBaseUrl(build) 即可
+3.蓝湖地址
+https://lanhuapp.com/web/#/item/project/detailDetach?pid=80948489-b74b-4a80-ad55-af0100d0f72f&tid=de3e5e3e-a489-4b19-862a-7c87ce113467&see=all&project_id=80948489-b74b-4a80-ad55-af0100d0f72f&image_id=81dcc2d5-c5f0-4111-8771-e8b110de9524&fromEditor=true&type=image
+
+4.测试域名
+https://sit-zhongliuyiyuan.4dage.com
+接口地址在后面拼接:/api/doc.html#/home
+测试网址在后面拼接:/web

BIN
web/src/assets/img/expert/bgM.jpg


BIN
web/src/assets/img/goods/bgM.png


BIN
web/src/assets/img/goods/leftM.png


BIN
web/src/assets/img/goods/rightM.png


BIN
web/src/assets/img/goods/toHome.png


BIN
web/src/assets/img/goods/xia.png


BIN
web/src/assets/img/home/1m.png


BIN
web/src/assets/img/home/2m.png


BIN
web/src/assets/img/home/3m.png


BIN
web/src/assets/img/home/bgM.jpg


BIN
web/src/assets/img/home/icon-jr.png


+ 24 - 2
web/src/assets/styles/base.css

@@ -41,7 +41,7 @@ textarea {
 }
 /* 主题色 */
 :root {
-  --themeColor: #f58543;
+  --themeColor: #c8a063;
   --themeColor2: #f58543;
 }
 /* 找不到页面 */
@@ -134,7 +134,29 @@ textarea {
   background-color: #fff;
 }
 #root .ant-carousel .slick-dots .slick-active button {
-  background-color: var(--themeColor);
+  background-color: var(--themeColor2);
+}
+@media screen and (max-width: 1000px) {
+  #root .ant-carousel .slick-arrow {
+    z-index: 100;
+    width: 40px;
+    height: 60px;
+    left: 0px;
+  }
+  #root .ant-carousel .slick-arrow::before {
+    top: 10px;
+    height: 40px;
+    width: 20px;
+    background-image: url('../img/goods/leftM.png');
+  }
+  #root .ant-carousel .slick-next {
+    left: auto;
+    right: 0px;
+  }
+  #root .ant-carousel .slick-next::before {
+    left: 20px;
+    background-image: url('../img/goods/rightM.png');
+  }
 }
 [hidden] {
   display: none !important;

+ 30 - 2
web/src/assets/styles/base.less

@@ -51,7 +51,7 @@ textarea {
 
 /* 主题色 */
 :root {
-  --themeColor: #f58543;
+  --themeColor: #c8a063;
   --themeColor2: #f58543;
 }
 
@@ -155,7 +155,35 @@ textarea {
         background-color: #fff;
       }
       .slick-active button {
-        background-color: var(--themeColor);
+        background-color: var(--themeColor2);
+      }
+    }
+  }
+
+  // 移动端
+  @media screen and (max-width: 1000px) {
+    // 走马灯样式
+    .ant-carousel {
+      // 左右箭头
+      .slick-arrow {
+        z-index: 100;
+        width: 40px;
+        height: 60px;
+        left: 0px;
+        &::before {
+          top: 10px;
+          height: 40px;
+          width: 20px;
+          background-image: url('../img/goods/leftM.png');
+        }
+      }
+      .slick-next {
+        left: auto;
+        right: 0px;
+        &::before {
+          left: 20px;
+          background-image: url('../img/goods/rightM.png');
+        }
       }
     }
   }

+ 45 - 4
web/src/pages/A1homeM/index.module.scss

@@ -1,5 +1,46 @@
-.A1homeM{
-  :global{
-    
+.A1homeM {
+  background-size: 100% 100%;
+  background-image: url('../../assets/img/home/bgM.jpg');
+  overflow: auto;
+  padding: 0 24px 40px;
+  :global {
+    .A1Mtitle {
+      text-align: center;
+      padding-top: 20px;
+    }
+    .A1Mlogo {
+      margin: 15px 0;
+      text-align: center;
+      & > img {
+        width: 200px;
+      }
+    }
+    .A1Mtxt {
+      font-size: 16px;
+      letter-spacing: 3px;
+      line-height: 22px;
+      transition: all 0.3s;
+    }
+    .A1MtxtShow {
+      line-height: 26px;
+    }
+    .A1Mtbtn {
+      margin-top: 5px;
+      font-size: 16px;
+      text-align: right;
+      color: var(--themeColor2);
+    }
+
+    .A1Mrow {
+      margin-top: 30px;
+      position: relative;
+      .A1Mjt {
+        position: absolute;
+        bottom: 20px;
+        right: 20px;
+        width: 40px;
+        height: auto;
+      }
+    }
   }
-}
+}

+ 51 - 3
web/src/pages/A1homeM/index.tsx

@@ -1,10 +1,58 @@
-import React from 'react'
+import React, { useMemo, useState } from 'react'
 import styles from './index.module.scss'
+import { useSelector } from 'react-redux'
+import { RootState } from '@/store'
+import classNames from 'classnames'
+
+// 图片导入
+import logoImg from '@/assets/img/home/logo.png'
+import m1Img from '@/assets/img/home/1m.png'
+import m2Img from '@/assets/img/home/2m.png'
+import m3Img from '@/assets/img/home/3m.png'
+import iconJrImg from '@/assets/img/home/icon-jr.png'
+
+import { rightArr, text } from '../A1home/data'
+import history from '@/utils/history'
+
+const imgObj = {
+  1: m1Img,
+  2: m2Img,
+  3: m3Img
+}
+
 function A1homeM() {
+  const { visitSum } = useSelector((state: RootState) => state.A0Layout)
+
+  // 文字介绍的展开和隐藏
+  const [txtShow, setTxtShow] = useState(false)
+
+  const txtRes = useMemo(() => {
+    let tempTxt = text
+    if (!txtShow) tempTxt = tempTxt.split('</p>')[0] + '</p>'
+    return tempTxt
+  }, [txtShow])
+
   return (
     <div className={styles.A1homeM}>
-      <h1>中山大学附属肿瘤医院线上院史馆</h1>
-      <h2>移动端开发中,敬请期待.</h2>
+      <div className='A1Mtitle'>欢迎您的到来,您是第{visitSum}位参观者!</div>
+      <div className='A1Mlogo'>
+        <img src={logoImg} alt='' />
+      </div>
+
+      <div
+        className={classNames('A1Mtxt', txtShow ? 'A1MtxtShow' : '')}
+        dangerouslySetInnerHTML={{ __html: txtRes }}
+      ></div>
+      <div className='A1Mtbtn' onClick={() => setTxtShow(!txtShow)}>
+        【{txtShow ? '收起' : '展开'}简介】
+      </div>
+
+      {rightArr.map(item => (
+        <div key={item.id} className='A1Mrow' onClick={() => history.push(item.path)}>
+          <img src={Reflect.get(imgObj, item.id)} alt='' />
+          <img className='A1Mjt' src={iconJrImg} alt='' />
+        </div>
+      ))}
     </div>
   )
 }

+ 59 - 0
web/src/pages/A3goods/A3look/index.module.scss

@@ -90,3 +90,62 @@
     }
   }
 }
+
+// 移动端
+.A3lookApp {
+  background-image: url('../../../assets/img/goods/bgM.png');
+  background-size: 100% 100%;
+  min-height: 100%;
+  overflow: hidden;
+  :global {
+    .Lcolse {
+      top: 10px;
+      right: 10px;
+      width: 30px;
+    }
+    .Lmain {
+      top: 50px;
+      left: 0;
+      transform: translate(0, 0);
+      width: 100%;
+      height: calc(100% - 50px);
+      .LmainCen {
+        height: calc(100% - 250px);
+        position: relative;
+        iframe {
+          width: 100%;
+          height: 100%;
+        }
+      }
+
+      .LmTxt {
+        color: #fff;
+        background-color: var(--themeColor);
+        padding: 30px 4px 20px 0;
+        border-radius: 24px 24px 0 0;
+        height: 200px;
+        text-align: left;
+        font-size: 16px;
+        display: flex;
+        align-items: center;
+
+        & > div {
+          padding: 0px 10px 20px 20px;
+          max-height: 180px;
+          overflow-y: auto;
+          width: 100%;
+          height: auto;
+          line-height: 24px;
+          letter-spacing: 3px;
+          white-space: pre-wrap;
+          & > span {
+            margin-top: 5px;
+            display: inline-block;
+            font-size: 14px;
+            line-height: 20px;
+          }
+        }
+      }
+    }
+  }
+}

+ 29 - 16
web/src/pages/A3goods/A3look/index.tsx

@@ -10,13 +10,15 @@ import { getGoodsInfo } from '@/store/action/all'
 import { baseURL } from '@/utils/http'
 import { Carousel } from 'antd'
 import store from '@/store'
+import { ImageViewer } from 'antd-mobile'
 
 type Props = {
   closeFu: () => void
   id: number
+  isApp?: boolean
 }
 
-function A3look({ closeFu, id }: Props) {
+function A3look({ closeFu, id, isApp }: Props) {
   const getInfoFu = useCallback(async (id: number) => {
     const res = await getGoodsInfo(id)
     if (res.code === 0) {
@@ -33,27 +35,35 @@ function A3look({ closeFu, id }: Props) {
 
   const [file, setFile] = useState([] as FileType[])
 
+  // 点击图片查看大图
+  const lookBigImg = useCallback(
+    (url: string) => {
+      if (isApp) {
+        ImageViewer.show({ image: baseURL + url })
+      } else {
+        store.dispatch({
+          type: 'layout/lookBigImg',
+          payload: { url: baseURL + url, show: true }
+        })
+      }
+    },
+    [isApp]
+  )
+
   return (
-    <div className={classNames(styles.A3look)} id='A3look'>
+    <div className={classNames(styles.A3look, isApp ? styles.A3lookApp : '')} id='A3look'>
       {info.id ? (
         <>
           {/* 关闭按钮 */}
           <img onClick={closeFu} className='Lcolse' src={closeImg} alt='' />
-          <div className={classNames('Lmain', info.fileType === 'model' ? 'LmainFull' : '')}>
+          <div
+            className={classNames('Lmain', info.fileType === 'model' && !isApp ? 'LmainFull' : '')}
+          >
             <div className='LmainCen'>
               {info.fileType === 'img' ? (
                 <Carousel arrows infinite={false}>
                   {file.map(v => (
-                    <div
-                      key={v.id}
-                      className='Limg'
-                      onClick={() =>
-                        store.dispatch({
-                          type: 'layout/lookBigImg',
-                          payload: { url: baseURL + v.filePath, show: true }
-                        })
-                      }
-                    >
+                    <div key={v.id} className='Limg' onClick={() => lookBigImg(v.filePath)}>
                       <LazyImg src={baseURL + v.thumb} />
                     </div>
                   ))}
@@ -71,9 +81,12 @@ function A3look({ closeFu, id }: Props) {
               ) : null}
             </div>
 
-            <div className='LmTxt'>
-              <div>{info.name}</div>
-              <div className='LmTxt2 mySorrl'>{info.remark}</div>
+            <div className={classNames('LmTxt')}>
+              <div>
+                {info.name}
+                {isApp ? <span>{info.remark}</span> : null}
+              </div>
+              {isApp ? null : <div className='LmTxt2 mySorrl'>{info.remark}</div>}
             </div>
           </div>
         </>

+ 1 - 1
web/src/pages/A3goods/index.tsx

@@ -201,7 +201,7 @@ function A3goods() {
                 <div className='A2IrowNull' hidden={!loding}>
                   <img src={nullImg} alt='' />
                   <p>暂时没有数据</p>
-                  <p>请试一下其他关键字</p>
+                  {/* <p>请试一下其他关键字</p> */}
                 </div>
               )}
             </div>

+ 123 - 0
web/src/pages/A3goodsM/A3Mtop/index.module.scss

@@ -0,0 +1,123 @@
+.A3Mtop {
+  height: 32px;
+  width: 100%;
+  display: flex;
+  justify-content: center;
+  padding-right: 10px;
+  :global {
+    .toHome {
+      width: 32px;
+      height: 100%;
+      background-size: 100% 100%;
+      background-image: url('../../../assets/img/goods/toHome.png');
+    }
+    .A3Minput {
+      width: calc(100% - 122px);
+      margin: 0 10px;
+      height: 100%;
+      .ant-input-affix-wrapper {
+        border-radius: 16px;
+        background-color: var(--themeColor2);
+
+        .ant-input-suffix {
+          .ant-input-clear-icon {
+            color: #fff;
+            font-size: 14px;
+          }
+        }
+
+        .ant-input {
+          color: #fff;
+          background-color: transparent;
+        }
+        input {
+          color: #fff;
+        }
+        input::-webkit-input-placeholder {
+          /* WebKit browsers */
+          color: rgba(255, 255, 255, 0.8);
+        }
+
+        input:-moz-placeholder {
+          /* Mozilla Firefox 4 to 18 */
+          color: rgba(255, 255, 255, 0.8);
+        }
+
+        input::-moz-placeholder {
+          /* Mozilla Firefox 19+ */
+          color: rgba(255, 255, 255, 0.8);
+        }
+
+        input:-ms-input-placeholder {
+          /* Internet Explorer 10+ */
+          color: rgba(255, 255, 255, 0.8);
+        }
+      }
+    }
+
+    .A3Mselect {
+      width: 70px;
+      height: 100%;
+      border-radius: 16px;
+      background-color: var(--themeColor2);
+      position: relative;
+      .A3Mselect1 {
+        width: 100%;
+        height: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        color: #fff;
+        & > span {
+          position: relative;
+          top: 1px;
+          left: 3px;
+          transition: all 0.3s;
+        }
+        .rotate {
+          transform: rotate(180deg);
+        }
+      }
+
+      .A3MseZhan {
+        pointer-events: none;
+        transition: all 0.3s;
+        position: absolute;
+        z-index: 10;
+        top: 40px;
+        right: 0px;
+        width: 80px;
+        max-height: 0px;
+        background-color: var(--themeColor2);
+        border-radius: 4px;
+        overflow: hidden;
+
+        & > div {
+          opacity: 0;
+          padding-right: 2px;
+          width: 100%;
+          text-align: center;
+          height: 30px;
+          line-height: 30px;
+          color: #fff;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+        }
+        .active {
+          color: #fcda99;
+          font-weight: 700;
+        }
+      }
+      .A3MseZhanShow {
+        pointer-events: auto;
+        max-height: 200px;
+        padding: 2px 2px 4px 4px;
+        overflow-y: auto;
+        & > div {
+          opacity: 1;
+        }
+      }
+    }
+  }
+}

+ 96 - 0
web/src/pages/A3goodsM/A3Mtop/index.tsx

@@ -0,0 +1,96 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react'
+import styles from './index.module.scss'
+import history from '@/utils/history'
+import { Input } from 'antd'
+import { FromDataType } from '@/pages/A4expert/data'
+import { DictType } from '@/types'
+import { getDictList } from '@/store/action/all'
+import { CaretDownOutlined } from '@ant-design/icons'
+import classNames from 'classnames'
+
+type Props = {
+  dataChangeFu: (obj: FromDataType) => void
+  type: 'goods' | 'expert'
+}
+
+function A3Mtop({ dataChangeFu, type }: Props) {
+  const [topArr, setTopArr] = useState<DictType[]>([])
+  const getDictListFu = useCallback(async () => {
+    const res = await getDictList(type)
+    if (res.code === 0) {
+      setTopArr([{ id: null, name: '全部' }, ...res.data])
+    }
+  }, [type])
+
+  useEffect(() => {
+    getDictListFu()
+  }, [getDictListFu])
+
+  const [fromData, setFromData] = useState<FromDataType>({
+    searchKey: '',
+    dictId: null,
+    pageNum: 1,
+    pageSize: 99999
+  })
+
+  useEffect(() => {
+    dataChangeFu(fromData)
+  }, [dataChangeFu, fromData])
+
+  // 输入框的输入
+  const timeRef = useRef(-1)
+  const txtChangeFu = useCallback(
+    (e: React.ChangeEvent<HTMLInputElement>, key: 'searchKey') => {
+      clearTimeout(timeRef.current)
+      timeRef.current = window.setTimeout(() => {
+        setFromData({ ...fromData, [key]: e.target.value.replaceAll("'", '') })
+      }, 500)
+    },
+    [fromData]
+  )
+
+  // 下拉框的显示隐藏
+  const [isSelShow, setIsSelShow] = useState(false)
+
+  return (
+    <div className={styles.A3Mtop}>
+      <div className='toHome' onClick={() => history.push('/')}></div>
+      <div className='A3Minput'>
+        <Input
+          placeholder='请输入搜索内容'
+          allowClear
+          onChange={e => txtChangeFu(e, 'searchKey')}
+        />
+      </div>
+
+      <div className='A3Mselect'>
+        <div className='A3Mselect1' onClick={() => setIsSelShow(!isSelShow)}>
+          类型
+          <span className={classNames(isSelShow ? 'rotate' : '')}>
+            <CaretDownOutlined />
+          </span>
+        </div>
+
+        {/* 展开 */}
+        <div className={classNames('A3MseZhan', isSelShow ? 'A3MseZhanShow' : '')}>
+          {topArr.map(v => (
+            <div
+              className={classNames(v.id === fromData.dictId ? 'active' : '')}
+              key={v.id}
+              onClick={() => {
+                setFromData({ ...fromData, dictId: v.id })
+                // setIsSelShow(false)
+              }}
+            >
+              {v.name}
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+const MemoA3Mtop = React.memo(A3Mtop)
+
+export default MemoA3Mtop

+ 67 - 0
web/src/pages/A3goodsM/index.module.scss

@@ -1,4 +1,71 @@
 .A3goodsM {
+  background-size: cover;
+  background-image: url('../../assets/img/goods/bgM.png');
+  padding: 20px 4px 0px 14px;
   :global {
+    .A3main {
+      margin-top: 20px;
+      width: 100%;
+      padding-right: 10px;
+      max-height: calc(100% - 52px);
+      overflow-y: auto;
+      display: flex;
+      flex-wrap: wrap;
+      .A3row {
+        width: calc(50% - 7px);
+        margin: 0 14px 20px 0;
+        height: 170px;
+        background-color: #fff;
+        border-radius: 4px;
+        overflow: hidden;
+        &:nth-of-type(2n) {
+          margin-right: 0;
+        }
+
+        .adm-image {
+          width: 100% !important;
+          height: calc(100% - 36px) !important;
+          img {
+            object-fit: cover !important;
+          }
+        }
+
+        & > p {
+          color: #666666;
+          height: 36px;
+          line-height: 36px;
+          padding: 0 26px 0 4px;
+          overflow: hidden;
+          text-overflow: ellipsis;
+          white-space: nowrap;
+          position: relative;
+          & > img {
+            position: absolute;
+            right: 4px;
+            top: 8px;
+            height: 20px;
+          }
+        }
+      }
+    }
+
+    // 没有数据
+    .A3none {
+      margin-top: 20px;
+      width: 100%;
+      padding-right: 10px;
+      height: calc(100% - 152px);
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      & > img {
+        width: 50%;
+      }
+      & > p {
+        font-size: 16px;
+        margin: 5px 0;
+      }
+    }
   }
 }

+ 61 - 4
web/src/pages/A3goodsM/index.tsx

@@ -1,10 +1,67 @@
-import React from 'react'
+import React, { useCallback, useRef, useState } from 'react'
 import styles from './index.module.scss'
+import A3Mtop from './A3Mtop'
+import { FromDataType } from '../A4expert/data'
+import { getGoodsList } from '@/store/action/all'
+import { GoodsRow } from '@/types'
+import LazyImg from '@/components/LazyImg'
+import { baseURL } from '@/utils/http'
+
+// 图片导入
+import iconSImg from '@/assets/img/goods/iconS.png'
+import nullImg from '@/assets/img/goods/null.png'
+
+import A3look from '../A3goods/A3look'
+
 function A3goodsM() {
+  const minHei = useRef(window.innerHeight)
+
+  const [loding, setLoding] = useState(false)
+
+  const getListFu = useCallback(async (info: FromDataType) => {
+    const res = await getGoodsList(info)
+    if (res.code === 0) {
+      setLoding(true)
+      setList(res.data.records)
+      setTimeout(() => {
+        if (sollrRef.current) sollrRef.current.scrollTo({ top: 0, behavior: 'smooth' })
+      }, 100)
+    }
+  }, [])
+
+  const [list, setList] = useState([] as GoodsRow[])
+
+  const sollrRef = useRef<HTMLDivElement>(null)
+
+  const [lookId, setLookId] = useState(0)
+
   return (
-    <div className={styles.A3goodsM}>
-      <h1>中山大学附属肿瘤医院线上院史馆</h1>
-      <h2>移动端开发中,敬请期待.</h2>
+    <div className={styles.A3goodsM} style={{ minHeight: minHei.current + 'px' }}>
+      <A3Mtop dataChangeFu={getListFu} type='goods' />
+
+      {list.length ? (
+        <div className='A3main' ref={sollrRef}>
+          {list.map(item => (
+            <div className='A3row' key={item.id} onClick={() => setLookId(item.id)}>
+              <LazyImg src={baseURL + item.thumb} />
+              <p>
+                {item.name}
+                <img src={iconSImg} alt='' />
+              </p>
+            </div>
+          ))}
+        </div>
+      ) : null}
+
+      {list.length <= 0 && loding ? (
+        <div className='A3none'>
+          <img src={nullImg} alt='' />
+          <p>暂时没有数据</p>
+          {/* <p>请试一下其他关键字</p> */}
+        </div>
+      ) : null}
+
+      {lookId ? <A3look isApp={true} id={lookId} closeFu={() => setLookId(0)} /> : null}
     </div>
   )
 }

+ 76 - 0
web/src/pages/A4expert/A4look/index.module.scss

@@ -93,6 +93,7 @@
           }
         }
         .A4Lrr3 {
+          white-space: pre-wrap;
           padding-right: 15px;
           height: calc(100% - 235px);
           overflow-y: auto;
@@ -104,3 +105,78 @@
     }
   }
 }
+
+// 移动端
+.A4lookM {
+  padding: 0;
+  background-image: url('../../../assets/img/expert/bgM.jpg');
+  background-size: 100% 100%;
+  padding: 30px 30px 70px;
+  :global {
+    .Lcolse {
+      z-index: 10;
+      cursor: pointer;
+      position: absolute;
+      top: 10px;
+      right: 10px;
+      width: 30px;
+    }
+
+    .A4LMmain {
+      width: 100%;
+      height: 100%;
+      .A4LMtimg {
+        width: 180px;
+        display: block;
+        margin: 0px auto 30px;
+      }
+
+      .A4LMcen {
+        background-color: #faf3e6;
+        overflow: hidden;
+        border-radius: 8px;
+        width: 100%;
+        height: calc(100% - 90px);
+        border: 3px solid #c8a063;
+        .A4LMcen1 {
+          width: 100%;
+          height: 260px;
+          .Limg {
+            width: 100%;
+            height: 100%;
+            video {
+              width: 100%;
+              height: 100%;
+              object-fit: cover;
+            }
+            & > div {
+              width: 100%;
+              height: 100%;
+              img {
+                object-fit: cover !important;
+              }
+            }
+          }
+        }
+        .A4LMcen2 {
+          width: 100%;
+          height: calc(100% - 300px);
+          margin-top: 40px;
+          overflow-y: auto;
+          padding: 0 15px 20px;
+          & > div {
+            min-height: 60px;
+            letter-spacing: 4px;
+            line-height: 26px;
+            font-size: 14px;
+            white-space: pre-wrap;
+            margin-bottom: 15px;
+            & > span {
+              font-weight: 700;
+            }
+          }
+        }
+      }
+    }
+  }
+}

+ 83 - 45
web/src/pages/A4expert/A4look/index.tsx

@@ -1,25 +1,29 @@
-import React, { useCallback, useEffect, useRef, useState } from 'react'
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
 import styles from './index.module.scss'
 
 import LazyImg from '@/components/LazyImg'
 
 // 图片导入
-import topBgImg from '@/assets/img/goods/close.png'
+import closeImg from '@/assets/img/goods/close.png'
 import li1Img from '@/assets/img/expert/li1.png'
 import li3Img from '@/assets/img/expert/li3.png'
+import topBgImg from '@/assets/img/expert/topBg.png'
 
 import { getExpertInfo } from '@/store/action/all'
 import { FileType, GoodsRow } from '@/types'
 import { baseURL } from '@/utils/http'
 import { Carousel } from 'antd'
 import store from '@/store'
+import classNames from 'classnames'
+import { ImageViewer } from 'antd-mobile'
 
 type Props = {
   editId: number
   closeFu: () => void
+  isApp?: boolean
 }
 
-function A4look({ editId, closeFu }: Props) {
+function A4look({ editId, closeFu, isApp }: Props) {
   const getInfoFu = useCallback(async (id: number) => {
     const res = await getExpertInfo(id)
     if (res.code === 0) {
@@ -45,61 +49,95 @@ function A4look({ editId, closeFu }: Props) {
     videoRef.current?.pause()
   }, [])
 
+  // 点击图片查看大图
+  const lookBigImg = useCallback(
+    (url: string) => {
+      if (isApp) {
+        ImageViewer.show({ image: baseURL + url })
+      } else {
+        store.dispatch({
+          type: 'layout/lookBigImg',
+          payload: { url: baseURL + url, show: true }
+        })
+      }
+    },
+    [isApp]
+  )
+
+  // 轮播图信息渲染
+  const swInfo = useMemo(() => {
+    return (
+      <Carousel arrows infinite={false} beforeChange={beforeChange}>
+        {file.map(v => (
+          <div key={v.id} className='Limg'>
+            {v.type === 'img' ? (
+              <div onClick={() => lookBigImg(v.filePath)}>
+                <LazyImg src={baseURL + v.thumb} />
+              </div>
+            ) : (
+              <video ref={videoRef} controls src={baseURL + v.filePath}></video>
+            )}
+          </div>
+        ))}
+      </Carousel>
+    )
+  }, [beforeChange, file, lookBigImg])
+
   return (
-    <div id='LookGood' className={styles.A4look}>
+    <div id='LookGood' className={classNames(styles.A4look, isApp ? styles.A4lookM : '')}>
       {/* 关闭按钮 */}
-      <img onClick={closeFu} className='Lcolse' src={topBgImg} alt='' />
-
-      <div className='A4Lmani'>
-        <div className='A4Lll'>
-          <Carousel arrows infinite={false} beforeChange={beforeChange}>
-            {file.map(v => (
-              <div key={v.id} className='Limg'>
-                {v.type === 'img' ? (
-                  <div
-                    onClick={() =>
-                      store.dispatch({
-                        type: 'layout/lookBigImg',
-                        payload: { url: baseURL + v.filePath, show: true }
-                      })
-                    }
-                  >
-                    <LazyImg src={baseURL + v.thumb} />
-                  </div>
-                ) : (
-                  <video ref={videoRef} controls src={baseURL + v.filePath}></video>
-                )}
+      <img onClick={closeFu} className='Lcolse' src={closeImg} alt='' />
+
+      {isApp ? (
+        <div className='A4LMmain'>
+          <img className='A4LMtimg' src={topBgImg} alt='' />
+
+          <div className='A4LMcen' style={{ minHeight: file.length > 1 ? '300px' : '260px' }}>
+            <div className='A4LMcen1'>{swInfo}</div>
+            <div className='A4LMcen2'>
+              <div>
+                <span>简介:</span>
+                {info.intro || '(空)'}
               </div>
-            ))}
-          </Carousel>
-        </div>
 
-        <div className='A4Lrr'>
-          <div className='A4Lrr1'>
-            <h1>{info.name}</h1>
+              <div>
+                <span>介绍:</span>
+                {info.remark || '(空)'}
+              </div>
+            </div>
           </div>
+        </div>
+      ) : (
+        <div className='A4Lmani'>
+          <div className='A4Lll'>{swInfo}</div>
 
-          <div className='A4Lrr2'>
-            <div className='A4Lrr2_1'>
-              <img src={li1Img} alt='' />
-              <span>简介:</span>
+          <div className='A4Lrr'>
+            <div className='A4Lrr1'>
+              <h1>{info.name}</h1>
             </div>
 
-            <div className='A4Lrr2_2'>{info.intro || '(空)'}</div>
-          </div>
+            <div className='A4Lrr2'>
+              <div className='A4Lrr2_1'>
+                <img src={li1Img} alt='' />
+                <span>简介:</span>
+              </div>
 
-          <div className='A4Lrr2'>
-            <div className='A4Lrr2_1'>
-              <img src={li3Img} alt='' />
-              <span>介绍:</span>
+              <div className='A4Lrr2_2'>{info.intro || '(空)'}</div>
             </div>
-          </div>
 
-          <div className='A4Lrr3 mySorrl'>
-            <div dangerouslySetInnerHTML={{ __html: info.remark || '(空)' }}></div>
+            <div className='A4Lrr2'>
+              <div className='A4Lrr2_1'>
+                <img src={li3Img} alt='' />
+                <span>介绍:</span>
+              </div>
+            </div>
+
+            <div className='A4Lrr3 mySorrl'>
+              <div dangerouslySetInnerHTML={{ __html: info.remark || '(空)' }}></div>
+            </div>
           </div>
         </div>
-      </div>
+      )}
     </div>
   )
 }

+ 1 - 0
web/src/pages/A4expert/index.module.scss

@@ -21,6 +21,7 @@
         max-width: 1000px;
         width: auto;
         .appSw {
+          min-width: 60px;
           width: calc(100% - 320px);
         }
         .swiper-slide {

+ 2 - 2
web/src/pages/A4expert/index.tsx

@@ -28,7 +28,7 @@ function A4expert() {
   const [topArr, setTopArr] = useState<DictType[]>([])
 
   const getDictListFu = useCallback(async () => {
-    const res = await getDictList()
+    const res = await getDictList('expert')
     if (res.code === 0) {
       setTopArr([{ id: null, name: '全部' }, ...res.data])
     }
@@ -151,7 +151,7 @@ function A4expert() {
           <div className='A4mNull' hidden={!loding}>
             <img src={nullImg} alt='' />
             <p>暂时没有数据</p>
-            <p>请试一下其他关键字</p>
+            {/* <p>请试一下其他关键字</p> */}
           </div>
         )}
       </div>

+ 122 - 0
web/src/pages/A4expertM/index.module.scss

@@ -1,4 +1,126 @@
 .A4expertM {
+  background-size: cover;
+  background-image: url('../../assets/img/expert/bgM.jpg');
   :global {
+    .A4topImg {
+      width: 180px;
+      display: block;
+      padding-top: 30px;
+      margin: 0px auto 30px;
+    }
+    .A4topInp {
+      padding: 0 4px 0 14px;
+    }
+
+    .A4Mmain {
+      width: 100%;
+      height: calc(100% - 250px);
+      margin-top: 50px;
+      padding: 0 20px 60px;
+      white-space: nowrap;
+      display: inline-block;
+      overflow-x: auto;
+      overflow-y: hidden;
+      &::-webkit-scrollbar {
+        height: 6px;
+      }
+      &::-webkit-scrollbar-track {
+        background: transparent;
+      }
+      &::-webkit-scrollbar-thumb {
+        background: #fcda99;
+      }
+
+      .A4Mrow {
+        border-radius: 30px;
+        overflow: hidden;
+        border: 2px solid #c8a063;
+        display: inline-block;
+        width: 70%;
+        height: 100%;
+        max-height: 460px;
+        min-height: 300px;
+        background-color: #faf3e6;
+        // background-image: url('../../assets/img/expert/moveBg.png');
+        // background-size: 100% 100%;
+        margin-right: 15px;
+        padding: 35px 35px 10px;
+
+        .adm-image {
+          height: 60% !important;
+          img {
+            pointer-events: none;
+            width: 100%;
+            height: 100%;
+            object-fit: cover !important;
+            display: inline-block;
+          }
+        }
+
+        .A4MtxtBox {
+          margin-top: 20px;
+          width: 100%;
+          height: calc(40% - 20px);
+          display: flex;
+          flex-direction: column;
+          justify-content: center;
+
+          .A4Mtit {
+            position: relative;
+            width: 100%;
+            margin-bottom: 20px;
+            color: var(--themeColor2);
+            font-weight: 700;
+            font-size: 18px;
+            background-size: 100% 100%;
+            padding: 0 40px;
+            text-align: center;
+            background-image: url('../../assets/img/expert/mjfc.png');
+            & > div {
+              width: 100%;
+              height: 100%;
+              overflow: hidden;
+              text-overflow: ellipsis;
+              white-space: nowrap;
+            }
+          }
+
+          .A4Mtxt {
+            text-align: center;
+            font-size: 12px;
+            padding: 0 20px;
+            line-height: 26px;
+            letter-spacing: 1px;
+            display: -webkit-box;
+            overflow: hidden;
+            white-space: normal !important;
+            text-overflow: ellipsis;
+            word-wrap: break-word;
+            line-clamp: 2;
+            -webkit-line-clamp: 2;
+            -webkit-box-orient: vertical;
+          }
+        }
+      }
+    }
+
+    // 没有数据
+    .A4none {
+      width: 100%;
+      height: calc(100% - 250px);
+      margin-top: 50px;
+      padding: 0 20px 60px;
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      & > img {
+        width: 50%;
+      }
+      & > p {
+        font-size: 16px;
+        margin: 5px 0;
+      }
+    }
   }
 }

+ 67 - 4
web/src/pages/A4expertM/index.tsx

@@ -1,10 +1,73 @@
-import React from 'react'
+import React, { useCallback, useRef, useState } from 'react'
 import styles from './index.module.scss'
+
+// 图片导入
+import nullImg from '@/assets/img/goods/null.png'
+import topBgImg from '@/assets/img/expert/topBg.png'
+import A3Mtop from '../A3goodsM/A3Mtop'
+import { FromDataType } from '../A4expert/data'
+import { getExpertList } from '@/store/action/all'
+import { GoodsRow } from '@/types'
+import LazyImg from '@/components/LazyImg'
+import { baseURL } from '@/utils/http'
+import A4look from '../A4expert/A4look'
+
 function A4expertM() {
+  const minHei = useRef(window.innerHeight)
+
+  const [loding, setLoding] = useState(false)
+
+  const [list, setList] = useState([] as GoodsRow[])
+
+  const sollrRef = useRef<HTMLDivElement>(null)
+
+  const getListFu = useCallback(async (info: FromDataType) => {
+    const res = await getExpertList(info)
+    if (res.code === 0) {
+      setLoding(true)
+      setList(res.data.records)
+      setTimeout(() => {
+        if (sollrRef.current) sollrRef.current.scrollTo({ left: 0, behavior: 'smooth' })
+      }, 100)
+    }
+  }, [])
+
+  const [editId, setEditId] = useState(0)
+
   return (
-    <div className={styles.A4expertM}>
-      <h1>中山大学附属肿瘤医院线上院史馆132</h1>
-      <h2>移动端开发中,敬请期待.</h2>
+    <div className={styles.A4expertM} style={{ minHeight: minHei.current + 'px' }}>
+      <img className='A4topImg' src={topBgImg} alt='' />
+      <div className='A4topInp'>
+        <A3Mtop dataChangeFu={getListFu} type='expert' />
+      </div>
+
+      {list.length ? (
+        <div className='A4Mmain mySorrl' ref={sollrRef}>
+          {list.map(item => (
+            <div className='A4Mrow' key={item.id} onClick={() => setEditId(item.id)}>
+              <LazyImg src={baseURL + item.thumb} />
+
+              <div className='A4MtxtBox'>
+                <div className='A4Mtit'>
+                  <div>{item.name}</div>
+                </div>
+                <div className='A4Mtxt'>{item.intro}</div>
+              </div>
+            </div>
+          ))}
+        </div>
+      ) : null}
+
+      {list.length <= 0 && loding ? (
+        <div className='A4none'>
+          <img src={nullImg} alt='' />
+          <p>暂时没有数据</p>
+          {/* <p>请试一下其他关键字</p> */}
+        </div>
+      ) : null}
+
+      {/* 查看详情 */}
+      {editId ? <A4look editId={editId} closeFu={() => setEditId(0)} isApp={true} /> : null}
     </div>
   )
 }

+ 3 - 3
web/src/store/action/all.ts

@@ -22,10 +22,10 @@ export const getGoodsInfo = (id: number) => {
 }
 
 /**
- * 获取专家字典列表
+ * 获取字典列表
  */
-export const getDictList = () => {
-  return http.get(`show/dict/getList?type=expert`)
+export const getDictList = (type: 'goods' | 'expert') => {
+  return http.get(`show/dict/getList?type=${type}`)
 }
 
 /**