小程序?qū)崿F(xiàn)在線選座實戰(zhàn)(上)

hi~ 大家好,我叫內(nèi)孤,一名web前端開發(fā)者/:B-,今天我來分享一個移動端在線選座的功能頁面,我們知道微信小程序可以用web-view嵌入html頁面,所以這個選座功能我們就用html、css、javascript一步一步實現(xiàn)。避免篇幅太長,我將整個功能的實現(xiàn)分為上、中、下。

假設(shè)下圖就是我們要實現(xiàn)的功能頁面,我們先對這個功能頁面進行組件劃分,header、main、footer三個組件來分別實現(xiàn):


image.png

1. 創(chuàng)建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是來重置樣式,結(jié)合header里的那段腳本實現(xiàn)rem不同屏幕自適應(yīng)。

2. 書寫對應(yīng)的布局樣式(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: 座位狀態(tài)示意圖

這里沒有互動,是很簡單的展示組件

// 在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中,座位狀態(tài)組件
.seatStatusList {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 1rem;
}
.seatStatusList .statusLabel img {
  width: .5rem;
  height: .4rem;
  margin-right: .1rem;
}

到這一步我們可以看到基本的架子了

image.png

5. 實現(xiàn)底部的組件

這個組件我們就創(chuàng)建一個footer.js實現(xiàn)

var Footer = (function(factory) {
  return factory.call();
}(function() {
  // 定義默認的回調(diào)
  var __DESC__ = {
    onClickInfoModule: function() {},
    onHandleSure: function() {},
    /**
     * 默認格式化數(shù)據(jù)的回調(diào)
     * @param {Array} data 例如:[{id: 1, price: 2}]
     * 注意: 如果沒有定義formatData回調(diào),需要確保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 不是一個數(shù)組');
     }
     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;
    },
    // 監(jiān)聽點擊了信息模塊的回調(diào)
    _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);
          }
        }
      });
    },
    // 監(jiān)聽確定選座按鈕
    _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>應(yīng)付: <span class="price">' + total + '元' + '</span></i>' +
        '<span>共' + count + '張</span></div>';
      this.$el.querySelector('.total').innerHTML = tpl;
    },
    // 私有方法:渲染默認的組件狀態(tài)
    _renderDefaultTpl: function() {
      var tpl = ('<div class="footer-component">' +
        '<div class="total">' +
        '請選擇座位' +
        '</div>' +
        '<div class="sureBtn">確定選座</div>' +
        '</div>');
      this.$el.innerHTML = tpl;
    },
    /**
     * 
     * @param {*} data 傳入的數(shù)據(jù)
     * 如果沒有自定義formatData回調(diào),約定data數(shù)據(jù)中必須包含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 返回的參數(shù)沒有total和count');
      }
    },
    // 重置初始化狀態(tài)
    resetStatus: function() {
      this.data = [];
      this._renderDefaultTpl();
    }
  };

  return __CORE__;
}));

以上我們用閉包創(chuàng)建了一個Footer組件,通過Footer.init實現(xiàn)組件初始化,對外留著一個setData方法,用來設(shè)置約定格式的數(shù)據(jù)然后進行視圖渲染。還有一個resetStatus方法,來重置狀態(tài)和視圖。

然后我們在index.js對組件進行初始化

// 監(jiān)聽document加載完畢才去初始化各個組件
document.addEventListener("DOMContentLoaded", function (e) {
  Footer.init({
    el: '.footer',
    onHandleSure: function(data) {
      console.log('點擊確定等到我們選中的座位信息,發(fā)送給服務(wù)器', 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);
});

創(chuàng)建了Footer組件后,我們完成的界面如下:


image.png

回顧

  1. 在整體布局的搭建中我們使用了rem自適應(yīng)屏幕大小方案
  2. 在Footer組件中,我們使用了閉包構(gòu)建組件、使用了事件代理等,實現(xiàn)了如何用javascript構(gòu)建一個自己的組件

接下去,我們將在《小程序?qū)崿F(xiàn)在線選座實戰(zhàn)(中)》實現(xiàn)選座組件,在《小程序?qū)崿F(xiàn)在線選座實戰(zhàn)(下)》中實現(xiàn)數(shù)據(jù)交互。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 問答題47 /72 常見瀏覽器兼容性問題與解決方案? 參考答案 (1)瀏覽器兼容問題一:不同瀏覽器的標簽?zāi)J的外補...
    _Yfling閱讀 14,154評論 1 92
  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5? 答:HTML5是最新的HTML標準。 注意:講述HT...
    kismetajun閱讀 28,818評論 1 45
  • 一:什么是閉包?閉包的用處? (1)閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。在本質(zhì)上,閉包就 是將函數(shù)內(nèi)部和函數(shù)外...
    xuguibin閱讀 10,037評論 1 52
  • $HTML, HTTP,web綜合問題 1、前端需要注意哪些SEO 2、 的title和alt有什么區(qū)別 3、HT...
    Hebborn_hb閱讀 4,779評論 0 20
  • 時間盲注原理: 代碼存在sql注入漏洞,然而頁面既不會回顯數(shù)據(jù),也不會回顯錯誤信息 語句執(zhí)行后也不提示真假,我們不...
    shadowflow閱讀 2,569評論 0 0

友情鏈接更多精彩內(nèi)容