hi~ 大家好喻括,我叫內(nèi)孤蒲拉,一名web前端開(kāi)發(fā)者/:B-甸各,今天我來(lái)分享一個(gè)移動(dòng)端在線選座的功能頁(yè)面慌随,我們知道微信小程序可以用web-view嵌入html頁(yè)面芬沉,所以這個(gè)選座功能我們就用html、css阁猜、javascript一步一步實(shí)現(xiàn)丸逸。避免篇幅太長(zhǎng),我將整個(gè)功能的實(shí)現(xiàn)分為上剃袍、中黄刚、下。
假設(shè)下圖就是我們要實(shí)現(xiàn)的功能頁(yè)面民效,我們先對(duì)這個(gè)功能頁(yè)面進(jìn)行組件劃分憔维,header、main畏邢、footer三個(gè)組件來(lái)分別實(shí)現(xiàn):
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
容器棵红,分別來(lái)裝載三個(gè)組件凶赁,所有的樣式都放在styles.css中咧栗,reset.css是來(lái)重置樣式逆甜,結(jié)合header里的那段腳本實(shí)現(xiàn)rem不同屏幕自適應(yīng)虱肄。
2. 書(shū)寫(xiě)對(duì)應(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)示意圖
這里沒(méi)有互動(dòng),是很簡(jiǎn)單的展示組件
// 在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;
}
到這一步我們可以看到基本的架子了
5. 實(shí)現(xiàn)底部的組件
這個(gè)組件我們就創(chuàng)建一個(gè)footer.js實(shí)現(xiàn)
var Footer = (function(factory) {
return factory.call();
}(function() {
// 定義默認(rèn)的回調(diào)
var __DESC__ = {
onClickInfoModule: function() {},
onHandleSure: function() {},
/**
* 默認(rèn)格式化數(shù)據(jù)的回調(diào)
* @param {Array} data 例如:[{id: 1, price: 2}]
* 注意: 如果沒(méi)有定義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('座位信息中沒(méi)有price字段');
break;
}
}
res.total = total;
res.count = count;
} else {
new Error('data 不是一個(gè)數(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)聽(tīng)點(diǎ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)聽(tīng)確定選座按鈕
_onClickSureBtn: function() {
var me = this;
me.$el.addEventListener('touchstart', function(e) {
var target = e.target;
// 用me.$el 代理點(diǎn)擊事件
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;
},
// 私有方法:渲染默認(rèn)的組件狀態(tài)
_renderDefaultTpl: function() {
var tpl = ('<div class="footer-component">' +
'<div class="total">' +
'請(qǐng)選擇座位' +
'</div>' +
'<div class="sureBtn">確定選座</div>' +
'</div>');
this.$el.innerHTML = tpl;
},
/**
*
* @param {*} data 傳入的數(shù)據(jù)
* 如果沒(méi)有自定義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ù)沒(méi)有total和count');
}
},
// 重置初始化狀態(tài)
resetStatus: function() {
this.data = [];
this._renderDefaultTpl();
}
};
return __CORE__;
}));
以上我們用閉包創(chuàng)建了一個(gè)Footer組件素征,通過(guò)Footer.init實(shí)現(xiàn)組件初始化集嵌,對(duì)外留著一個(gè)setData方法,用來(lái)設(shè)置約定格式的數(shù)據(jù)然后進(jìn)行視圖渲染御毅。還有一個(gè)resetStatus方法根欧,來(lái)重置狀態(tài)和視圖。
然后我們?cè)趇ndex.js對(duì)組件進(jìn)行初始化
// 監(jiān)聽(tīng)document加載完畢才去初始化各個(gè)組件
document.addEventListener("DOMContentLoaded", function (e) {
Footer.init({
el: '.footer',
onHandleSure: function(data) {
console.log('點(diǎn)擊確定等到我們選中的座位信息端蛆,發(fā)送給服務(wù)器', data);
},
onClickInfoModule: function(data) {
console.log('點(diǎn)擊了信息模塊', data);
}
});
var selectedData = [
{
id: 1,
price: 1,
},
{
id: 2,
price: 2
}
];
Footer.setData(selectedData);
setTimeout(function() {
Footer.resetStatus();
}, 2000);
});
創(chuàng)建了Footer組件后凤粗,我們完成的界面如下:
回顧
- 在整體布局的搭建中我們使用了rem自適應(yīng)屏幕大小方案
- 在Footer組件中,我們使用了閉包構(gòu)建組件今豆、使用了事件代理等嫌拣,實(shí)現(xiàn)了如何用javascript構(gòu)建一個(gè)自己的組件
接下去,我們將在《小程序?qū)崿F(xiàn)在線選座實(shí)戰(zhàn)(中)》實(shí)現(xiàn)選座組件呆躲,在《小程序?qū)崿F(xiàn)在線選座實(shí)戰(zhàn)(下)》中實(shí)現(xiàn)數(shù)據(jù)交互异逐。