react结合vant的Dialog实现签到弹框操作

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

相关推荐
yzp01123 小时前
css收集
前端·css
暴富的Tdy3 小时前
【Webpack 的核心应用场景】
前端·webpack·node.js
遇见很ok3 小时前
Web Worker
前端·javascript·vue.js
风舞红枫3 小时前
前端可配置权限规则案例
前端
zhougl9963 小时前
前端模块化
前端
暴富暴富暴富啦啦啦4 小时前
Map 缓存和拿取
前端·javascript·缓存
天问一4 小时前
前端Vue使用js-audio-plugin实现录音功能
前端·javascript·vue.js
dodod20124 小时前
Ubuntu24.04.3执行sudo apt install yarnpkg 命令失败的原因
java·服务器·前端
小魏的马仔4 小时前
【elementui】el-date-picker日期选择框,获取焦点后宽度增加问题调整
前端·vue.js·elementui
JarvanMo4 小时前
想让你的 Flutter UI 更上一层楼吗?
前端