【微信开发】基于微信公众号的早起签到程序

版权声明:本文为博主原创文章。转载请联系博主授权。博主微信公众号【知行校园汇】。 https://blog.csdn.net/cxh_1231/article/details/90744494
  • 说明:本文讲述基于公众号实现的签到程序的全部开发过程。开发环境:PHP+MySQL。
  • 源码下载地址:敬请期待
  • 关注微信公众号【知行校园汇】可免费下载全部源码。
  • >>点击查看WUTer计算机专业实验汇总
  • 谨记:纸上得来终觉浅,绝知此事要躬行。

0 成果展示

本文较长,为了减少读者的时间,先展示本项目的成果图,以便读者快速确实这篇文章是不是正在寻找的文章。

这里有必要说明一下,项目中的前端页面配色配图等参考自网络公开的CSS样式表。

关于项目的详情,请见后文。

0.1 签到主页面:

用户点击公众号的菜单【早起签到】后,即实现自动微信登录,进入签到主页面。

主页面展示如下:

左图:未到签到时间页面      中图:签到页面      右图:签到完成后显示名次页面

主页面分为三个展示页面。未到签到时间(0:00到05:50期间)显示图如上左图所示。到签到时间时,按钮状态可点击,如上中图。签到完成后,将显示该用户在本公众号的当日签到名次。

0.2 签到记录页

用户签到后,可以查看本用户自己的签到记录。详情如下图所示:

左图:可兑换奖励签到记录和已过期签到记录  右图:已兑换签到记录

在本项目中,用户签到后,可以在本日或者次日的06:50至08:30到指定地点领取奖励。超过这个时间,本条签到记录即作废。三种状态的签到记录如上图所示。

0.3 后台兑换页

下图为后台兑换的测试页面。输入签到ID即可查询该签到记录。

左图:查询已过期签到记录      中图:查询已兑换签到记录      右图:查询可兑换签到记录

如上图。如果前来兑换的用户的签到记录已过期,则显示如上左图所示。如果用户的签到记录已兑换,则显示如上中图所示。如果用户的签到记录可兑换,则显示如上右图所示。

点击“立即兑换”按钮,则显示“兑换成功”弹窗,如下图所示。

兑换成功弹窗(微信Web开发者工具中运行)

看到这里,如果这不是你想要的项目,那么你可以关闭本篇博客了。

如果这是你想要的项目,或者本项目和你当前项目接近,或者想学习这个项目的编写,或者……

请继续往下看。下面正式开始^_^

1 项目背景与需求分析

1.1 项目背景

关于此需求分析部分,先从项目的背景说起吧。

起因是这样的。项目组要在公众号内举办一个活动。这个活动简单易懂,就是“早起签到领奖励”。每天早上指定时间开启签到系统,然后用户点击菜单栏的“早起签到”按钮,即可通过微信登录后,进入签到系统。

用户签到完成后,用户可以凭签到记录到指定地点领取早餐一份。同时每日的早餐限量,所以先到先得。同时签到记录也有兑换期限,本项目组指定的计划是本日或者次日的8:30前均可领取。所以今天签到,后天早上就领不到早餐了^_^

1.2 需求分析

通过对项目背景的分析,本项目的需求有如下几部分:

1、本项目为公众号项目,微信网页开发;

2、实现用户的微信登录授权,以确定用户身份,并实现单日只能签到一次;

3、用户签到后,记录签到时间以及当日签到名次;

4、显示用户签到记录,以及签到记录的状态(可兑换、已兑换、已过期);

5、后台管理员进行兑换确定。

2 概要设计

2.1 开发技术分析

既然是微信网页开发,并且要实现用户的登录,那么就需要学习一下微信的公众号网页授权机制

微信开放平台中有关于这方面的使用说明的介绍,可以通过开发者文档自行学习。链接如下:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

具体而言,网页授权流程分为四步:

1、引导用户进入授权页面同意授权,获取code

2、通过code换取网页授权access_token(与基础支持中的access_token不同)

3、如果需要,开发者可以刷新网页授权access_token,避免过期

4、通过网页授权access_token和openid获取用户基本信息(支持UnionID机制)

只有通过微信认证的服务号可以使用“网页授权”接口。

如果想用来进行开发测试,可以申请测试账号。申请链接如下:

https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

关于微信网页授权登录的详细过程,将在实践代码中(后边编码实现过程)进行讲解。

————————————————————

然后需要对开发语言进行分析确定。

微信网页可以采用任意Web开发语言实现。

但是结合本项目来说,需要对数据库的数据进行增删改查,所以最终采用的编程语言是PHP

(在此项目前,笔者未接触过PHP开发o(╥﹏╥)o,笔者熟悉的Web开发语言是Java Web。所以编码实现过程。如有不妥之处,多多包涵(*^▽^*))

最后数据库采用MySQL。

2.2 数据库设计

这个项目本身并不大,所以设计的数据库表单也很简单。

首先是用户表。

在本项目中,只需要获取用户对此公众号产生的唯一标识OpenID即可,并不需要获取用户的昵称、城市、头像等其他开放信息。如果需要用户的这些信息,可以对user表进行扩展。

用户表user的字段只有2列。如下图所示:

各个字段含义:

  • userid:用户在本签到系统中的用户ID,即项目中的唯一标识。主键。int型。
  • openid:通过微信授权接口获取的用户在此公众号所产生的唯一标识。int型。

然后是用户签到记录表。signin表单详情如下:

各个字段含义:

  • signid:用户签到记录的唯一标识。主键。int型。
  • userid:用户ID,为表user的外键。int型。
  • signdata:用户签到的时间戳,单位为ms,含义为1970年至今的时间秒数。int型。
  • signmon:签到的月份。int型。
  • signday:签到记录为本月的第几日。int型
  • signhour:签到记录的时。int
  • signmin:签到记录的分。int
  • mingci:本日签到名次(这里请忽略使用拼音表示Ծ‸Ծ主要是因为排名(rank)为SQL语言的关键字)int
  • isused:本签到记录是否已兑换奖品。是:1,否:0。
  • usetime:兑换时间,同为时间戳。(上图中此列拼写错误,可忽略。)

如果你的项目还需要其他表单,可自行设计。

3 开发环境配置

工欲善其事,必先利其器。进行开发之前,首先需要对开发环境等进行配置。

四个用到的软件

3.1 编程环境:

本项目使用PHP语言。所以编译程序使用的是JetBrains PhpStorm

PHPStorm为付费软件。如果你是学生用户,拥有教育邮箱,可以从JetBrains官网授权获取免费版。

3.2 编译环境:

项目编写过程,难免要进行调试。所以这里用到的PHP网页运行环境为phpStudy

此软件为免费软件。具体如何,可从phpStudy官网查询相关文档。

3.3 MySQL数据库可视化:

调试过程,难免需要对数据库中的数据进行核实检查。

笔者使用的MySQL数据库可视化工具为:Navicat for MySQL

此软件为付费软件。

3.4 网页调试:

如何对公众号网页进行调试?微信公众平台的开发者工具栏就给出了Web开发者工具:微信web开发者工具

如上图,点击【web开发者工具】,跳转到绑定开发者微信号页面。这里将开发者的微信号进行绑定。

 然后前往:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 下载微信web开发者工具电脑客户端。

下载安装后,运行后,需要开发者扫描登录才能运行。运行页面如下:

3.5 微信公众后台配置

如果直接将编写完成的项目配置到公众号的菜单栏,微信是不认可你这个项目的。

