小程序实现在线选座实战(上)

作者: 悟C | 来源:发表于2019-03-17 13:18 被阅读1次

    hi~ 大家好,我叫内孤,一名web前端开发者/:B-,今天我来分享一个移动端在线选座的功能页面,我们知道微信小程序可以用web-view嵌入html页面,所以这个选座功能我们就用html、css、javascript一步一步实现。避免篇幅太长,我将整个功能的实现分为上、中、下。

    假设下图就是我们要实现的功能页面,我们先对这个功能页面进行组件划分,header、main、footer三个组件来分别实现:


    image.png

    1. 创建index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1.0,user-scalable=no,viewport-fit=cover"/>
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
     <!-- 初始化样式 -->
      <link rel="stylesheet" href="./css/reset.css">
      <link rel="stylesheet" href="./css/styles.css">
      <!-- rem 的解决方案 -->
      <script type="text/javascript">
      (function(e,t){var i=document,n=window;var l=i.documentElement;var r,a;var d,o=document.createElement("style");var s;function m(){var i=l.getBoundingClientRect().width;if(!t){t=540}if(i>t){i=t}var n=i*100/e;o.innerHTML="html{font-size:"+n+"px;}"}r=i.querySelector('meta[name="viewport"]');a="width=device-width,initial-scale=1,maximum-scale=1.0,user-scalable=no,viewport-fit=cover";if(r){r.setAttribute("content",a)}else{r=i.createElement("meta");r.setAttribute("name","viewport");r.setAttribute("content",a);if(l.firstElementChild){l.firstElementChild.appendChild(r)}else{var c=i.createElement("div");c.appendChild(r);i.write(c.innerHTML);c=null}}m();if(l.firstElementChild){l.firstElementChild.appendChild(o)}else{var c=i.createElement("div");c.appendChild(o);i.write(c.innerHTML);c=null}n.addEventListener("resize",function(){clearTimeout(s);s=setTimeout(m,300)},false);n.addEventListener("pageshow",function(e){if(e.persisted){clearTimeout(s);s=setTimeout(m,300)}},false);if(i.readyState==="complete"){i.body.style.fontSize="16px"}else{i.addEventListener("DOMContentLoaded",function(e){i.body.style.fontSize="16px"},false)}})(750,750);
      </script>
      <title>在线选座</title>
    </head>
    <body>
      <div class="page">
    
        <div class="header">
        </div>
    
        <div class="main">
    
        </div>
    
        <div class="footer">
    
        </div>
        
      </div>
    </body>
    </html>
    

    这里我们定义了header、main、footer容器,分别来装载三个组件,所有的样式都放在styles.css中,reset.css是来重置样式,结合header里的那段脚本实现rem不同屏幕自适应。

    2. 书写对应的布局样式(styles.css)

    /* 基本布局样式 */
    .page {
      width: 100vw;
      height: 100vh;
      display: flex;
      flex-direction: column;
      color: #999;
    }
    .header {
      height: .9rem;
      overflow: hidden;
      background-color: #fff;
    }
    .main {
      background-color: #eee;
      flex-grow: 1;
      overflow: hidden;
    }
    .footer {
      display: flex;
      justify-content: space-between;
      align-items: center;
      height: 1.2rem;
      background-color: #fff;
    }
    .mrgR60 {
      margin-right: 0.6rem;
    }
    

    3. reset.css

      body,dl,dd,ul,ol,h1,h2,h3,h4,h5,h6,pre,form,input,textarea,p,hr,thead,tbody,tfoot,th,td{margin:0;padding:0;}
      ul,ol{list-style:none;}
      a{text-decoration:none;}
      html{-ms-text-size-adjust:none;-webkit-text-size-adjust:none;text-size-adjust:none;font-size:50px;}
      body{line-height:1.5;font-size:16px;}
      body,button,input,select,textarea{font-family:'helvetica neue',tahoma,'hiragino sans gb',stheiti,'wenquanyi micro hei',\5FAE\8F6F\96C5\9ED1,\5B8B\4F53,sans-serif;}
      b,strong{font-weight:bold;}
      i,em{font-style:normal;}
      table{border-collapse:collapse;border-spacing:0;}
      table th,table td{border:1px solid #ddd;padding:5px;}
      table th{font-weight:inherit;border-bottom-width:2px;border-bottom-color:#ccc;}
      img{border:0 none;width:auto\9;max-width:100%;vertical-align:top;}
      button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;vertical-align:baseline;}
      button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}
      button[disabled],input[disabled]{cursor:default;}
      input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}
      input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}
      input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
      @media screen and (-webkit-min-device-pixel-ratio:0){
      input{line-height:normal!important;}
      }
      select[size],select[multiple],select[size][multiple]{border:1px solid #AAA;padding:0;}
      article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}
      audio,canvas,video,progress{display:inline-block;}
    
    
      .g-doc{width:7.5rem;margin:0px auto;}
    

    4. 组件1: 座位状态示意图

    这里没有互动,是很简单的展示组件

    // 在index.html中的header中添加
    
    <div class="header">
      <div class="seatStatusList">
        <div class="statusLabel mrgR60">
          <img src="./image/seat.png"/>
          <span>已选中</span>
        </div>
        <div class="statusLabel">
          <img src="./image/seat_disabled.png"/>
          <span>不可选</span>
        </div>
      </div>
    </div>
    
    // 添加到styles.css中,座位状态组件
    .seatStatusList {
      display: flex;
      justify-content: center;
      align-items: center;
      height: 1rem;
    }
    .seatStatusList .statusLabel img {
      width: .5rem;
      height: .4rem;
      margin-right: .1rem;
    }
    

    到这一步我们可以看到基本的架子了

    image.png

    5. 实现底部的组件

    这个组件我们就创建一个footer.js实现

    var Footer = (function(factory) {
      return factory.call();
    }(function() {
      // 定义默认的回调
      var __DESC__ = {
        onClickInfoModule: function() {},
        onHandleSure: function() {},
        /**
         * 默认格式化数据的回调
         * @param {Array} data 例如:[{id: 1, price: 2}]
         * 注意: 如果没有定义formatData回调,需要确保item中包含price
         * @return {total, count}
         */
        formatData: function(data) {
         var total = 0, count = 0, res = {};
         if (Object.prototype.toString.call(data) === "[object Array]") {
          for (var i = 0, len = data.length; i < len; i++) {
            count++;
            if (data[i].price) {
              total += data[i].price;
            } else {
              new Error('座位信息中没有price字段');
              break;
            }
          }
          res.total = total;
          res.count = count;
         } else {
          new Error('data 不是一个数组');
         }
         return res;
        }
      };
      var __CORE__ = {
        init: function(options) {
          this.$el = document.querySelector(options.el);
          this.onHandleSure = options.onHandleSure || __DESC__.onHandleSure;
          this.formatData = options.formatData || __DESC__.formatData;
          this.onClickInfoModule = options.onClickInfoModule || __DESC__.onClickInfoModule;
          this.data = [];
          this._renderDefaultTpl();
          this._onClickSureBtn();
          this._onClickInfoModule();
          return this;
        },
        // 监听点击了信息模块的回调
        _onClickInfoModule: function () {
          var me = this;
    
          me.$el.addEventListener('touchstart', function(e) {
            var target = e.target;
            var parentNode = target.parentNode;
            if (parentNode.className && parentNode.className.indexOf('priceBox') > -1 || parentNode.parentNode.className && parentNode.parentNode.className.indexOf('priceBox') > -1) {
              if (typeof me.onClickInfoModule === 'function') {
                me.onClickInfoModule.call(me, me.data);
              }
            }
          });
        },
        // 监听确定选座按钮
        _onClickSureBtn: function() {
          var me = this;
          me.$el.addEventListener('touchstart', function(e) {
            var target = e.target;
            // 用me.$el 代理点击事件
            if (target.className && target.className.indexOf('sureBtn') > -1) {
              if (typeof me.onHandleSure === 'function') {
                me.onHandleSure.call(me, me.data);
              }
            }
          });
        },
        // 私有方法:渲染选中的座位信息
        _renderSelectedSeatInfo: function(total, count) {
          var tpl = '<div class="priceBox"><i>应付: <span class="price">' + total + '元' + '</span></i>' +
            '<span>共' + count + '张</span></div>';
          this.$el.querySelector('.total').innerHTML = tpl;
        },
        // 私有方法:渲染默认的组件状态
        _renderDefaultTpl: function() {
          var tpl = ('<div class="footer-component">' +
            '<div class="total">' +
            '请选择座位' +
            '</div>' +
            '<div class="sureBtn">确定选座</div>' +
            '</div>');
          this.$el.innerHTML = tpl;
        },
        /**
         * 
         * @param {*} data 传入的数据
         * 如果没有自定义formatData回调,约定data数据中必须包含price, 例如: [{ price: 2 }]
         */
        setData: function(data) {
          var res = {};
          this.data = data;
          if (typeof this.formatData === 'function') {
            res = this.formatData(data);
          }
          if (res && res.total && res.count) {
            this._renderSelectedSeatInfo(res.total, res.count);
          } else {
            new Error('formatData 返回的参数没有total和count');
          }
        },
        // 重置初始化状态
        resetStatus: function() {
          this.data = [];
          this._renderDefaultTpl();
        }
      };
    
      return __CORE__;
    }));
    

    以上我们用闭包创建了一个Footer组件,通过Footer.init实现组件初始化,对外留着一个setData方法,用来设置约定格式的数据然后进行视图渲染。还有一个resetStatus方法,来重置状态和视图。

    然后我们在index.js对组件进行初始化

    // 监听document加载完毕才去初始化各个组件
    document.addEventListener("DOMContentLoaded", function (e) {
      Footer.init({
        el: '.footer',
        onHandleSure: function(data) {
          console.log('点击确定等到我们选中的座位信息,发送给服务器', data);
        },
        onClickInfoModule: function(data) {
          console.log('点击了信息模块', data);
        }
      });
      var selectedData = [
        {
          id: 1,
          price: 1,
        },
        {
          id: 2,
          price: 2
        }
      ];
    
      Footer.setData(selectedData);
    
      setTimeout(function() {
        Footer.resetStatus();
      }, 2000);
    });
    

    创建了Footer组件后,我们完成的界面如下:


    image.png

    回顾

    1. 在整体布局的搭建中我们使用了rem自适应屏幕大小方案
    2. 在Footer组件中,我们使用了闭包构建组件、使用了事件代理等,实现了如何用javascript构建一个自己的组件

    接下去,我们将在《小程序实现在线选座实战(中)》实现选座组件,在《小程序实现在线选座实战(下)》中实现数据交互。

    相关文章

      网友评论

        本文标题:小程序实现在线选座实战(上)

        本文链接:https://www.haomeiwen.com/subject/awjsmqtx.html