chenlei 1 år sedan
förälder
incheckning
038c138e32
53 ändrade filer med 882 tillägg och 212 borttagningar
  1. BIN
      fonts/ALIBABA-PUHUITI-BOLD.OTF
  2. 8 0
      src/api/index.ts
  3. 8 3
      src/app.config.ts
  4. 31 0
      src/app.scss
  5. BIN
      src/components/PageSwiper/images/Group23@2x-min.png
  6. BIN
      src/components/PageSwiper/images/LOGO@2x-min.png
  7. BIN
      src/components/PageSwiper/images/Mask-group@2x-min.png
  8. BIN
      src/components/PageSwiper/images/bg@2x-min.jpg
  9. BIN
      src/components/PageSwiper/images/box1@2x-min.png
  10. BIN
      src/components/PageSwiper/images/pic-or-2@2x-min.jpg
  11. BIN
      src/components/PageSwiper/images/pic-or-3@2x-min.jpg
  12. BIN
      src/components/PageSwiper/images/pic-or@2x-min.jpg
  13. BIN
      src/components/PageSwiper/images/tab1@2x-min.png
  14. BIN
      src/components/PageSwiper/images/tab2@2x-min.png
  15. BIN
      src/components/PageSwiper/images/tab3@2x-min.png
  16. 105 1
      src/components/PageSwiper/index.scss
  17. 244 3
      src/components/PageSwiper/index.tsx
  18. BIN
      src/components/Video/btn0@2x-min.png
  19. 27 0
      src/components/Video/index.scss
  20. 23 0
      src/components/Video/index.tsx
  21. BIN
      src/images/logo-min.png
  22. 5 0
      src/pages/banner/index.config.ts
  23. 8 0
      src/pages/banner/index.tsx
  24. 51 44
      src/pages/home/components/Menu/index.tsx
  25. 53 50
      src/pages/home/components/SearchLayout/index.tsx
  26. 15 0
      src/pages/home/components/SightDetailLayout/index.scss
  27. 14 7
      src/pages/home/components/Swiper/index.tsx
  28. 9 1
      src/pages/home/components/VisitCard/index.tsx
  29. BIN
      src/pages/home/components/VisitCard/pop_bg@2x-min.png
  30. 1 0
      src/pages/home/index.config.ts
  31. 0 34
      src/pages/home/index.scss
  32. 16 16
      src/pages/home/index.tsx
  33. BIN
      src/subModule/pages/museum/images/Group33@2x-min.png
  34. BIN
      src/subModule/pages/museum/images/bg@2x-min.jpg
  35. 3 0
      src/subModule/pages/museum/index.config.ts
  36. 6 0
      src/subModule/pages/museum/index.scss
  37. 18 0
      src/subModule/pages/museum/index.tsx
  38. 16 11
      src/subModule/pages/shopmall/components/Products/index.tsx
  39. BIN
      src/subModule/pages/shopmall/components/Ranking/default-avatar-min.png
  40. BIN
      src/subModule/pages/shopmall/components/Ranking/icon_winner@2x-min.png
  41. 70 0
      src/subModule/pages/shopmall/components/Ranking/index.scss
  42. 36 0
      src/subModule/pages/shopmall/components/Ranking/index.tsx
  43. 1 1
      src/subModule/pages/shopmall/components/Records/index.scss
  44. 66 29
      src/subModule/pages/shopmall/components/Records/index.tsx
  45. 1 1
      src/subModule/pages/shopmall/index.config.ts
  46. 4 1
      src/subModule/pages/shopmall/index.scss
  47. 40 10
      src/subModule/pages/shopmall/index.tsx
  48. 3 0
      src/utils/index.ts
  49. BIN
      src/videos/bg@3x.mp4
  50. BIN
      src/videos/bwg.mp4
  51. BIN
      src/videos/city.mp4
  52. BIN
      src/videos/login.mp4
  53. BIN
      src/videos/xszc.mp4

BIN
fonts/ALIBABA-PUHUITI-BOLD.OTF


+ 8 - 0
src/api/index.ts