不过,你也不可能在不配置公众号后台的情况下编写出来这个程序O(∩_∩)O

所谓对公众号后台进行配置,就是获取公众号的秘钥,以及将域名添加到公众号接口白名单的过程。

具体如下:

3.5.1 配置域名

项目最终是需要使用域名进行访问的。并且域名必须启用SSL证书(HTTPS协议)

至于如何在PHPStudy中配置SSL证书,笔者前面写过,点击此处查看。

这里讲述的是将域名加入到公众号的“白名单”中

首先进入公众号后台,点击【设置】>>【公众号设置】>>【功能设置】,如下图所示:

点击上图红框中的两个模块的设置,即可将域名添加授权

为了核验你对这个域名拥有所有权,需要将指定的文件上传到域名服务器的目录中进行校验。如下图所示:

具体配置过程,根据上图提示进行配置即可。

微信公众号支持配置2个网页授权域名,和3个JS接口安全域名。

3.5.2 配置IP白名单

本项目需要用到access_token接口,所以需要将最终部署网站的服务器的IP地址配置到IP白名单中!

也就是将域名所解析到的IP地址配置到公众号IP白名单中(具体在域名解析列表中查看)。

点击微信公众平台后台的【开发】>>【基本配置】,即可进行IP白名单修改配置。

点击上图下面红框中的【查看】,即可对IP白名单进行查看与修改。 

3.5.3 获取开发者密码

开发者密码是校验公众号开发者身份的密码,具有极高的安全性。

点击微信公众平台后台的【开发】>>【基本配置】,即可对开发者密码进行重置获取

重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!

重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!

重要:这里需要将开发者ID(AppID)和开发者密码(AppSecret)记录下来!

4 编码实现

都说PHP是一门松散的语言。笔者也是第一次接触PHP,并使用PHP来实现这个项目……总体感觉,PHP挺好用\(^o^)/~

项目本身就不大,客户端一共2个页面,所以也就不采用什么框架还是MVC开发模式了(下面模仿MVC开发模式)

4.1 模型(Model)层DAO类代码:

首先需要新建一个Dao类,这个类实现的是构造Dao类,以实现与数据库中的数据进行打交道。

通俗的将,就是实现了对数据库的增删改查,所以的SQL语句均在这里执行。

文件名:dao.php

详细代码以及注释如下所示:

<?php
//模型层,实现与数据库的数据交换
//设置时区
date_default_timezone_set('Asia/Chongqing');
//忽略错误警告
error_reporting(0);
Class Dao{

    /**
     * constructor:构造函数
     */
    function __construct()
    {
        //这里换成你自己的数据库信息,如服务器地址127.0.0.1,登录名root,密码123456,数据库名qiandao,端口号3306
        $this->conn=mysqli_connect("127.0.0.1","root","123456","qiandao","3306");
        mysqli_query($this->conn,"SET NAMES gbk");
    }

    /**
     * __destruct:析构函数
     */
    function __destruct()
    {
        // TODO: Implement __destruct() method.
        mysqli_close($this->conn);
    }

    /**
     *  根据用户的OpenID查询该用户是否已登录注册
     *  输入:用户OpenID
     *  返回值:如果注册,返回用户userID,否则,返回0
     */
    public function getUser($openid){
        $sql="SELECT userid FROM user WHERE openid='".$openid."'";
        $results = $this->conn->query($sql);
        if($row = $results->fetch_row()){
            return $row[0];//返回用户userID
        }else{
            return 0;//返回0
        }
    }

    /**
     *  查询当前数据库中的userID最大值
     *  返回:最后注册的用户的userID
     */
    public function getMaxUserID(){
        $sql="SELECT MAX(userid) FROM user";
        $results = $this->conn->query($sql);
        if($row = $results->fetch_row()){
            return $row[0];//返回用户userID
        }else{
            return 10000;//返回初始值10000
        }
    }

    /**
     *  为该用户创建新用户userID
     *  输入:用户OpenID
     *  返回:成功true  或者失败 false
     */
    public function createID($userid,$openid){
        $sql="INSERT INTO user(userid,openid) VALUES('".$userid."','".$openid."')";
        $this->conn->query($sql);
    }

    /**
     *  获取目前签到日(signday)中的排名最大值(rank)
     *  如果查询为空,说明当日没有签到的用户,返回:0
     *  返回:0  或者最大值
     */
    public function getMaxRank(){
        $now=getdate(date("U"));
        $day = $now[mday];
        $sql="SELECT MAX(mingci) FROM signin where signday='".$day."'";
        $results = $this->conn->query($sql);
        if($row = $results->fetch_row()){
            //print $row[0];
            return $row[0];//返回最大值
        }else{
            return 0;//返回0
        }
    }

    /**
     *  获取当前签到表中签到ID最大值
     *  返回:最大签到ID signid
     */
    public function getMaxSignID(){
        $sql="SELECT MAX(signid) FROM signin";
        $results = $this->conn->query($sql);
        if($row = $results->fetch_row()){
            return $row[0];//返回最大值
        }else{
            return 10000;//返回0
        }

    }

    /**
     *  将签到记录保存至数据库
     *  输入:signid  userid  签到时间signdata  签到日signday  签到排名rank  isuserd默认为0
     */
    public function saveSign($signid,$userid,$signdata,$signmon,$signday,$signhour,$signmin,$mingci,$isused,$usertime){
        $sql="INSERT INTO signin(signid,userid,signdata,signmon,signday,signhour,signmin,mingci,isused,usertime) VALUES('".$signid."','".$userid."','".$signdata."','".$signmon."','".$signday."','".$signhour."','".$signmin."','".$mingci."','".$isused."','".$usertime."')";
        try{
            $this->conn->query($sql);
        }catch (Exception $exception){
            print $exception->getMessage();
            return false;
        }
        return true;
    }

    /**
     *  查询指定用户的当日签到记录
     */
    public function judgesign($userid){
        $now=getdate(date("U"));
        $day = $now[mday];
        $sql="SELECT mingci FROM signin WHERE userid='".$userid."' AND signday='".$day."'";
        $results = $this->conn->query($sql);
        if($row = $results->fetch_row()){
            return $row[0];//返回本日的签到名次
        }else{
            return 0;//返回0,表示名次为0,即未签到
        }
    }

    /**
     *  查询所有的签到记录
     *  返回:结果集
     */
    public function getSign($userid){
        $sql = "SELECT signid,signdata,mingci,isused,usertime,signday,signhour,signmin FROM signin WHERE userid='".$userid."' ORDER BY signdata DESC";
        $results = $this->conn->query($sql);
        return $results;//返回结果集
    }

    /**
     *  查询指定签到ID的签到信息
     */
    public function getUserSignInfo($usersignid){
        $sql = "SELECT signid,signdata,mingci,isused,usertime,signday,signhour,signmin FROM signin WHERE signid='".$usersignid."'";
        $results = $this->conn->query($sql);
        return $results;
    }

    /**
     * 更新指定签到ID的签到信息(兑换、兑换时间等)
     */
    public function updateSignInfo($usetime,$signid){
        $sql = "UPDATE signin SET isused='1',usertime='".$usetime."' WHERE signid='".$signid."'";
        try{
            $this->conn->query($sql);
        }catch (Exception $exception){
            print $exception->getMessage();
            return false;
        }
        return true;
    }
}

上面代码中,细节方面就不纠结了,保证正确的情况下,能实现功能即可。其实应该对数据库操作相关的语句进行Try-Catch判断……

