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

语言: CN / TW / HK

theme: channing-cyan highlight: atelier-lakeside-light


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

关于游戏的灵感来源

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

假如我是游戏策划

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

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

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

产品视角

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

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

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

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

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

交互设计

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

首页

日常任务

端午活动

功能设计

首页

内容

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

称号规则

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

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

功能实现

首页页面

文件路径:/home/index.jsx

```js /* * @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 (

); }; return (
{//}
{entranceContent()}
{/ 地板 /}
); };

export default Home; ```

样式:/home/index.less

js .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

```js /* * @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 (

叶一一
{getDesignationByZongziNum()}
); };

export default Avatar; ```

样式:/components/Avatar/index.less

```js .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

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

const FlowerCluster = () => { return (

); }; export default FlowerCluster; ```

样式:/components/FlowerCluster/index.less

js .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

```js /* * @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: , }, { key: 'cai', title: '才', name: '助宫易物', num: 0, harvestFalg: true, taskKey: 'nuomi', icon: , }, { key: 'mei', title: '魅', name: '布施济民', num: 0, harvestFalg: true, taskKey: 'hongzao', icon: , }, ];

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: (

内务打理

内务分为“开源节流”,“助宫易物”,“布施济民”三种类型,分别可以获得铜币、珍品和名望。

打理内务有一定几率获得包粽子的材料。

内务奖励

开源节流有一定几率获得粽叶。

助宫易物有一定几率获得糯米。

布施济民有一定几率获得红枣。

), showCloseButton: true, }); };

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

return (

{list.map(item => { return (
{item.icon}
{getTaskNumContent(tasksObj[item.key])} {tasksObj[item.key] > 0 ? '万' : ''}
); })}
内务打理
{list.map((item, index) => { return (
{item.title}
{item.name}
{item.num}
handleHarvest(index)}>
{item.harvestFalg ? '收获' : '恢复'}
); })}
宫规
内务收获 +5%
); };

export default Tasks; ```

样式:/tasks/index.less

```js .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

```js /* * @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 (

); }; Back.propTypes = { path: PropTypes.string, // 跳转路径 };

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

export default Back; ```

样式:/components/Back/index.less

js .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

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

const Flower = () => { return (

); }; export default Flower; ```

样式:/components/Flower/index.less

js .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

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

const FlowerTree = () => { return (

); }; export default FlowerTree; ```

样式:/components/FlowerTree/index.less

js .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

```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

```js /* * @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: (

合成粽子

10糯米+2粽叶+2*红枣可以兑换1个糯米粽子。

当糯米、粽叶、红枣的比例不是5:1:1时,无法进行兑换。

称号奖励

当前粽子数量达到50个可获得称号“淑仪倾城”。

当前粽子数量达到100个可获得称号“花容初绽”。

当前粽子数量达到200个可获得称号“花成蜜就”。

当前粽子数量达到300个可获得称号“宠冠六宫”。

当前粽子数量达到400个可获得称号“凤仪千载”。

称号自动获取无需额外操作

), showCloseButton: true, }); };

// 粽子展示 const zongziContent = () => { return (

); };

// 兑换确定操作 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 (

"粽"得凤仪
{countdown}
{zongziContent()}
{zongziContent()}
{list.map(item => { return (
{item.name}: {festivalObj[item.key]}
); })}
x 10
x 2
x 2
兑换
浣溪沙·端午
宋·苏轼
轻汗微微透碧纨,明朝端午浴芳兰。流香涨腻满晴川。彩线轻缠红玉臂,小符斜挂绿云鬟。佳人相见一千年。
\
最多可以兑换: {count}\
\
\ { setConvertNum(value); }} />
convertOnConfirm()}> 兑换
} showCloseButton={true} closeOnAction onClose={() => { setVisible(false); }} />
); };

export default Festival; ```

样式:/festival/index.less

```js .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地址。

参考文章