@@ -92,3 +92,11 @@ export const getShopRuleApi = () => {
 export const handleVisitStateApi = (m: number) => {
   return requestByGet(`/api/cms/game/user/update/invite/${m}`);
 };
+
+export const getRankingListApi = (params?: any) => {
+  return requestByGet("/api/cms/game/point/user/getSort", params, {
+    meta: {
+      showLoading: true,
+    },
+  });
+};

+ 8 - 3
src/app.config.ts

@@ -1,5 +1,5 @@
 export default defineAppConfig({
-  pages: ["pages/home/index", "pages/temp/index"],
+  pages: ["pages/banner/index", "pages/home/index", "pages/temp/index"],
   subpackages: [
     {
       root: "subModule",
@@ -9,6 +9,7 @@ export default defineAppConfig({
         "pages/order/index",
         "pages/iframe/index",
         "pages/portrait-iframe/index",
+        "pages/museum/index",
       ],
     },
   ],
@@ -21,9 +22,13 @@ export default defineAppConfig({
   tabBar: {
     list: [
       {
-        pagePath: "pages/home/index",
-        text: "首页",
+        pagePath: "pages/banner/index",
+        text: "",
       },
+      // {
+      //   pagePath: "pages/home/index",
+      //   text: "首页",
+      // },
       {
         pagePath: "pages/temp/index",
         text: "",

+ 31 - 0
src/app.scss

@@ -44,3 +44,34 @@ page,
   font-family: "Source Han Serif CN-Bold";
   src: url("https://houseoss.4dkankan.com/project/wx-csbwg-public/fonts/SourceHanSerifCN-SemiBold.otf");
 }
+@font-face {
+  font-family: "Alibaba PuHuiTi-Bold";
+  src: url("https://houseoss.4dkankan.com/project/wx-csbwg-public/fonts/ALIBABA-PUHUITI-BOLD.OTF");
+}
+
+.ld-page {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(255, 255, 255);
+  transition: opacity ease-in 1s;
+  z-index: 999;
+
+  &__icon {
+    animation: loading-rotate 2s linear infinite;
+  }
+  &.hide {
+    opacity: 0;
+  }
+}
+
+@keyframes loading-rotate {
+  100% {
+    transform: rotate(360deg);
+  }
+}

BIN
src/components/PageSwiper/images/Group23@2x-min.png


BIN
src/components/PageSwiper/images/LOGO@2x-min.png


BIN
src/components/PageSwiper/images/Mask-group@2x-min.png


BIN
src/components/PageSwiper/images/bg@2x-min.jpg


BIN
src/components/PageSwiper/images/box1@2x-min.png


BIN
src/components/PageSwiper/images/pic-or-2@2x-min.jpg


BIN
src/components/PageSwiper/images/pic-or-3@2x-min.jpg


BIN
src/components/PageSwiper/images/pic-or@2x-min.jpg


BIN
src/components/PageSwiper/images/tab1@2x-min.png


BIN
src/components/PageSwiper/images/tab2@2x-min.png


BIN
src/components/PageSwiper/images/tab3@2x-min.png


+ 105 - 1
src/components/PageSwiper/index.scss

@@ -4,6 +4,110 @@
   left: 0;
   right: 0;
   bottom: 0;
-  background: url("./images/bg@2x-min.jpg") no-repeat center / cover;
+  padding-bottom: 30px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
   z-index: 997;
+
+  &.is-error {
+    background: url("./images/bg@2x-min.jpg") no-repeat center / cover;
+  }
+  &__bg {
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    z-index: -1;
+  }
+  &__title {
+    position: absolute;
+    top: 8.5vh;
+    left: 50%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    height: 15vh;
+    transform: translateX(-50%);
+    z-index: 2;
+  }
+  &-footer {
+    position: absolute;
+    left: 0;
+    right: 0;
+    bottom: 70px;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    z-index: 2;
+
+    &__logo {
+      height: 6vh;
+    }
+    &__tips {
+      height: 2.5vh;
+    }
+    &__text {
+      padding: 25px 0 42px;
+      font-size: 23px;
+      color: #405166;
+    }
+  }
+}
+
+.banner {
+  position: relative;
+  width: 100%;
+  height: 52vh;
+  z-index: 2;
+
+  &-swiper-item {
+    perspective: 800px;
+    transform-style: preserve-3d;
+  }
+  &-item {
+    position: relative;
+    margin: 20px auto 0;
+    width: 345px;
+    height: calc(52vh - 40px);
+    border: 4px solid #ffffff;
+    transform-style: preserve-3d;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    &.active {
+      border-radius: 20px;
+      border: 6px solid rgba($color: #ffffff, $alpha: 0.7);
+      box-shadow: 0 0 20px #ffffff;
+    }
+    &::before {
+      content: "";
+      position: absolute;
+      top: -60px;
+      left: 0;
+      right: 0;
+      bottom: -60px;
+      background: url("./images/Mask-group@2x-min.png") no-repeat center / cover;
+      // animation: linear fade-in 0.2s forwards;
+      z-index: 1;
+    }
+    &__img {
+      width: 100%;
+      height: 100%;
+      background-repeat: no-repeat;
+      background-size: cover;
+      will-change: background-position;
+    }
+  }
+}
+
+@keyframes fade-in {
+  0% {
+    opacity: 0;
+  }
+  100% {
+    opacity: 1;
+  }
 }

+ 244 - 3
src/components/PageSwiper/index.tsx

@@ -1,7 +1,248 @@
-import { View } from "@tarojs/components";
-import { FC } from "@tarojs/taro";
+import { Swiper, SwiperItem, View, Image, Video } from "@tarojs/components";
+import Taro, { FC, pxTransform } from "@tarojs/taro";
+import classNames from "classnames";
+import { useRef, useState } from "react";
+import TitleImg1 from "./images/tab2@2x-min.png";
+import TitleImg2 from "./images/tab1@2x-min.png";
+import TitleImg3 from "./images/tab3@2x-min.png";
+import LogoImg from "./images/LOGO@2x-min.png";
+import FootPageImg from "./images/Group23@2x-min.png";
+import baseStore from "../../store/base";
+import { getSceneUrl, login } from "../../utils";
+import { VisitCard } from "../../pages/home/components/VisitCard";
+import { VideoWrap } from "../Video";
+import { AtIcon } from "taro-ui";
 import "./index.scss";
 
+const IMAGES = [
+  "https://houseoss.4dkankan.com/project/wx-csbwg-public/images/pic-or%402x-min.jpg",
+  "https://houseoss.4dkankan.com/project/wx-csbwg-public/images/pic-or-2%402x-min.jpg",
+  "https://houseoss.4dkankan.com/project/wx-csbwg-public/images/pic-or-3%402x-min.jpg",
+];
+
+const TITLE_IMAGES = [TitleImg2, TitleImg1, TitleImg3];
+
+const system = Taro.getSystemInfoSync();
+const getPX = (num: number) => num / (750 / system.windowWidth);
+const swipeOffsetX = getPX(390);
+const damping = 50 / swipeOffsetX;
+const rotateDamping = 20 / swipeOffsetX;
+
+const POS_MAP = {
+  0: [50, 100, 200],
+  1: [0, 50, 100],
+  2: [-100, 0, 50],
+};
+
+const ROTATEY_MAP = {
+  0: [0, -20, -40],
+  1: [20, 0, -20],
+  2: [40, 20, 0],
+};
+
 export const PageSwiper: FC = () => {
-  return <View className="page-swiper"></View>;
+  const [loaded, setLoaded] = useState(false);
+  const [loading, setLoading] = useState(true);
+  // 背景视频是否报错
+  const [videoError, setVideoError] = useState(false);
+  const [XSZCVideoVisible, setXSZCVideoVisible] = useState(false);
+  const [BWGVideoVisible, setBWGVideoVisible] = useState(false);
+  const [active, setActive] = useState(1);
+  const [moveX, setMoveX] = useState(0);
+  const [bgPos, setBgPos] = useState([...POS_MAP[1]]);
+  const [bgRotateY, setBgRotateY] = useState([...ROTATEY_MAP[1]]);
+  const moving = useRef(false);
+  const isFirstMove = useRef(true);
+  const [visitVisible, setVisitVisible] = useState(false);
+
+  const handleClick = (idx: number) => {
+    if (idx !== active) return;
+
+    switch (idx) {
+      case 0:
+        setXSZCVideoVisible(true);
+        break;
+      case 1:
+        Taro.navigateTo({
+          url: "/pages/home/index",
+        });
+        break;
+      case 2:
+        setBWGVideoVisible(true);
+        break;
+    }
+  };
+
+  const goUnityPage = async () => {
+    const userInfo = baseStore.userInfo;
+
+    if (!userInfo) {
+      await login();
+    }
+
+    setVisitVisible(true);
+
+    // if (userInfo.invite === 0) {
+    //   setVisitVisible(true);
+    //   return;
+    // }
+
+    // Taro.navigateTo({
+    //   url:
+    //     "/subModule/pages/iframe/index?url=" +
+    //     encodeURIComponent(getSceneUrl()),
+    // });
+  };
+
+  const handleBgLoaded = () => {
+    setLoaded(true);
+    setTimeout(() => {
+      setLoading(false);
+    }, 1000);
+  };
+
+  return (
+    <View className={classNames("page-swiper", { "is-error": videoError })}>
+      <View
+        className={classNames("ld-page", { hide: loaded })}
+        style={{ zIndex: loading ? 999 : -1 }}
+      >
+        <AtIcon
+          className="ld-page__icon"
+          value="loading"
+          color="#589498"
+          size={40}
+        />
+      </View>
+
+      <Image
+        className="page-swiper__title"
+        src={TITLE_IMAGES[active]}
+        mode="heightFix"
+      />
+
+      <Swiper
+        className="banner"
+        current={active}
+        display-multiple-items={1}
+        nextMargin={pxTransform(180)}
+        previousMargin={pxTransform(180)}
+        onTransition={({ detail: { dx } }) => {
+          moving.current = true;
+          setMoveX(dx - (isFirstMove.current ? swipeOffsetX : 0));
+        }}
+        onAnimationFinish={({ detail: { current } }) => {
+          moving.current = false;
+          isFirstMove.current = false;
+          setActive(current);
+          setBgPos([...POS_MAP[current]]);
+          setBgRotateY([...ROTATEY_MAP[current]]);
+        }}
+      >
+        {IMAGES.map((url, idx) => {
+          const isActive = active === idx;
+          const bgPosition = moving.current
+            ? Math.min(Math.max(bgPos[idx] - moveX * damping, 0), 100)
+            : bgPos[idx];
+          const rotateY = moving.current
+            ? Math.min(
+                Math.max(bgRotateY[idx] + moveX * rotateDamping, -20),
+                20
+              )
+            : bgRotateY[idx];
+
+          return (
+            <SwiperItem
+              key={idx}
+              className="banner-swiper-item"
+              onClick={handleClick.bind(undefined, idx)}
+            >
+              <View
+                className={classNames("banner-item", {
+                  active: isActive,
+                })}
+                style={{
+                  transform: `rotateY(${rotateY}deg)`,
+                }}
+              >
+                <View
+                  className="banner-item__img"
+                  style={{
+                    backgroundImage: `url(${url})`,
+                    backgroundPosition: `${bgPosition}%`,
+                  }}
+                ></View>
+              </View>
+            </SwiperItem>
+          );
+        })}
+      </Swiper>
+
+      <View className="page-swiper-footer">
+        <Image
+          className="page-swiper-footer__logo"
+          src={LogoImg}
+          mode="heightFix"
+        />
+        <View className="page-swiper-footer__text">
+          让我们一起创造温暖,传递心
+        </View>
+        <Image
+          className="page-swiper-footer__tips"
+          src={FootPageImg}
+          mode="heightFix"
+        />
+      </View>
+
+      {!videoError && (
+        <Video
+          className="page-swiper__bg"
+          src="https://houseoss.4dkankan.com/project/wx-csbwg-public/videos/bg%403x.mp4"
+          autoplay
+          muted
+          loop
+          controls={false}
+          objectFit="cover"
+          onLoadedMetaData={handleBgLoaded}
+          onError={() => {
+            setVideoError(true);
+            handleBgLoaded();
+          }}
+        />
+      )}
+
+      <VisitCard
+        isOpened={visitVisible}
+        onClose={() => setVisitVisible(false)}
+      />
+
+      {XSZCVideoVisible && (
+        <VideoWrap
+          autoplay
+          controls={false}
+          objectFit="cover"
+          src="https://houseoss.4dkankan.com/project/wx-csbwg-public/videos/xszc.mp4"
+          onEnded={() => {
+            goUnityPage();
+            setXSZCVideoVisible(false);
+          }}
+        />
+      )}
+
+      {BWGVideoVisible && (
+        <VideoWrap
+          autoplay
+          controls={false}
+          objectFit="cover"
+          src="https://houseoss.4dkankan.com/project/wx-csbwg-public/videos/bwg.mp4"
+          onEnded={() => {
+            Taro.navigateTo({
+              url: "/subModule/pages/museum/index",
+            });
+            setBWGVideoVisible(false);
+          }}
+        />
+      )}
+    </View>
+  );
 };

BIN
src/components/Video/btn0@2x-min.png


+ 27 - 0
src/components/Video/index.scss

@@ -0,0 +1,27 @@
+.video-wrap {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  z-index: 998;
+
+  &__video {
+    width: 100%;
+    height: 100%;
+  }
+  &__skip {
+    position: absolute;
+    right: 30px;
+    bottom: 30px;
+    display: flex;
+    justify-content: center;
+    line-height: 86px;
+    width: 234px;
+    height: 102px;
+    color: white;
+    font-size: 28px;
+    font-family: "Source Han Serif CN-Bold";
+    background: url("./btn0@2x-min.png") no-repeat center / contain;
+  }
+}

+ 23 - 0
src/components/Video/index.tsx

@@ -0,0 +1,23 @@
+import { Video, VideoProps, View } from "@tarojs/components";
+import { FC } from "@tarojs/taro";
+import "./index.scss";
+
+export interface VideoWrapProps extends Omit<VideoProps, "onEnded"> {
+  onEnded: Function;
+}
+
+export const VideoWrap: FC<VideoWrapProps> = ({ onEnded, ...props }) => {
+  return (
+    <View className="video-wrap">
+      <Video
+        className="video-wrap__video"
+        {...props}
+        onEnded={() => onEnded()}
+      />
+
+      <View className="video-wrap__skip" onClick={() => onEnded()}>
+        跳过
+      </View>
+    </View>
+  );
+};

BIN
src/images/logo-min.png


+ 5 - 0
src/pages/banner/index.config.ts

@@ -0,0 +1,5 @@
+export default definePageConfig({
+  enableShareAppMessage: true,
+  enableShareTimeline: true,
+  navigationStyle: "custom",
+});

+ 8 - 0
src/pages/banner/index.tsx

@@ -0,0 +1,8 @@
+import { FC } from "@tarojs/taro";
+import { PageSwiper } from "../../components/PageSwiper";
+
+const BannerPage: FC = () => {
+  return <PageSwiper />;
+};
+
+export default BannerPage;

+ 51 - 44
src/pages/home/components/Menu/index.tsx

@@ -6,7 +6,7 @@ import { AtDrawerProps } from "taro-ui/types/drawer";
 import { observer } from "mobx-react";
 import { useMemo } from "react";
 import { getShopRuleApi } from "../../../../api";
-import { getSceneUrl, login } from "../../../../utils";
+import { getSceneUrl, login, MUSEUM_URL } from "../../../../utils";
 import baseStore from "../../../../store/base";
 import CloseIcon from "../../../../images/icon_back_menu@2x.png";
 // import SearchIcon from "../../../../images/icon_search_black@2x-min.png";
@@ -47,9 +47,7 @@ export const Menu: FC<MenuProps> = observer((props) => {
             label: "慈善博物馆",
             link:
               "/subModule/pages/iframe/index?url=" +
-              encodeURIComponent(
-                "https://houseoss.4dkankan.com/project/wuxicishanbwg/index.html?m=SG-igv7wQAyyyG_01&showBack=1"
-              ),
+              encodeURIComponent(MUSEUM_URL),
           },
           {
             label: "慈善云学校",
@@ -90,11 +88,18 @@ export const Menu: FC<MenuProps> = observer((props) => {
           },
           {
             label: "用户反馈",
-            link: "/subModule/pages/feedback/index",
-            needAuth: true,
+            method: () => {
+              Taro.showToast({
+                title: "暂未开发,敬请期待",
+                icon: "none",
+                duration: 3000,
+              });
+            },
+            // link: "/subModule/pages/feedback/index",
+            // needAuth: true,
           },
           {
-            label: "爱心币商城",
+            label: "爱心",
             method: async () => {
               const { display, rtf } = await getShopRuleApi();
               if (display === 0) {
@@ -146,49 +151,51 @@ export const Menu: FC<MenuProps> = observer((props) => {
         onClick={() => props.openSearch()}
       /> */}
 
-      <ScrollView scrollY style={{ height: "100%" }}>
-        {LIST.map((item, idx) => {
-          const isActive = idx === 0;
+      {props.show && (
+        <ScrollView scrollY style={{ height: "100%" }}>
+          {LIST.map((item, idx) => {
+            const isActive = idx === 0;
 
-          return (
-            <View
-              key={item.label}
-              className={classNames("menu-item", { active: isActive })}
-            >
+            return (
               <View
-                className={classNames("menu-item__title", {
-                  "is-active": isActive,
-                })}
+                key={item.label}
+                className={classNames("menu-item", { active: isActive })}
               >
-                <Image
-                  className="menu-item__title__icon"
-                  src={isActive ? item.activeIcon : item.icon}
-                />
-                <Text>{item.label}</Text>
-
-                {isActive && (
-                  <View className="menu-item__title reflect">
-                    <Image
-                      className="menu-item__title__icon"
-                      src={isActive ? item.activeIcon : item.icon}
-                    />
-                    <Text>{item.label}</Text>
-                  </View>
-                )}
-              </View>
-
-              {item.children.map((subItem) => (
                 <View
-                  className="menu-item__sub"
-                  onClick={handleSubTab.bind(undefined, subItem)}
+                  className={classNames("menu-item__title", {
+                    "is-active": isActive,
+                  })}
                 >
-                  {subItem.label}
+                  <Image
+                    className="menu-item__title__icon"
+                    src={isActive ? item.activeIcon : item.icon}
+                  />
+                  <Text>{item.label}</Text>
+
+                  {isActive && (
+                    <View className="menu-item__title reflect">
+                      <Image
+                        className="menu-item__title__icon"
+                        src={isActive ? item.activeIcon : item.icon}
+                      />
+                      <Text>{item.label}</Text>
+                    </View>
+                  )}
                 </View>
-              ))}
-            </View>
-          );
-        })}
-      </ScrollView>
+
+                {item.children.map((subItem) => (
+                  <View
+                    className="menu-item__sub"
+                    onClick={handleSubTab.bind(undefined, subItem)}
+                  >
+                    {subItem.label}
+                  </View>
+                ))}
+              </View>
+            );
+          })}
+        </ScrollView>
+      )}
     </AtDrawer>
   );
 });

+ 53 - 50
src/pages/home/components/SearchLayout/index.tsx

@@ -56,58 +56,61 @@ export const SearchLayout: FC<SearchLayoutProps> = (props) => {
 
   return (
     <AtFloatLayout className="search-layout" {...props}>
-      <View className="search-input">
-        {props.isOpened && (
-          <AtInput
-            clear
-            className="search-input__input"
-            name="keyword"
-            value={input}
-            confirm-type="search"
-            placeholder="请输入要搜索的内容..."
-            onChange={(v) => {
-              setInput(v as string);
-              debounceSearch(v as string);
-            }}
-          />
-        )}
-        <View className="search-input__cancel" onClick={props.onClose}>
-          取消
-        </View>
-      </View>
+      {props.isOpened && (
+        <>
+          <View className="search-input">
+            <AtInput
+              clear
+              className="search-input__input"
+              name="keyword"
+              value={input}
+              confirm-type="search"
+              placeholder="请输入要搜索的内容..."
+              onChange={(v) => {
+                setInput(v as string);
+                debounceSearch(v as string);
+              }}
+            />
 
-      <AtTabs
-        current={curTab}
-        scroll
-        className="search-tab"
-        tabList={CITY_LIST}
-        onClick={handleTabClick}
-      >
-        {CITY_LIST.map((item, idx) => (
-          <AtTabsPane current={curTab} index={item.id}>
-            {(list[idx] || [])
-              .filter((i) => (keyword ? fakeSearch(i.name) : true))
-              .map((subItem) => (
-                <View
-                  key={subItem.id}
-                  className="search-tab__item"
-                  onClick={props.openDetail.bind(undefined, subItem)}
-                >
-                  <View className="search-tab__item-inner">
-                    <View className="search-tab__item-inner__label">
-                      {subItem.name}
-                    </View>
-                    <View className="limit-line">
-                      {subItem.description.slice(0, 50)}
-                    </View>
-                  </View>
+            <View className="search-input__cancel" onClick={props.onClose}>
+              取消
+            </View>
+          </View>
 
-                  <View className="search-tab__item__more" />
-                </View>
-              ))}
-          </AtTabsPane>
-        ))}
-      </AtTabs>
+          <AtTabs
+            current={curTab}
+            scroll
+            className="search-tab"
+            tabList={CITY_LIST}
+            onClick={handleTabClick}
+          >
+            {CITY_LIST.map((item, idx) => (
+              <AtTabsPane current={curTab} index={item.id}>
+                {(list[idx] || [])
+                  .filter((i) => (keyword ? fakeSearch(i.name) : true))
+                  .map((subItem) => (
+                    <View
+                      key={subItem.id}
+                      className="search-tab__item"
+                      onClick={props.openDetail.bind(undefined, subItem)}
+                    >
+                      <View className="search-tab__item-inner">
+                        <View className="search-tab__item-inner__label">
+                          {subItem.name}
+                        </View>
+                        <View className="limit-line">
+                          {subItem.description.slice(0, 50)}
+                        </View>
+                      </View>
+
+                      <View className="search-tab__item__more" />
+                    </View>
+                  ))}
+              </AtTabsPane>
+            ))}
+          </AtTabs>
+        </>
+      )}
     </AtFloatLayout>
   );
 };

+ 15 - 0
src/pages/home/components/SightDetailLayout/index.scss

@@ -24,12 +24,26 @@
     max-height: unset;
     min-height: unset;
     height: 100%;
+
+    &::after {
+      content: "";
+      position: absolute;
+      left: 0;
+      bottom: 0;
+      width: 685px;
+      height: 722px;
+      background: url("../../../../images/logo-min.png") no-repeat center /
+        contain;
+      z-index: -1;
+    }
   }
 
   &-wrap {
+    position: relative;
     padding: 85px 55px 210px;
     height: 100%;
     box-sizing: border-box;
+    z-index: 2;
   }
   &-head {
     position: relative;
@@ -102,5 +116,6 @@
     width: 80px;
     height: 80px;
     transform: translateX(-50%);
+    z-index: 2;
   }
 }

+ 14 - 7
src/pages/home/components/Swiper/index.tsx

@@ -3,6 +3,7 @@ import { RoundItem } from "../RoundItem";
 import Taro from "@tarojs/taro";
 import {
   forwardRef,
+  useCallback,
   useImperativeHandle,
   useMemo,
   useRef,
@@ -36,12 +37,15 @@ const getItemStack = () => {
   return stack;
 };
 
+const ANGLE = 360 / 12;
+
 export const Swiper = forwardRef<SwiperMethods, SwiperProps>(
   ({ curSwiperItem, setCurSwiperItem, handleDetail }, ref) => {
     const [itemStack, setItemStack] = useState(getItemStack());
     const touchStartX = useRef(0);
     const moving = useRef(false);
     const [isRunning, setIsRunning] = useState(false);
+    const loaded = useRef(false);
 
     const getNextItem = (curIndex: number, length: number) => {
       return (curIndex + 1) % length;
@@ -133,6 +137,14 @@ export const Swiper = forwardRef<SwiperMethods, SwiperProps>(
 
     useInterval(toNext, isRunning ? 5000 : null);
 
+    const handleActLoaded = useCallback(() => {
+      setTimeout(() => {
+        moving.current = false;
+        loaded.current && setIsRunning(true);
+        loaded.current = true;
+      }, 500);
+    }, []);
+
     return (
       <View
         className="sights"
@@ -150,18 +162,13 @@ export const Swiper = forwardRef<SwiperMethods, SwiperProps>(
               <RoundItem
                 key={id}
                 id={realId}
-                angle={(360 / 12) * idx}
+                angle={ANGLE * idx}
                 bigR={Big_R}
                 minR={MIN_R}
                 isActive={isActive}
                 isNear={nearIds.includes(id)}
                 onClick={handleDetail.bind(undefined, realId)}
-                onActLoaded={() => {
-                  setTimeout(() => {
-                    moving.current = false;
-                    setIsRunning(true);
-                  }, 500);
-                }}
+                onActLoaded={handleActLoaded}
               />
             )
           );

+ 9 - 1
src/pages/home/components/VisitCard/index.tsx

@@ -3,7 +3,7 @@ import { FC } from "@tarojs/taro";
 import { AtButton, AtCurtain, AtInput } from "taro-ui";
 import { AtCurtainProps } from "taro-ui/types/curtain";
 import BorderImg from "./line@2x-min.png";
-import { useState } from "react";
+import { useEffect, useState } from "react";
 import {
   createRandomString,
   getSceneUrl,
@@ -33,6 +33,14 @@ export const VisitCard: FC<VisitCardProps> = (props) => {
   const [wechatLoading, setWechatLoading] = useState(false);
   const [name, setName] = useState(`云城居民${createRandomString()}`);
 
+  useEffect(() => {
+    if (baseStore.userInfo) {
+      if (baseStore.userInfo.invite === 1) {
+        setName(baseStore.userInfo.nickName);
+      }
+    }
+  }, [baseStore.userInfo]);
+
   const handleValidate = async () => {
     const errors = await onValidateField(
       {

BIN
src/pages/home/components/VisitCard/pop_bg@2x-min.png


+ 1 - 0
src/pages/home/index.config.ts

@@ -1,3 +1,4 @@
 export default definePageConfig({
   enableShareAppMessage: true,
+  enableShareTimeline: true,
 });

+ 0 - 34
src/pages/home/index.scss

@@ -231,40 +231,6 @@
   }
 }
 
-.ld-page {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: rgba(255, 255, 255);
-  transition: opacity ease-in 1s;
-  will-change: opacity;
-  z-index: 999;
-
-  &__icon {
-    animation: loading-rotate 2s linear infinite;
-  }
-  &.hide {
-    animation: loading-leave 1s ease-in forwards;
-  }
-}
-
-@keyframes loading-leave {
-  100% {
-    opacity: 0;
-  }
-}
-
-@keyframes loading-rotate {
-  100% {
-    transform: rotate(360deg);
-  }
-}
-
 @keyframes flashing {
   0% {
     opacity: 1;

+ 16 - 16
src/pages/home/index.tsx

@@ -1,4 +1,4 @@
-import { memo, useMemo, useRef, useState } from "react";
+import { memo, useCallback, useMemo, useRef, useState } from "react";
 import {
   Image,
   View,
@@ -30,7 +30,6 @@ import { VisitCard } from "./components/VisitCard";
 import { getSceneUrl, login } from "../../utils";
 import baseStore from "../../store/base";
 import SearchIcon from "../../images/icon_search_white@2x-min.png";
-import { PageSwiper } from "../../components/PageSwiper";
 import "./index.scss";
 
 const PointIcon = memo(() => {
@@ -85,18 +84,21 @@ const HomePage: FC = () => {
     }
   };
 
-  const handleDetail = async (id?: number) => {
-    swiperRef.current?.setIsRunning(false);
-    const target = SIGHT_LIST.find(
-      (i) => i.id === (id || SORT_MAP_ID[curSwiperItem])
-    );
-    const data = await getSignListApi({
-      searchKey: target?.title,
-      type: "scene",
-    });
-    setItem(data[0] || target);
-    setShowSight(true);
-  };
+  const handleDetail = useCallback(
+    async (id?: number) => {
+      swiperRef.current?.setIsRunning(false);
+      const target = SIGHT_LIST.find(
+        (i) => i.id === (id || SORT_MAP_ID[curSwiperItem])
+      );
+      const data = await getSignListApi({
+        searchKey: target?.title,
+        type: "scene",
+      });
+      setItem(data[0] || target);
+      setShowSight(true);
+    },
+    [curSwiperItem]
+  );
 
   const closeDetail = () => {
     setShowSight(false);
@@ -313,8 +315,6 @@ const HomePage: FC = () => {
         isOpened={visitVisible}
         onClose={() => setVisitVisible(false)}
       />
-
-      {/* <PageSwiper /> */}
     </>
   );
 };

BIN
src/subModule/pages/museum/images/Group33@2x-min.png


BIN
src/subModule/pages/museum/images/bg@2x-min.jpg


+ 3 - 0
src/subModule/pages/museum/index.config.ts

@@ -0,0 +1,3 @@
+export default definePageConfig({
+  navigationBarTitleText: "无锡慈善博物馆",
+});

+ 6 - 0
src/subModule/pages/museum/index.scss

@@ -0,0 +1,6 @@
+.museum {
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+  background: url("./images/bg@2x-min.jpg") no-repeat center bottom / cover;
+}

+ 18 - 0
src/subModule/pages/museum/index.tsx

@@ -0,0 +1,18 @@
+import { View } from "@tarojs/components";
+import Taro from "@tarojs/taro";
+import { FC } from "@tarojs/taro";
+import { MUSEUM_URL } from "../../../utils";
+import "./index.scss";
+
+const MuseumPage: FC = () => {
+  const goRoam = () => {
+    Taro.navigateTo({
+      url:
+        "/subModule/pages/iframe/index?url=" + encodeURIComponent(MUSEUM_URL),
+    });
+  };
+
+  return <View className="museum" onClick={goRoam} />;
+};
+
+export default MuseumPage;

+ 16 - 11
src/subModule/pages/shopmall/components/Products/index.tsx

@@ -49,18 +49,23 @@ export const Products: FC<ProductsProps> = ({ point }) => {
   });
 
   const handleBuy = (item: any) => {
-    if (point < item.score) {
-      Taro.showToast({
-        title: "积分不足,无法兑换",
-        icon: "none",
-        duration: 3000,
-      });
-      return;
-    }
-
-    Taro.navigateTo({
-      url: `/subModule/pages/order/index?id=${item.id}&score=${item.score}&point=${point}`,
+    Taro.showToast({
+      title: "暂未开发,敬请期待",
+      icon: "none",
+      duration: 3000,
     });
+    // if (point < item.score) {
+    //   Taro.showToast({
+    //     title: "积分不足,无法兑换",
+    //     icon: "none",
+    //     duration: 3000,
+    //   });
+    //   return;
+    // }
+
+    // Taro.navigateTo({
+    //   url: `/subModule/pages/order/index?id=${item.id}&score=${item.score}&point=${point}`,
+    // });
   };
 
   return (

BIN
src/subModule/pages/shopmall/components/Ranking/default-avatar-min.png


BIN
src/subModule/pages/shopmall/components/Ranking/icon_winner@2x-min.png


+ 70 - 0
src/subModule/pages/shopmall/components/Ranking/index.scss

@@ -0,0 +1,70 @@
+.ranking {
+  flex: 1;
+  padding-top: 30px;
+  height: 0;
+  box-sizing: border-box;
+
+  &-item {
+    display: flex;
+    align-items: center;
+    padding: 17px 50px;
+
+    &.is-first {
+      .ranking-item__sort::after {
+        content: "";
+        position: absolute;
+        top: 0;
+        left: 0;
+        right: 0;
+        bottom: 0;
+        background: url("./icon_winner@2x-min.png") no-repeat center / contain;
+      }
+      .ranking-item__val {
+        color: #ffd337;
+      }
+    }
+    &.is-me {
+      background: #589498;
+
+      .ranking-item__sort,
+      .ranking-item__val,
+      .ranking-item__name {
+        color: white;
+      }
+    }
+    &__sort {
+      position: relative;
+      color: #393939;
+      font-size: 31px;
+      width: 56px;
+      height: 56px;
+      line-height: 56px;
+      text-align: center;
+      font-family: "Alibaba PuHuiTi-Bold";
+
+      &__num {
+        position: relative;
+        z-index: 2;
+      }
+    }
+    &__avatar {
+      margin: 0 30px 0 42px;
+      width: 80px;
+      height: 80px;
+      border-radius: 50%;
+      overflow: hidden;
+      border: 4px solid #ffffff;
+    }
+    &__name {
+      flex: 1;
+      width: 0;
+      color: #424a4a;
+      font-size: 31px;
+    }
+    &__val {
+      color: #424a4a;
+      font-size: 38px;
+      font-family: "Alibaba PuHuiTi-Bold";
+    }
+  }
+}

+ 36 - 0
src/subModule/pages/shopmall/components/Ranking/index.tsx

@@ -0,0 +1,36 @@
+import { Image, ScrollView, View } from "@tarojs/components";
+import { FC } from "@tarojs/taro";
+import classNames from "classnames";
+import baseStore from "../../../../../store/base";
+import DefaultAvatar from "./default-avatar-min.png";
+import "./index.scss";
+
+export interface RankingProps {
+  list: any[];
+}
+
+export const Ranking: FC<RankingProps> = ({ list }) => {
+  return (
+    <ScrollView scrollY className="ranking">
+      {list.map((item, idx) => (
+        <View
+          key={item.id}
+          className={classNames("ranking-item", {
+            "is-first": idx === 0,
+            "is-me": baseStore.userInfo?.id === item.id,
+          })}
+        >
+          <View className="ranking-item__sort">
+            <View className="ranking-item__sort__num">{idx + 1}</View>
+          </View>
+
+          <Image className="ranking-item__avatar" src={DefaultAvatar} />
+
+          <View className="ranking-item__name limit-line">{item.nickName}</View>
+
+          <View className="ranking-item__val">{item.pcs}</View>
+        </View>
+      ))}
+    </ScrollView>
+  );
+};

+ 1 - 1
src/subModule/pages/shopmall/components/Records/index.scss

@@ -1,6 +1,6 @@
 .records {
   flex: 1;
-  padding: 20px 0;
+  margin: 20px 0;
   height: 0;
   box-sizing: border-box;
 

+ 66 - 29
src/subModule/pages/shopmall/components/Records/index.tsx

@@ -1,44 +1,81 @@
 import { View, Text, ScrollView } from "@tarojs/components";
-import { FC, pxTransform } from "@tarojs/taro";
+import Taro, { FC, pxTransform } from "@tarojs/taro";
 import classNames from "classnames";
 import baseStore from "../../../../../store/base";
+import VirtualList from "@tarojs/components/virtual-list";
+import { useEffect, useState } from "react";
 import "./index.scss";
 
 export interface RecordsProps {
   list: any[];
+  active: boolean;
 }
 
-export const Records: FC<RecordsProps> = ({ list }) => {
+export interface RecordsItemProps {
+  data: any[];
+  index: number;
+}
+
+export const RecordsItem: FC<RecordsItemProps> = ({ data, index }) => {
+  const item = data[index];
+
+  return (
+    <View key={item.id} className="records-item">
+      <View className="records-item__lf">
+        <View className="records-item__lf__label">{item.type}</View>
+        <View className="records-item__lf__date">
+          {item.createTime}
+
+          {item.prizeName && (
+            <Text style={{ paddingLeft: pxTransform(20) }}>
+              {item.prizeName}
+            </Text>
+          )}
+        </View>
+      </View>
+
+      <View
+        className={classNames("records-item__rg", {
+          red: item.score < 0,
+        })}
+      >
+        {item.score > 0 ? "+" : ""}
+        {item.score}
+      </View>
+    </View>
+  );
+};
+
+export const Records: FC<RecordsProps> = ({ list, active }) => {
+  const [listHeight, setListHeight] = useState(0);
+
+  useEffect(() => {
+    if (active && !listHeight) {
+      Taro.createSelectorQuery()
+        .select(".records")
+        .boundingClientRect((rect) => {
+          setListHeight(rect.height);
+        })
+        .exec();
+    }
+  }, [active]);
+
   return (
     <>
       <ScrollView scrollY className="records">
-        {!list.length && <View className="records__nomore">暂无记录</View>}
-
-        {list.map((item) => (
-          <View key={item.id} className="records-item">
-            <View className="records-item__lf">
-              <View className="records-item__lf__label">{item.type}</View>
-              <View className="records-item__lf__date">
-                {item.createTime}
-
-                {item.prizeName && (
-                  <Text style={{ paddingLeft: pxTransform(20) }}>
-                    {item.prizeName}
-                  </Text>
-                )}
-              </View>
-            </View>
-
-            <View
-              className={classNames("records-item__rg", {
-                red: item.score < 0,
-              })}
-            >
-              {item.score > 0 ? "+" : ""}
-              {item.score}
-            </View>
-          </View>
-        ))}
+        {!list.length ? (
+          <View className="records__nomore">暂无记录</View>
+        ) : listHeight ? (
+          <VirtualList
+            width="100%"
+            height={listHeight}
+            itemData={list}
+            itemCount={list.length}
+            itemSize={59}
+            children={RecordsItem}
+            overscanCount={20}
+          />
+        ) : null}
       </ScrollView>
 
       {baseStore.shopTips && (

+ 1 - 1
src/subModule/pages/shopmall/index.config.ts

@@ -1,3 +1,3 @@
 export default definePageConfig({
-  navigationBarTitleText: "爱心币商城",
+  navigationBarTitleText: "爱心",
 });

+ 4 - 1
src/subModule/pages/shopmall/index.scss

@@ -76,7 +76,7 @@
       display: none;
     }
     .at-tabs__item {
-      padding: 0 70px;
+      padding: 0;
       font-size: 38px;
       color: #424a4a;
       height: 100%;
@@ -88,6 +88,9 @@
         color: #589498;
         font-family: "Source Han Sans CN-Bold";
       }
+      &:nth-child(2) {
+        padding: 0 80px;
+      }
     }
     .at-tabs__body {
       flex: 1;

+ 40 - 10
src/subModule/pages/shopmall/index.tsx

@@ -4,19 +4,33 @@ import { AtTabs, AtTabsPane } from "taro-ui";
 import { useState } from "react";
 import { Products } from "./components/Products";
 import { Records } from "./components/Records";
-import { getPointRecordListApi, getUserPointApi } from "../../../api";
+import {
+  getPointRecordListApi,
+  getRankingListApi,
+  getUserPointApi,
+} from "../../../api";
+import { Ranking } from "./components/Ranking";
 import "./index.scss";
 
+enum TABS {
+  SHOP,
+  RANKING,
+  RECORD,
+}
+
 const ShopmallPage: FC = () => {
-  const [curTab, setCurTab] = useState(0);
+  const [curTab, setCurTab] = useState(TABS.SHOP);
   const [point, setPoint] = useState<string | number>("--");
   const [recordList, setRecordList] = useState<any[]>([]);
+  const [rankingList, setRankingList] = useState<any[]>([]);
 
   const handleTabClick = (idx: number) => {
     setCurTab(idx);
 
-    if (idx === 1) {
+    if (idx === TABS.RECORD) {
       getPointRecordList();
+    } else if (idx === TABS.RANKING) {
+      getRankingList();
     }
   };
 
@@ -30,6 +44,13 @@ const ShopmallPage: FC = () => {
     setRecordList(data);
   };
 
+  const getRankingList = async () => {
+    const data = await getRankingListApi({
+      limit: 10,
+    });
+    setRankingList(data);
+  };
+
   useDidShow(() => {
     getUserPoint();
   });
@@ -37,7 +58,7 @@ const ShopmallPage: FC = () => {
   return (
     <View className="shopmall">
       <View className="shopmall-banner">
-        <View className="shopmall-banner__label">爱心</View>
+        <View className="shopmall-banner__label">爱心</View>
         <View className="shopmall-banner__count">{point}</View>
       </View>
 
@@ -46,17 +67,26 @@ const ShopmallPage: FC = () => {
           current={curTab}
           scroll
           className="shopmall-tab"
-          tabList={[{ title: "爱心币兑换" }, { title: "兑换记录" }]}
+          tabList={[
+            { title: "爱心捐赠" },
+            { title: "排行榜" },
+            { title: "捐赠记录" },
+          ]}
           onClick={handleTabClick}
         >
-          {/* 爱心币兑换 */}
-          <AtTabsPane current={curTab} index={0}>
+          {/* 爱心捐赠 */}
+          <AtTabsPane current={curTab} index={TABS.SHOP}>
             <Products curTab={curTab} point={point as number} />
           </AtTabsPane>
 
-          {/* 兑换记录 */}
-          <AtTabsPane current={curTab} index={1}>
-            <Records list={recordList} />
+          {/* 排行榜 */}
+          <AtTabsPane current={curTab} index={TABS.RANKING}>
+            <Ranking list={rankingList} />
+          </AtTabsPane>
+
+          {/* 捐赠记录 */}
+          <AtTabsPane current={curTab} index={TABS.RECORD}>
+            <Records list={recordList} active={curTab === TABS.RECORD} />
           </AtTabsPane>
         </AtTabs>
       </View>

+ 3 - 0
src/utils/index.ts

@@ -7,6 +7,9 @@ export const TOKEN_KEY = "token";
 export const NICKNAME_KEY = "nickname";
 export const USER_INFO_KEY = "userinfo";
 
+export const MUSEUM_URL =
+  "https://houseoss.4dkankan.com/project/wuxicishanbwg/index.html?m=SG-igv7wQAyyyG_01&showBack=1";
+
 export const getSceneUrl = (scene?: number) => {
   const name = getStorageSync(NICKNAME_KEY);
   const token = getStorageSync(TOKEN_KEY);

BIN
src/videos/bg@3x.mp4


BIN
src/videos/bwg.mp4


BIN
src/videos/city.mp4


BIN
src/videos/login.mp4


BIN
src/videos/xszc.mp4