【端午节】新奇体验,我用react实现网页游戏的全过程(包括规则设计)

“我正在参加「初夏创意投稿大赛」详情请看:初夏创意投稿大赛

关于游戏的灵感来源

今年元宵节的时候,我玩的小游戏里面有限时任务,可以解锁节日限定物品,于是那几天我玩的很欢乐很积极。端午节到来之前,我想玩一下身份转换,从玩家转换到游戏策划。一个有趣的想法在脑海中逐渐清晰。

假如我是游戏策划

假如我是游戏策划,首先会对自己灵魂三连问:活动内容什么?活动怎么玩?活动奖励是什么?

现有大体的想法,然后再拆分到各个细节中去。

因为游戏中的一些场景搭配、日常活动名称、称号等借鉴了我最近沉迷的游戏《美人传》,所以这次的游戏仅供学习练习,不做任何商业用途。

产品视角

站在产品的角度思考活动设计,我的产品视角是这样的:

一入夏,就盼着假期,过了五一很快就会到端午,一想到端午就不由自主的想到美味的粽子。所以端午的活动就来了,包粽子。众所周知,包粽子需要糯米、粽叶等必备材料,而粽子的内馅有很多种,本次活动中需要的是红枣。所以包粽子的材料就选定了糯米、粽叶、红枣三种。(活动内容是什么)

游戏中有日常收集任务,每个收集任务掉落的材料都是固定的。活动期间一般会增加活动材料限时掉落,所以在活动期间,日常收集时会掉落包粽子需要的材料,不同收集任务掉落不同材料。(活动怎么玩)

粽子积累到一定数量就可以兑换节日限定物品。一般游戏中的节日限定物品都是精心设计的,但是由于时间和精力有限,我这次活动设计的比较简单,不同数量的粽子可以兑换不同的称号,最高称号为“荣宠万千”。(活动奖励是什么)

(^U^)ノ~YO,一切准备就绪,开始干活。

交互设计

大致画了一下设计草图,帮助理清楚布局思路。(第一次画,还有待提高。)

首页

日常任务

端午活动

功能设计

首页

内容

主要包括用户信息、任务入口、活动入口等展示。

称号规则

称号和糯米粽子数量对应如下:

称号 糯米粽子数量
殿上佳人 <50
淑仪倾城 >=50 && < 100
花容初绽 >=100 && < 200
花成蜜就 >=200 && < 300
宠冠六宫 >=300 && < 400
凤仪千载 >=400

功能实现

首页页面

文件路径:/home/index.jsx

/**
 * @description 首页
 */
import React from 'react';
import { useHistory } from 'react-router-dom';
import Avatar from '@/components/Avatar';
import FlowerCluster from '@/components/FlowerCluster';
import { Button } from 'antd-mobile';
import './index.less';

const Home = () => {
  const history = useHistory();

  // 页面跳转
  const goTo = path => {
    history.push(path);
  };

  // 入口展示
  const entranceContent = () => {
    return (
      <div className='home-entrance'>
        <Button block shape='rounded' className='entrance-btn' onClick={() => goTo('/tasks')}>
          日常任务
        </Button>
        <Button block shape='rounded' className='entrance-btn' onClick={() => goTo('/festival')}>
          端午活动
        </Button>
      </div>
    );
  };
  return (
    <div className='home'>
      <div className='home-head'>
        <Avatar />
      </div>
      <div className='home-center'></div>
      <div className='home-bg'>
        {/* 门 */}
        <div className='door'>
          <div className='door-beam'>
            <div className='tiaoliang'></div>
          </div>
          <div className='door-frame'>
            <div className='door-top'></div>
            <div className='door-line door-line-left'></div>
            <div className='door-line door-line-right'></div>
            <div className='door-line door-line-bottom'></div>
            <div className='door-frame'>
              <div className='stick-h stick-h1'></div>
              <div className='stick-h stick-h2'></div>
              <div className='stick-h stick-h3'></div>
              <div className='stick-h stick-h4'></div>
              <div className='stick-h stick-h5'></div>
              <div className='stick-h stick-h6'></div>
              <div className='stick-h stick-h7'></div>
              <div className='stick-h stick-h8'></div>
              <div className='stick-h stick-h9'></div>
              <div className='stick-h stick-h10'></div>
              <div className='stick-h stick-h11'></div>
              <div className='stick-h stick-h12'></div>
              <div className='stick-d stick-d1'></div>
              <div className='stick-d stick-d2'></div>
              <div className='stick-d stick-d3'></div>
              <div className='stick-d stick-d4'></div>
              <div className='stick-d stick-d5'></div>
              <div className='stick-d stick-d6'></div>
            </div>
            <div className='door-opening'>
              <div className='door-opening-center'>{entranceContent()}</div>
              <div className='door-opening-decorate door-opening-decorate1'></div>
              <div className='door-opening-decorate door-opening-decorate2'></div>
              <div className='door-opening-decorate door-opening-decorate3'></div>
              <div className='door-opening-flowers'>
                <FlowerCluster />
              </div>
            </div>
          </div>
        </div>
        {/* 地板 */}
        <div className='floor'>
          <div className='floor-line floor-line1'></div>
          <div className='floor-line floor-line2'></div>
          <div className='floor-line floor-line3'></div>
          <div className='floor-line floor-line4'></div>
          <div className='floor-line floor-line5'></div>
          <div className='floor-line floor-line6'></div>
          <div className='floor-line floor-line7'></div>
          <div className='floor-line floor-line8'></div>
          <div className='floor-line floor-line9'></div>
          <div className='floor-line floor-line10'></div>
          <div className='home-cat'>
            <div className='body'></div>
            <div className='head'>
              <div className='ear ear-left'></div>
              <div className='ear ear-right'></div>
              <div className='nose'></div>
              <div className='whisker whisker-left'></div>
              <div className='whisker whisker-right'></div>
            </div>
            <div className='tail'>
              <div className='tail-line'></div>
              <div className='tail-round'></div>
              <div className='tail-end'></div>
            </div>
          </div>
          <div className='home-table'></div>
        </div>
      </div>
    </div>
  );
};

export default Home;
复制代码

样式:/home/index.less

