微信开发_后端开发微信发送对普通微信和企业微信的区别 - CSDN
  • 通过本课程的学习,学员能够入门微信公众平台开发,能够胜任企业级的订阅号、服务号、企业号的应用开发工作。 通过本课程的学习,学员能够对微信公众平台有一个清晰的、系统性的认识。例如,公众号是什么,它有...
  • 微信已经融入到人们的生活中,很多企业也在做跟微信相关的开发,但是网络上缺少微信开发相关的教程,特别是视频教程。课程的大特点是,实战 = 理论 + 演示 + 敲代码。这是一部中课程,需要有学习的开发者拥有微信...
  • 标准版主要讲解企业微信开发的关键技术,接收普通消息、回复普通消息、发送消息、网页授权、网页应用、JS-SDK,等知识点。 该课程适合有微信企业号、微信服务号基础或具有一定的C# web开发基础的学员。 “一对一...
  • 学习微信公众号的开发,必须先阅读微信开发文档,通过阅读文档,了解有关微信公众开发的步骤及相关限制,了解其工作原理等。 微信开发之入门指引       ...

    说明

    公司需要开发微信服务号,要求做有关技术验证,学习了微信公众号的开发,在这里记录总结下。

    正文

          学习微信公众号的开发,必须先阅读微信开发文档,通过阅读文档,了解有关微信公众开发的步骤及相关限制,了解其工作原理等。

    微信开发之入门指引

          通过阅读入门指引,发现在正式开发之前必须要具备几个条件:1.申请微信公众号 2. 后台服务器 3.开发者配置

    申请测试公众号

    在微信官网申请公众号时,发现目前公众号只有两类帐号:服务号和订阅号,个人只能申请订阅号。原来的企业号变为企业微信,增加了微信小程序。申请地址为:https://mp.weixin.qq.com/
    这里写图片描述

    通过阅读微信文档的公众号接口权限说明,发现未经认证的订阅号接口权限十分有限,无法完成接下来的技术验证。
    这里写图片描述
    这里使用微信提供的在线测试平台,申请一个接口测试号完成有关技术验证。地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
    这里写图片描述

    使用自己的微信号通过扫码即可申请一个测试号
    这里写图片描述
    微信会自动为测试号分配appID和appsecret,这两个参数是开发的关键。
    测试号申请到这里就结束了,接下来配置服务器。

    配置本地服务器

    阅读微信开发文档时发现,服务器必须外网可以访问且端口为80。要么需要购买云服务器,但这对我们开发调试有很大的不便,这里我使用了内网穿透工具实现外网访问,将本地的tomcat作为后台的应用服务器。
    工具为NATAPP,使用此工具可以自动将它产生的域名映射为本地的ip地址+端口。地址:https://natapp.cn/
    这里写图片描述

    在下方的客户端下载中选择适合自己电脑的版本
    这里写图片描述

    下载成功后需要注册登录申请免费隧道,每个用户可以拥有两条不同协议的隧道,免费隧道域名随机分配且会强制更换,这点在开发时需要注意,需要在微信配置中随时更换URL。
    这里写图片描述

    在申请时注意隧道协议为web,且本地端口为80
    这里写图片描述
    申请好会为隧道分配一个authtoken,这个token时让NATAPP可以运行起来的关键,接下来在运行前必须配置文件,关于配置文件的下载地址:https://natapp.cn/article/config_ini ,更多教程参考:https://natapp.cn/article/natapp_newbie
    配置文件必须与之前解压的natapp.exe在同一级目录,然后将得到的authtoken填入保存。
    这里写图片描述

    运行结果为:
    这里写图片描述
    这里可以看到随机分配的域名指向了本地的80端口。这里的域名就是测试号配置URL需要填写的参数。

    关于本地服务器的搭建到这里就结束了,接下来在之前申请的测试号中进行配置。

    配置测试号

    在配置之前必须搭建一个web项目,因为填写配置时微信服务器会向后台进行验证,后台给出正确的响应才会配置成功。这里我使用了SpringBoot构建web项目,关于SpringBoot如何构建web项目,可以在网上查阅相关资料,对于SpringBoot的学习推荐翟永超的博客程序猿DD。关于如果将SpringBoot项目部署到本地Tomcat中请参考另一篇博文《将Spring Boot项目部署到本地Tomcat中》

    这里写图片描述
    URL即为NATAPP运行时得到的域名,Token随意填写,程序中需要使用。

    校验代码:

    @RequestMapping(value="hello", method = RequestMethod.GET)
        public void hello(HttpServletRequest request,
                            HttpServletResponse response){
            System.out.println("success");
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            String echostr = request.getParameter("echostr");
    
            PrintWriter out = null;
            try {
                out = response.getWriter();
                if(CheckUtil.checkSignature(signature,timestamp, nonce)){
                    out.write(echostr);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                out.close();
            }
    
        }

    在校验类CheckUtil中对微信服务器传来的参数进行校验

    import java.security.MessageDigest;
    import java.util.Arrays;
    
    public class CheckUtil {
        public static final String tooken = "testdemo";
        public static boolean checkSignature(String singnature, String timestamp, String nonce){
            String[] arr = {tooken, timestamp,nonce};
            Arrays.sort(arr);
            StringBuilder sb = new StringBuilder();
            for(String s : arr){
                sb.append(s);
            }
            String temp = getSha1(sb.toString());
            return temp.equals(singnature);
        }
    
        private static String getSha1(String str){
            if(str==null||str.length()==0){
                return null;
            }
    
            char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',
                    'a','b','c','d','e','f'};
    
            try {
                MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
    
                mdTemp.update(str.getBytes("UTF-8"));
    
                byte[] md = mdTemp.digest();
                int j = md.length;
                char buf[] = new char[j*2];
                int k = 0;
    
                for (int i = 0; i < j; i++) {
                    byte byte0 = md[i];
                    buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                    buf[k++] = hexDigits[byte0 & 0xf];
                }
    
                return new String(buf);
    
            } catch (Exception e) {
                return null;
    
            }
        }
    }

    能正常响应时,就会出现配置成功,接下来就可以进行验证开发了。

    展开全文
  • 微信公众号开发(一)服务器及接口的配置 关于微信公众号中的订阅号和服务的区别这里不多加讨论,网上有很多资源可以搜到,这里直接进入正题,如果是个人开发者,这里建议使用测试号进行开发学习,测试号的权限要比...

    微信公众号开发(一)服务器及接口的配置

    关于微信公众号中的订阅号和服务的区别这里不多加讨论,网上有很多资源可以搜到,这里直接进入正题,如果是个人开发者,这里建议使用测试号进行开发学习,测试号的权限要比个人订阅号要多的多,而本篇博客也是基于测试号进行开发的。

     

    在开始微信号开发之前需要准备好两样东西,1、需要一个测试号,2、需要一个拥有域名的服务器,下面将分别介绍怎样获取这两样东西。

    1、测试号

    点击此链接测试号登录可直接用微信扫一扫注册一个测试号,相应的界面如下所示

     

    进入测试号界面之后可以获得一些开发所需要的东西,以及关注者列表和开发权限等,如下所示

     

    2、服务器

    微信号开发需要一个第三方服务器来和微信服务器沟通,这里我使用的是一个百度云的BAE虚拟主机,理由有以下几点:

    1、性价比高,该主机一天只需要2毛钱,而且不限制充值金额,如果你只是用来学习微信公众号开发,而且预计一个月学会的话,那么你只需要充值6元钱就可以,相比其他最低充值一年的平台相比,要划算的多。

    2、可以免去购买域名的费用,该BAE可免费设置一个二级域名,可用于微信公众号开发,并且数据库的使用也是免费的。

    3、该BAE的代码可以使用git或svn管理,我们在本地写好代码之后,直接push上去就可以了,相当方便,而且可以设置快捷发布,提交代码当即发布。

    缺点就是它的日志系统不够完善,不过这个缺点很容易解决,我们可以自己打印信息到文件里,然后读取出来,下面将会介绍到。

     

    百度云网址是:https://login.bce.baidu.com/?account=,登录之后,点击BAE后如图

     

    好了,这样我们就准备好了微信开发的两个必备的东西。接下来就要配置接口了。

     

    3、配置接口

    上面进入测试号界面的时候我们可以看到有一个接口配置信息的模块,如下所示

     

     

    这里的URL就是填写以上BAE中的域名就好了,当然了要注意加上http://前缀,以后用户发送的消息都会经过微信服务器转发到该接口。Token可以随便填写一些东西,这里填写了weixin。关于Token的用途下面会讲到。

    好了,现在先别急着点击提交,因为我们还没有在URL指向的服务器里编写任何的代码,还不能正确响应微信服务器的请求。

     

    在编写任何的代码之前,我先说一下本博客的一些习惯,为了便于理解,我会先将文件的结构和代码先贴出来,然后才解释具体代码的含义,这样如果熟悉的人就可以直接跳过该部分了。

     

    文件结构如下

     

    index.php用于处理消息。

    output_log.php和output_query.php分别用来输出post过来的数据和请求的查询字符串,Utils.php主要用来将数据输出到文件中,看了下面的代码你就明白了,其实相当简单,这三个东西是我用来调试用的,相比起微信公众号提供的在线调试接口而言(需要设置一堆的信息),我觉得这样更加简单。

     

    Utils.php,提供了两个函数,traceHttp()将请求的时间、远程主机地址和查询字符串输出到query.xml文件中。logger()将类型、时间和post数据输出到log.xml中。

    <?php
    class Utils
    {
        public static function traceHttp()
        {
            $content = date('Y-m-d H:i:s')."\n\rremote_ip:".$_SERVER["REMOTE_ADDR"].
                "\n\r".$_SERVER["QUERY_STRING"]."\n\r\n\r";
            $max_size = 1000;
            $log_filename = "./query.xml";
            if (file_exists($log_filename) and (abs(filesize($log_filename))) > $max_size){
                unlink($log_filename);
            }else {
    
            }
            file_put_contents($log_filename, $content, FILE_APPEND);
        }
    
        public static function logger($log_content, $type = '用户')
        {
            $max_size = 3000;
            $log_filename = "./log.xml";
            if (file_exists($log_filename) and (abs(filesize($log_filename)) >
                    $max_size)) {
                unlink($log_filename);
            }
            file_put_contents($log_filename, "$type  ".date('Y-m-d H:i:s')."\n\r".$log_content."\n\r",
                FILE_APPEND);
        }
    }

     

    output_query.php,输出query.xml的内容

    <?php
    @header('Content-type: text/plain;charset=UTF-8');
    $filepath = './query.xml';
    readfile($filepath);


    output_log.php,输出log.xml的内容。

    <?php
    @header('Content-type: text/plain;charset=UTF-8');
    $filepath = './log.xml';
    readfile($filepath);


    是不是非常简单,然后我们开始写处理消息index.php

    <?php
    //设置时区
    date_default_timezone_set("Asia/Shanghai");
    //定义TOKEN常量,这里的"weixin"就是在公众号里配置的TOKEN
    define("TOKEN", "weixin");
    
    require_once("Utils.php");
    //打印请求的URL查询字符串到query.xml
    Utils::traceHttp();
    
    $wechatObj = new wechatCallBackapiTest();
    /**
     * 如果有"echostr"字段,说明是一个URL验证请求,
     * 否则是微信用户发过来的信息
     */
    if (isset($_GET["echostr"])){
        $wechatObj->valid();
    }else {
        $wechatObj->responseMsg();
    }
    
    class wechatCallBackapiTest
    {
        /**
         * 用于微信公众号里填写的URL的验证,
         * 如果合格则直接将"echostr"字段原样返回
         */
        public function valid()
        {
            $echoStr = $_GET["echostr"];
            if ($this->checkSignature()){
                echo $echoStr;
                exit;
            }
        }
    
        /**
         * 用于验证是否是微信服务器发来的消息
         * @return bool
         */
        private function checkSignature()
        {
            $signature = $_GET["signature"];
            $timestamp = $_GET["timestamp"];
            $nonce = $_GET["nonce"];
    
            $token = TOKEN;
            $tmpArr = array($token, $timestamp, $nonce);
            sort($tmpArr);
            $tmpStr = implode($tmpArr);
            $tmpStr = sha1($tmpStr);
    
            if ($tmpStr == $signature){
                return true;
            }else {
                return false;
            }
        }
    
        /**
         * 响应用户发来的消息
         */
        public function responseMsg()
        {
            //获取post过来的数据,它一个XML格式的数据
            $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
            //将数据打印到log.xml
            Utils::logger($postStr);
            if (!empty($postStr)){
                //将XML数据解析为一个对象
                $postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
                $RX_TYPE = trim($postObj->MsgType);
                //消息类型分离
                switch($RX_TYPE){
                    case "event":
                        $result = $this->receiveEvent($postObj);
                        break;
                    default:
                        $result = "unknow msg type:".$RX_TYPE;
                        break;
                }
                //打印输出的数据到log.xml
                Utils::logger($result, '公众号');
                echo $result;
            }else{
                echo "";
                exit;
            }
        }
    
        /**
         * 接收事件消息
         */
        private function receiveEvent($object)
        {
            switch ($object->Event){
                //关注公众号事件
                case "subscribe":
                    $content = "欢迎关注微微一笑很倾城";
                    break;
                default:
                    $content = "";
                    break;
            }
            $result = $this->transmitText($object, $content);
            return $result;
        }
    
        /**
         * 回复文本消息
         */
        private function transmitText($object, $content)
        {
            $xmlTpl = "<xml>
        <ToUserName><![CDATA[%s]]></ToUserName>
        <FromUserName><![CDATA[%s]]></FromUserName>
        <CreateTime><![CDATA[%s]]></CreateTime>
        <MsgType><![CDATA[text]]></MsgType>
        <Content><![CDATA[%s]]></Content>
    </xml>";
            $result = sprintf($xmlTpl, $object->FromUserName, $object->ToUserName, time(), $content);
            return $result;
        }
    }


    这几个文件写好之后,直接通过git push到BAE上,如果设置了快捷发布,过1、2秒钟就会自动发布了,状态里面会显示绿色的"正常",如果没有设置快捷发布,上传代码后需要手动点击右边的快捷发布按钮。

     

    这时我们就可以点击测试号界面里面的那个提交按钮了。

     

     

    如果配置正确,则会提示配置成功。

     

    4、调试

    在浏览器地址栏上输入,xxx/output_query.php,xxx是你的域名。则会出现你点击提交后发送过来的请求,类似如下

     

     

    可以看到该查询字符串有4个字段

    • signature:微信加密签名
    • echostr:随机字符串
    • timestamp:时间戳
    • nonce:随机数

    只有在验证URL的时候查询字符串中才会有“echostr”这个字段,验证的方法是

    1. 将token、timestamp、nonce三个参数进行字典序排序
    2. 将三个参数字符串拼接成一个字符串进行sha1加密
    3. 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信

     

    验证通过之后,我们可以用微信扫一扫,扫描测试号里面的公众号二维码,关注该公众号,关注之后,公众号列表会显示出关注者的列表。

     

    此时微信应该会回复一条文本消息,也就是以上在index.php中设置的“欢迎关注XXX”。

     

    这时我们重新刷新一下xxx/output_query.php,发现没有“echostr”这个字段了,因为这个是用户发来的消息,而不是验证URL的消息。多出来的openid字段是用户的微信号,如果采用的是加密模式,还会有encrypt_type和msg_signature等字段。测试号只有明文模式。

    接着我们重新打开一个标签,访问XXX/log.xml,查看发送的post数据,下面是一个关注公众号的事件,和我们返回给微信的XML数据。

     

    现在不比纠结这些数据格式的问题,以后我们会提到,这样我们的微信公众号开发就准备好了,记得把这两个日志URL保存为浏览器的标签方便下次访问,以后调试只要F5一下就可以了,是不是比微信提供的在线调试容易多了。

    注意:必须在5秒内响应微信的服务器,否则会导致重传或者报错

     

    下一章我们将会尝试微信的基本消息接口,那时候就会理解这些数据结构了。

    相关博客

    微信公众号开发(一)服务器及接口的配置

    微信公众号开发(二)基础接口

    微信公众号开发(三)获取access_token

    微信公众号开发(四)自定义菜单

    微信公众号开发(五)个性化菜单

    微信公众号开发(六)素材管理

    微信公众号开发(七)发送客服消息

    微信公众号开发(八)用户管理

    微信公众号开发(九)群发消息接口

    微信公众号开发(十)模板消息

    微信公众号开发(十一)生成带参数二维码

    微信公众号开发(十二)OAuth2.0网页授权

    展开全文
  • 由于该公众号主要的用途是用于公司售后服务,经沟通确定相关需求. 第一: 了解相关的UI框架,最终选定了Jquery-WeUI框架. 第二: 制作简单案例,在移动端进行测试,要达到移动端的自适应. 目前完成三个界面如下: ...

    由于该公众号主要的用途是用于公司售后服务,经沟通确定相关需求.

    第一: 了解相关的UI框架,最终选定了Jquery-WeUI框架.

    第二: 制作简单案例,在移动端进行测试,要达到移动端的自适应.

    目前完成三个界面如下:

    上面的就是 目前的界面以及手机适应的效果,后期需要修改.

    下面的是第一个界面的代码:

     

    <!DOCTYPE html>
    <html>
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0" />
            
            <!-- Jquery  -->
            <script src="/WeChatDemo/jquery-weui/dist/lib/jquery-2.1.4.js"></script>
            <!--weui css-->
            <link rel="stylesheet" href="https://cdn.bootcss.com/weui/1.1.0/style/weui.min.css" />
            <!--jQuery weui-->
            <link rel="stylesheet" href="https://cdn.bootcss.com/jquery-weui/0.8.0/css/jquery-weui.min.css">
            <!--切换样式-->
            <style type="text/css">
                .weui_navbar_item.weui_bar_item_on {
                    color: #2196F3;
                    cursor: pointer;
                }
            </style>
            <title>售后测试	</title>
        </head>
    <body ontouchstart>
    <header class="ui-header">
    			<h1>**电气售后服务公众号</h1></header>
    			
    			
    <div class="weui-cells weui-cells_form">
    <!-- 车型 -->
      <div class="weui-cell">
        <div class="weui-cell__hd"><label class="weui-label ">车型</label></div>
        <div class="weui-cell__bd">
        <!-- 这个地方的pattern 需要进行修改 -->
        <!-- 此处输入车型信息 添加 车型class  -->
          <input class="weui-input chexing" type="text"   placeholder="点击选择车型">
        </div>
      </div>
    <!-- 车型 -->
    <!-- 车号 -->
      <div class="weui-cell weui-cell_vcode">
        <div class="weui-cell__hd">
          <label class="weui-label">车号</label>
        </div>
        <div class="weui-cell__bd">
        <!-- 此处添加车号信息  添加车号 class -->
          <input class="weui-input chehao" type="text" placeholder="请手动输入机车号">
        </div>
      </div>
    <!-- 车号 -->
    <!-- 日期 -->
      <div class="weui-cell">
        <div class="weui-cell__hd"><label for="" class="weui-label">日期</label></div>
        <div class="weui-cell__bd">
        <!-- 此处添加 日期信息 添加日期 class -->
          <!-- <input class="weui-input faultdate" type="date" value=""/> -->
          <input class="weui-input faultdate" type="text" data-toggle='date'/>
        </div>
      </div>
    <!-- 日期 -->
    <!-- 故障发生地点 -->
    <!-- 暂时有些问题 先去掉 -->
     <!--  <div class="weui-cell">
        <div class="weui-cell__hd"><label for="" class="weui-label">地点</label></div>
        <div class="weui-cell__bd">
          <input class="weui-input faultarea" type="text"  data-toggle="city-picker" value="浙江 杭州 拱墅区" />
        </div>
      </div> -->
    
    <!-- 故障发生地点 -->
    <!-- 时间 -->
    <!-- 这个部分暂时省略,等待后期需求需要在改回去 -->
      <!-- <div class="weui-cell">
        <div class="weui-cell__hd"><label for="" class="weui-label">时间</label></div>
        <div class="weui-cell__bd">
        此处添加 时间信息 添加时间 class
          <input class="weui-input faulttime" type="datetime-local" value="" placeholder="">
        </div>
      </div> -->
    <!-- 时间 -->
    <!-- 机车故障信息 -->
    <div class="weui-cells__title">机车故障填报区</div>
    <div class="weui-cells weui-cells_form">
      <div class="weui-cell">
        <div class="weui-cell__bd">
        <!-- 故障信息 添加故障class faultinfo -->
          <textarea class="weui-textarea faultinfo" placeholder="请在此处填写机车故障信息!" rows="3"></textarea>
          <div class="weui-textarea-counter"><span>0</span>/200</div>
        </div>
      </div>
    </div>
    <!-- 机车故障信息 -->
    </div>
    
    <!-- 提交成功提示界面 -->
    <a  class="weui-btn weui-btn_primary">提交</a>
    <a  class="weui-btn weui-btn_warn">取消</a>
    <!-- 提交成功提示界面 -->
    <!-- 底部foot -->
    <div class="weui-footer weui-footer_fixed-bottom">
      <p class="weui-footer__links">
        <a href="javascript:void(0);" class="weui-footer__link ">**电气售后</a>
      </p>
      <p class="weui-footer__text ">Copyright © 2010-2018 **dq.com</p>
    </div>
    <!-- 底部foot -->
    
    
    
    
    <script type="text/javascript">
    $(document).ready(function(){
    	$("a.weui-btn.weui-btn_primary").click(function(){
    	 alert("已经获取点击事件"); 
    	 		
    	 	var chexing = $(".weui-input.chexing").val();
    	 	var chehao = $(".weui-input.chehao").val();
    	 	var faultdate = $(".weui-input.faultdate").val();
    	 	var faulttime = $(".weui-input.faulttime").val();
    	 	var faultinfo = $(".weui-textarea.faultinfo").val();
    	 	alert("车型是:" + chexing + "车号是:" + chehao + "故障填报时间是:" + faultdate
    	 			+"故障时间是:" + faulttime + "故障信息是:" + faultinfo); 
    	 	
    	/*  	 $.ajax({
    	        //直接"post"或者"get",不需要"doPost","doGet",该函数到后端接收缓冲区会自动匹配
    	        type : "post",      
    	        //servlet文件名为Calculator,需要提前在web.xml里面注册
    	        url : "FaultServlet", 
    	       dataType :  "json",  //数据类型,可以为json,xml等等,
    	        data :
    	        {
    	             "chexing" : chexing,//车型
    	             "chehao":chehao,//车号
    	             "faultdate":faultdate,//故障时间
    	             "faultinfo":faultinfo //故障信息
    	        },
    	        success : function(response)
    	        {
    	               //处理后端传递过来的 JSON 数据.
    	               var success = response.success;
    	               alert("后端处理完毕");
    	               if(success == "SUCCESS"){
    	            	   //使用jquery 代码进行跳转.
    	               $(location).attr('href', 'http://inut5w.natappfree.cc//WeChatDemo/submitsuccess.html');
    	               }
    	        },
    	        error : function(xhr, status, errMsg,response)
    	        {	
    	        	//服务器错误处理
    	        	var v1 = xhr;
    	        	var v2 = status;
    	        	var v3 = errMsg;
    	            alert("数据传输失败!");
    	        }
    	    });  */
    	 	
    //	 	 $(location).attr('href', 'http://inut5w.natappfree.cc//WeChatDemo/submitsuccess.html');
    	 	 $(location).attr('href', 'http://localhost:20000//WeChatDemo/submitsuccess.html');
    	  });
    	  <!--这个是机车车型的Picker-->
    	  $(".weui-input.chexing").picker({
    		 // input: '.weui-input.chexing',//这两个属性加上之后没有效果.有待测试.
    		 // container: '#container',
    		  title: "选择您的故障车型",
    		  cols: [
    		    {
    		      textAlign: 'center',
    		      values: ['HXD1', 'HXD3', 'HXN5', 'HXN5B', 'HXN3', 'HXN3B', 'HXD3D']
    		    }
    		  ]
    		});
    	  <!--这个是机车车型的Picker-->
    	  <!--这个是机车型号的Picker-->
    	 /*  $(".weui-input.chehao").picker({
    		 // input: '.weui-input.chexing',//这两个属性加上之后没有效果.有待测试.
    		 // container: '#container',
    		  title: "选择您的故障车号",
    		  cols: [
    		    {
    		      textAlign: 'center',
    		      values: ['0001', '0002', '0003', '0004', '0005', '0006', '0007']
    		    }
    		  ]
    		}); */
    		//车号的picker 暂时禁用 由于车号估计数量比较多 
    		//所以 为了便于用户的使用 决定使用 手动输入的方式.
    	  <!--这个是机车型号的Picker-->
    	  <!--这个是日期的Picker-->
    	/*   $(".weui-input.faultdate").calendar(); */ //这个只是日期的简单的日期 不包括时间的格式.
    	//下面的这个是包括的日期还有时间
    	  $(".weui-input.faultdate").calendar();
    	  <!--这个是日期的Picker-->
    	  <!--故障填报区这写字体增加样式-->
    	  $(".weui-cells__title").css({"color":"red","font-size":"12px"});
    	  /* #18b4ed  蓝色的背景    不正常的绿色  #33cc00 */
    	  $(".ui-header").css({"color":"#fff","font-size":"15px","background-color":"#1AAD19","text-align": "center"});
    	  /*  $(".ui-header h1").css({"color":"#fff","font-size":"20px",}); */
    	  
    	  <!--故障填报区这写字体增加样式-->
    	  <!--故障地点-->
    	  //$(".weui-input.faultarea").cityPicker();//目前有问题 暂时先去掉
    	  <!--故障地点-->
    	  
    	});
    </script>
    
    
            <!--页面切换-->
            <script src="https://res.wx.qq.com/open/libs/zepto/1.1.6/zepto.js"></script>
            <script src="https://cdn.bootcss.com/jquery/1.11.0/jquery.min.js"></script>
            <script src="https://cdn.bootcss.com/jquery-weui/0.8.2/js/jquery-weui.min.js"></script>
        </body>
    </html>


    上面引入文件的时候注意这个地方:

     

     

     <!--weui css-->
            <link rel="stylesheet" href="https://cdn.bootcss.com/weui/1.1.0/style/weui.min.css" />
            <!--jQuery weui-->
            <link rel="stylesheet" href="https://cdn.bootcss.com/jquery-weui/0.8.0/css/jquery-weui.min.css">


    本地文件也有如下图:

     

    但是引入本地的文件,在电脑测试的时候是可以出现效果的,可是在手机端测试的时候无法适应移动端设备.

    改为代码中的引入方式就可以做到适应,不清楚是版本的问题,还是文件的问题.

    这里是Jquery-WeUI官网首页:JQueryWeUI官网.

    我们可以参照相关Demo,根据自身需求进行前端界面的设计以及代码的编写.

    展开全文
  • 注册微信小程序 如果你还没有微信公众平台的账号,请先进入微信公众平台首页,点击 “立即注册” 按钮进行注册。... ... 激活邮箱之后,选择主体类型为 “个人类型”,并按要求登记主体信息。主体信息提...

    注册微信小程序

    如果你还没有微信公众平台的账号,请先进入微信公众平台首页,点击 “立即注册” 按钮进行注册。注册的账号类型可以是订阅号、服务号、小程序以及企业微信,我们选择 “小程序” 即可。

    接着填写账号信息,需要注意的是,填写的邮箱必须是未被微信公众平台注册、未被个人微信号绑定的邮箱,而且每个邮箱仅能申请一个小程序。

    激活邮箱之后,选择主体类型为 “个人类型”,并按要求登记主体信息。主体信息提交后不可修改,该主体将成为你使用微信公众平台各项服务和功能的唯一法律主体与缔约主体,在后续开通其他业务功能时不得变更或修改。

    在这里插入图片描述

    一切 OK 就可以直接进入小程序的管理平台了。如果直接跳转失败,也可以从微信公众平台上手动登录。填写小程序的基本信息,包括名称、图标、描述等。提交成功之后,再添加开发者。开发者默认为管理员,我们也可以从这里新增绑定开发者,这是管理员才有权限的操作。

    然后在左侧导航栏点击 “设置”,找到开发设置,获得小程序的 AppID。

    微信开发者工具

    下载微信web开发者工具,根据自己的操作系统下载对应的安装包进行安装即可。

    打开开发者工具,用微信扫码登录开发者工具,准备开发你的第一个小程序吧!

    第一个小程序

    新建项目

    打开开发者工具,选择 “小程序项目”,点击右下角的 “+” 新建项目。

    选择一个空的文件夹作为项目目录,填入刚刚的 AppID,再填写一个项目名称,比如我这里叫做 GoZeroWaster。点击 “确定” 进入工具主界面。

    项目目录结构

    微信小程序的基本文件构造和项目目录结构说明如下:

        .
        ├── app.js     # 小程序的逻辑文件
        ├── app.json   # 小程序的配置文件
        ├── app.wxss   # 全局公共样式文件
        ├── pages      # 存放小程序的各个页面
        │   ├── index  # index页面
        │   │   ├── index.js     # 页面逻辑
        │   │   ├── index.wxml   # 页面结构
        │   │   └── index.wxss   # 页面样式表
        │   └── logs   # logs页面
        │       ├── logs.js      # 页面逻辑
        │       ├── logs.json    # 页面配置
        │       ├── logs.wxml    # 页面结构
        │       └── logs.wxss    # 页面样式表
        ├── project.config.json
        └── utils
            └── util.js
    

    根目录下有3个文件:app.js、app.json、app.wxss,小程序必须有这3个描述 APP 的文件,并放在根目录下。这3个是应用程序级别的文件,与之平行的还有一个 pages 文件夹,用来存放小程序的各个页面。

    我们可以和 web 前端开发技术做个类比:

    • wxml 类似于 HTML 文件,用来编写页面的标签和骨架,但里面只能用小程序自己封装的组件;
    • wxss 类似于 CSS 文件,用来编写页面样式,只是把 css 文件换成了 wxss 文件;
    • js 文件类似于前端编程中的 JavaScript 文件,用来编写小程序的页面逻辑;
    • json 文件用来配置页面的样式和行为。

    目标成果

    我们先来看看最终的目标和成果,很简单,一共两页:

    在这里插入图片描述

    (为了让广大程序员也能保护环境和热爱生活,我特意选了 “零垃圾生活” 主题来做 Demo)

    步骤分解

    Demo 代码下载:https://gitee.com/luhuadong/Web_Learning/tree/master/WeChat/GoZeroWaster

    分解目标成果:

    1. 个人中心
    2. 生活指南
    3. 模拟弹窗
    4. 预览图片

    页头页尾

    在目标成果预览中我们看到,两个页面都有共同的部分 —— 页头和页尾。所以在构建页面内容之前,我们先把页头和页尾处理好。我们很容易猜到,这两部分属于小程序的全局配置,因此需要修改 app.json 文件。

    最初的内容如下:

        {
            "pages":[
                "pages/index/index",
                "pages/logs/logs"
            ],
            "window":{
                "backgroundTextStyle": "light",
                "navigationBarBackgroundColor": "#fff",
                "navigationBarTitleText": "WeChat",
                "navigationBarTextStyle": "balack"
            }
        }
    

    pages 属性用来设置页面路径,它是一个数组,每一项都是字符串来指定小程序由哪些页面组成。数组的第一项代表小程序的初始页面。小程序中新增或减少页面,都需要对 pages 数组进行修改。

    window 属性用于设置小程序的状态栏、导航条、标题、窗口背景色。

    我们把页头的标题和颜色修改一下,页尾部分我们做一个 tab 栏来切换页面,这个属性叫做 tabBar,代码如下:

    {
      "pages":[
        "pages/index/index",
        "pages/logs/logs"
      ],
      "window":{
        "backgroundTextStyle":"light",
        "navigationBarBackgroundColor": "#2f2f8f",
        "navigationBarTitleText": "GoZeroWaste",
        "navigationBarTextStyle":"white"
      },
      "tabBar":{
        "color": "#bfc1ab",
        "selectedColor": "#13b11c",
        "backgroundColor": "#1f1f4f",
        "list": [
          {
            "pagePath": "pages/index/index",
            "iconPath": "image/icon_component.png",
            "selectedIconPath": "image/icon_component_HL.png",
            "text": "个人中心"
          },
          {
            "pagePath": "pages/details/details",
            "iconPath": "image/icon_API.png",
            "selectedIconPath": "image/icon_API_HL.png",
            "text": "生活指南"
          }
        ]
      }
    }
    

    (所用到的图片放在项目的 image 目录,你也可以使用自己的图片)

    这里用到几个 tabBar 的属性是 color、selectedColor、backgroundColor 和 list,list 是一个数组,主要用于设定导航的路径。

    CTRL + S 保存之后,模拟器就会自动刷新,马上可以看到效果。

    个人中心

    在这里插入图片描述

    简单起见,我们就在 pages/index 目录下实现 “个人中心” 页面好了。双击打开 index.wxml,初始内容如下:

        <!--index.wxml-->
        <view class="container">
          <view class="userinfo">
            <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
            <block wx:else>
              <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
              <text class="userinfo-nickname">{{userInfo.nickName}}</text>
            </block>
          </view>
          <view class="usermotto">
            <text class="user-motto">{{motto}}</text>
          </view>
        </view>
    

    这里已经有一些代码了,虽然现在可能还看不懂,但我们知道,这就是现在页面的源代码。我们把 “Hello World” 部分注释掉,增加我们希望显示的内容:

    <!--index.wxml-->
    <view class="container">
      <view class="userinfo">
        <button wx:if="{{!hasUserInfo && canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
        <block wx:else>
          <image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" background-size="cover"></image>
          <text class="userinfo-nickname">{{userInfo.nickName}}</text>
        </block>
      </view>
       <!-- <view class="usermotto">
        <text class="user-motto">{{motto}}</text>
      </view>  -->
      <view class="ID_Badge">
        <view>
          <text class="ID_info">{{company}}</text>
        </view>
        <view>
          <text class='ID_info'>{{position}}</text>
        </view>
        <view>
          <text class='ID_info'>{{lesson}}</text>
        </view>
      </view>
    </view>
    

    这里分别使用 {{company}}{{position}}{{lesson}} 作为占位符,用法类似于 Django 的模板语言。当然也可以直接用相应的字符来替换它,只不过我们想沿用 {{motto}} 的做法,让你知道在哪里修改这些数据。没错,就是在 index.js 文件:

    Page({
      data: {
        motto: 'Hello World',
        company: "GoZeroWaste",
        lesson: "21天零垃圾生活指南",
        position: "垃圾魔法师",
        /* ... */
      },
    

    wxml 文件中的 <view> 组件类似于网页开发中的 <div>,而 <text> 组件是用来写文本的,需要注意的是 <text/> 组件内只支持 <text/> 嵌套。当然,可用用 <image> 插入图片,图片要保存到 image 目录,否则在测试的时候是无法上传的。

        <view class="ID_Badge">
            <!-- 省略 -->
            <view>
              <text class='ID_info'>{{lesson}}</text>
            </view>
            <view>
              <image class='pic' mode='widthFix' src='../../image/GoZeroWaste.jpg'></image>
            </view>
          </view>
    

    mode=‘widthFix’ 表示以宽度不变,高度自动变化,保持原图宽高比不变的方式进行缩放以适应屏幕大小。

    接下来还需要修改 index.wxss 文件来设置样式:

    /**index.wxss**/
    .userinfo {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    
    .userinfo-avatar {
      width: 128rpx;
      height: 128rpx;
      margin: 20rpx;
      border-radius: 50%;
    }
    
    .userinfo-nickname {
      color: #aaa;
    }
    
    .usermotto {
      margin-top: 200px;
    }
    
    .ID_Badge {
      padding-top: 20rpx;
      color: blue;
    }
    
    .ID_info {
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    
    .pics {
      width: 400rpx;
    }
    

    保存刷新,“个人中心” 页面就完成了。

    生活指南

    在这里插入图片描述

    原来的项目中 pages 目录下只有 index 和 logs 两个目录,因此我们还需要为第二个页面创建一个目录。

    创建页面有两种方法:

    • 在目录结构的 pages 图表上,新建目录,然后在目录下逐一创建页面构成文件
    • 在 app.json 下,直接添加

    建议采用第二种方法,修改 app.json 文件:

        {
          "pages":[
            "pages/index/index",
            "pages/logs/logs",
            "pages/details/details"
          ],
    

    保存刷新之后就会发现,目录结构里自动创建了这一页。对应的,也要修改 app.json 中的 tabBar 的链接(实际上我们已经做了):

        	{
                "pagePath": "pages/details/details",
                "iconPath": "image/icon_API.png",
                "selectedIconPath": "image/icon_API_HL.png",
                "text": "生活指南"
            }
    

    然后修改 details.wxml 设置这一页的标题:

        <!--pages/details/details.wxml-->
        <view>
          <view class='title'>
            <text>21天零垃圾生活指南</text>
          </view>
        </view>
    

    修改 details.wxss 设置样式:

        /* pages/details/details.wxss */
        .title {
          display: flex;
          flex-direction: column;
          align-items: center;
          margin-top: 40rpx;
          margin-bottom: 40rpx;
          font-size: 40rpx;
        }
    

    这个页面是一个列表展示的页面,我们先在 details.js 文件中准备好数据:

    // pages/details/details.js
    Page({
    
      /**
       * 页面的初始数据
       */
      data: {
        showModalStatus: false,
        list: [
          {
            id: 0,
            name : "写一篇《垃圾日记》",
            introduce: "零垃圾并不是一项宏大的工程,而是由日常生活中一个个小小的习惯和选择组成的。最难的,是迈出第一步。",
            src: '../../image/day01.jpg',
            showModalStatus: false,
            catalog: [
              { section: "1. xxx" },
              { section: "2. xxx" },
              { section: "3. xxx" },
              { section: "4. xxx" },
            ]
          },
          {
            id: 1,
            name: "带上自己的购物袋",
            introduce: "在我们家,当时垃圾桶里最多的就是塑料袋,而这些袋子跟着我回家后,都几乎难逃被丢进垃圾桶的命运。",
            src: '../../image/day02.jpg',
            showModalStatus: false,
            catalog: [
              { section: "1. xxx" },
              { section: "2. xxx" },
              { section: "3. xxx" },
              { section: "4. xxx" },
            ]
          },
          /* 省略 */
        ]
      },
    

    接下来我们要使用列表渲染(wx:for)的方法将这些数据绑定一个数组,并在页面上重复渲染。修改 details.wxml 文件:

          <view>
            <view wx:for="{{list}}" wx:key="id" >
              <view class="lesson" id="{{item.id}}">
                <image class="lessonPic" mode='aspectFit' src="{{item.src}}"></image>
                <view class="lessonName">{{item.name}}</view>
                <view class="lessonIntroduce">{{item.introduce}}</view>
              </view>
            </view>
          </view>
    

    默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item。

    修改 details.wxss 文件添加样式:

        .lesson {
          height: 190rpx;
          padding-left: 20rpx;
        }
        
        .lessonPic {
          position: absolute;
          height: 150rpx;
          width: 150rpx;
        }
        
        .lessonName {
          position: absolute;
          margin-left: 220rpx;
          font-size: 35rpx;
        }
        
        .lessonIntroduce {
          position: absolute;
          margin-left: 220rpx;
          margin-top: 60rpx;
          margin-right: 20rpx;
          color: rgb(185, 161, 161);
          font-size: 28rpx;
        }
    

    好啦,第二个页面也完成了。

    模拟弹窗

    在这里插入图片描述

    接下来我们要在 “生活指南” 页面模拟一个弹窗的效果,正常的时候不显示,只有在点击的时候才出现,摁下面的 “确定” 就会消失。

    完了实现这个功能,我们要在组件中绑定一个事件处理函数 bindtap,点击该组件的时候,小程序会在该页面对应的 Page 中找到相应的事件处理函数。

    我们先在 details.js 中为每一列数据里引入一个 boolean 变量 showModalStatus 来描述对应的弹窗状态,并且初始值为 false,表示不显示。同时外层也增加一个初始值为 false 的 showModalStatus 变量实现遮罩效果。如下:

           data: {
            showModalStatus: false,
            list: [
              {
                id: 0,
                name : "写一篇《垃圾日记》",
                introduce: "零垃圾并不是一项宏大的工程,而是由日常生活中一个个小小的习惯和选择组成的。最难的,是迈出第一步。",
                src: '../../image/day01.jpg',
                showModalStatus: false,
                catalog: [
                  { section: "1. xxx" },
                  { section: "2. xxx" },
                  { section: "3. xxx" },
                  { section: "4. xxx" },
                ]
              },
    

    然后在 details.wxml 中插入弹窗,并用条件渲染(wx:if)来判断是否渲染(显示)弹窗。同时为每一个 item 添加 data-statu 来表示弹窗的状态。如下:

      <view>
        <view wx:for="{{list}}" wx:key="id" >
          <view class="lesson" bindtap='powerDrawer' data-statu='open' id="{{item.id}}">
            <image class="lessonPic" mode='aspectFit' src="{{item.src}}"></image>
            <view class="lessonName">{{item.name}}</view>
            <view class="lessonIntroduce">{{item.introduce}}</view>
          </view>
            
          <!-- 弹窗 -->
          <view class='drawer_box' wx:if='{{item.showModalStatus}}' id='{{item.id}}'>
            <view class="title">{{item.name}}</view>
            <view class='drawer_content'>
              <view class='title' wx:for='{{item.catalog}}' wx:for-item='catalog' wx:key='id'>
                {{catalog.section}}
              </view>
            </view>
            <!-- 确定按钮 -->
            <view class='btn_ok' bindtap='powerDrawer' data-statu='close' id='{{item.id}}'>确定</view>
          </view>
        </view>
          
        <!-- 遮罩层 -->
        <view class='drawer_screen' data-statu='close' wx:if='{{showModalStatus}}'></view>
      </view>
    

    在 details.js 添加 powerDrawer 事件处理,包括显示和关闭事件:

          powerDrawer: function (e) {
            console.log("clicked");
        
            var currentStatu = e.currentTarget.dataset.statu;
            var index = e.currentTarget.id;
        
            // 关闭
            if (currentStatu == 'close') {
              this.data.list[index].showModalStatus = false;
              this.setData({
                showModalStatus: false,
                list: this.data.list,
              });
            }
        
            // 显示
            if (currentStatu == 'open') {
              this.data.list[index].showModalStatus = true;
              this.setData({
                showModalStatus: true,
                list: this.data.list,
              });
            }
          },
    

    最后在 details.wxss 设置一下弹窗和遮罩层的样式:

        .drawer_box {
          width: 650rpx;
          overflow: hidden;
          position: fixed;
          top: 50%;
          z-index: 1001;
          background: #FAFAFA;
          margin: -150px 50rpx 0 50rpx;
        }
        
        .drawer_content {
          border-top: 1.5px solid #E8E8EA;
          height: 210px;
          overflow-y: scroll; /* 超出父盒子高度可滚动 */
        }
        
        .btn_ok {
          padding: 10px;
          font: 20px "microsoft yahei";
          text-align: center;
          border-top: 1.5px solid #E8E8EA;
          color: #3CC51F;
        }
        
        .drawer_screen {
          width: 100%;
          height: 100%;
          position: fixed;
          top: 0;
          left: 0;
          z-index: 1000;
          background: black;
          opacity: 0.5;
          overflow: hidden;
        }
    

    OK,模拟弹窗也实现了。

    预览图片

    在这里插入图片描述

    最后一步就是在第一个页面实现图片预览和图片保存的功能,在 index.wxml 中为图片添加一个点击事件 previewImage。

        <image class='pic' mode='widthFix' src='../../image/GoZeroWaste.jpg' bindtap='previewImage'></image>
    

    在 index.js 中添加 imgalist 项(我们直接把公众号的二维码图片上传到 CSDN 的图片服务器了),并且实现 previewImage 事件处理。如下:

        Page({
          data: {
            motto: 'Hello World',
            company: "GoZeroWaste",
            lesson: "21天零垃圾生活指南",
            position: "垃圾魔法师",
            imgalist: ['https://img-blog.csdnimg.cn/20190109104518898.jpg'],
            userInfo: {},
            hasUserInfo: false,
            canIUse: wx.canIUse('button.open-type.getUserInfo')
          },
          previewImage: function (e) {
            wx.previewImage({
              current: this.data.imgalist,  // 当前显示图片的http链接
              urls: this.data.imgalist      // 需要预览的图片http链接列表
            })
          },
    

    大功告成,点击开发者工具中的 “预览”,使用微信扫描生成的二维码即可在手机端查看。

    教程就到这里囖,再见吧~

    在这里插入图片描述

    展开全文
  • 这次的项目主要是关于微信公众号的一个开发,本人这次分配的模块是后台微信公众号的支付和退款,第一次接触微信公众的项目刚开始一脸懵逼,开发过程中遇到各种坑,所以想自己写一篇详细的关于微信公众号的开发,希望...
  • 微信公众号开发教程(一)验证接入本篇文章主要介绍了微信公众号开发接入详细流程,希望对刚接触公众号开发的同学有所帮助,有兴趣的同学可多多关注叩丁狼公众号,后续会更新不同的公众号小案例。公众号的分类我们平常...
  • 过年前后做了个微信公众号项目,已经过去一段时间了,抽空回忆总结下基本流程吧,不然很快估计自己就忘了。。 微信公众平台官网:https://mp.weixin.qq.com 文章目录一、注册公众号二、了解公众号管理页面三、必备...
  • 微信开发流程

    2017-12-12 18:53:34
    一、绑定服务器成为开发者微信公众号配置登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者...
  • 1.创建菜单栏 //菜单栏 public static void createMenu(){ HashMap<String,Object> hashMap = new LinkedHashMap<>(); List<HashMap> list = new ArrayList<>... Ha...
  • 微信开发的概念

    2019-12-03 23:23:16
    微信开发的概念 什么是微信开发 微信这个软件,提供了聊天、支付、分享、收藏等各种功能,同时用户基数庞大; 微信对外开放了很多接口和能力,程序员基于这些功能进行的二次开发,叫做微信开发 微信开放平台 微信...
  • 同学们大家好,我是小伊同学,今天继续学习WXML部分,我们来介绍一下常用的组件和属性。 在上一节教程中,我们已经知道了什么是组件什么是属性。通常情况下,组件和属性都是配合使用,同时出现。...
  • 微信开发之入门教程

    2016-01-12 10:55:26
    微信开发也是有了一定的认识。在此,小宝鸽再次无私地分享给大家啦。其实微信开发跟web开发没有多大的区别,只是经过了微信,然后再由浏览器打开。因为经过微信,因此你的web会附加一些微信平台所提供的一些功能,...
  • 同时它也取代了我们传统的一些交流方式,为我们节省了很多的信息费用,因此很多人在开发微信的时候,都将微信当成了是一个商业的平台,有更多的商家开始驻足于微信的开发,那么,微信开发需要掌握哪些系统和技术?...
  • 微信开发本地调试工具(模拟微信客户端)开源免费微信管家系统(java)源码下载微信部署需要依靠80端口,如何快速有效的调试本地微信开发程序,捷微团队(jeewx-java微信开源系统),采用微信开发调试工具,供本地...
  • 经常有同学过来询问“小白想学习微信开发,需要会哪些技术?”,今天我就系统的回答这个问题。想弄清楚这个问题,你必须知道一些微信开发的相关知识。微信公众号主要有:订阅号、服务号、企业号(已经升级到企业微信...
  • 微信群发多图文消息开发是子恒老师《子恒说微信开发》视频教程的第16部。详细讲解了用php开发微信群发图文消息。内容包含图文消息前端页面,一个页面插入多个百度UEditor,群发多图文,查询和删除图文消息等等。欢迎...
  • 发送消息,是指用户公众号向用户发送相应形式的消息。根据微信开发文档,由以下四种形式:被动回复,群发接口,客服消息接口以及模板消息接口。...这一部分较为简单,正式进行微信开发的第一步就是,在公众号中基本
1 2 3 4 5 ... 20
收藏数 338,395
精华内容 135,358
关键字:

微信开发