1.需求
有时候在开发的时候,需要实现一个签到获取积分的功能,使用react怎么实现呢?
需求如下:
1.当点击"签到"按钮时,弹出签到框
2.展示签到信息:
签到天数,
对应天数签到能够获取的积分,
对应天数是否签到效果展示,
签到按钮,
签到说明,
3.点击"签到"按钮,实现签到功能
(1).如果当天没有签到,按钮文案"请签到",点击后,执行签到功能,签到成功后,提示签到获取,然后提示慢慢隐藏
(2).如果已经签到,按钮文案"已签到",不能触发点击事件
图示如下:
2.实现
要实现上面的功能,需要使用vant的Dialog弹出框组件,代码如下:
主页代码:点击"签到按钮",设置Dialog签到弹出框visible属性:显示弹出框
javascript
import GetSign from "../components/GetSign";
const Index = () => {
//签到弹出框显示设置
const [signDiaLogVisible, setSignDiaLogVisible] = useState(false);
return (
//点击签到按钮,设置弹出框显示
<div onClick={() => setVisible(true)}>签到按钮</div>
//签到弹框组件引用
<GetSign signDiaLogVisible={signDiaLogVisible} setSignDiaLogVisible={setSignDiaLogVisible} />
);
}
export default Index;
签到弹框组件js:
javascript
import React, { useEffect } from "react";
import { useHistory } from "react-router-dom";
import { Dialog } from "react-vant";
import { PersistContext } from "../../data/PersistProvider";
import { RootStateContext } from "../../data/RootStateProvider";
import { SendCMD } from "../../utils/HTTPRequest";
import style from "./style.less";
//todo: 签到数据(可根据自己项目需求自行构建结构):
//checked:是否已经签到,award:积分,
//数组的key就是天数,key从0开始,所以需要加1才是真正的天数
const data = { //前端构建的签到结构,这里一般是从服务端获取数据,具体字段名称根据需求情况自行确定
check_days: [ //天数
{ // key: 0, 第一天
checked: true, //是否已经签到
award: 2000, //积分数
},
{ // key: 1, 第二天,下面依次类推
checked: true,
award: 3000,
},
{
checked: true,
award: 3000,
},
{
checked: false,
award: 4000,
},
{
checked: false,
award: 5000,
},
{
checked: false,
award: 5000,
},
{
checked: false,
award: 8000,
},
],
today_checkable: true, //今天是否可以签到
};
const GetSign = (props) => {
let history = useHistory();
//persist:保存到浏览器的临时缓存数据
const { getPersist} = React.useContext(PersistContext);
//用户信息
const userInfo = getPersist("user_info");
//是否展示签到所得
const [visible, setVisible] = React.useState(-1);
//弹框数据初始化
const [signData, setSignData] = React.useState(data);
//获取签到数据
const sign_data = () => { //从服务端api获取签到数据
SendCMD("getCheckIn", { token: userInfo.token }).then((res) => {
if (res.check_in_data) {
setSignData(res.check_in_data); //数据应该和上面data中的保持一致
}
});
};
//签到请求
const sign_cmd = () => {
SendCMD("doCheckIn", { token: userInfo.token }).then((res) => {
if (res[0].check_success && res[0].check_in_data) { //判断是否签到成功,并更新签到信息
setSignData(res[0].check_in_data);
}
//判断最后一次签到的index
let activeIndex = -1;
res[0].check_in_data.check_days.map((item, index) => { //循环,设置最后一次签到的index
if (item.checked) {
activeIndex = index;
}
});
//设置显示的log index
setVisible(activeIndex);
});
};
//初始化签到数据
useEffect(() => {
if (userInfo) {
sign_data();
}
}, [userInfo]);
//点击签到按钮事件
const onSignClick = () => {
//判断是否登录
if (!userInfo) {
//跳转到注册页面
history.push("./login");
return;
}
//todo 判断是否可以点击
if (!signData["today_checkable"]) {
//已经签到,弹框提示
alert("今天已签到");
return;
}
//请求接口签到,并根据返回结果响应改变展示效果
sign_cmd();
};
return (
<Dialog
closeable={true}
closeOnClickOverlay={true}
width={"85%"}
// closeIcon={<Close />}
visible={props.visible != 0}
showCancelButton={false}
className={style.signDialog}
showConfirmButton={false}
onClose={() => {
props.setVisible(false);
}}
>
<div class={style.firstSignDialog}>
<div style={{ width: "100%", height: "4rem" }}>
<img style={{ width: "43%", marginTop: "1.2rem" }} src="../images/sign_title.png" />
</div>
<div class={style.firstSignSteps}>
<div class={style.btntop}>
{signData.check_days
? signData.check_days.map((item, index) => {
return (
<div key={index} class={style.checkactivebox} id={"index" + index}>
<div class={style.c_l}>
<div class={style.c_l_text}>
<span>+{item.award / 100}</span>
</div>
<div class={item.checked ? style.yuanbox_yes : style.yuanbox}>
{item.checked ? (
<sapn className={style.yuanbox_num}>
<img src={"../images/sign_yes.png"} style={{ width: "1rem" }} />
</sapn>
) : (
<sapn className={style.yuanbox_num}>{index + 1}</sapn>
)}
</div>
<div class={style.tian}>Day {index + 1}</div>
</div>
<div>
{index === 3 || index === 6 ? (
<img alt="" />
) : signData.check_days[index + 1].checked ? (
<img className={style.img_2} src="../images/sign_line2.png" alt="" />
) : (
<img className={style.img_2} src="../images/sign_line.png" alt="" />
)}
</div>
<div class={`${style.p_log} ${pLogVisible == index ? style.p_log_active : ""}`}>
{" "}
Coins +{item.award / 100}
</div>
</div>
);
})
: null}
</div>
</div>
<div class={style.firstSignBtn} onClick={onSignClick}>
<div class={style.firstSignDialogBtnText}>
{!userInfo ? (
<span>注册</span>
) : !signData.today_checkable ? (
<span>已签到</span>
) : (
<span>签到</span>
)}
</div>
</div>
<div class={style.signBottom}>
<div class={style.signBottom_title}>签到说明</div>
<div class={style.signBottom_content}>
<span>.说明1</span>
<br />
<span>.说明2</span>
<br />
<span>.说明3</span>
</div>
</div>
</div>
</Dialog>
);
};
export default GetSign;
签到弹出框css:
css
.signDialog {
--rv-dialog-background-color: rgba(0, 0, 0, 0);
}
.firstSignDialog {
text-align: center;
background-size: 100% 100%;
background-repeat: no-repeat;
background-image: url("../assets/images/sign.png");
}
.btntop {
margin: 0 0.53333rem;
display: flex;
flex-wrap: wrap;
font-weight: 700;
}
.c_l_text {
color: #CAE7DC;
font-weight: bold;
}
.img_1 {
width: 0.66667rem;
}
.tian {
color: #CAE7DC;
text-align: center;
font-weight: bold;
}
.checkactivebox {
width: 25%;
position: relative;
}
.img_1 {
width: 0.66667rem;
}
.yuanbox_yes {
width: 2rem;
height: 2rem;
z-index: 2;
border-radius: 99rem;
background-color: #00FFCC;
}
.yuanbox {
width: 2rem;
height: 2rem;
z-index: 2;
border-radius: 99rem;
background-color:#CAE7DC;
}
.yuanbox_num {
color: #035d3f;
font-weight: 700;
font-size: 1.2rem;
line-height: 2rem;
text-align: center;
}
.sign_toast {
margin-top: -7rem;
background-color: #f00 !important;
color: #006a2d !important;
}
.c_l {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
font-size: 1rem;
}
.img_2 {
position: absolute;
top: 1.9rem;
z-index: 1;
width: 100%;
}
.p_log {
position: absolute;
top: -0.53333rem;
left: 0.47333rem;
line-height: 1.43333rem;
text-align: center;
background-color: #035d3f;
font-size: 0.5rem;
color: #ffffff;
border-radius: 0.5rem;
padding-left: 0.15rem;
padding-right: 0.15rem;
z-index: 100;
opacity: 0;
}
@keyframes fadenum{
0%{opacity: 1;}
100%{opacity: 0;}
}
.p_log_active {
animation:fadenum 5s 1;
}
.firstCoinsSteps {
margin-top: 3%;
}
.firstSignBtn {
display: inline-block;
margin-bottom: 1.5rem;
background: url("../assets/home/sign_btn.png") no-repeat 100%;
background-size: 100%;
width: 80%;
border-radius: 2rem;
height: 3rem;
}
.firstSignDialogBtnText {
margin-top: 1rem;
transform: scale(1, 1.5);
}
.firstSignDialogBtnText span {
text-align: center;
font-weight: 700;
font-size: 1rem;
background-color: #000000;
color: #035d3f;
text-shadow: 0px 0px 1px rgba(255, 255, 255, 0.925);
-webkit-background-clip: text;
-moz-background-clip: text;
background-clip: text;
}
.signBottom {
margin-top: 2%;
color: #969696;
font-weight: 700;
}
.signBottom_title {
font-size: 1.2rem;
}
.signBottom_content {
margin-top: 0.3rem;
}
.signBottom_content span {
text-align: left;
margin-left: 0.5rem;
width: 96%;
font-size: 0.8rem;
font-weight: 700;
display: inline-block;
}
上面要引入的setPersist组件:
css
import { createContext, useState, useEffect } from "react";
import { SendCMD } from "../utils/HTTPRequest";
import { DeepEqual, GetOS, GetBrowser } from "../utils/JSTool";
// 创建一个 Context
export const PersistContext = createContext(defaultPersist);
let untrackedData = defaultPersist;
// 创建一个 DataProvider 组件
export const PersistProvider = ({ children }) => {
const [data, setData] = useState(untrackedData);
const [expires, setExpires] = useState({});
useEffect(() => {
loadPersistDataOnce();
}, []);
const loadPersistDataOnce = () => {
if (untrackedData.dataLoaded) {
return;
}
let persistStr = localStorage.getItem("persist");
if (persistStr) {
try {
let persistData = JSON.parse(persistStr);
for (let key in persistData) {
if (typeof persistData[key] == "undefined" || persistData[key] == null) {
delete persistData[key];
}
}
untrackedData = Object.assign({}, untrackedData, persistData);
} catch (e) {
console.error(e);
}
}
let expiresStr = localStorage.getItem("expires");
if (expiresStr) {
try {
let expires = JSON.parse(expiresStr);
setExpires(expires);
} catch (e) {
console.error(e);
}
}
untrackedData.dataLoaded = true;
setData(Object.assign({}, untrackedData));
};
const setPersist = (key, value, expireSeconds) => {
loadPersistDataOnce();
if (DeepEqual(untrackedData[key], value)) return;
untrackedData[key] = value;
if (expireSeconds && expireSeconds > 0) {
let expireData = { ...expires };
expireData[key] = Date.now() + expireSeconds * 1000;
setExpires(expireData);
localStorage.setItem("expires", JSON.stringify(expireData));
}
setData(() => Object.assign({}, untrackedData));
localStorage.setItem("persist", JSON.stringify(untrackedData));
};
const getPersist = (key) => {
loadPersistDataOnce();
let value = data[key];
if (typeof value == "undefined") return null;
let keyExpire = expires[key];
if (keyExpire && keyExpire < Date.now()) {
let dataTmp = { ...data };
delete dataTmp[key];
delete untrackedData[key];
let expiresTmp = { ...expires };
delete expiresTmp[key];
localStorage.setItem("persist", JSON.stringify(dataTmp));
localStorage.setItem("expires", JSON.stringify(expiresTmp));
return null;
}
return value;
};
return (
<PersistContext.Provider value={{ getPersist, setPersist, reloadUserInfo }}>{children}</PersistContext.Provider>
);
};
好了,签到获取积分操作ok