- 说明:本文讲述基于公众号实现的签到程序的全部开发过程。开发环境:PHP+MySQL。
- 源码下载地址:敬请期待
- 关注微信公众号【知行校园汇】可免费下载全部源码。
- >>点击查看WUTer计算机专业实验汇总
- 谨记:纸上得来终觉浅,绝知此事要躬行。
本文较长,为了减少读者的时间,先展示本项目的成果图,以便读者快速确实这篇文章是不是正在寻找的文章。
这里有必要说明一下,项目中的前端页面配色配图等参考自网络公开的CSS样式表。
关于项目的详情,请见后文。
用户点击公众号的菜单【早起签到】后,即实现自动微信登录,进入签到主页面。
主页面展示如下:
主页面分为三个展示页面。未到签到时间(0:00到05:50期间)显示图如上左图所示。到签到时间时,按钮状态可点击,如上中图。签到完成后,将显示该用户在本公众号的当日签到名次。
用户签到后,可以查看本用户自己的签到记录。详情如下图所示:
在本项目中,用户签到后,可以在本日或者次日的06:50至08:30到指定地点领取奖励。超过这个时间,本条签到记录即作废。三种状态的签到记录如上图所示。
下图为后台兑换的测试页面。输入签到ID即可查询该签到记录。
如上图。如果前来兑换的用户的签到记录已过期,则显示如上左图所示。如果用户的签到记录已兑换,则显示如上中图所示。如果用户的签到记录可兑换,则显示如上右图所示。
点击“立即兑换”按钮,则显示“兑换成功”弹窗,如下图所示。
看到这里,如果这不是你想要的项目,那么你可以关闭本篇博客了。
如果这是你想要的项目,或者本项目和你当前项目接近,或者想学习这个项目的编写,或者……
请继续往下看。下面正式开始^_^
关于此需求分析部分,先从项目的背景说起吧。
起因是这样的。项目组要在公众号内举办一个活动。这个活动简单易懂,就是“早起签到领奖励”。每天早上指定时间开启签到系统,然后用户点击菜单栏的“早起签到”按钮,即可通过微信登录后,进入签到系统。
用户签到完成后,用户可以凭签到记录到指定地点领取早餐一份。同时每日的早餐限量,所以先到先得。同时签到记录也有兑换期限,本项目组指定的计划是本日或者次日的8:30前均可领取。所以今天签到,后天早上就领不到早餐了^_^
通过对项目背景的分析,本项目的需求有如下几部分:
1、本项目为公众号项目,微信网页开发;
2、实现用户的微信登录授权,以确定用户身份,并实现单日只能签到一次;
3、用户签到后,记录签到时间以及当日签到名次;
4、显示用户签到记录,以及签到记录的状态(可兑换、已兑换、已过期);
5、后台管理员进行兑换确定。
既然是微信网页开发,并且要实现用户的登录,那么就需要学习一下微信的公众号网页授权机制。
微信开放平台中有关于这方面的使用说明的介绍,可以通过开发者文档自行学习。链接如下:
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。
这个项目本身并不大,所以设计的数据库表单也很简单。
首先是用户表。
在本项目中,只需要获取用户对此公众号产生的唯一标识OpenID即可,并不需要获取用户的昵称、城市、头像等其他开放信息。如果需要用户的这些信息,可以对user表进行扩展。
用户表user的字段只有2列。如下图所示:
各个字段含义:
然后是用户签到记录表。signin表单详情如下:
各个字段含义:
如果你的项目还需要其他表单,可自行设计。
工欲善其事,必先利其器。进行开发之前,首先需要对开发环境等进行配置。
本项目使用PHP语言。所以编译程序使用的是JetBrains PhpStorm。
PHPStorm为付费软件。如果你是学生用户,拥有教育邮箱,可以从JetBrains官网授权获取免费版。
项目编写过程,难免要进行调试。所以这里用到的PHP网页运行环境为phpStudy。
此软件为免费软件。具体如何,可从phpStudy官网查询相关文档。
调试过程,难免需要对数据库中的数据进行核实检查。
笔者使用的MySQL数据库可视化工具为:Navicat for MySQL。
此软件为付费软件。
如何对公众号网页进行调试?微信公众平台的开发者工具栏就给出了Web开发者工具:微信web开发者工具
如上图,点击【web开发者工具】,跳转到绑定开发者微信号页面。这里将开发者的微信号进行绑定。
然后前往:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html 下载微信web开发者工具电脑客户端。
下载安装后,运行后,需要开发者扫描登录才能运行。运行页面如下:
如果直接将编写完成的项目配置到公众号的菜单栏,微信是不认可你这个项目的。
不过,你也不可能在不配置公众号后台的情况下编写出来这个程序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)记录下来!
都说PHP是一门松散的语言。笔者也是第一次接触PHP,并使用PHP来实现这个项目……总体感觉,PHP挺好用\(^o^)/~
项目本身就不大,客户端一共2个页面,所以也就不采用什么框架还是MVC开发模式了(下面模仿MVC开发模式)
首先需要新建一个Dao类,这个类实现的是构造Dao类,以实现与数据库中的数据进行打交道。
通俗的将,就是实现了对数据库的增删改查,所以的SQL语句均在这里执行。
文件名:dao.php
详细代码以及注释如下所示:
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判断……
这个类文件中实现的是从前端获取用户的数据,如时间等信息,控制签到程序,然后将数据发送到模型层,对前端和数据库操作进行控制,起到中介的作用。
文件名:control.php
详细代码及注释如下所示:
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;
}
}
下面这篇开发者文档给出了获取用户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语句前面一行的字符串中的几个变量值说明一下:
如果用户同意授权,页面将跳转至 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;
}
这里链接中用到的三个变量的说明如下:
如果函数执行正确,返回的数据为一个JSON数据包,可以简单理解为C++中的结构体。开发者文档的解释如下:
这里边就包含用户的唯一标识:OpenID。
在本项目中,进行到这里就结束了。
4.3.3 完整代码
如果你还想继续获取用户的昵称、头像等公开信息,请看完整代码。
各个过程就是请求链接、获取JSON包的过程。
文件名:weixin.class.php
$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补充完整。
主页面主要是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
早起签到
活动详情
1、 每日05:50至23:59开启早起签到。
2、 其他内容……
3、 其他内容……
这个主页面用到的CSS文件,来自网络,篇幅较长,将在文末展示。
这里注意上面代码中的“立即签到”按钮所在的form表单中,action是跳转到sign.php文件,这里要注意!
所以需要对sign.php文件进行编写。这个文件,就是个控制页面,以及跳转页面。详细代码如下:
文件名:sign.php
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 "";
//echo "";
}
else{
echo "";
}
前面index.php代码中,有一个“我的签到记录”按钮,是个超链接的形式,所跳转的页面是mysign.php。详细代码如下:
文件名:mysign.php
我的签到记录
您还没有签到记录,快去签到吧^_^
后台兑换页面就是一个很简单的HTML页面。其中主要由两个PHP文件组成:
主页面文件名:search.php
完整代码:
后台查询系统
兑换按钮文件名:submit.php
完整代码:
{ window.alert('兑换成功!')}; setTimeout( window.parent.location.href='a.php',2000); ";
//echo "";
}
else{
echo "";
}
至此,本项目的关键代码展示完毕。
本文结束。
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}