4.2 控制(Control)层control类代码:

这个类文件中实现的是从前端获取用户的数据,如时间等信息,控制签到程序,然后将数据发送到模型层,对前端和数据库操作进行控制,起到中介的作用。

文件名:control.php

详细代码及注释如下所示:

<?php
include ('dao.php');
session_start();
class control{

    /**
     *  用户登录时执行的操作
     *  新用户:分配ID
     *  旧用户:直接获取ID
     */
    public static function login($openid){

        if(empty($openid))
            return 0;

        $dao = new Dao();
        $results = $dao->getUser($openid);          //新用户:返回0,老用户:返回userID

        //新用户
        if($results === 0){
            $userid = $dao->getMaxUserID() + 1 ;    //计算新用户ID
            $dao->createID($userid,$openid);        //创建新用户
            return $userid;                         //返回用户userID
        }
        //老用户
        else{
            return $results;                        //返回用户userID
        }
    }

    /**
     *  获取当前用户当日是否已经签到,即用户的名次
     */
    public static function issign(){
        $dao = new Dao();
        $mingci = $dao->judgesign($_SESSION["userid"]);
        return $mingci;
    }

    /**
     *  获取当前用户签到记录
     */
    public static function getMySign(){
        $dao = new Dao();
        $results = $dao->getSign($_SESSION["userid"]);
        return $results;
    }

    /**
     *  获取当前指定签到ID的签到信息
     */
    public static function getSignInfo(){
        $dao = new Dao();
        $signid = $_SESSION["usersignid"];
        $results = $dao->getUserSignInfo($signid);
        return $results;
    }

    /**
     *  进行兑换
     */
    public static function duiHuan(){
        $usedata = microtime(true);//时间戳
        $usersignid = $_SESSION["usersignid"];
        $dao = new Dao();
        $success = $dao->updateSignInfo($usedata,$usersignid);
        return $success;
    }

}

4.3 授权接口调用weixin类

下面这篇开发者文档给出了获取用户OpenID,以及用户个人信息的几个步骤:

https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140842

建议读者认真阅读开发者文档中的这篇文章,然后再往下看。

4.3.1 获取(或者说是创建)用户授权链接

用户点击公众号菜单栏后,如果用户未登录,需要引导用户前往确认授权页面。

用户点击这个链接,打开的授权页面是这样的:

这里的关键代码如下所示:

    /**
     * 第1步: 获取用户授权code url
     * @param string $scope 授权作用域:snsapi_base or snsapi_userinfo,这里选择base
     * @param string $state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值
     * @param string $redirect_url 重定向URL
     * @return string
     */
    public static function createCodeUrl($scope,$state,$redirect_url){
        $open_url = 'https://open.weixin.qq.com';
        $redirect_url = urlencode($redirect_url);//这里必须对链接进行处理,即连接中的斜线转换成字符
        //参考链接示例:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
        $url = $open_url.'/connect/oauth2/authorize?appid='.APPID.'&redirect_uri='.$redirect_url.'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
        return $url;
    }

return语句前面一行的字符串中的几个变量值说明一下:

  • APPID:这是开发者ID,本文前面3.3.5节记录的就是这个,也是公众号的唯一标识;
  • redirect_uri:这是用户允许公众号获取信息后,继续跳转到的页面;
  • scope:这是公众号应用授权的作用域。这个变量只有两个值:①值为snsapi_base时,只获取用户的OpenID,不获取用户的昵称、头像等信息,如果scope=snsapi_base,则不会出现上图的授权页面,即实现的是静默授权,最多用户的屏幕会显示“正在登录……”几个字。②值为snsapi_userinfo 时,弹出授权页面,可通过openid拿到昵称、性别、所在地。并且, 即使在未关注公众号的情况下,只要用户授权,也能获取其信息。
  • state:重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值,最多128字节。这个参数根据项目实际进行赋值。

如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。

在开发者工具中可以清晰的看到这些信息:

其中code的值为换取access_token的票据。每次用户授权后,code值均不同,code只能使用一次,有效期5分钟。

4.3.2 通过code换取网页授权access_token,从Token中读取用户OpenID

这部分的关键代码如下:

    /**
     * 第2步:  获取用户授权access_token
     * @param type $code 授权时获得code值
     * @return type
     */
    public static function getAuthToken($code){
        //参考链接示例:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        $url = self::API_URL.'/sns/oauth2/access_token?appid='.APPID.'&secret='.APPSECRET.'&code='.$code.'&grant_type=authorization_code';
        $content = curl_get( $url );
        $ret = json_decode($content, true );
        return self::getResult( $ret ) ? $ret : null;
    }

这里链接中用到的三个变量的说明如下:

  • APPID:公众号的唯一标识,同上;
  • secret:公众号的appsecret,前面3.5.3节让记下的一串字符;
  • code:授权时跳转的链接中带的参数。

如果函数执行正确,返回的数据为一个JSON数据包,可以简单理解为C++中的结构体。开发者文档的解释如下:

这里边就包含用户的唯一标识:OpenID

在本项目中,进行到这里就结束了。

4.3.3 完整代码

如果你还想继续获取用户的昵称、头像等公开信息,请看完整代码。

各个过程就是请求链接、获取JSON包的过程。

文件名:weixin.class.php

<?php

//下面是全局变量的几个值
define('APPID','这里换成你自己的APPID');//
define('APPSECRET','这里换成你自己的APPSECRET');
//define('open_url','https://open.weixin.qq.com');

class weixin extends wxcommon{

    /**
     * 第1步: 获取用户授权code url
     * @param string $scope 授权作用域:snsapi_base or snsapi_userinfo,这里选择base
     * @param string $state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值
     * @param string $redirect_url 重定向URL
     * @return string
     */
    public static function createCodeUrl($scope,$state,$redirect_url){
        $open_url = 'https://open.weixin.qq.com';
        $redirect_url = urlencode($redirect_url);
        //参考链接示例:https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
        $url = $open_url.'/connect/oauth2/authorize?appid='.APPID.'&redirect_uri='.$redirect_url.'&response_type=code&scope='.$scope.'&state='.$state.'#wechat_redirect';
        return $url;
    }

    /**
     * 第2步:  获取用户授权access_token
     * @param type $code 授权时获得code值
     * @return type
     */
    public static function getAuthToken($code){
        //参考链接示例:https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
        $url = self::API_URL.'/sns/oauth2/access_token?appid='.APPID.'&secret='.APPSECRET.'&code='.$code.'&grant_type=authorization_code';
        $content = curl_get( $url );
        $ret = json_decode($content, true );
        return self::getResult( $ret ) ? $ret : null;
    }

    /**
     * 第3步:刷新用户授权access_token
     * @param type $refresh_token 用户刷新access_token
     * @return type
     */
    public static function refershAuthToken($refresh_token){
        //参考链接示例:https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=APPID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN
        $url = self::API_URL.'/sns/oauth2/refresh_token?appid='.APPID.'&grant_type=refresh_token&refresh_token='.$refresh_token;
        $content = curl_get( $url );
        $ret = json_decode($content, true );
        return self::getResult( $ret ) ? $ret : null;
    }