.home {
  width: 100%;
  height: 100vh;
  position: relative;
  background: #46272d;
  &-head {
    width: 100%;
    height: 60px;
    background: #f3a29f;
    position: relative;
  }
  &-center {
    width: 200px;
    height: 200px;
    z-index: 99;
    margin-top: 60px;
  }
  &-bg {
    width: 100%;
    position: absolute;
    top: 70px;
    left: 0;
    z-index: 10;
    .door {
      &-beam {
        width: 100%;
        height: 90px;
        border-top:3px solid #9b6d59;
        background: #825146;
        position: relative;
        .tiaoliang {
          width: 100%;
          height: 50px;
          background: #4e2e29;
          background-image: repeating-linear-gradient(45deg, transparent, transparent 13px, #9b6d59 13px, #9b6d59 15px), repeating-linear-gradient(-45deg, transparent, transparent 13px, #9b6d59 13px, #9b6d59 15px);
          border-top: 5px solid #f5a672;
          border-bottom: 5px solid #f5a672;
          position: absolute;
          top: 20px;
          left: 0;
        }
      }
      &-frame {
        width: 100%;
        height: 300px;
        position: relative;
        overflow: hidden;
        .door-top {
          width: 100%;
          height: 30px;
          border-top: 4px solid #f5a672;
          border-bottom: 4px solid #f5a672;
          background: #89544c;
          position: absolute;
          top: 0;
          left: 0;
          z-index: 99;
        }
        .door-line {    
          background: #673a35;
          position: absolute;
          z-index: 89;
          &-left{
            width: 15px;
            height: 100%;
            top: 0;
            left: 0;
            border-right: 2px solid #815345;
          }
          &-right{
            width: 15px;
            height: 100%;
            top: 0;
            right: 0;
            border-left: 2px solid #815345;
          }
          &-bottom{
            width: 100%;
            height: 15px;
            bottom: 0;
            left: 0;
            z-index: 87;
            border-top: 2px solid #815345;
          }
        }
        .door-frame {
          width: 100%;
          height: 100%;
          position: absolute;
          top: 0;
          left: 0;
          .stick-h {
            width: 6px;
            height: 100%;
            background: #774747;
            position: absolute;
            top: 50px;
          }
          .stick-h1 {
            left: 30px;
          }
          .stick-h2 {
            left: 70px;
          }
          .stick-h3 {
            left: 85px;
          }
          .stick-h4 {
            left: 100px;
          }
          .stick-h5 {
            left: 115px;
          }
          .stick-h6 {
            left: 130px;
          }
          .stick-h7 {
            right: 130px;
          }
          .stick-h8 {
            right: 115px;
          }
          .stick-h9 {
            right: 100px;
          }
          .stick-h10 {
            right: 85px;
          }
          .stick-h11 {
            right: 70px;
          }
          .stick-h12 {
            right: 30px;
          }
          .stick-d {
            width: 30px;
            height: 6px;
            background: #774747;
            position: absolute;
          }
          .stick-d1 {
            width: 100%;
            top: 50px;
            left: 0;
          }
          .stick-d2 {
            top: 65px;
            left: 86px;
          }
          .stick-d3 {
            width: 20px;
            top: 80px;
            left: 70px;
          }
          .stick-d4 {
            top: 65px;
            right: 86px;
          }
          .stick-d5 {
            width: 20px;
            top: 80px;
            right: 70px;
          }
          .stick-d6 {
            width: 100%;
            bottom: 30px;
            left: 0;
          }
        }
        .door-opening {
          width: 300px;
          height: 300px;
          border-radius: 50%;
          position: absolute;
          top: 35px;
          left: 50%;
          margin-left: -150px;
          background: #7c5655;
          overflow: hidden;
          &-center{
            width: 250px;
            height: 250px;
            border-radius: 50%;
            position: absolute;
            top: 25px;
            left: 25px;
            background: #fff;
          }
          &-decorate {
            width: 50px;
            height: 80px;
            border-radius: 50%;
            background: #f3c068;
            position: absolute;
          }
          &-decorate1 {
            left: -30px;
            top: 100px;
          }
          &-decorate2 {
            left: 50%;
            top: -43px;
            margin-left: -25px;
            transform: rotate(90deg);
          }
          &-decorate3 {
            right: -30px;
            top: 100px;
          }
          &-flowers {
            position: absolute;
            bottom: 55px;
            right: 43px;
            .flowercluster {
              transform: scale(0.85);
            }
          }
        }
      }
    }
    .floor {
      width: 100%;
      height: 300px;
      position: relative;
      background: #946962;
      overflow: hidden;
      &-line {
        width: 1px;
        height: 100%;
        background: linear-gradient( to bottom, #b48e5e 20%, #eebe88 40%, #fce49c 60%, #9f725a 80%, #f7c887 100%);
        position: absolute;
        top: 0;
      }
      &-line1 {
        left: 0;
        transform: rotate(10deg);
      }
      &-line2 {
        left: 10%;
        transform: rotate(10deg);
      }
      &-line3 {
        left: 23%;
        transform: rotate(5deg);
      }
      &-line4 {
        left: 34%;
        transform: rotate(2deg);
      }
      &-line5 {
        left: 45%;
      }
      &-line6 {
        right: 43%;
        transform: rotate(-2deg);
      }
      &-line7 {
        right: 32%;
        transform: rotate(-5deg);
      }
      &-line8 {
        right: 20%;
        transform: rotate(-8deg);
      }
      &-line9 {
        right: 10%;
        transform: rotate(-10deg);
      }
      &-line10 {
        right: 0;
        transform: rotate(-10deg);
      }
    }
  }
  &-cat {
    width: 200px;
    height: 60px;
    position: absolute;
    top: 95px;
    right: 10px;
    .body {
      width: 110px;
      height: 50px;
      background-color: #745341;
      position: absolute;
      top: -4px;
      border-top-left-radius: 90px;
      border-top-right-radius: 90px;
      animation: catbody 10s none infinite;
    }
    @keyframes catbody {
      5% {
        transform: scaleY(1);
      }
      10% {
        transform: scaleY(1.15);
      }
      15% {
        transform: scaleY(1);
      }
      20% {
        transform: scaleY(1.25);
      }
      25% {
        transform: scaleY(1);
      }
      30% {
        transform: scaleY(1.15);
      }
      40% {
        transform: scaleY(1);
      }
      50% {
        transform: scaleY(1.15);
      }
    }
    .head {
      width: 70px;
      height: 34px;
      background-color: #745341;
      position: absolute;
      top: 13px;
      left: -45px;
      border-top-left-radius: 70px;
      border-top-right-radius: 70px;
    }
    .ear {
      width: 0;
      height: 0;
      position: absolute;
      left: 5px;
      top: -4px;
      border-left: 12px solid transparent;
      border-right: 12px solid transparent;
      border-bottom: 20px solid #745341;
      transform: rotate(-30deg);
      animation: catearleft 10s both infinite;
    }
    .ear-right {
      top: -11px;
      left: 21px;
      animation: catearright 10s both infinite;
    }
    @keyframes catearleft {
      0% {
        transform: rotate(-20deg);
      }
      5% {
        transform: rotate(-5deg);
      }
      15% {
        transform: rotate(-15deg);
      }
      25% {
        transform: rotate(-15deg);
      }
      35% {
        transform: rotate(-30deg);
      }
      40% {
        transform: rotate(-30deg);
      }
      45% {
        transform: rotate(0deg);
      }
      50% {
        transform: rotate(0deg);
      }
      80% {
        transform: rotate(-15deg);
      }
      90% {
        transform: rotate(-5deg);
      }
      100% {
        transform: rotateZ(-5deg);
      }
    }
    @keyframes catearright {
      0% {
        transform: rotateZ(-15deg);
      }
      15% {
        transform: rotateZ(-20deg);
      }
      25% {
        transform: rotateZ(-20deg);
      }
      30% {
        transform: rotateZ(-30deg);
      }
      34% {
        transform: rotateZ(-20deg);
      }
      38% {
        transform: rotateZ(-30deg);
      }
      40% {
        transform: rotateZ(-20deg);
      }
      42% {
        transform: rotateZ(-20deg);
      }
      44% {
        transform: rotateZ(-30deg);
      }
      45% {
        transform: rotateZ(-20deg);
      }
      50% {
        transform: rotateZ(-10deg);
      }
      55% {
        transform: rotateZ(-10deg);
      }
      60% {
        transform: rotateZ(-20deg);
      }
      61% {
        transform: rotateZ(-30deg);
      }
      62% {
        transform: rotateZ(-20deg);
      }
      63% {
        transform: rotateZ(-20deg);
      }
      64% {
        transform: rotateZ(-30deg);
      }
      65% {
        transform: rotateZ(-20deg);
      }
      80% {
        transform: rotateZ(-20deg);
      }
      90% {
        transform: rotateZ(-15deg);
      }
      100% {
        transform: rotateZ(-15deg);
      }
    }
    .nose {
      width: 5px;
      height: 5px;
      background-color: #dc9d90;
      position: absolute;
      bottom: 10px;
      left: 30px;
      border-radius: 50%;
    }
    .whisker {
      width: 16px;
      height: 10px;
      position: absolute;
      bottom: 5px;
      left: 7px;
      transform-origin: right;
    }
    .whisker::before,
    .whisker::after {
      content: '';
      width: 100%;
      position: absolute;
      top: 0;
      border: 1px solid #fff;
      transform-origin: 100% 0;
      transform: rotate(10deg);
    }
    .whisker::after {
      transform: rotate(-20deg);
    }
    .whisker-left {
      animation: catwhiskerleft 10s both infinite;
    }
    .whisker-right {
      left: 27px;
      bottom: 12px;
      transform: rotate(180deg);
      animation: catwhiskerright 10s both infinite;
    }
    @keyframes catwhiskerleft {
      5% {
        transform: rotate(0);
      }
      10% {
        transform: rotate(0deg);
      }
      15% {
        transform: rotate(-5deg);
      }
      20% {
        transform: rotate(0deg);
      }
      25% {
        transform: rotate(0deg);
      }
      30% {
        transform: rotate(10deg);
      }
      40% {
        transform: rotate(-5deg);
      }
      50% {
        transform: rotate(10deg);
      }
    }
    @keyframes catwhiskerright {
      5% {
        transform: rotate(180deg);
      }
      10% {
        transform: rotate(190deg);
      }
      15% {
        transform: rotate(180deg);
      }
      20% {
        transform: rotate(175deg);
      }
      25% {
        transform: rotate(190deg);
      }
      30% {
        transform: rotate(180deg);
      }
      40% {
        transform: rotate(185deg);
      }
      50% {
        transform: rotate(175deg);
      }
    }
    .tail {
      width: 14px;
      height: 100px;
      position: absolute;
      top: 42px;
      right: 90px;
      z-index: 99;
    }
    .tail-line {
      width: 14px;
      height: 60px;
      background: #745341;
      position: absolute;
      left: 0;
      top: 0;
      z-index: 99;
    }
    .tail-round {
      width: 48px;
      height: 48px;
      background: #745341;
      position: absolute;
      top: 36px;
      left: -34px;
      border-radius: 50%;
    }
    .tail-round::before {
      content: '';
      width: 20px;
      height: 20px;
      background: #946962;
      position: absolute;
      top: 14px;
      left: 14px;
      border-radius: 50%;
    }
    .tail-round::after {
      content: '';
      width: 48px;
      height: 22px;
      background: #946962;
      position: absolute;
      top: 0;
      left: 0;
    }
    .tail-end {
      width: 14px;
      height: 10px;
      background: #745341;
      border-radius: 14px 14px 0 0;
      position: absolute;
      bottom: 39px;
      left: -34px;
      z-index: 99;
    }
  }
  &-table {
    width: 200px;
    height: 20px;
    background-color: #e3895e;
    position: absolute;
    top: 140px;
    right: 80px;
    border-radius: 20px;
    z-index: 9;
  }
  &-entrance {
    position: absolute;
    top: 60px;
    left: 35px;
    .entrance-btn {
      width: 180px;
      line-height: 28px;
      font-size: 16px;
      font-weight: 600;
      color: #fff;
      border: 0;
      background-image: linear-gradient(to right, #ed6ea0, #ec8c69, #f7186a, #FBB03B);
      background-size: 300% 100%;
      box-shadow: 0 4px 15px 0 #ed6ea0;
      margin-bottom: 20px;
      animation: 5s ease-in-out entrance infinite;
    }
  }
}
@keyframes entrance {
  0% {
    background-image: linear-gradient(to right, #ed6ea0, #ec8c69, #f7186a, #FBB03B);
    background-size: 300% 100%;
  }
  100% {
    background-image: linear-gradient(to right, #FBB03B, #ec8c69, #f7186a, #ed6ea0);
    background-position: 100% 0;
  }
}
复制代码

头像组件

文件路径:/components/Avatar/index.jsx

/**
 * @description 头像组件
 */
import React from 'react';
import './index.less';
import util from '../../utils/util';

const Avatar = () => {
  const userInfo = util.getUserInfo() || {};

  const getDesignationByZongziNum = () => {
    const festival = userInfo.festival ? userInfo.festival : {};
    const zongzi = festival.zongzi ? festival.zongzi : 0;
    let name = '殿上佳人';
    if (zongzi < 50) {
      name = '殿上佳人';
    } else if (zongzi <= 100) {
      name = '淑仪倾城';
    } else if (zongzi <= 200) {
      name = '花容初绽';
    } else if (zongzi <= 300) {
      name = '花成蜜就';
    } else if (zongzi <= 400) {
      name = '宠冠六宫';
    } else if (zongzi > 400) {
      name = '凤仪千载';
    }
    return name;
  };

  return (
    <div className='avatar'>
      <img className='avatar-img' src='https://p6-passport.byteacctimg.com/img/user-avatar/c6c1a335a3b48adc43e011dd21bfdc60~300x300.image' alt='' />
      <div className='avatar-nickname'>叶一一</div>
      <div className='avatar-designation'>
        <span>{getDesignationByZongziNum()}</span>
        <div className='avatar-flower'>
          <div className='avatar-flower-leaf avatar-flower-leaf1'></div>
          <div className='avatar-flower-leaf avatar-flower-leaf2'></div>
          <div className='avatar-flower-leaf avatar-flower-leaf3'></div>
          <div className='avatar-flower-leaf avatar-flower-leaf4'></div>
          <div className='avatar-flower-leaf avatar-flower-leaf5'></div>
          <div className='avatar-flower-circle'></div>
        </div>
      </div>
    </div>
  );
};

export default Avatar;
复制代码

样式:/components/Avatar/index.less

.avatar {
  width: 100%;
  height: 60px;
  position: relative;
  &-img {
    width: 70px;
    height: 70px;
    border-radius: 50%;
    z-index: 99;
    position: absolute;
    left: 10px;
    bottom: -20px;
    border: 3px solid #c03e34;
  }
  &-nickname {
    height: 24px;
    line-height: 24px;
    background: #ff8fa7;
    border-radius: 24px;
    position: absolute;
    left: 60px;
    top: 35px;
    color: #fff;
    font-size: 14px;
    font-weight: 300;
    text-align: center;
    z-index: 89;
    border:1px solid #fff;
    padding: 0 10px 0 25px;
  }
  &-designation {
    width: 110px;
    height: 32px;
    line-height: 32px;
    position: absolute;
    right: 0;
    top: 15px;
    z-index: 89;
    text-align: center;
    padding-left: 10px;
    border-radius: 28px 0 0 0;
    background-color: #f0ecfc;
    background-image: linear-gradient(315deg,#ffeded 0,#fed6d6 74%);
    span {
      font-size: 18px;
      color: #fff;
      text-shadow:  1px 1px #ffb53a,-1px -1px #ffb53a,1px -1px #ffb53a,-1px 1px #ffb53a;
    }
    
  }
  &-flower {
    position: absolute;
    top: 5px;
    left: 10px;
    transform: rotate(-30deg) scale(0.8);
    &-leaf {
      position: absolute;
      border-radius: 51% 49% 47% 53%;
      background-color: #a7ffee;
      background-image: linear-gradient(to top, #ffeded 15%, #ff8fa7 100%);
      transform-origin: bottom center;
      opacity: 0.9;
      box-shadow: inset 0 0 6px #fed6d6;
    }
    &-leaf1 {
      width: 28px;
      height: 34px;
      bottom: -10px;
      left: -14px;
      transform: translate(-10%, 1%) rotateY(40deg) rotateX(-50deg);
    }
    &-leaf2 {
      width: 23px;
      height: 32px;
      bottom: -4px;
      left: -5px;
      transform: translate(-50%, -4%) rotateX(40deg);
    }
    &-leaf3 {
      width: 28px;
      height: 30px;
      bottom: -3px;
      left: 0px;
      transform: translate(-90%, 0%) rotateY(45deg) rotateX(50deg);
    }
    &-leaf4 {
      width: 28px;
      height: 24px;
      bottom: -5px;
      left: 6px;
      transform: translate(-61%, -19%) rotateX(67deg) rotate(193deg);
    }
    &-leaf5 {
      width: 28px;
      height: 25px;
      bottom: -5px;
      left: -4px;
      transform: translate(-55%, -20%) rotateX(71deg) rotate(211deg);
    }
    &-circle {
      position: absolute;
      left: -12px;
      top: -10px;
      width: 16px;
      height: 8px;
      border-radius: 50%;
      background-color: #fdfd8e;
    }
  }
}
复制代码

花丛组件

这个是参考的网站是的,参考地址我放到了文章末尾。

文件路径:/components/FlowerCluster/index.jsx

/**
 * @description 花丛组件
 */
import React from 'react';
import './index.less';

const FlowerCluster = () => {
  return (
    <div className='flowercluster'>
      <div className='flower-leaves'></div>
      <div className='bunch'>
        <div className='flower'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='flower'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='flower'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
      </div>
    </div>
  );
};
export default FlowerCluster;
复制代码

样式:/components/FlowerCluster/index.less

.flowercluster {
  width: 60px;
  height: 60px;
  .flowers:after {
    content: '';
    position: absolute;
    width: 60px;
    height: 35px;
    background-color: rgba(0, 0, 0, 0.1);
    bottom: 0;
    z-index: -2;
    border-radius: 100%;
    left: -10px;
    bottom: -15px;
  }
  .flower-leaves {
    position: relative;
    width: 100%;
    height: 20px;
    background-color: #a8e6ba;
    border-radius: 100% 10%;
    top: 80%;
    left: 5px;
    box-shadow: -1px 1px black, 1px 1px black, 1px -1px black;
  }
  .flower-leaves:before,
  .flower-leaves:after {
    content: '';
    position: absolute;
    background-color: #a8e6ba;
  }
  .flower-leaves:before {
    width: 60px;
    height: 20px;
    border-radius: 100% 10%;
    transform: rotate(30deg);
    right: 10px;
    box-shadow: -1px 1px black, 1px 1px black, 1px -1px black;
  }
  .flower-leaves:after {
    width: 50px;
    height: 20px;
    border-radius: 100% 10%;
    transform: rotate(15deg);
    top: 2px;
  }
  .flower {
    position: absolute;
    width: 30px;
    height: 30px;
  }
  .flower:after {
    content: '';
    position: absolute;
    width: 8px;
    height: 8px;
    border-radius: 100%;
    left: 6px;
    top: 8px;
    background-image: radial-gradient(8px 8px at center, #9379aa 30%, #521c81 41%, 60%, transparent);
  }
  .flower > .petal {
    position: absolute;
    width: 10px;
    height: 10px;
    background-color: #f8f8ff;
    background-image: linear-gradient(45deg, #f8f8ff, #d3cce3);
    border-radius: 50% 80%;
    box-shadow: -0.04em -0.04em purple, -0.05em -0.05em black;
  }
  .flower > .petal:nth-child(1) {
    transform: rotate(40deg);
    left: 5px;
    top: 2px;
  }
  .flower > .petal:nth-child(2) {
    transform: rotate(-20deg);
    top: 6px;
    left: 0;
  }
  .flower > .petal:nth-child(3) {
    transform: rotate(-90deg);
    top: 12px;
    left: 2px;
  }
  .flower > .petal:nth-child(4) {
    transform: rotate(180deg);
    top: 12px;
    left: 10px;
  }
  .flower > .petal:nth-child(5) {
    transform: rotate(100deg);
    top: 5px;
    left: 10px;
  }
  .bunch .flower:nth-child(1) {
    left: 33px;
    transform: scale(1.5) rotate(30deg);
  }
  .bunch .flower:nth-child(2) {
    left: 10px;
    transform: scale(1.5) rotate(-20deg);
  }
  .bunch .flower:nth-child(3) {
    left: 25px;
    top: 40px;
    transform: scale(1.5) rotate(5deg);
  }
}
复制代码

最终UI

设计为古代的室内,参考的《美人传》小游戏中的UI设计,包括木质的墙壁、门和地板。除此之外还加了一些动画效果增加趣味性:

  • 称号上面加了一个花朵做装饰;
  • 任务和活动入口上加了光效闪动的效果;
  • 地板上的猫咪耳朵和肚子随着呼吸而动;

日常任务

日常任务收集规则

  • 每天0点开始进行资源生产,每个小时生产1万资源,不足1个小时的时候不产生,满足1个小时的时候产生;
  • 可以进行资源收集,每次收集完成,对应的资源值进行叠加;
  • 不同资源收集时,随机掉落不同的活动材料。对应如下:
任务名称 活动材料名称 活动材料数量
开源节流 粽叶 5~10
助宫易物 糯米 5~10
布施济民 红枣 2~5

功能实现

日常页面

文件路径:/tasks/index.jsx

/**
 * @description 日常任务
 */
import React, { useState, useEffect } from 'react';
import classnames from 'classnames';
import moment from 'moment';
import Back from '@/components/Back';
import Flower from '@/components/Flower';
import FlowerTree from '@/components/FlowerTree';
import { Modal } from 'antd-mobile';
import { QuestionCircleFill, KoubeiFill, FireFill, HeartFill } from 'antd-mobile-icons';
import util from '../../utils/util';
import './index.less';

const Tasks = () => {
  const userInfo = util.getUserInfo() || {};
  const [tasksObj, setTasksObj] = useState(
    userInfo.tasks
      ? userInfo.tasks
      : {
          zheng: 0,
          cai: 0,
          mei: 0,
          creatAt: 0,
        },
  );
  const listInit = [
    {
      key: 'zheng',
      title: '政',
      name: '开源节流',
      num: 0,
      harvestFalg: true,
      taskKey: 'zongye',
      icon: <KoubeiFill fontSize={16} color='#fcb887' />,
    },
    {
      key: 'cai',
      title: '才',
      name: '助宫易物',
      num: 0,
      harvestFalg: true,
      taskKey: 'nuomi',
      icon: <FireFill fontSize={16} color='#f6f6f6' />,
    },
    {
      key: 'mei',
      title: '魅',
      name: '布施济民',
      num: 0,
      harvestFalg: true,
      taskKey: 'hongzao',
      icon: <HeartFill fontSize={16} color='#59ca94' />,
    },
  ];

  const [list, setList] = useState(listInit);

  // 获取当前内务展示数据
  const getNewNum = () => {
    // 梯龄换算成月
    const newData = new Date();
    let diffData = tasksObj.creatAt;
    if (!tasksObj.creatAt) {
      // 如果收获时间默认活动开始时间
      diffData = moment('2022-06-01');
    }
    let hour = moment(newData).diff(moment(diffData), 'hours');

    console.log(hour, 'hour');
    let numCurr = hour * 1000;
    const listInit = [...list];
    listInit.map(item => {
      item.num += numCurr;
    });
    setList(listInit);
  };

  useEffect(() => {
    getNewNum();
  }, []);

  // 获取随机数
  const getRandomNumber = key => {
    const randomObj = {
      zheng: [5, 10],
      cai: [5, 10],
      mei: [2, 5],
    };
    const randomItem = randomObj[key];
    const m = randomItem[1];
    const n = randomItem[0];
    let randomNum = Math.random() * (m - n) + n;
    randomNum = Math.round(randomNum);
    console.log(randomNum, 'randomNum');
    return randomNum;
  };

  // 收获
  const handleHarvest = index => {
    const newData = new Date();
    let userInfoInit = { ...userInfo };
    const handleList = [].concat(list);
    let item = handleList[index];
    let tasksObjInit = { ...tasksObj };
    tasksObjInit.creatAt = newData;
    const festivalObjInit = userInfo.festival
      ? userInfo.festival
      : {
          nuomi: 0,
          zongye: 0,
          hongzao: 0,
          zongzi: 0,
        };
    // 收获操作
    if (item.harvestFalg) {
      tasksObjInit[item.key] += item.num;
      item.num = 0;
      festivalObjInit[item.taskKey] = getRandomNumber(item.key);
      // 设置缓存
      userInfoInit.festival = festivalObjInit;
      userInfoInit.tasks = tasksObjInit;
      util.saveUserInfo(userInfoInit);
      setList(list);
      setTasksObj(tasksObjInit);
    }
    item.harvestFalg = !item.harvestFalg;
    setList(handleList);
  };

  // 顶部提示
  const headTip = () => {
    return Modal.show({
      title: '内务',
      content: (
        <div className='tasks-modal'>
          <div className='tasks-modal-title'>内务打理</div>
          <div className='tasks-modal-content mb10'>
            <p className='mb10'>内务分为“开源节流”,“助宫易物”,“布施济民”三种类型,分别可以获得铜币、珍品和名望。</p>
            <p>打理内务有一定几率获得包粽子的材料。</p>
          </div>
          <div className='tasks-modal-title'>内务奖励</div>
          <div className='tasks-modal-content'>
            <p className='mb10'>开源节流有一定几率获得粽叶。</p>
            <p className='mb10'>助宫易物有一定几率获得糯米。</p>
            <p>布施济民有一定几率获得红枣。</p>
          </div>
        </div>
      ),
      showCloseButton: true,
    });
  };

  // 将数据除以10000进行展示
  const getTaskNumContent = num => {
    num = num / 10000;
    return num;
  };

  return (
    <div className='tasks'>
      <Back />
      <div className='tasks-info'>
        {list.map(item => {
          return (
            <div className='tasks-info-item' key={item.key}>
              <div className='tasks-info-item-icon'>{item.icon}</div>
              <span>
                {getTaskNumContent(tasksObj[item.key])} {tasksObj[item.key] > 0 ? '万' : ''}
              </span>
            </div>
          );
        })}
      </div>
      <div className='tasks-head'>
        <div className='tasks-head-tip' onClick={headTip}>
          <QuestionCircleFill fontSize={28} color='#f69bad' />
        </div>
        <div className='tasks-head-title'>内务打理</div>
      </div>
      <div className='tasks-list'>
        {list.map((item, index) => {
          return (
            <div className='tasks-item' key={item.key}>
              <div className='tasks-item-top'></div>
              <div className='tasks-item-title'>{item.title}</div>
              <div className='tasks-item-name'>
                <span>{item.name}</span>
                <div className='name-circular name-circular1'></div>
                <div className='name-circular name-circular2'></div>
                <div className='name-circular name-circular3'></div>
                <div className='name-circular name-circular4'></div>
                <div className='name-circular name-circular5'></div>
                <div className='name-circular name-circular6'></div>
              </div>
              <div className='tasks-item-num'>{item.num}</div>
              <div className={classnames('tasks-item-btn', { inactive: !item.harvestFalg })} onClick={() => handleHarvest(index)}>
                <div className='btn-flower1'>
                  <Flower />
                </div>
                <div className='btn-flower2'>
                  <Flower />
                </div>
                <span>{item.harvestFalg ? '收获' : '恢复'}</span>
              </div>
            </div>
          );
        })}
      </div>
      <div className='tasks-footer'></div>
      <div className='tasks-tree'>
        <FlowerTree />
      </div>
      <div className='tasks-rule'>
        <div className='tasks-rule-title'>
          <span>宫规</span>
        </div>
        <div className='tasks-rule-text'>内务收获 +5%</div>
      </div>
    </div>
  );
};

export default Tasks;
复制代码

样式:/tasks/index.less

.tasks {
  width: 100%;
  max-width: 100%;
  height: 100vh;
  background: #ffe7e7;
  padding-top: 36px;
  position: relative;
  overflow: hidden;
  &-info {
    width: 70%;
    position: absolute;
    top: 15px;
    right: 5px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    &-item {
      width: 28%;
      height: 17px;
      border-radius: 0 20px 20px 0; 
      background: #a5888c;
      position: relative;
      span {
        font-size: 12px;
        color: #fff;
        line-height: 17px;
        text-align: center;
        position: absolute;
        left: 10px;
        top: 0;
        z-index: 99;
      }
      &-icon {
        position: absolute;
        top: -3px;
        left: -16px;
        width: 22px;
        height: 22px;
        border-radius: 50%;
        background: #a5888c;
        z-index: 89;
        display: flex;
        justify-content: center;
        align-items: center;
      }
    }
  }
  &-head {
    width: 100%;
    height: 50px;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    &-tip {
      margin-left: 80px;
      margin-right: 40px;
    }
    &-title {
      color: #a08cc9;
      line-height: 50px;
      font-size: 26px;
      text-align: center;
      font-weight: 500;
    }
  }
  &-modal {
    width: 100%;
    padding: 0 5px;
    &-title {
      position: relative;
      height: 22px;
      line-height: 22px;
      text-align: center;
      margin-bottom: 10px;
      color: #af8368;
      &::before {
        content: '';
        width: 40px;
        height: 2px;
        background: #eec2c1;
        position: absolute;
        top: 10px;
        left: 15px;
        border-radius: 2px;
      }
      &::after {
        content: '';
        width: 40px;
        height: 2px;
        background: #eec2c1;
        position: absolute;
        top: 10px;
        right: 15px;
        border-radius: 2px;
      }
    }
    p {
      line-height: 1.4;
      font-weight: 300;
      font-size: 13px;
      position: relative;
      padding-left: 10px;
      &::before {
        content: '';
        width: 4px;
        height: 4px;
        background: #af8368;
        border-radius: 50%;
        position: absolute;
        top: 6px;
        left: -2px;

      }
    }
  }
  &-list {
    display: flex;
    justify-content:space-between;
    padding: 0 15px;
    margin-top: 25px;
    z-index: 20;
  }
  &-item {
    width: 30%;
    height: 340px;
    border: 5px solid #ffb4c0;
    border-radius: 0 0 30px 30px;
    position: relative;
    &:nth-child(2) {
      .tasks-item-title {
        background: #8fc4f6;
      }
    }
    &:nth-child(3) {
      .tasks-item-title {
        background: #da9ce9;
      }
    }
    &-top {
      position: absolute;
      left: -13px;
      top: -11px;
      height: 10px;
      width: 124%;
      background: #fca0ab;
      border: 1px solid #f5d896;
      border-radius: 10px;
    }
    &-title {
      position: absolute;
      left: 2px;
      top: 2px;
      width: 42px;
      height: 42px;
      border: 2px solid #fff;
      background: #fcaf5d;
      color: #fff;
      font-size: 20px;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
    }
    &-name {
      position: absolute;
      top: 60px;
      left: 7px;
      width: 80px;
      height: 80px;
      transform: scale(0.85);
      span {
        display: block;
        width: 40px;
        line-height: 15px;
        font-size: 15px;
        color: #b67b53;
        position: absolute;
        top: 43%;
        left: 34%;
        z-index: 99;
      }
      .name-circular {
        width: 36px;
        height: 36px;
        background: #ffb4c0;
        border-radius: 50%;
        position: absolute;
      }
      .name-circular1  {
        top: 3px;
        left: 20px;
      }
      .name-circular2  {
        top: 20px;
        left: 46px;
      }
      .name-circular3  {
        top: 49px;
        left: 38px;
      }
      .name-circular4  {
        top: 51px;
        left: 11px;
      }
      .name-circular5  {
        top: 25px;
        left: -1px;
      }
      .name-circular6  {
        width: 45px;
        height: 45px;
        top: 26px;
        left: 20px;
        background: #ffe7e7;
      }
    }
    &-num {
      position: absolute;
      bottom: 120px;
      left: 5px;
      width: 90%;
      height: 22px;
      line-height: 22px;
      border: 1px solid #f6e2db;
      background: #fff;
      color: #89775f;
      font-size: 14px;
      font-weight: 300;
      border-radius: 22px;
      text-align: center;
    }
    &-btn {
      position: absolute;
      bottom: 30px;
      left: 15px;
      width: 64px;
      height: 64px;
      line-height: 64px;
      border: 1px solid #fed18d;
      background: #fff6d6;
      border-radius: 50%;
      text-align: center;
      &::after {
        content: '';
        width: 56px;
        height: 56px;
        background: #fed18d;
        border-radius: 50%;
        position: absolute;
        top: 3px;
        left: 3px;
        z-index: 89;
      }
      span {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        color: #fff;
        font-size: 16px;
        z-index: 99;
      }
      .btn-flower1 {
        position: absolute;
        top: -6px;
        left: 36px;
        z-index: 990;
        transform: scale(0.9);
      }
      .btn-flower2 {
        position: absolute;
        top: -2px;
        left: 40px;
        z-index: 990;
        transform: scale(0.6);
      }
      &.inactive {
        border: 1px solid #e5c7fd;
        &::after {
          background: #e5c7fd;
        }
      }
    }
    &::before {
      content: '';
      width: 60px;
      height: 45px;
      background: #ffe7e7;
      position: absolute;
      bottom: -49px;
      left: 20px;
      z-index: 99;
      border-radius: 0;
    }
    &::after {
      content: '';
      width: 60px;
      height: 60px;
      background: #ffe7e7;
      border: 5px solid #ffb4c0;
      border-radius: 50%;
      position: absolute;
      bottom: -44px;
      left: 20px;
      z-index: 98;
    }
  }
  &-footer {
    position: absolute;
    top: 480px;
    left: -10%;
    background: #ffc3d2;
    width: 120%;
    height: 100px;
    border-radius: 0 0 50% 50%;
    z-index: 1;
    &::before {
      position: absolute;
      bottom: 10px;
      left: 10px;
      background: #ffc3d2;
      width: 200px;
      height: 200px;
    }
    &::after {
      content: '';
      position: absolute;
      bottom: 20px;
      left: -10%;
      background: #ffe7e7;
      width: 120%;
      height: 100px;
      border-radius: 0 0 50% 50%;
    }
  }
  &-tree {
    position: absolute;
    top: 88%;
    left: 60px;
    z-index: 999;
  }
  &-rule {
    width: 200px;
    position: absolute;
    top: 88%;
    right: 10px;
    &-title {
      width: 70px;
      height: 70px;
      line-height: 70px;
      position: absolute;
      top: 0;
      left: 0;
      background: #fff;
      border: 2px solid #f8d4d6;
      border-radius: 50%;
      text-align: center;
      z-index: 90;
      span {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        color: #fff;
        font-size: 18px;
        z-index: 99;
      }
      &::after {
        content: '';
        width: 60px;
        height: 60px;
        background: #deb4fc;
        border-radius: 50%;
        position: absolute;
        top: 3px;
        left: 3px;
        z-index: 89;
      }
    }
    &-text {
      width: 140px;
      line-height: 36px;
      font-size: 15px;
      font-weight: 300;
      color: #e34f4b;
      text-align: center;
      position: absolute;
      left: 56px;
      top: 16px;
      border-radius: 0 36px 36px 0;
      background: #fbf1ef;
      z-index: 80;
    }
  }
}
复制代码

返回组件

文件路径:/components/Back/index.jsx

/**
 * @description 回退按钮组件
 */
import React from 'react';
import PropTypes from 'prop-types';
import { useHistory } from 'react-router-dom';
import './index.less';

const Back = ({ ...props }) => {
  const history = useHistory();
  const { path } = props;

  // 点击事件
  const handleClick = () => {
    history.push(path);
  };

  return (
    <div className='back' onClick={handleClick}>
      <div className='back-left'></div>
      <div className='back-right'></div>
    </div>
  );
};
Back.propTypes = {
  path: PropTypes.string, // 跳转路径
};

Back.defaultProps = {
  path: '/home',
};

export default Back;
复制代码

样式:/components/Back/index.less

.back {
  width: 56px;
  height: 56px;
  background: #f69bad;
  border: 2px solid #fef4f3;
  border-radius: 50%;
  position: absolute;
  top: 10px;
  left: 10px;
  &-left {
    position: absolute;
    top: 21px;
    left: 10px;
    &::before {
      content: '';
      width: 22px;
      height: 3px;
      background: #fff;
      position: absolute;
      top: 10px;
      left: 0;
      transform: rotate(30deg);
      border-radius: 2px;
    }
    &::after {
      content: '';
      width: 22px;
      height: 3px;
      background: #fff;
      position: absolute;
      top: -1px;
      left: 0;
      transform: rotate(-30deg);
      border-radius: 2px;
    }
  }
  &-right {
    position: absolute;
    top: 27px;
    left: 27px;
    &::before {
      content: '';
      position: absolute;
      bottom: 0;
      left: 0;
      width: 0;
      height: 0;
      border-bottom: 4px solid #fff;
      border-right: 8px solid transparent;
      border-left: 8px solid transparent;
    }
    &::after {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 0;
      height: 0;
      border-top: 4px solid #fff;
      border-right: 8px solid transparent;
      border-left: 8px solid transparent;
    }
  }
}
复制代码

花朵组件

文件路径:/components/Flower/index.jsx

/**
 * @description 花朵组件
 */
import React from 'react';
import './index.less';

const Flower = () => {
  return (
    <div className='flower'>
      <div className='flower-leaf flower-leaf1'></div>
      <div className='flower-leaf flower-leaf2'></div>
      <div className='flower-leaf flower-leaf3'></div>
      <div className='flower-leaf flower-leaf4'></div>
      <div className='flower-leaf flower-leaf5'></div>
      <div className='flower-circle'></div>
    </div>
  );
};
export default Flower;
复制代码

样式:/components/Flower/index.less

.flower {
  width: 50px;
  height: 50px;
  transform: rotate(-3deg);
  &-leaf {
    position: absolute;
    width: 6px;
    height: 8px;
    border-radius: 51% 49% 47% 53%;
    background-color: #a7ffee;
    background-image: linear-gradient(to top, #ffeded 15%, #ff8fa7 100%);
  }
  &-leaf1 {
    top: 2px;
    left: 2px;
    transform: rotate(-3deg);
    z-index: 999;
  }
  &-leaf2 {
    top: 5px;
    left: 7px;
    transform: rotate(60deg);
    z-index: 998;
  }
  &-leaf3 {
    top: 10px;
    left: 6px;
    transform: rotate(160deg);
    z-index: 997;
  }
  &-leaf4 {
    top: 11px;
    left: 0px;
    transform: rotate(200deg);
    z-index: 996;
  }
  &-leaf5 {
    top: 6px;
    left: -2px;
    transform: rotate(-75deg);
    z-index: 995;
  }
  &-circle {
    position: absolute;
    left: 3px;
    top: 8px;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background-color: #fff3b4;
    border: 1px solid #fff;
  }
}
复制代码

开满花的树组件

这个是参考的网站是的,参考地址我放到了文章末尾。

文件路径:/components/FlowerTree/index.jsx

/**
 * @description 开满花的树组件
 */
import React from 'react';
import './index.less';

const FlowerTree = () => {
  return (
    <div className='flowertree'>
      <div className='trunk'>
        <div className='roots'>
          <div className='root'></div>
          <div className='root'></div>
          <div className='root'></div>
          <div className='root'></div>
          <div className='root'></div>
        </div>
      </div>
      <div className='leaves cherry-blossoms'>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
        <div className='cherry-blossom'>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
          <div className='petal'></div>
        </div>
      </div>
    </div>
  );
};
export default FlowerTree;
复制代码

样式:/components/FlowerTree/index.less

.flowertree {
  width: 60px;
  height: 200px;
  transform:  scale(1.1);
  .trunk {
    width: 58%;
    height: 30%;
    background-color: #df916a;
    border-left: 0.09em solid black;
    border-right: 0.09em solid black;
    box-shadow: inset 0 30px 10px #a64f24;
  }
  .trunk:after {
    content: '';
    position: absolute;
    width: 60px;
    height: 28px;
    background-color: rgba(0, 0, 0, 0.2);
    top: 40px;
    z-index: -2;
    right: 15px;
    border-radius: 100%;
  }
  .trunk .roots {
    position: relative;
    z-index: -1;
    top: 95%;
    display: flex;
    justify-content: center;
    align-items: center;
    width: 110%;
    height: auto;
    left: -5%;
  }
  .trunk .roots .root {
    flex: 1 1 0;
    background-color: #dd8b62;
    height: 15px;
    margin: 0 -1px;
    border-radius: 10px;
  }
  .trunk .roots .root:nth-child(1) {
    transform: rotate(30deg);
  }
  .trunk .roots .root:nth-child(2) {
    transform: rotate(15deg);
  }
  .trunk .roots .root:nth-child(3) {
    transform: rotate(0deg);
  }
  .trunk .roots .root:nth-child(4) {
    transform: rotate(-15deg);
  }
  .trunk .roots .root:nth-child(5) {
    transform: rotate(-30deg);
  }
  .trunk .roots .root:first-child {
    box-shadow: -1px 1px #d0632d, -0.1em 1px black;
  }
  .trunk .roots .root:nth-child(n + 2):nth-child(-n + 4) {
    box-shadow: 0 1px #d0632d, 0em 1px black;
  }
  .trunk .roots .root:last-child {
    box-shadow: 1px 1px #d0632d, 0.1em 1px black;
  }
  .leaves,
  .cherry-blossoms {
    position: relative;
    width: 60px;
    height: 60px;
    top: -120px;
    left: -12px;
    background-color: #4cbda4;
    border-radius: 100%;
    box-shadow: inset 4px -10px #41af97;
    border: 1px solid #3a9c87;
    border: 0.1em solid black;
  }
  .leaves:before,
  .cherry-blossoms:before,
  .leaves:after,
  .cherry-blossoms:after {
    content: '';
    position: absolute;
    width: 60px;
    height: 60px;
    background-color: #4cbda4;
    border-radius: 100%;
    top: 35px;
    border: 1px solid #3a9c87;
    border: 0.1em solid black;
    border-top: 0;
    border-bottom: 2px solid #91451f;
    box-shadow: inset 4px -10px #41af97;
  }
  .leaves:before,
  .cherry-blossoms:before {
    left: -20px;
  }
  .leaves:after,
  .cherry-blossoms:after {
    left: 20px;
  }
  .leaves .leaf,
  .cherry-blossoms .leaf {
    position: absolute;
    width: 10px;
    height: 15px;
    background-color: #4cbda4;
    background-image: linear-gradient(to bottom, #097465, transparent);
    border-radius: 10% 80%;
    border-bottom: 1px solid #2c7766;
    border-right: 1px solid #2c7766;
    transform: scale(0.5);
  }
  .leaves .leaf:before,
  .cherry-blossoms .leaf:before,
  .leaves .leaf:after,
  .cherry-blossoms .leaf:after {
    content: '';
    position: absolute;
    background-color: #4cbda4;
    background-image: linear-gradient(to bottom, #008b79, transparent);
    width: 7px;
    height: 12px;
    border-radius: 10% 80%;
    border-right: 1px solid #338a76;
  }
  .leaves .leaf:before,
  .cherry-blossoms .leaf:before {
    left: -3px;
    transform: rotate(40deg);
    border-left: 1px solid #338a76;
  }
  .leaves .leaf:after,
  .cherry-blossoms .leaf:after {
    left: 4px;
    top: -2px;
    transform: rotate(-40deg);
    border-bottom: 1px solid #338a76;
  }
  .leaves .leaf:nth-child(1),
  .cherry-blossoms .leaf:nth-child(1) {
    top: 85px;
    transform: scale(0.8) rotate(-25deg);
  }
  .leaves .leaf:nth-child(2),
  .cherry-blossoms .leaf:nth-child(2) {
    top: 80px;
    left: -15px;
  }
  .leaves .leaf:nth-child(3),
  .cherry-blossoms .leaf:nth-child(3) {
    top: 85px;
    left: 15px;
    transform: scale(1.1) rotate(-25deg);
  }
  .leaves .leaf:nth-child(4),
  .cherry-blossoms .leaf:nth-child(4) {
    top: 80px;
    left: 25px;
    z-index: 3;
  }
  .leaves .leaf:nth-child(5),
  .cherry-blossoms .leaf:nth-child(5) {
    top: 85px;
    left: 60px;
    z-index: 2;
    transform: scale(0.9) rotate(70deg);
  }
  .leaves .leaf:nth-child(6),
  .cherry-blossoms .leaf:nth-child(6) {
    top: 80px;
    left: 45px;
    z-index: 2;
    transform: scale(1.2) rotate(50deg);
  }
  .leaves .leaf:nth-child(7),
  .cherry-blossoms .leaf:nth-child(7) {
    top: 72px;
    left: -20px;
  }
  .leaves .leaf:nth-child(8),
  .cherry-blossoms .leaf:nth-child(8) {
    top: 75px;
    left: -1px;
    transform: scale(1.02) rotate(-25deg);
  }
  .leaves .leaf:nth-child(9),
  .cherry-blossoms .leaf:nth-child(9) {
    top: 70px;
    left: 10px;
  }
  .leaves .leaf:nth-child(10),
  .cherry-blossoms .leaf:nth-child(10) {
    top: 55px;
    left: -20px;
    transform: scale(1.5) rotate(18deg);
  }
  .leaves .leaf:nth-child(11),
  .cherry-blossoms .leaf:nth-child(11) {
    top: 60px;
    transform: scale(0.9);
    left: -5px;
  }
  .leaves .leaf:nth-child(12),
  .cherry-blossoms .leaf:nth-child(12) {
    z-index: 2;
    left: 28px;
    top: 60px;
  }
  .leaves .leaf:nth-child(13),
  .cherry-blossoms .leaf:nth-child(13) {
    z-index: 2;
    left: 40px;
    top: 70px;
    transform: rotate(40deg);
  }
  .leaves .leaf:nth-child(14),
  .cherry-blossoms .leaf:nth-child(14) {
    z-index: 2;
    left: 55px;
    top: 70px;
    transform: rotate(60deg) scale(1.3);
  }
  .leaves .leaf:nth-child(15),
  .cherry-blossoms .leaf:nth-child(15) {
    z-index: 2;
    left: 25px;
    top: 70px;
    transform: rotate(-20deg) scale(1.1);
  }
  .leaves .leaf:nth-child(16),
  .cherry-blossoms .leaf:nth-child(16) {
    z-index: 2;
    left: 70px;
    top: 70px;
    transform: rotate(50deg) scale(0.7);
  }
  .leaves .leaf:nth-child(17),
  .cherry-blossoms .leaf:nth-child(17) {
    z-index: 2;
    left: 70px;
    top: 60px;
    transform: scale(0.6) rotate(60deg);
  }
  .leaves .leaf:nth-child(18),
  .cherry-blossoms .leaf:nth-child(18) {
    z-index: 2;
    left: 50px;
    top: 60px;
    transform: scale(0.7) rotate(60deg);
  }
  .leaves .leaf:nth-child(19),
  .cherry-blossoms .leaf:nth-child(19) {
    z-index: 2;
    left: 65px;
    top: 48px;
    transform: scale(1.5) rotate(50deg);
  }
  .leaves .leaf:nth-child(20),
  .cherry-blossoms .leaf:nth-child(20) {
    z-index: 2;
    left: 40px;
    top: 50px;
    transform: scale(0.8) rotate(70deg);
  }
  .leaves .leaf:nth-child(21),
  .cherry-blossoms .leaf:nth-child(21) {
    z-index: 2;
    left: 58px;
    top: 35px;
    transform: scale(0.7) rotate(60deg);
  }
  .leaves .leaf:nth-child(22),
  .cherry-blossoms .leaf:nth-child(22) {
    z-index: 2;
    left: 20px;
    top: 55px;
    transform: scale(1) rotate(60deg);
  }
  .leaves .leaf:nth-child(23),
  .cherry-blossoms .leaf:nth-child(23) {
    z-index: 2;
    left: 40px;
    top: 60px;
    transform: scale(0.4) rotate(60deg);
  }
  .leaves .leaf:nth-child(24),
  .cherry-blossoms .leaf:nth-child(24) {
    z-index: 2;
    left: 10px;
    top: 60px;
    transform: scale(0.6) rotate(-10deg);
  }
  .leaves .leaf:nth-child(25),
  .cherry-blossoms .leaf:nth-child(25) {
    left: -12px;
    top: 38px;
    transform: scale(1) rotate(60deg);
  }
  .leaves .leaf:nth-child(26),
  .cherry-blossoms .leaf:nth-child(26) {
    left: -5px;
    top: 45px;
    transform: scale(0.8) rotate(60deg);
  }
  .leaves .leaf:nth-child(27),
  .cherry-blossoms .leaf:nth-child(27) {
    left: 8px;
    top: 52px;
    transform: scale(1) rotate(-10deg);
  }
  .leaves .leaf:nth-child(28),
  .cherry-blossoms .leaf:nth-child(28) {
    z-index: 2;
    left: 50px;
    top: 52px;
    transform: scale(1) rotate(-10deg);
  }
  .leaves .leaf:nth-child(29),
  .cherry-blossoms .leaf:nth-child(29) {
    left: 48px;
    top: 10px;
    transform: scale(1) rotate(-10deg);
  }
  .leaves .leaf:nth-child(30),
  .cherry-blossoms .leaf:nth-child(30) {
    left: 30px;
    top: -5px;
    transform: scale(0.8) rotate(-10deg);
  }
  .leaves .leaf:nth-child(31),
  .cherry-blossoms .leaf:nth-child(31) {
    left: 20px;
    top: -5px;
    transform: scale(0.5) rotate(-10deg);
  }
  .leaves .leaf:nth-child(32),
  .cherry-blossoms .leaf:nth-child(32) {
    left: 40px;
    top: -3px;
    transform: scale(0.5) rotate(-10deg);
  }
  .leaves .leaf:nth-child(33),
  .cherry-blossoms .leaf:nth-child(33) {
    left: 10px;
    top: 10px;
    transform: scale(0.7) rotate(-10deg);
  }
  .leaves .leaf:nth-child(34),
  .cherry-blossoms .leaf:nth-child(34) {
    left: 0;
    top: 25px;
    transform: scale(1) rotate(-10deg);
  }
  .leaves .leaf:nth-child(35),
  .cherry-blossoms .leaf:nth-child(35) {
    left: 20px;
    top: 30px;
    transform: scale(1.2) rotate(0deg);
  }
  .leaves .leaf:nth-child(36),
  .cherry-blossoms .leaf:nth-child(36) {
    left: 50px;
    top: 30px;
    transform: scale(1.2) rotate(60deg);
  }
  .leaves .leaf:nth-child(37),
  .cherry-blossoms .leaf:nth-child(37) {
    left: 50px;
    top: 30px;
    transform: scale(1.2) rotate(60deg);
  }
  .leaves .leaf:nth-child(38),
  .cherry-blossoms .leaf:nth-child(38) {
    left: 50px;
    top: 20px;
    transform: scale(1) rotate(40deg);
  }
  .leaves .leaf:nth-child(39),
  .cherry-blossoms .leaf:nth-child(39) {
    left: 40px;
    top: 23px;
    transform: scale(0.8) rotate(50deg);
  }
  .leaves .leaf:nth-child(40),
  .cherry-blossoms .leaf:nth-child(40) {
    left: 30px;
    top: 20px;
    transform: scale(0.5) rotate(-20deg);
  }
  .leaves .leaf:nth-child(40),
  .cherry-blossoms .leaf:nth-child(40) {
    left: 30px;
    top: 10px;
    transform: scale(1.5) rotate(-20deg);
  }
  .leaves .leaf:nth-child(41),
  .cherry-blossoms .leaf:nth-child(41) {
    left: 1px;
    top: 15px;
    transform: scale(0.9) rotate(-5deg);
  }
  .leaves .leaf:nth-child(42),
  .cherry-blossoms .leaf:nth-child(42) {
    left: 10px;
    top: 18px;
    transform: scale(0.5) rotate(5deg);
  }
  .leaves .leaf:nth-child(44),
  .cherry-blossoms .leaf:nth-child(44) {
    left: 5px;
    top: 35px;
    transform: scale(1.5) rotate(-5deg);
  }
  .leaves .leaf:nth-child(45),
  .cherry-blossoms .leaf:nth-child(45) {
    z-index: 2;
    left: 38px;
    top: 35px;
    transform: scale(1.8) rotate(70deg);
  }
  .leaves .leaf:nth-child(46),
  .cherry-blossoms .leaf:nth-child(46) {
    left: 10px;
    top: 5px;
    transform: scale(1.3) rotate(70deg);
  }
  .cherry-blossoms {
    background-color: #ffd9df;
    border: 0.05em solid black;
    box-shadow: none;
  }
  .cherry-blossoms:before,
  .cherry-blossoms:after {
    background-color: #ffd9df;
    border: 0.05em solid black;
    border-top: 0;
    box-shadow: none;
  }
  .cherry-blossoms:after {
    border-left: none;
  }
  .cherry-blossoms .cherry-blossom {
    position: absolute;
    width: 20px;
    height: 20px;
  }
  .cherry-blossoms .cherry-blossom .petal {
    position: absolute;
    width: 10px;
    height: 10px;
    background-color: #ffd9df;
    background-image: linear-gradient(-45deg, #ec87bf 10%, #ffd9df 65%);
    border-radius: 20% 80%;
    box-shadow: -0.04em -0.04em #ec87bf, -0.05em -0.05em black;
  }
  .cherry-blossoms .cherry-blossom .petal:nth-child(1) {
    transform: rotate(40deg);
    left: 25%;
    top: 2px;
  }
  .cherry-blossoms .cherry-blossom .petal:nth-child(2) {
    transform: rotate(-20deg);
    top: 6px;
    left: 0;
  }
  .cherry-blossoms .cherry-blossom .petal:nth-child(3) {
    transform: rotate(-90deg);
    top: 12px;
    left: 2px;
  }
  .cherry-blossoms .cherry-blossom .petal:nth-child(4) {
    transform: rotate(180deg);
    top: 12px;
    left: 10px;
  }
  .cherry-blossoms .cherry-blossom .petal:nth-child(5) {
    transform: rotate(100deg);
    top: 5px;
    left: 10px;
  }
  .cherry-blossoms .cherry-blossom:nth-child(1) {
    z-index: 2;
    left: 30px;
    transform: rotate(10deg) scale(0.8);
    top: 70px;
  }
  .cherry-blossoms .cherry-blossom:nth-child(2) {
    z-index: 2;
    left: 50px;
    transform: rotate(20deg) scale(0.4);
    top: 65px;
  }
  .cherry-blossoms .cherry-blossom:nth-child(3) {
    z-index: 2;
    left: 45px;
    top: 75px;
    transform: scale(0.6);
  }
  .cherry-blossoms .cherry-blossom:nth-child(4) {
    z-index: 2;
    left: 45px;
    top: 50px;
    transform: scale(1);
  }
  .cherry-blossoms .cherry-blossom:nth-child(5) {
    z-index: 2;
    left: 58px;
    top: 70px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(6) {
    z-index: 2;
    left: 62px;
    top: 55px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(7) {
    z-index: 2;
    left: 55px;
    top: 35px;
    transform: scale(0.8);
  }
  .cherry-blossoms .cherry-blossom:nth-child(8) {
    z-index: 2;
    left: 30px;
    top: 55px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(9) {
    z-index: 2;
    left: 30px;
    top: 30px;
    transform: scale(1.1);
  }
  .cherry-blossoms .cherry-blossom:nth-child(10) {
    z-index: 2;
    left: 15px;
    top: 50px;
    transform: scale(0.9);
  }
  .cherry-blossoms .cherry-blossom:nth-child(11) {
    z-index: 2;
    left: -7px;
    top: 40px;
    transform: scale(1.1);
  }
  .cherry-blossoms .cherry-blossom:nth-child(12) {
    left: 6px;
    top: 60px;
    transform: scale(0.8);
  }
  .cherry-blossoms .cherry-blossom:nth-child(13) {
    left: 10px;
    top: 75px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(14) {
    left: 0;
    top: 75px;
    transform: scale(0.8);
  }
  .cherry-blossoms .cherry-blossom:nth-child(15) {
    left: -15px;
    top: 65px;
    transform: scale(0.7);
  }
  .cherry-blossoms .cherry-blossom:nth-child(16) {
    left: -22px;
    top: 55px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(17) {
    left: 5px;
    top: 18px;
    transform: scale(1.2) rotate(-40deg);
  }
  .cherry-blossoms .cherry-blossom:nth-child(18) {
    left: 30px;
    top: 10px;
    transform: scale(1);
  }
  .cherry-blossoms .cherry-blossom:nth-child(19) {
    left: 45px;
    top: 18px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(20) {
    left: 12px;
    top: 38px;
    transform: scale(0.5);
  }
  .cherry-blossoms .cherry-blossom:nth-child(21) {
    left: 12px;
    top: 0;
    transform: scale(0.7) rotate(60deg);
  }
  .cherry-blossoms .cherry-blossom:nth-child(22) {
    left: 25px;
    top: -2px;
    transform: scale(0.4) rotate(60deg);
  }
  .cherry-blossoms .cherry-blossom:nth-child(23) {
    left: -20px;
    top: 45px;
    transform: scale(0.4) rotate(60deg);
  }
  .fruits {
    position: absolute;
    width: 100px;
    height: 100px;
    top: -50%;
    left: -50%;
    z-index: 2;
  }
  .fruits .pear {
    position: absolute;
    width: 13px;
    height: 15px;
    background-color: #ffef94;
    border-radius: 100%;
    box-shadow: inset 2px 0 #fad500;
    border: 0.003em solid black;
  }
  .fruits .pear:before,
  .fruits .pear:after {
    content: '';
    position: absolute;
  }
  .fruits .pear:before {
    width: 20px;
    height: 20px;
    background-color: #ffef94;
    border-radius: 100%;
    top: 6px;
    border-right: 0.003em solid black;
    box-shadow: inset 2px -2px #fad500, -0.06em 0.08em black;
  }
  .fruits .pear:after {
    width: 5px;
    height: 8px;
    border-left: 2px solid #641b1b;
    border-radius: 100%;
    top: -7px;
    left: 3px;
  }
}
复制代码

公共方法

页面路径:/utils/util.js

/**
 * @description 公共方法
 */

// 获取用户信息
const getUserInfo = () => {
  let userInfo = localStorage.getItem('userInfo');
  if (userInfo) {
    return JSON.parse(userInfo);
  }
  return null;
};

// 保存用户信息
const saveUserInfo = userInfo => {
  if (userInfo) {
    localStorage.setItem('userInfo', JSON.stringify(userInfo));
  }
};

/**
 * 两个是否可以整除
 * @param {number} num1 除数
 * @param {number} num2 被除数
 * @return {boolean} 是否整除的布尔值
 */
const getNumDivisibleFlag = (num1, num2) => {
  let flag = false;
  // 如果除数小于被除数 则表示不可以被整除
  if (num1 > num2 && num1 / num2 > 1) {
    flag = true;
  }
  return flag;
};

export default { getUserInfo, saveUserInfo, getNumDivisibleFlag };
复制代码

最终UI

端午活动

活动规则

活动时间

1.2022-5-31 至 2022-6-5,提前预热3天。

2.页面上设置活动倒计时

  • 活动结束前,展示距离活动结束还剩多长时间,时间格式为DD天 hh:mm:ss;
  • 活动结束后,展示内容为"活动已结束";

兑换规则

食材兑换比例

粽子类型 需要材料
糯米粽子 10 * 糯米 + 2 * 粽叶 + 2 * 红枣

食材兑换规则

  1. 通过页面按钮进行兑换,当食材数量不足时,按钮不可点击,当食材数量充足时可以进行点击。
  2. 点击兑换按钮唤起兑换弹窗,可以通过加减号进行兑换数量的修改,当达到最大可兑换值时,加号不可点击。
  3. 确定兑换之后,粽子数量增加,食材数量对应减少。

功能实现

活动页面

文件路径:/festival/index.jsx

/**
 * @description 端午活动
 */
import React, { useEffect, useState } from 'react';
import classnames from 'classnames';
import moment from 'moment';
import Back from '@/components/Back';
import { Modal, Stepper } from 'antd-mobile';
import { QuestionCircleFill } from 'antd-mobile-icons';
import util from '../../utils/util';
import './index.less';

const Festival = () => {
  const userInfo = util.getUserInfo() || {};
  const [festivalObj, setFestivalObj] = useState(
    userInfo.festival
      ? userInfo.festival
      : {
          nuomi: 20,
          zongye: 10,
          hongzao: 10,
          zongzi: 150,
        },
  );
  const list = [
    {
      key: 'nuomi',
      name: '糯米',
    },
    {
      key: 'zongye',
      name: '粽叶',
    },
    {
      key: 'hongzao',
      name: '红枣',
    },
    {
      key: 'zongzi',
      name: '粽子',
    },
  ];

  // 是否可以进行兑换操作的布尔值 true-能 false-不能
  const [activeFlag, setActiveFlag] = useState(false);

  const [visible, setVisible] = useState(false);
  const [count, setCount] = useState(0);
  // 兑换的粽子数量
  const [convertNum, setConvertNum] = useState(1);
  const [countdown, setCountdown] = useState('');
  let timer = null;

  // 获取当前兑换按钮是否可以点击
  const getInactiveFlag = festivalObj => {
    let activeInit = false;
    let nuomi = festivalObj.nuomi;
    let zongye = festivalObj.zongye;
    let hongzao = festivalObj.hongzao;
    if (nuomi && zongye && hongzao) {
      let nuomiFlag = util.getNumDivisibleFlag(nuomi, 10);
      let zongyeFlag = util.getNumDivisibleFlag(zongye, 2);
      let hongzaoFlag = util.getNumDivisibleFlag(hongzao, 2);
      if (nuomiFlag && zongyeFlag && hongzaoFlag) {
        activeInit = true;
      }
    }
    setActiveFlag(activeInit);
  };

  const getCountdown = () => {
    let nowDate = new Date();
    // console.log(nowDate, 'nowDate');
    // 获取的2022-06-05的23:59:59的时间戳
    let endTime = moment('2022-06-05').endOf('day').format('x');

    let countdownInit = '';
    // 剩余时间 毫秒
    let surplusTime = endTime - nowDate.getTime();
    if (surplusTime <= 0) {
      clearTimeout(timer);
      countdownInit = '活动已结束';
      setCountdown(countdownInit);
    } else {
      // 剩余时间 秒
      let runTime = surplusTime / 1000;
      const day = Math.floor(runTime / 86400);
      runTime = runTime % 86400;
      const hour = Math.floor(runTime / 3600);
      runTime = runTime % 3600;
      const minute = Math.floor(runTime / 60);
      runTime = runTime % 60;
      const second = Math.floor(runTime);
      const dayText = day ? `${day}天` : '';
      countdownInit = `剩余时间:${dayText} ${hour}:${minute}:${second}`;
      setCountdown(countdownInit);
      timer = setTimeout(getCountdown, 1000);
    }
  };

  useEffect(() => {
    getInactiveFlag(festivalObj);
    getCountdown();
  }, []);

  useEffect(() => {
    // 清除定时
    return () => {
      clearInterval(timer);
    };
  }, []);

  // 顶部提示
  const headTip = () => {
    return Modal.show({
      title: '"粽"得凤仪',
      content: (
        <div className='festival-modal'>
          <div className='festival-modal-title'>合成粽子</div>
          <div className='festival-modal-content mb10'>
            <p className='mb10'>10*糯米+2*粽叶+2*红枣可以兑换1个糯米粽子。</p>
            <p>当糯米、粽叶、红枣的比例不是5:1:1时,无法进行兑换。</p>
          </div>
          <div className='festival-modal-title'>称号奖励</div>
          <div className='festival-modal-content'>
            <p className='mb10'>当前粽子数量达到50个可获得称号“淑仪倾城”。</p>
            <p className='mb10'>当前粽子数量达到100个可获得称号“花容初绽”。</p>
            <p className='mb10'>当前粽子数量达到200个可获得称号“花成蜜就”。</p>
            <p className='mb10'>当前粽子数量达到300个可获得称号“宠冠六宫”。</p>
            <p className='mb10'>当前粽子数量达到400个可获得称号“凤仪千载”。</p>
            <p>称号自动获取无需额外操作</p>
          </div>
        </div>
      ),
      showCloseButton: true,
    });
  };

  // 粽子展示
  const zongziContent = () => {
    return (
      <div className='festival-zongzi'>
        <div className='festival-zongzi-left'></div>
        <div className='festival-zongzi-center'></div>
        <div className='festival-zongzi-right'></div>
      </div>
    );
  };

  // 兑换确定操作
  const convertOnConfirm = () => {
    setVisible(false);
    let festivalObjInit = { ...festivalObj };
    console.log(convertNum, 'convertNum');
    festivalObjInit.nuomi -= convertNum * 10;
    festivalObjInit.zongye -= convertNum * 2;
    festivalObjInit.hongzao -= convertNum * 2;
    festivalObjInit.zongzi += convertNum;
    console.log(festivalObjInit, 'festivalObjInit');
    // 设置缓存
    let userInfoInit = { ...userInfo };
    userInfoInit.festival = festivalObjInit;
    util.saveUserInfo(userInfoInit);
    setFestivalObj(festivalObjInit);
    getInactiveFlag(festivalObjInit);
  };

  // 获取可以兑换的数量
  const getConvertCount = () => {
    let nuomi = festivalObj.nuomi;
    let zongye = festivalObj.zongye;
    let hongzao = festivalObj.hongzao;
    let nuomiNum = Math.floor((nuomi * 100) / (10 * 100));
    let zongyeNum = Math.floor((zongye * 100) / (2 * 100));
    let hongzaoNum = Math.floor((hongzao * 100) / (2 * 100));
    return Math.min(nuomiNum, zongyeNum, hongzaoNum);
  };

  // 兑换操作
  const handleConvert = () => {
    if (!activeFlag) return;
    const count = getConvertCount();
    setConvertNum(1);
    setCount(count);
    setVisible(true);
  };

  return (
    <div className='festival'>
      <div className='festival-content'>
        <Back />
        <div className='festival-head'>
          <div className='festival-head-tip' onClick={headTip}>
            <QuestionCircleFill fontSize={28} color='#f69bad' />
          </div>
          <div className='festival-head-title'>"粽"得凤仪</div>
        </div>
        <div className='festival-time'>{countdown}</div>
        <div className='festival-convert'>
          <div className='festival-convert-zongzi'>{zongziContent()}</div>
          <div className='festival-convert-zongzi2'>{zongziContent()}</div>
          <div className='festival-convert-num'>
            {list.map(item => {
              return (
                <div className='festival-convert-num-item' key={item.key}>
                  {item.name}: {festivalObj[item.key]}
                </div>
              );
            })}
          </div>
          <div className='festival-convert-rule'>
            <div className='festival-convert-rule-nuomi'></div> x 10<div className='festival-convert-rule-add'></div>
            <div className='festival-convert-rule-zongye'></div> x 2<div className='festival-convert-rule-add'></div>
            <div className='festival-convert-rule-hongzao'></div> x 2
          </div>
          <div className={classnames('festival-convert-btn', { inactive: !activeFlag })} onClick={handleConvert}>
            兑换
          </div>
        </div>
      </div>
      <div className='festival-room'>
        <div className='festival-room-wall'>
          <div className='wall-poetry'>
            <div className='wall-poetry-nail'></div>
            <div className='wall-poetry-shaft wall-poetry-shaft-top'></div>
            <div className='wall-poetry-inner'>
              <div className='wall-poetry-title'>浣溪沙·端午</div>
              <div className='wall-poetry-author'>宋·苏轼</div>
              <div className='wall-poetry-content'>轻汗微微透碧纨,明朝端午浴芳兰。流香涨腻满晴川。彩线轻缠红玉臂,小符斜挂绿云鬟。佳人相见一千年。</div>
            </div>
            <div className='wall-poetry-shaft wall-poetry-shaft-bottom'></div>
          </div>
        </div>
        <div className='festival-room-floor'>
          <div className='floor-line floor-line1'></div>
          <div className='floor-line floor-line2'></div>
          <div className='floor-line floor-line3'></div>
          <div className='floor-line floor-line4'></div>
          <div className='floor-line floor-line5'></div>
          <div className='floor-line floor-line6'></div>
          <div className='floor-line floor-line7'></div>
          <div className='floor-line floor-line8'></div>
          <div className='floor-line floor-line9'></div>
          <div className='floor-line floor-line10'></div>
        </div>
      </div>
      <Modal
        visible={visible}
        content={
          <div className='festival-modal'>
            <div className='festival-modal-text'>最多可以兑换: {count}</div>
            <div className='festival-modal-stepper'>
              <Stepper
                step={1}
                value={convertNum}
                min={1}
                max={count}
                onChange={value => {
                  setConvertNum(value);
                }}
              />
            </div>
            <div className='festival-modal-confirm' onClick={() => convertOnConfirm()}>
              兑换
            </div>
          </div>
        }
        showCloseButton={true}
        closeOnAction
        onClose={() => {
          setVisible(false);
        }}
      />
    </div>
  );
};

export default Festival;
复制代码

样式:/festival/index.less

.festival {
  width: 100%;
  max-width: 100%;
  height: 100vh;
  background: #46272d;
  position: relative;
  overflow: hidden;
  &-content {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 999;
    width: 100%;
    height: 100%;
  }
  &-room {
    width: 100%;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 99;
    &-wall {
      width: 100%;
      height: 450px;
      background: #e8dfe0;
      position: relative;
      .wall-poetry  {
        position: absolute;
        top: 180px;
        left: 100px;
        width: 170px;
        height: 230px;
        background: #c1a98f;
        &-nail {
          width: 14px;
          height: 14px;
          position: absolute;
          top: -60px;
          left: 50%;
          border: 2px solid #ffedbb;
          border-radius: 50%;
          background: #b5a9a9;
          margin-left: -7px;
          &::before {
            content: '';
            position: absolute;
            top: 27px;
            left: 0;
            width: 90px;
            height: 3px;
            border-radius: 3px;
            background: #b5a9a9;
            transform: rotate(30deg);
            z-index: 20;
          }
          &::after {
            content: '';
            position: absolute;
            top: 27px;
            right: 0;
            width: 90px;
            height: 3px;
            border-radius: 3px;
            background: #b5a9a9;
            transform: rotate(-30deg);
            z-index: 20;
          }
        }
        
        &-shaft {
          position: absolute;
          left: -18px;
          height: 8px;
          width: 120%;
          background: #a97e78;
          border-radius: 8px;
          z-index: 90;
          &-top {
            top: -8px;
          }
          &-bottom {
            bottom: -8px;
          }
          &::before {
            content: '';
            position: absolute;
            top: 0;
            left: 15px;
            height: 100%;
            width: 10px;
            background: #c8b044;
          }
          &::after {
            content: '';
            position: absolute;
            top: 0;
            right: 15px;
            height: 100%;
            width: 10px;
            background: #c8b044;
          }
        }
        
        &-inner {
          position: absolute;
          top: 20px;
          left: 20px;
          width: 130px;
          height: 190px;
          background: #ece8e5;
          font-size: 13px;
          font-weight: 300;
          color: #333;
          padding: 15px 13px;
          z-index: 99;
        }
        &-title {
          font-size: 14px;
          line-height: 1.5;
          margin-bottom: 8px;
        }
        &-author {
          line-height: 1.5;
          margin-bottom: 5px;
        }
      }
    }
    &-floor {
      width: 100%;
      height: 250px;
      position: relative;
      background: #946962;
      overflow: hidden;
      .floor { 
        &-line {
          width: 1px;
          height: 100%;
          background: linear-gradient( to bottom, #b48e5e 20%, #eebe88 40%, #fce49c 60%, #9f725a 80%, #f7c887 100%);
          position: absolute;
          top: 0;
        }
        &-line1 {
          left: 0;
          transform: rotate(10deg);
        }
        &-line2 {
          left: 10%;
          transform: rotate(10deg);
        }
        &-line3 {
          left: 23%;
          transform: rotate(5deg);
        }
        &-line4 {
          left: 34%;
          transform: rotate(2deg);
        }
        &-line5 {
          left: 45%;
        }
        &-line6 {
          right: 43%;
          transform: rotate(-2deg);
        }
        &-line7 {
          right: 32%;
          transform: rotate(-5deg);
        }
        &-line8 {
          right: 20%;
          transform: rotate(-8deg);
        }
        &-line9 {
          right: 10%;
          transform: rotate(-10deg);
        }
        &-line10 {
          right: 0;
          transform: rotate(-10deg);
        }
      }
    }
  }
  &-head {
    width: 100%;
    height: 50px;
    display: flex;
    justify-content: flex-start;
    align-items: center;
    margin-top: 30px;
    &-tip {
      margin-left: 80px;
      margin-right: 30px;
    }
    &-title {
      color: #67b898;
      line-height: 50px;
      font-size: 28px;
      text-align: center;
      font-weight: 500;
    }
  }
  &-time {
    line-height: 30px;
    width: 60%;
    text-align: center;
    margin: 5px auto;
    font-size: 13px;
    color: #e45453;
    background: #fff;
    border-radius: 30px;
  }
  &-modal {
    width: 100%;
    padding: 0 5px;
    &-title {
      position: relative;
      height: 22px;
      line-height: 22px;
      text-align: center;
      margin-bottom: 10px;
      color: #af8368;
      &::before {
        content: '';
        width: 40px;
        height: 2px;
        background: #eec2c1;
        position: absolute;
        top: 10px;
        left: 15px;
        border-radius: 2px;
      }
      &::after {
        content: '';
        width: 40px;
        height: 2px;
        background: #eec2c1;
        position: absolute;
        top: 10px;
        right: 15px;
        border-radius: 2px;
      }
    }
    p {
      line-height: 1.4;
      font-weight: 300;
      font-size: 13px;
      position: relative;
      padding-left: 10px;
      &::before {
        content: '';
        width: 4px;
        height: 4px;
        background: #af8368;
        border-radius: 50%;
        position: absolute;
        top: 6px;
        left: -2px;

      }
    }
    &-text {
      line-height: 20px;
      font-size: 15px;
      color: #cd9769;
      margin: 20px auto 15px;
      text-align: center;
    }
    &-stepper {
      margin: 0 auto;
      width: 40%;
    }
    &-confirm {
      width: 120px;
      height: 30px;
      line-height: 30px;
      background: #d5834b;
      color: #fff;
      font-size: 15px;
      font-weight: 300;
      border-radius: 30px;
      margin: 20px auto 10px;
      text-align: center;
    }
  }
  &-convert {
    width: 90%;
    height: 270px;
    position: absolute;
    top: 250px;
    left: 5%;
    z-index: 999;
    background: linear-gradient(to top right,rgba(148,215,102,0.9) 0,rgba(67,171,174,0.9) 40%);
    box-shadow: 1px 1px 30px #8fde5f;
    border-radius: 10%;
    &-zongzi {
      position: absolute;
      bottom: -14px;
      right: -8px;
      transform: scale(0.8);
      z-index: 1000;
    }
    &-zongzi2 {
      position: absolute;
      bottom: -20px;
      right: 15px;
      transform: scale(0.6) rotate(20deg);
      z-index: 1001;
    }
    &-num {
      width: 90%;
      height: 30px;
      margin-left: 5%;
      margin-top: 40px;
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-bottom: 2px solid #ffdaa3;
      padding-bottom: 10px;
      &-item {
        font-size: 16px;
        color: #ffdaa3;
        font-weight: 500;
      }
    }
    &-rule {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-top: 30px;
      font-size: 20px;
      color: #fff;
      width: 80%;
      margin-left: 10%;
      &-nuomi {
        width: 30px;
        height: 46px;
        background: #fff;
        border-radius: 50%;
        position: relative;
        box-shadow: inset 6px -1px 1px 0 #efefe5;
        transform: rotate(20deg);
      }
      &-zongye {
        width: 24px;
        height: 50px;
        background: #080;
        border-radius: 300%;
        transform: rotate(30deg) skewY(50deg);
      }
      &-hongzao {
        width: 30px;
        height: 46px;
        background: #f65662;
        border-radius: 40%;
        position: relative;
        box-shadow: inset -1px -1px 1px 0 #fff;
        transform: rotate(20deg);
        &::before {
          content: '';
          position: absolute;
          top: -1px;
          left: 10px;
          width: 10px;
          height: 3px;
          background: #a84e43;
          border-radius: 50%;
        }
      }
    }
    &-btn {
      width: 60px;
      height: 60px;
      border: 2px solid #fff8db;
      background: #f295a7;
      color: #fff;
      font-size: 16px;
      border-radius: 50%;
      margin: 30px auto 0;
      display: flex;
      justify-content: center;
      align-items: center;
      &.inactive {
        background: #afa9e7;
      }
    }
  }
  &-zongzi {
    position: relative;
    width: 50px;
    height: 60px;
    z-index: 990;
    overflow: hidden;
    border-radius: 0 0 35% 35%;
    &-left {
      width: 60px;
      height: 40px;
      background: #73bd5c;
      border-radius: 30% 50% 0 0;
      position: absolute;
      bottom: -22px;
      left: -22px;
      z-index: 993;
      transform: rotate(47deg);
    }
    &-center {
      width: 50px;
      height: 70px;
      background: #fff;
      border-radius: 50% 70% 0 0 ;
      position: absolute;
      top: 6px;
      left: 0;
      z-index: 991;
      box-shadow: inset -6px -1px 1px 0 #efefe5;
    }
    &-right {
      width: 60px;
      height: 40px;
      background: #73bd5c;
      border-radius: 0 30% 0 0;
      position: absolute;
      bottom: -17px;
      right: -19px;
      z-index: 993;
      transform: rotate(-47deg);
    }
  }
}
复制代码

最终UI

活动展示

兑换弹窗展示

总结

本次收获还是挺多的。

  1. CSS用的别以前熟练了很多,这次的游戏里除了头像图片、一颗树、一簇花,其他的都是我用CSS写出来的,没有用图片素材,实现过程不断收获新的创意。说起来多亏这段时间码上掘金活动,我才能使用CSS实现功能做的如此之快,ღ( ´・ᴗ・` );
  2. 游戏设计,体验了一把产品/策划的感觉,站在不同的角度去思考需要实现的功能,锻炼逻辑思维,很有收获;
  3. 核心功能的实现,包括内务收集的计算、食材的随机掉落计算、粽子兑换的计算等多个计算功能,虽然方法可能不是最优,但是在遇到类似的功能实现算是有经验了;

还差一个github的地址,等有时间我把所有代码上传后,补充一下github地址。

参考文章

猜你喜欢

转载自juejin.im/post/7103578323644743717