【端午節】新奇體驗,我用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地址。

參考文章