    /**
     * 第4步: 获取用户基本信息
     * @access_token 网页授权接口调用凭证,注意:此access_token与基础支持的access_token不同
     * @param type $openid 普通用户的标识,对当前公众号唯一
     * @param string $lang 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语
     * @return type
     */
    public static function getUserInfoByID( $access_token, $openid, $lang='zh_CN' ){
        if( !$lang ) $lang = 'zh_CN';
        //$access_token = self::getToken();
        //参考链接示例:https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
        $url = self::API_URL . "/sns/userinfo?access_token={$access_token}&openid={$openid}&lang={$lang}";
        $ret = json_decode(curl_get( $url ), true );
        return self::getResult( $ret ) ? $ret : null;
    }

}

/**
 * GET方式获取服务器响应
 * @param {string} $url
 * @return {string|boolen} 成功时返回服务器响应内容,失败则返回false
 */
function curl_get( $url ){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);;
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
    if(!curl_exec($ch)){
        error_log( curl_error ( $ch ));
        $data = '';
    } else {
        $data = curl_multi_getcontent($ch);
    }
    curl_close($ch);
    return $data;
}

/**
 *微信通用接口
 */
class wxcommon{
    const API_URL  = 'https://api.weixin.qq.com';
    private static $access_token;
    private static $expries_time = 0;

    /**
     * 用于获取AccessToken。如成功返回AccessToken,失败返回false
     */
    public static function getToken(){
        if(isset(self::$access_token) && time() < self::$expries_time){
            return self::$access_token;
        }
        $url = self::API_URL."/cgi-bin/token?grant_type=client_credential&appid=".APPID."&secret=".APPSECRET;
        $content=curl_get($url);
        $ret=json_decode($content,true);//{"access_token":"ACCESS_TOKEN","expires_in":7200}
        if(array_key_exists('errcode',$ret) && $ret['errcode'] != 0){
            return false;
        }else{
            self::$access_token = $ret['access_token'];
            self::$expries_time = time() + intval($ret['expires_in']);
            return self::$access_token;
        }
    }
    public static function getResult($ret) {
        if(!is_array($ret) || !array_key_exists('errcode',$ret)){
            return $ret;
        }
        $errcode = intval($ret['errcode']);
        if(in_array($errcode, self::$ERRCODE_MAP)){
            if($errcode == 0){
                return true;
            }
            return array('errcode' => $errcode, 'errinfo' => self::$ERRCODE_MAP[$errcode]);
        }
        return array('errcode'=>'-2','errinfo'=>'未知错误');
    }
    static $ERRCODE_MAP = array(
        '-1' => '系统繁忙',
        '0' => '请求成功',
        '40001' => '获取access_token时AppSecret错误,或者access_token无效',
        '40002' => '不合法的凭证类型',
        '40003' => '不合法的OpenID',
        '40004' => '不合法的媒体文件类型',
        '40005' => '不合法的文件类型',
        '40006' => '不合法的文件大小',
        '40007' => '不合法的媒体文件id',
        '40008' => '不合法的消息类型',
        '40009' => '不合法的图片文件大小',
        '40010' => '不合法的语音文件大小',
        '40011' => '不合法的视频文件大小',
        '40012' => '不合法的缩略图文件大小',
        '40013' => '不合法的APPID',
        '40014' => '不合法的access_token',
        '40015' => '不合法的菜单类型',
        '40016' => '不合法的按钮个数',
        '40017' => '不合法的按钮个数',
        '40018' => '不合法的按钮名字长度',
        '40019' => '不合法的按钮KEY长度',
        '40020' => '不合法的按钮URL长度',
        '40021' => '不合法的菜单版本号',
        '40022' => '不合法的子菜单级数',
        '40023' => '不合法的子菜单按钮个数',
        '40024' => '不合法的子菜单按钮类型',
        '40025' => '不合法的子菜单按钮名字长度',
        '40026' => '不合法的子菜单按钮KEY长度',
        '40027' => '不合法的子菜单按钮URL长度',
        '40028' => '不合法的自定义菜单使用用户',
        '40029' => '不合法的oauth_code',
        '40030' => '不合法的refresh_token',
        '40031' => '不合法的openid列表',
        '40032' => '不合法的openid列表长度',
        '40033' => '不合法的请求字符,不能包含\uxxxx格式的字符',
        '40035' => '不合法的参数',
        '40038' => '不合法的请求格式',
        '40039' => '不合法的URL长度',
        '40050' => '不合法的分组id',
        '40051' => '分组名字不合法',
        '41001' => '缺少access_token参数',
        '41002' => '缺少appid参数',
        '41003' => '缺少refresh_token参数',
        '41004' => '缺少secret参数',
        '41005' => '缺少多媒体文件数据',
        '41006' => '缺少media_id参数',
        '41007' => '缺少子菜单数据',
        '41008' => '缺少oauth code',
        '41009' => '缺少openid',
        '42001' => 'access_token超时',
        '42002' => 'refresh_token超时',
        '42003' => 'oauth_code超时',
        '43001' => '需要GET请求',
        '43002' => '需要POST请求',
        '43003' => '需要HTTPS请求',
        '43004' => '需要接收者关注',
        '43005' => '需要好友关系',
        '44001' => '多媒体文件为空',
        '44002' => 'POST的数据包为空',
        '44003' => '图文消息内容为空',
        '44004' => '文本消息内容为空',
        '45001' => '多媒体文件大小超过限制',
        '45002' => '消息内容超过限制',
        '45003' => '标题字段超过限制',
        '45004' => '描述字段超过限制',
        '45005' => '链接字段超过限制',
        '45006' => '图片链接字段超过限制',
        '45007' => '语音播放时间超过限制',
        '45008' => '图文消息超过限制',
        '45009' => '接口调用超过限制',
        '45010' => '创建菜单个数超过限制',
        '45015' => '回复时间超过限制',
        '45016' => '系统分组,不允许修改',
        '45017' => '分组名字过长',
        '45018' => '分组数量超过上限',
        '46001' => '不存在媒体数据',
        '46002' => '不存在的菜单版本',
        '46003' => '不存在的菜单数据',
        '46004' => '不存在的用户',
        '47001' => '解析JSON/XML内容错误',
        '48001' => 'api功能未授权',
        '50001' => '用户未授权该api',
    );
}

上面这个代码是笔者从一本参考书中摘抄的,可以直接拿来使用。

但是需要将前两行的APPID和APPSECRET补充完整

4.4 签到主页面设计

主页面主要是HTML代码,以及加载HTML代码前执行的几段PHP代码。

这里我们要想清楚几个问题:

用户真正能够授权,后台并且能够拿到code的链接长这样:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http%3A%2F%2Fnba.bluewebgame.com%2Foauth_response.php&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect
//注:这是微信开发者文档给出的实例链接

但是这样的链接,太长,看起来就不舒服。

我们正常理解的,我们能接受的公众号网页链接,应该是这样的:

https://wx.yourdomain.com/index.php

所以这就需要在index.php中进行控制。

这里笔者简单说一下笔者的思路

