PHP实现连续签到的功能在很多网站无处不见,有一些是实现心情签到,发表心情。有一些是实现连续签到领取积分或者礼品之类的,根据业务的需求改变而改变,今天要来给大家讲解的是如何实现签到领取积分,连续签到领取礼品之类的功能如图所示,签到之前
签到之后的页面显示
这里给大家贴各种实现的核心代码,学习php主要是大家能借鉴,看懂思路,而不是各种复制黏贴哈!需求的需要,本功能实现需要用到三张表:
签到奖励设置表
CREATE TABLE `le_sign_in` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '递增主键',
`name` varchar(100) NOT NULL COMMENT '签到名称',
`icon` varchar(100) NOT NULL COMMENT '签到图标',
`type` tinyint(3) unsigned NOT NULL DEFAULT '2' COMMENT '1. 常规 2. 额外',
`keep_sign_in` tinyint(3) unsigned NOT NULL COMMENT '保持签到',
`reward_rule` varchar(255) NOT NULL COMMENT '签到奖励规则:[ {reward_type:奖励类型1.积分 2.红包 reward_min:奖励最小值,reward_max:奖励最大值}]',
`reward_sort` tinyint(3) unsigned NOT NULL DEFAULT '100' COMMENT '奖励排序',
`admin_id` int(10) unsigned NOT NULL COMMENT '管理员用户ID',
`created_at` datetime NOT NULL,
`updated_at` datetime NOT NULL,
`deleted_at` datetime DEFAULT NULL COMMENT '是否删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 COMMENT='签到奖励设置表';
日志表
CREATE TABLE `le_sign_in_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`user_id` int(10) unsigned NOT NULL COMMENT '用户ID',
`created_at` datetime DEFAULT NULL COMMENT '签到时间',
PRIMARY KEY (`id`),
KEY `u_user_and_time` (`user_id`,`created_at`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=245 DEFAULT CHARSET=utf8 COMMENT='用户签到日志表';
签到奖励日志表
CREATE TABLE `zmq_sign_in_reward_log` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'v',
`sign_in_log_id` int(10) unsigned NOT NULL COMMENT 'sign_id_log表id',
`sign_in_id` int(10) NOT NULL COMMENT '签到ID',
`reward_type` tinyint(3) unsigned NOT NULL COMMENT '奖励类型(1.积分 2.红包 )',
`reward_quantity` decimal(10,2) unsigned NOT NULL COMMENT '奖励数量',
`is_get` tinyint(3) unsigned NOT NULL DEFAULT '0' COMMENT '是否收到, 0未收到 1已收到',
`get_time` datetime DEFAULT NULL COMMENT '收到时间',
`type` tinyint(4) NOT NULL COMMENT '对应sign_in表中的type=2',
`max_sign_in` smallint(5) unsigned NOT NULL COMMENT '最大连续签到',
`keep_sign_in` smallint(5) unsigned NOT NULL COMMENT '保持签到次数',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=224 DEFAULT CHARSET=utf8 COMMENT='签到奖励日志表';
前端模板文件,这里主要是用vue页面实现
<p id="sign_in" v-cloak>
<p class="sign_in_box">
<p class="sign_in_record">
<p class="active_box">
<p>连续签到</p>
<p class="number">{{ keepSignIn }}<em>天</em></p>
</p>
<p class="slash_bg"></p>
<p class="history_box">
<p>最高连续记录</p>
<p class="number">{{ maxSignIn }}<em>天</em></p>
</p>
</p>
<p v-if="!signIn" class="sign_in_btn" @click="sign_in">签到</p>
<p v-else class="sign_in_btn">已签到,获得{{ signReward }}</p>
<a href="/%20mobile /signIn/log" class="sign_in_entrance">签到记录</a>
</p>
<p class="sign_in_hint" v-show="nextRewardDay">再坚持连续签到{{ nextRewardDay }}天,就可以获得额外奖励哦~</p>
<p class="calendars_box">
<p class="today_title">{{currentYear}}年{{currentMonth}}月{{currentDay}}日</p>
<ul class="week_box">
<li>日</li>
<li>一</li>
<li>二</li>
<li>三</li>
<li>四</li>
<li>五</li>
<li>六</li>
</ul>
<ul class="calendars_list">
<li v-for="i in last_month"></li>
<li v-for="(item,index) in signInData" @click="showRewardList(item)" :class="{'active':item.isActive == 1, 'today':index == currentDay-1}">
<p v-if="item.is_get == 1" class="prize_price"><p>{{ item.reward_type_zh }}</p><p>{{ parseFloat(item.reward_quantity) }}</p></p>
<p v-else >{{ index+1 }}</p>
<!--有奖励并且没有领取则显示图片, 并且只有当天有额外奖励才显示-->
<p v-if="item.has_reward == 1 && item.is_get == 0" class="signIn_prize"><img :src="item.icon"></p>
<!-- 当天已签到,并且有奖励,还没有领取的则显示提示 -->
<p v-if="item.isActive == 1 && item.has_reward == 1 && item.is_get == 0 && (index) == (currentDay-1)" class="item_hint"></p>
</li>
</ul>
</p>
<p class="signIn_banner" v-if="ad"><a :href="ad.link"><img :src="ad.pic"></a></p>
<p v-show="signIn_pop" @click="signIn_pop = !signIn_pop" class="mark_bg2"></p>
<p v-show="signIn_pop" class="signIn_pop">
<p v-if="!continue_signIn" class="top_bg"></p>
<p v- el se class="top_bg2"></p>
<p v-if="signIn_pop_style==1" class="pop_title">{{ reward }}</p>
<p v-if="signIn_pop_style==2" class="pop_title">请选择你的奖励!</p>
<p v-if="signIn_pop_style==3" class="pop_title">{{ reward }}</p>
<p v-if="signIn_pop_style==1||signIn_pop_style==3" class="odd_goods" :class="{ 'active':signIn_pop_style == 3}"><img :src="`/static/mobile/images/new/sign_in_type_${signInRewrdType}.png`"></p>
<ul v-if="signIn_pop_style==2" class="even_goods">
<li v-for="(type, index) in currentReward.reward_type" @click="select_prize(index)" v-if="rewardNum >= index" :class="{'active':selectPrize == index}">
<img :src="`/static/mobile/images/new/sign_in_type_${type}.png`">
<p v-if="type==1">积分</p>
<p v-if="type==2">现金红包</p>
</li>
</ul>
<a @click="signIn_pop = !signIn_pop" v-if="signIn_pop_style==1||signIn_pop_style==3" class="close_pop" href="javascript:;">我知道了</a>
</p>
</p>
vue实现的代码,主要是与php后端代码的交互,与如何渲染到模板
new Vue({
el: '#sign_in',
data: {
ad:undefined, //广告
signIn:false,//是否已经签到
signInData:[],
today:'',
currentYear: 1970, // 年份
currentMonth: 1, // 月份
currentDay: 1, // 日期
currentWeek: 1, // 星期
last_month:'',//上个月剩余多少天
signIn_pop:false,//签到弹窗
continue_signIn:true,//是否持续签到
signIn_pop_style:3,//1.2.3分别是1领取单个2领取两个3领取后
selectPrize:-1,//两种奖品选择
keepSignIn:0,//连续签到
maxSignIn:0,//最大签到
reward:"", //弹出显示的奖励(用于签到和额外奖励)
signReward:"", //签到显示的奖励(仅用于签到)
nextRewardDay:0, // 下一次签到奖励
rewardNum:1, // 额外奖励可选数量
currentReward:0, // 现在额外奖励
signInRewrdType:0,
},
created () {
layer.open({type: 2, shadeClose: false})
var that = this;
this.$http.post('/mobile/signIn', {}, {emulateJSON:true}).then( function (res) {
if (res.body.status == 1) {
var data = res.data.data
that.signInData = data.calendars;
that.maxSignIn = data.max_sign_in;
that.keepSignIn = data.keep_sign_in;
if (data.today_sign_in) {
that.signReward = that.format_sign_in(data.today_sign_in)
that.reward = that.format_reward(data.today_sign_in)
that.signIn = true
} else {
that.signIn = data.today_sign_in
}
layer.closeAll()
this.nextRewardTips()
}
})
this.$http.get('/mobile/index/ads?ad_type=12').then(function(res) {
if (res.body.status == 1) {
if (res.body.data[12]) this.ad = res.body.data[12][0]
}
})
this.initData(null)
},
methods: {
format_sign_in: function (reward) {
if (!reward) return
switch (parseInt(reward.reward_type)) {
case 1:
return ` ${reward.reward_quantity} 积分`;
case 2:
return ` ${reward.reward_quantity} 元`;
}
},
format_reward: function (reward) {
if (!reward) return
switch (parseInt(reward.reward_type)) {
case 1:
return `恭喜你,获得积分奖励 ${reward.reward_quantity} 积分`;
case 2:
return `恭喜你,获得红包奖励 ${reward.reward_quantity} 元`;
default:
console.log(reward.reward_type)
}
},
select_prize:function(e){
layer.open({type: 2, shadeClose: false})
this.selectPrize = e;
var data = {log_id: this.currentReward.log_id, select: e}
this.$http.post('/mobile/signIn/getReward', data, {emulateJSON:true}).then(function (res) {
layer.closeAll()
if (res.body.status == 1 && res.body.data) {
var data = res.body.data
this.reward = this.format_reward(data)
this.currentReward.is_get = 1
this.currentReward.reward_type = data.reward_type
this.currentReward.reward_type_zh = data.reward_type_zh
this.currentReward.reward_quantity = parseFloat(data.reward_quantity)
this.signInRewrdType = data.reward_type
this.showPopStyle(1)
} else {
layer.open({
content: res.body.message,
skin: 'msg',
time: 2 //2秒后自动关闭
});
}
})
},
// 用户激活签到
sign_in:function(){
this.$http.post('/mobile/signIn/active', {}, {emulateJSON:true}).then(function (res) {
if (res.body.status == 1) {
var data = res.body.data
this.signReward = this.format_sign_in(data)
this.reward = this.format_reward(data)
this.signInRewrdType = data.reward_type
this.signIn=true;
this.signInData[this.currentDay-1].isActive = true;
this.signInData[this.currentDay-1].log_id = data.log_id
this.showPopStyle(1)
if (this.keepSignIn == this.maxSignIn) {
this.maxSignIn++
}
this.keepSignIn++
this.nextRewardTips()
}
})
},
// 下一次签到奖励
nextRewardTips: function () {
for (var index in this.signInData) {
if (index >= this.currentDay) {
if (this.signInData[index].icon) {
this.nextRewardDay = index - this.currentDay + 1
break;
}
}
}
},
//格式化日期
formatDate: function (year, month, day) {
const y = year
let m = month
if (m < 10) m = `0${m}`
let d = day
if (d < 10) d = `0${d}`
return `${y}-${m}-${d}`
},
initData: function (cur) {
let date = ''
if (cur) {
date = new Date(cur)
} else {
date = new Date()
}
this.currentDay = date.getDate() // 今日日期 几号
this.currentYear = date.getFullYear() // 当前年份
this.currentMonth = date.getMonth() + 1 // 当前月份
this.currentWeek = date.getDay() // 1...6,0 // 今天是星期几
//当前月的第一天是星期几
date.setDate(1);
this.firstWeek = date.getDay();
this.last_month=this.firstWeek;
},
// 显示额外奖励列表
showRewardList: function (item) {
if (item.isActive == 1 && item.has_reward == 1 && item.is_get == 0) {
this.rewardNum = item.reward_num;
this.currentReward = item
// 额外奖励只有一个,直接领取
if (this.rewardNum == 1) {
this.select_prize(0)
} else {
this.showPopStyle(2)
}
}
if (item.is_get) {
this.reward = this.format_reward(item)
this.signInRewrdType = item.reward_type
this.showPopStyle(1)
}
},
// 显示弹出框类型
showPopStyle: function (i) {
this.signIn_pop_style = i
this.signIn_pop=true;
}
},
mounted: function () {
},
computed: {
rewardNumZh: function () {
if (this.rewardNum == 1) {
return "一";
}
if (this.rewardNum == 2) {
return "二";
}
}
}
})
前端的代码基本就是那样,接下来看看后端实现的核心代码
用户签到,首先要获取签到的日历,处理过去和未来可能签到的奖励
/**
* 获取签到日历
* @param $uid int 用户ID
* @param $containPast bool 是否包含过去签到奖励
* @return array
*/public function getCalendars($uid, $containPast=true)
{
$signIns = $this->fetchPaginate(['return_model'=>1]);
$calendars = $this->getCalendarsArray();
// 处理过去已获得的签到奖励
$past = function () use (&$calendars, &$uid) {
$thisMonths = $this->log->fetchThisMonths($uid);
$thisMonths->load(['extraReward.signIn'=>ToolModel::withSelect(['id','icon', 'reward_rule'])]);
foreach ($thisMonths as $item) {
$day = $item->day-1;
$calendars[$day]['isActive'] = 1;
if (isset($item->extraReward)) {
$calendars[$day]['icon'] = $item->extraReward->signIn->icon;
$calendars[$day]['has_reward'] = 1;
$calendars[$day]['log_id'] = $item->id;
$calendars[$day]['is_get'] = $item->extraReward->is_get;
if ($item->extraReward->is_get == 0) {
$calendars[$day]['reward_num'] = count($item->extraReward->signIn->reward_rule);
$calendars[$day]['reward_type'] = array_column($item->extraReward->signIn->reward_rule, 'reward_type');
} else {
$calendars[$day]['reward_quantity'] = $item->extraReward->reward_quantity;
$calendars[$day]['reward_type'] =$item->extraReward->reward_type;
$calendars[$day]['reward_type_zh'] = $item->extraReward->reward_type_zh;
}
}
}
};
// 处理未来可能有的签到奖励
$future = function () use (&$calendars, &$signIns, &$uid) {
if (empty($signIns))return;
$user = $this->userSignIn->findByUserId($uid);
$signInLen = count($signIns);
$monthNum = count($calendars);
$toDay = (date('d')-1);
// 是否已签到,如果未签到则-1
if ($this->isActive($uid)) {
$start = $toDay - $user->keep_sign_in;
} else {
$start = $toDay - $user->keep_sign_in-1;
}
while ($start < $monthNum) {
for ($i=0; $i<$signInLen; $i++) {
$start += $signIns[$i]->keep_sign_in;
if ($start >= $monthNum) {
return;
}
if (0 > $start) {
continue;
}
$calendars[$start]['icon'] = $signIns[$i]['icon'];
$calendars[$start]['has_reward'] = 1;
$calendars[$start]['reward_num'] = count($signIns[$i]->reward_rule);
if ($calendars[$start]['is_get'] == 0) {
$calendars[$start]['reward_type'] = array_column($signIns[$i]->reward_rule, 'reward_type');
}
// 设置今天的签到奖励
if ($start == $toDay) {
$this->toDayExtraSignReward = $signIns[$i];
}
}
}
};
if ($containPast) $past();
$future();
return $calendars;
}
签到成功后,获取奖励,
/**
* 获取今天的签到奖励
* @return array
*/public function getToDayReward($uid)
{
$log = $this->log->findByToDay($uid);
if (empty($log)) return false;
$log->load(['defaultReward']);
return ['reward_quantity'=> $log->defaultReward->reward_quantity,
'log_id' => $log->id,
'reward_type'=>$log->defaultReward->reward_type,
'reward_type_zh'=>$log->defaultReward->reward_type_zh,
];
}
添加用户奖励
/**
* 添加用户签到奖励
* @param UserSignBaseModel $userSign 用户签到
* @param SignInLogBaseModel $signInLog 用户签到日志
* @param SignBaseModel $defaultReward 用户签到默认奖励
* @param SignBaseModel $extraReward 用户签到额外奖励
*/public function store($userSign, $signInLog, $defaultReward, $extraReward = null)
{
$data = [];
// 默认奖品立刻发放
if (isset($defaultReward)) {
foreach ($defaultReward->reward_rule as $rule) {
$quantity = $this->getRewardQuantity($rule);
$data[] = [
'sign_in_log_id' => $signInLog->id,
'sign_in_id' => $defaultReward->id,
'reward_type' => $rule['reward_type'],
'reward_quantity' => $quantity,
'is_get' => 1,
'type' => 1,
'get_time' => date('Y-m-d H:i:s'),
'max_sign_in' => $userSign->max_sign_in,
'keep_sign_in' => $userSign->keep_sign_in,
];
$this->sendReward($signInLog->user_id, $rule['reward_type'], $quantity,
['signInLog'=>$signInLog]);
}
}
// 额外签到奖品
if (isset($extraReward)) {
$data[] = [
'sign_in_log_id' => $signInLog->id,
'sign_in_id' => $extraReward->id,
'reward_type' => 0,
'reward_quantity' => 0,
'is_get' => 0,
'type' => 2,
'get_time' => null,
'max_sign_in' => $userSign->max_sign_in,
'keep_sign_in' => $userSign->keep_sign_in,
];
}
SignInRewardLogBaseModel::insert($data);
}
获取额外的奖励以及发放奖励,比如今天你是连续签到的7天的,那你除了获取到积分外,还可以获取到系统设置的红包奖励等。
/**
* 发放签到奖励
* @param $uid int 用户ID
* @param $type int 类型
* @param $quantity float 数量
* @param $extra array 额外参数
*/public function sendReward($uid, $type, $quantity, $extra=[])
{
$related_id = empty($extra['signInLog']) ? 0 : $extra['signInLog']->id;
// 奖励类型(1.积分 2.红包 3.桶装水 4.5100)
switch ($type) {
case 1:
$params = [
'user_id' => $uid,
'change_quantity' => $quantity,
'related_type' => 25,
'related_id' => $related_id,
'remark' => '签到奖励'
];
Recharge::sendOf("PointsRecharge", $params);
break;
case 2:
$openid = WeixinUserBaseModel::where('user_id', $uid)
->where('weixin_ config _id', config('common.weixin_config_id.zmqcenter'))->first();
if (empty($openid)) {
$id = config('common.weixin_config_id.zmqcenter');
throw new Exception("无法找OPENID,user_id={$$uid},weixin_config_id={$id}");
}
$params = [
'weixin_config_id' => config('common.weixin_config_id.zmqcenter'),
'amount' => $quantity,
'openid' => $openid->wxid,
'desc' => '签到奖励红包:'.$quantity.'元',
'trade_no'=> 'qiandao'.$related_id.substr(uniqid(time()), 0, 8),
];
Recharge::sendOf("RedEnvelopeRecharge", $params);
break;
case 3:
$params = [
'user_id' => $uid,
'goods_id' => config('goods.goods_water_id'),
'change_quantity' => $quantity,
'related_type' => 101,
'related_id' => $related_id,
];
Recharge::sendOf("AccumulativeWaterReward", $params);
break;
case 4:
$params = [
'user_id' => $uid,
'goods_id' => config('goods.goods_5100_water_id'),
'change_quantity' => $quantity,
'related_type' => 101,
'related_id' => $related_id,
];
Recharge::sendOf("AccumulativeWaterReward", $params);
break;
}
}
至此,基本所有的核心代码都已贴出,根据需求来实现,主要还是要设计到数据表,这样才能做到事半功倍!
以上是本文的全部内容,希望对大家的学习有帮助,也希望大家多多支持 php自学中心 感谢阅读!