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.png5. 实现底部的组件
这个组件我们就创建一个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
回顾
- 在整体布局的搭建中我们使用了rem自适应屏幕大小方案
- 在Footer组件中,我们使用了闭包构建组件、使用了事件代理等,实现了如何用javascript构建一个自己的组件
接下去,我们将在《小程序实现在线选座实战(中)》实现选座组件,在《小程序实现在线选座实战(下)》中实现数据交互。
网友评论