if(没有创建客户端到服务端的session["userid"]){    //说明是第一次访问,session中没有userid这个字段
    if(GET到了链接中的state值){                 //说明已经拿到code
        //通过code获取Token
        //解析Token获取openid
        //将openid与数据库即有数据对比,获取用户userid
        //获取其他信息
    }
    else{
        //跳转到授权链接,就是前面那个很长的链接
    }
else{        //反之就是已经创建包含userid的session,用于已经登录,则可以获取网页中需要的信息
    //获取其他信息
}

所以这里的关键代码,笔者是这样写的:

/**
 *  首次访问进行登录
 */
//从登录链接返回的两个参数:code和state,其中state用来获取用户的OpenID,state用来判断是否是首次打开页面
//如果没有设置全局session变量userID,执行if内函数
if(!isset($_SESSION["userid"])){
    if(isset($_GET["state"])){
        $token=weixin::getAuthToken($_GET['code']);                 //根据请求链接获取返回的Token,其中包含access_token
        $_SESSION["userid"] = control::login($token['openid']);     //根据返回的JSON包,将用户的OpenID进行登录验证,并拿到用于的userID,保存到session中。
        $_SESSION["issign"] = control::issign();                    //拿到用户的登签到名次,未签到为0
    }else{
        $url = weixin::createCodeUrl("snsapi_base","123","https://wx.XXX.com/index.php");
        header("location:$url");
    }
}
else{
    $_SESSION["issign"] = control::issign();                    //拿到用户的登签到名次issign,未签到为0
}

完整代码,以及代码详解如下:

文件名:index.php

<?php
//设置时区
date_default_timezone_set('Asia/Chongqing');
session_start();
/**
 * 本项目中使用session全局数组保存用户名,以及用户的签到名次
 * 当然也可以改用cookie
 * */

//加载几个引用的文件
require 'lib/weixin.class.php';
require 'lib/control.php';

/**
 *  首次访问进行登录
 */
//从登录链接返回的两个参数:code和state,其中state用来获取用户的OpenID,state用来判断是否是首次打开页面
//如果没有设置全局session变量userID,执行if内函数
if(!isset($_SESSION["userid"])){
    if(isset($_GET["state"])){
        $token=weixin::getAuthToken($_GET['code']);                 //根据请求链接获取返回的Token,其中包含access_token
        $_SESSION["userid"] = control::login($token['openid']);     //根据返回的JSON包,将用户的OpenID进行登录验证,并拿到用于的userID,保存到session中。
        $_SESSION["issign"] = control::issign();                    //拿到用户的登签到名次,未签到为0
    }else{
        $url = weixin::createCodeUrl("snsapi_base","123","https://wx.XXX.com/index.php");
        header("location:$url");
    }
}
else{
    $_SESSION["issign"] = control::issign();                    //拿到用户的登签到名次,未签到为0
}

//下面是获取用户的信息,没用到……
//$userinfo=weixin::getUserInfoByID($token['access_token'],$token['openid'],'zh_CN');//根据access_Token获取用户的OpenID
//$nickname = $userinfo["nickname"];
//$usersex = $userinfo["sex"];
//………………

?>

<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0,viewport-fit=cover">
    <title>早起签到</title>
    <link rel="stylesheet" href="css/style.css">
</head>

<body>

<div class="banner2"><img src="images/banner.jpg" class="img-responsive"></div>

        <?php
        $now=getdate(date("U"));    //当前时间
        $hour = $now[hours];    //签到时
        $min = $now[minutes];   //签到分
        //如果未到签到时间,则不能签到!
        if($hour< 6 || ($hour==6 && $min<50) ){
        ?>
            <form name="sign" action="sign.php">
                <div id="fromBox" class="fromBox">
                    <button id="btn-qiandao" class="btn3" disabled="true">5:50开启今日签到</button>
                </div>
            </form>
        <?php
        }
        //如果用户未签到,显示可签到
        else if($_SESSION["issign"]==0) {
        //if($issign===0) {
        ?>
        <form name="sign" action="sign.php">
            <div id="fromBox" class="fromBox">
                <button id="btn-qiandao" class="btn3">立即签到</button>
            </div>
        </form>
        <?php
        }
        //用户已经签到,显示签到名次
        else{
        ?>
        <form name="sign">
            <div id="fromBox" class="fromBox">
                <button id="btn-qiandao" class="btn3" disabled="true">今日签到名次:<?php echo $_SESSION["issign"]; ?> </button>
            </div>
        </form>
        <?php
        }
        ?>


<p align="right"><a href="mysign.php" style="text-decoration: none">>>我的签到记录  </a></p>

<div class="pt10lr10 mt10">
    <div class="pline"></div>
    <div class="prizebox">
        <div class="ptitle"><strong>活动详情</strong></div>
        <div class="" id="demo" style="">
            <table style="font-size: 13px;color: #b25d06">
                <tr>
                    <td width="6PX" valign="top" align="right">1、</td><td>每日05:50至23:59开启早起签到。</td>
                </tr>
                <tr>
                    <td valign="top" align="right">2、</td><td>其他内容……</td>
                </tr>
                <tr>
                    <td valign="top" align="right">3、</td><td>其他内容……</td>
                </tr>
            </table>
            <br>
        </div>
    </div>
</div>
<div align="center">
    <a style="font-size: 10px;color: #ff820b;text-decoration: none" href="https://mp.weixin.qq.com/mp/profile_ext?action=home&__biz=MzIwODkwOTg5Nw==&scene=124#wechat_redirect">技术支持:@拾年之璐</a>
</div>
</body>

这个主页面用到的CSS文件,来自网络,篇幅较长,将在文末展示

这里注意上面代码中的“立即签到”按钮所在的form表单中,action是跳转到sign.php文件,这里要注意!

所以需要对sign.php文件进行编写。这个文件,就是个控制页面,以及跳转页面。详细代码如下:

文件名:sign.php

<?php
    header("Content-type:text/html;charset=UTF-8");
    error_reporting(0);
    include('lib/dao.php');

//下面代码完全可以放在control.php文件中实现,然后此处调用!
    session_start();
    $dao = new Dao();
    //获取签到的基本信息
    $signid = $dao->getMaxSignID()+1;//签到ID
    $userid=$_SESSION["userid"];//用户ID
    $signdata = microtime(true);//时间戳
    $now=getdate(date("U"));    //当前时间
    $signmon = $now[mon];     //签到月份
    $signday = $now[mday];      //签到日
    $signhour = $now[hours];    //签到时
    $signmin = $now[minutes];   //签到分
    $mingci = $dao->getMaxRank()+1;//签到名次
    $isused = 0;        //是否兑换
    $usertime = 0;      //兑换时间
    $success = $dao->saveSign($signid,$userid,$signdata,$signmon,$signday,$signhour,$signmin,$mingci,$isused,$usertime);
//上面代码完全可以放在control.php文件中实现,然后此处调用!

    //签到名次保存到session中!
    //setcookie("issign",$mingci,time()+60*60*24); //有效期24个小时,弃用cookie
    $_SESSION["issign"] = $mingci;

    if($success === true) {
       echo "<script charset='UTF-8' language='javascript' type='text/javascript'> { window.alert('签到成功!')};  setTimeout( window.parent.location.href='index.php',2000); </script>";
        //echo "<script> {window.alert('签到成功!')} </script>";
    }
    else{
        echo "<script charset='UTF-8'language='javascript' type='text/javascript'> {window.alert('签到失败,请联系管理员!')} ;window.parent.location.href='index.php'</script>";
    }

4.5 我的签到记录页面

前面index.php代码中,有一个“我的签到记录”按钮,是个超链接的形式,所跳转的页面是mysign.php。详细代码如下:

文件名:mysign.php

<?php
require 'lib/control.php';
$results = control::getMySign();//得到签到记录集合
$exist = false;//是否存在签到记录

//时间戳转换成标准时间格式
function get_microtime_format($time)
{
    if(strstr($time,'.')){
        sprintf("%01.3f",$time); //小数点。不足三位补0
        list($usec, $sec) = explode(".",$time);
        $sec = str_pad($sec,3,"0",STR_PAD_RIGHT); //不足3位。右边补0
    }else{
        $usec = $time;
        $sec = "000";
    }
    $date = date("Y-m-d H:i:s.x",$usec);
    return str_replace('x', $sec, $date);
}

?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>我的签到记录</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="wrap">
    <div class="banner2"><img src="images/banner.jpg"/></div>

    <div  class="tabsbox tabsbox2">
        <div class="title1">我的签到记录</div>

        <?php
        while($row = $results->fetch_row()) {
            $exist = true;
        ?>
        <div class="Prize">
            <p>签到ID:<?php echo $row[0]; ?>    当日签到名次:<?php echo $row[2]; ?></p>
            <div>签到时间:<?php echo get_microtime_format($row[1]);
            $now=getdate(date("U"));    //当前时间
            $nowday = $now[mday];      //当前日
            $nowhour = $now[hours];    //当前时
            $nowmin = $now[minutes];   //当前分

            //1、昨天5.5.到24.00签到的用户,今天8.30前可兑换
            //2、今天5.50到8.30之间签到的用户,可在今天8.30前兑换,或者明天兑换
            if(($row[3]==0 && $row[5] == ($nowday-1) && $nowhour<=8 && $nowmin<=30) || ($row[3]==0 && $row[5] == $nowday) ){
                ?><a style="font-weight: bold;color:green">  可兑换</a></div><?php
            }
            else if($row[3]==1 ){
                ?><a style="font-weight: bold;color:red">  已兑换</a></div>
                <div>兑换时间:<?php echo get_microtime_format($row[4]); ?></div>
                <?php
            }
            else{
                ?><a style="font-weight: bold;color:orange">  已过期</a></div><?php
            }
            ?>
        </div>
        <?php
        }
        if($exist === false){?>
        <div class="Prize">
            <div align="center">您还没有签到记录,快去签到吧^_^</div>
        </div>
        <?php
        }
        ?>

    </div>
    <a href="index.php" class="btn4" style="text-decoration: none">返回</a>
</div>
</body>
</html>

4.6 后台兑换页面

后台兑换页面就是一个很简单的HTML页面。其中主要由两个PHP文件组成:

主页面文件名:search.php

完整代码:

<?php

require 'lib/control.php';
//测试用
//$_SESSION["usersignid"] = 10005;
//↑ 测试用
$exist = false;
if(isset($_REQUEST["usersignid"])){
    $usersignid2 = $_REQUEST["usersignid"];
    $_SESSION["usersignid"] = $usersignid2;
}
if(isset($_SESSION["usersignid"])){
    $result = control::getSignInfo();
}
else{
    $result = 0;
}
//时间戳转换
function get_microtime_format($time)
{
    if(strstr($time,'.')){
        sprintf("%01.3f",$time); //小数点。不足三位补0
        list($usec, $sec) = explode(".",$time);
        $sec = str_pad($sec,3,"0",STR_PAD_RIGHT); //不足3位。右边补0
    }else{
        $usec = $time;
        $sec = "000";
    }
    $date = date("Y-m-d H:i:s.x",$usec);
    return str_replace('x', $sec, $date);
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>后台查询系统</title>
    <link rel="stylesheet" href="css/style.css">
    <style type="text/css">
        input{
            border: 1px solid #ccc;
            padding: 10px 0px;
            border-radius: 5px; /*css3属性IE不支持*/
            padding-left:10px;
            width: 150px;
        }
        .btn5{  display:block;
            border:0px;
            margin:0rem auto 0% auto;
            width: 94%;
            background-color: #ef2122;
            text-align: center;
            font-weight: bold;
            font-size:17px ;
            color: #fff3f0;
            border-radius: 10px;
        }
    </style>
    <script type="text/javascript">
        function doAction() {
            var usersignid = document.getElementById("usersignid");
            window.location.href = "a.php?usersignid="+ usersignid.value;
        }        
    </script>
</head>

<body>
<div>
    <br><br><br>
</div>
<form action="a.php" method="post">
    <table align="center" width="85%">
        <tr>
            <td width="40%">
                <input placeholder="请输入签到ID" type="text" tabindex="1" name="usersignid" id="usersignid" required autofocus autocomplete="on">
            </td>
            <td width="40%">
                <input class="btn5" type="button" value="立即查询" onclick="doAction()">
            </td>
        </tr>
    </table>
</form>
<div>
    <br><br><br><br>
</div>

<div id="wrap">
    <div  class="tabsbox tabsbox2">
        <div class="title1">用户签到记录</div>
        <?php
        if(isset($_SESSION["usersignid"])){
            while($row = $result->fetch_row()) {
                $exist = true;
                ?>
                <div class="Prize">
                    <p>签到ID:<?php echo $row[0]; ?>    签到名次:<?php echo $row[2]; ?></p>
                    <div>签到时间:<?php echo get_microtime_format($row[1]);
                        $now=getdate(date("U"));    //当前时间
                        $nowday = $now[mday];      //当前日
                        $nowhour = $now[hours];    //当前时
                        $nowmin = $now[minutes];   //当前分

                        //1、昨天5.5.到24.00签到的用户,今天8.30前可兑换
                        //2、今天5.50到8.30之间签到的用户,可在今天8.30前兑换,或者明天兑换
                        if(($row[3]==0 && $row[5] == ($nowday-1) && $nowhour<=8 && $nowmin<=30) || ($row[3]==0 && $row[5] == $nowday) ){
                            ?><a style="font-weight: bold;color:green">  可兑换</a>
                            <br>
                            <br>
                            <a href="duihuan.php" class="btn4" style="text-decoration: none">立即兑换</a>
                            <?php
                        }
                        else if($row[3]==1){
                            ?><a style="font-weight: bold;color:red">  已兑换</a>
                            <div>兑换时间:<?php echo get_microtime_format($row[4]); ?></div>
                            <?php
                        }
                        else{
                                ?><a style="font-weight: bold;color:orangered">  已过期</a><?php
                        }?>
                </div>

                <?php
            }
        }
        if($exist === false){?>
            <div class="Prize">
                <div align="center">未查询到该签到记录!</div>
            </div>
            <?php
        }
        ?>
    </div>
</div>
</body>
</html>

兑换按钮文件名:submit.php

完整代码:

<?php
header("Content-type:text/html;charset=UTF-8");
require 'lib/control.php';

$success = control::duiHuan();

if($success === true) {
    echo "<script charset='UTF-8' language='javascript' type='text/javascript'> { window.alert('兑换成功!')};  setTimeout( window.parent.location.href='a.php',2000); </script>";
    //echo "<script> {window.alert('签到成功!')} </script>";
}
else{
    echo "<script charset='UTF-8'language='javascript' type='text/javascript'> {window.alert('兑换失败,请联系管理员!')} ;window.parent.location.href='a.php'</script>";
}

 至此,本项目的关键代码展示完毕。

本文结束。

5 参考文献:

[1] 软件开发技术联盟编著.PHP+MySQL开发实战[M].北京:清华大学出版社.2013.

[2] 刘乃琦,李忠主编.PHP和MySQL Web应用开发[M].北京:人民邮电出版社.2013.
[3] 于荷云编著.PHP+MySQL网站开发全程实例[M].北京:清华大学出版社.2012.
[4] 易伟著.微信公众平台服务号开发 揭秘九大高级接口[M].北京:机械工业出版社.2014.
[5] 席新亮编著.微信公众平台JSSDK开发实战 公众号与HTML5混合模式揭秘[M].北京:电子工业出版社.2015.
[6] 闫小坤,周涛.微信公众平台应用开发实践[M].北京:清华大学出版社.2017.
[7] 闫小坤,周涛著.微信公众平台应用开发从入门到精通[M].北京:清华大学出版社.2015.
[8] 张暑军主编.基于HTML 5的APP开发教程[M].北京:北京理工大学出版社.2016.

6 附:style.css文件代码 

注:此文件来自网络!

文件名:style.css

完整代码:

@charset "UTF-8";
h1,h2,h3,h4,h5,h6,span,p,a,.btn,input,select,textarea,div{ font-weight: normal; font-family: "Microsoft YaHei",微软雅黑,"MicrosoftJhengHei",华文细黑,STHeiti,MingLiu,Helvetica Neue, Helvetica, Arial, sans-serif ;}
ul,li{list-style: none;margin:0;padding:0;}
a{ color: #323232;}
a:hover{ color: #323232; text-decoration: none;}
.w100{width:100%;}
body{background-color:#faca34}

/*背景颜色*/
.bodybg{background:#f7f7f7;}
.bgbody{background:#eeeff3;}
.bg-white{background:#fff;}
.bg-red{background:#e3393a;}
.bgmainy{background-color:#ffa15c;}/*crm商品管理,页面主题黄色*/
.bgea{background:#EA6846;}
.bgf1{background:#F1F2F4;}
.bgf7{background:#f7f7f7;}
.bgfb{background:#fbfbfb;}
.bgf36{background:#ff3366;}
.bgf2f1{background:#f2f1f1;}
.bgef1e3b{background:#ef1e3b;}
/*字体大小*/
.font10{font-size:10px;}
.font12{font-size:12px;}
.font13{font-size:13px;}
.font14{font-size:14px;}
.font15{font-size:15px;}
.font16{font-size:16px;}
.font18{font-size:18px;}
.font20{font-size:20px;}
.font42{font-size:42px;}
.font2m{font-size: 2em;}
.font3m{font-size: 3em;}
/*字体颜色*/
.text-red{color:#df493b;}
.text-white{color:#fff;}
.text-grey{color:#ccc;}
.text-grey1{color:#9e9ea1;}
.text1{color:#50d2c2;}
.text-f36{color:#ff3366;}
.colorb5{color:#b5b5b5;}
.colorstar {color:#ff4444;}
.color29{color:#292929;}
.text-orange{color:#ffa15c;}
.text-blue{color:#41a4e3;}
.text-jiangjiu{color:#da5141;}/* 广东酱酒专家 */
.texthidden{text-overflow:ellipsis;white-space: nowrap;overflow: hidden;}/* 1行 */
.rows2{display:-webkit-box;-webkit-line-clamp: 2;-webkit-box-orient: vertical; overflow: hidden;}/* 2行 */
/*高度和行高*/
.h30{height:30px;}
.h35{height:35px;}
.h43{height:43px;line-height: 43px;}
.h48{height:48px;line-height: 48px;}
.h50{height:50px;line-height: 50px;}
.h70{height:70px;}
.lh25{line-height: 25px;}
.lh33{line-height: 33px;}
.lh34{line-height: 34px;}
.lh47{line-height: 47px;}
/*边框样式*/
.bordernone{border:none;}
.border0{border:none;}
.border{border: 1px solid #e8e9eb;}
.bordert{border-top: 1px solid #e8e9eb;}
.borderr{border-right: 1px solid #e8e9eb;}
.borderb{border-bottom: 1px solid #e8e9eb;}
.borderl{border-left:1px solid #e8e9eb;}
.bordertb{border-top:1px solid #e8e9eb;border-bottom:1px solid #e8e9eb;}
.btn-simple{width:100%;border-radius:0;border:none;}
.bradius3{border-radius:3px;}
.bradius20{border-top-left-radius:20px;border-bottom-left-radius:20px;border-top-right-radius:20px;border-bottom-right-radius:20px;}
.bg-success1{background: #66c300;}
/*delete*/
.bordertop{border-top: 1px solid #e8e9eb;}
.borderright{border-right: 1px solid #e8e9eb;}
.borderbottom{border-bottom: 1px solid #e8e9eb;}
/* 内外边距 */
.clearMargin{margin:0;}
.clearPadding{padding:0;}
.clearPtb{padding-top:0px;padding-bottom:0;}
.clearMb{margin-bottom:0;}
.clearPb{padding-bottom:0;}
/* 内边距 */
.padding5{padding:5px;}
.padding10{padding:10px; background-color:#FFF; width:90%; margin:0px auto 10px auto}
.padding15{padding:15px;}
.p15{padding:15px;}
.pt10b3{padding-top:10px;padding-bottom:3px;}
.ptb5{padding-top: 5px;padding-bottom: 5px;}
.ptb6{padding-top: 6px;padding-bottom: 6px;}
.ptb10{padding-top:10px;padding-bottom: 10px;}
.ptb15{padding-top: 15px;padding-bottom: 15px;}
.ptb20{padding-top: 20px;padding-bottom: 20px;}
.ptb30{padding-top:30px;padding-bottom:30px;}
.plr0{padding-left:0px;padding-right:0px;}
.plr10{padding-left:10px;padding-right:10px;}
.plr15{padding-left:15px;padding-right:15px;}
.plr25{padding-left: 25px;padding-right: 25px;}
.pb5{padding-bottom: 5px}
.pb10{padding-bottom: 10px}
.pb13{padding-bottom: 13px}
.pb15{padding-bottom: 15px}
.pb20{padding-bottom: 20px}
.pb55{padding-bottom: 55px}
.pt5{padding-top:5px;}
.pt10{padding-top: 10px;}
.pt12{padding-top: 12px}
.pt15{padding-top: 15px;}
.pt20{padding-top: 20px;}
.pt25{padding-top: 25px;}
.pt42{padding-top: 42px;}
.pt5p{padding-top: 5%;}
.pr0{padding-right: 0;}
.pr5{padding-right: 5px;}
.pl0{padding-left:0;}
.pl2{padding-left: 2px;}
.pl5{padding-left:5px;}
.pl10{padding-left:10px;}
.pl15{padding-left: 15px;}
.pl60{padding-left: 60px;}
.-pl20{padding-left:-20px;}
.pl12p{padding-left: 12%;}
/* 外边距 */
.margin15{margin:15px;}
.margin30{margin:30px;}
.m30{margin: 30px;}
.mt0{margin-top: 0;}
.mt1{margin-top: 1px;}
.mt5 {margin-top: 5px;}
.mt9{margin-top: 9px;}
.mt10{margin-top: 10px;}
.mt15{margin-top: 15px;}
.mt20 {margin-top: 20px;}
.mt30{margin-top: 30px;}
.mt-1{margin-top:-1px;}
.mb0{margin-bottom: 0;}
.mb5{margin-bottom: 5px;}
.mb10{margin-bottom: 10px;}
.mb15{margin-bottom: 15px;}
.mb46{margin-bottom:46px;}
.mb60{margin-bottom: 60px;}
.mr6{margin-right:6px;}
.mr20{margin-right:20px;}
.ml20{margin-left:20px;}
.mtb5{margin-top:5px;margin-bottom:5px;}
.mtb10{margin-top: 10px;margin-bottom: 10px;}
.mtb15{margin-top: 15px;margin-bottom: 15px;}
.mtb20{margin-top: 20px;margin-bottom: 20px;}
.mt20b50{margin-top: 20px;margin-bottom: 50px;}
.mlr3{margin-right: 3px;margin-left: 3px;}
.mlr10{margin-right:10px;margin-left:10px;}
.mlr15{margin-left: 15px;margin-right: 15px;}
/*签到*/
.maskbox{width:100%;height:100%;background:rgba(0,0,0,0.7);display: none;position: absolute;z-index:1000;top:0;left:0;}
.calendar{background:#faca34;padding:0px 15px 0;}
.libaolist .bg-red{background:#e60012;}
.libaolist .pt2{padding-top:2px;}
.libaolist .pt3{padding-top:3px;}
.libaolist .btn-lingqu{width:70px;text-align:center;background:#e60012;color:#fff;}
.libaolist .btn-disable{width:70px;text-align:center;background:#c9c9c9;color:#fff;}
.btn-qiandao{width:160px;height:50px;background:#e60012;border:5px solid #faca34;color:#fff;font-size:18px;font-weight:bolder;border-radius:25px;text-align:center;position:relative;bottom:-20px;}
.qdbox{display:none;padding:15px 0;width:250px;border:3px solid #f82729;border-radius:10px;background:#fff;position:fixed;z-index:1001;top:50%;left:50%;margin-top:-113px;margin-left:-120px;}
.qdbox .text-green{color:#e60012;}
.btn-lottery{width:120px;text-align:center;color:#fff;background:#e60012;font-size:16px;}
.calenbox{width:100%;margin:0 auto;background:#faca34;}
.calenbox .date{width:14%;text-align:center;background:#fff;border-radius:7px;color:#6a3906;font-weight:bolder;font-size:18px;padding:10px 0;float:left;border-right:1px solid #faca34;border-bottom:1px solid #faca34;}

.singer_r_img{display:block;width:114px;height:52px;line-height:45px;background:url(images/sing_week.gif) right 2px no-repeat;vertical-align:middle;*margin-bottom:-10px;text-decoration:none;}
.singer_r_img:hover{background-position:right -53px;text-decoration:none;}
.singer_r_img span{margin-left:14px;font-size:16px;font-family:'Hiragino Sans GB','Microsoft YaHei',sans-serif !important;font-weight:700;color:#165379;}
.singer_r_img.current{background:url(images/sing_sing.gif) no-repeat 0 2px;border:0;text-decoration:none;}
.sign table{width:100%;border-collapse: collapse;border-spacing: 0;color: #a46626;font-weight: bold;font-size:20px;}
.sign th,.sign td {width: 30px;height: 40px;text-align: center;line-height: 40px;border:1px solid #faca34;border-radius:6px;background:#fff;}
.sign th {font-size: 16px;border-radius:6px;background:#fff;}
.sign td {color: #404040;vertical-align: middle;border-radius:6px;background:#fff;color: #a46626;}
.sign .on {background-color:#f0bc1a;}
.calendar_month_next,.calendar_month_prev{width: 34px;height: 40px;cursor: pointer;background:url(images/sign_arrow.png) no-repeat;}
.calendar_month_next {float:right;line-height:40px;}
.calendar_month_span {display:inline;line-height: 40px;font-size: 16px;color: #a46626;letter-spacing: 2px;font-weight: bold;}
.calendar_month_prev {float:left;line-height:40px;}
.sign_succ_calendar_title {text-align: center;border-left:1px solid #faca34;border-right:1px solid #faca34;background:#faca34;}
.sign_main{border-top:1px solid #faca34;font-family: "Microsoft YaHei",SimHei;}
/* 大转盘样式 */
.turbg{background:#e60012;}
.banner{display:block;width:90%;margin:-60px auto 0;}
.banner .turnplate{display:block;width:100%;position:relative;}
.banner .turnplate canvas.item{width:100%;}
.banner .turnplate img.pointer{position:absolute;width:31.5%;height:42.5%;left:34.6%;top:23%;}
.prizebox{background:#ffffff;margin:-3px 15px 10px;box-shadow:#d9d9d9 0 5px 20px 3px;color:#6a3906;}
.pline{height:12px;border-radius:10px;background:#ef2122;}
.ptitle{color:#6a3906;font-size:16px;padding:10px 15px;font-size:16px;}
.prizebox .ptitle .text-yellow{color:#6a3906;}
.prizebox .ptitle .text-red{color:#ff0000;}
.prizebox .prizelist{padding:0 15px;}
.prizebox .prizelistwrap{height:auto;overflow:scroll;}
.pt10lr10{padding:10px 10px 0;}
.turRule{padding:0 15px;color:#7d0000;margin-bottom:20px;}
.turRule .text-brown{color:#7d0000;}
.turRule .line{height:3px;background:#7d0000;margin-top:10px;}
.turRule .ball{display:inline-block;width:10px;height:10px;border-radius:5px;position:absolute;background:#7d0000;top:7px;}
.turRule .ball1{left:0;}
.turRule .ball2{right:0;}
.turRule dl{margin-bottom:10px;}
.turRule dl dt{margin-bottom:5px;}
.turRule dl dt strong{font-size:16px;}
.turRule dl span{display:inline-block;width:18px;height:18px;border-radius:9px;background:#faca34;text-align:center;margin-right:5px;}

.banner2{ width:100%}
.banner2 img{ width:100%}

.from{ width:90%; margin:0px auto;}
.from input {width: 100%; border: none; font-size:15px; background-color: #fff; border-radius: 0.5rem; padding: 15px 10px; margin-bottom: 5%;}
.from > div input {width: 50%; float: left;}
.from > div button {width: 42%; float: right; padding: 15px 0px;display: inline-block;}
.sorrytext{ display:none; text-align:center; color:#FFF; margin-bottom:10px}
.btn2 {border-radius: 0.5rem; background-color: #ffe335; border: none; padding: 15px 0px; color: #d31427; }
.btn3{
    display:block;
    border:0px;
    margin:0rem auto;
    width: 90%;
    padding: 17px 0px;
    background-color: #ef2122;
    text-align: center;
    font-weight: bold;
    font-size:18px;
    color: #ffffff;
    border-radius: 0.26666667rem;
}
a.btn-lingqu2{background:#e60012;color:#fff;}
.btn-disable3{background:#c9c9c9;color:#fff;}


.banner2{ width:100%; margin:0px auto 5% auto;}
.banner2 img{ width:100%;}
.tabsbox { width:94%;  margin:0px auto 5% auto;font-size: 80%; color: #fff; background-color:#FFF; padding:11% 0% 5% 0%; position:relative; }
.tabsbox2{padding:11% 0% 0% 0%;}
.tabsbox .title1 {width:160px;height:44px; line-height:44px;background:#e60012;border:5px solid #faca34;color:#fff;font-size:18px;font-weight:bolder;border-radius:25px;text-align:center;position:absolute; left:50%;top:-27px; margin-left:-85px;}
.tabsbox p { width:90%; margin:0px auto; line-height:26px; color:#a46626}

.btn4{  display:block;
    border:0px;
    margin:0rem auto 5% auto;
    width: 94%;
    height: 44px;
    line-height:  44px;
    background-color: #ef2122;
    text-align: center;
    font-weight: bold;
    font-size:18px ;
    color: #fff3f0;
    border-radius: 4px;}

.Prize { border-bottom: solid 1px #faca34;    padding:3% 2% 3% 2%; }
.Prize > div { color: #000; width:90%; margin:0 auto; line-height:40px}

 

 

猜你喜欢

转载自blog.csdn.net/cxh_1231/article/details/90744494
今日推荐