已经好久没有更新技术博客了,因为最近一直在做跨平台web app应用的开发,由于是刚做这个,也没太多经验同大家分享,可我是一个闲不住的人,我还是决定于百忙之中抽空整理一篇,记录下开发历程......——前言
需求描述
可以选择日期,按月份分别查看应出勤数、已出勤数、迟到数、早退数,用特殊标记标出某天是迟到还是早退等等,选中某天,直接加载该天的所有考勤记录,看上去挺简单的功能,要我写js估计可以写到吐血,还好懂得拿来主义,网上找个开源的日历组件,然后进行改造。日历组件源码地址Calendar.js由于公司没有平面和美工,也没有前端,所以这些活我就兼做了,所以大家不要对界面吐槽,我已经尽力了,555~,下面的界面截图我都是用的谷歌浏览器,模拟iphone6的效果,同真机上比自然会有一定的出入,通常来说真机上面要比模拟器上面更漂亮和清晰,界面效果如下:
注:橙色表示当前日期,浅蓝色表示选中日期,默认情况下显示当前日期,并加载当天的考勤记录。
功能实现
之前本来打算使用区域滚动的,后面看见原型界面是整个页面滚动,所以我就暂时注释了这块。
日历支持左右滑动进行翻页(按月进行翻),顶部按钮也支持翻页。
技术选型:mui、h5、h5+、vue.js
html代码如下:
doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>我的考勤title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<link href="../../css/mui.min.css" rel="stylesheet" />
<link rel="stylesheet" href="../../css/base.css" />
<link rel="stylesheet" href="../../css/app/home/timeline.css" />
<link rel="stylesheet" href="../../css/app/my/punch-card.css" />
<link rel="stylesheet" href="../../css/calendar.css" />
head>
<body>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left">a>
<h1 class="mui-title">我的考勤h1><span id="bigTime" class="bigTime" style="visibility:hidden;">span>
header>
<div id="app" class="mui-content">
<div class="div-yearmonth">
<i id="prePage" class="mui-icon iconfont icon-shangyiye">i>
<span class="spn-today"><i class="mui-icon iconfont icon-rili">i><span id="timeNow">span>span>
<i id="nextPage" class="mui-icon iconfont icon-xiayiye">i>
div>
<div class="div-operate">
<div class="mui-segmented-control ul-operate">
<a class="mui-control-item mui-active" v-on:tap="getDataByType(1)">
<p><span v-text="obj.attendance">span>天p>
<p>应出勤p>
a>
<a class="mui-control-item" v-on:tap="getDataByType(2)">
<p><span v-text="obj.actualAttendance">span>天p>
<p>实际出勤p>
a>
<a class="mui-control-item" v-on:tap="getDataByType(3)">
<p><span v-text="obj.beLate">span>次p>
<p>迟到p>
a>
<a class="mui-control-item" v-on:tap="getDataByType(4)">
<p><span v-text="obj.leaveEarly">span>次p>
<p>早退p>
a>
div>
div>
<div id="container">div>
<div class="div-list">
<div class="line">div>
<div class="date"><span class="spn-date"><i v-text="monthDay" class="monthDay">i><i v-text="week">星期一i>span>div>
<section v-if="list.length>0" id="cd-timeline" class="cd-container">
<div v-for="(item,index) in list" class="cd-timeline-block">
<div class="cd-timeline-img cd-picture" v-bind:class="{first:!index}"><span class="worktag">早班上班span>div>
<div class="cd-timeline-content">
<div class="timeline-head">
<span class="fl" v-text="item.name">span>
<span class="fl">打卡时间span><span class="fl" v-text="item.time">span>
<span v-if="item.errorType!=''" class="mui-badge fl" v-bind:class="g.getTimeCardTypeBcByVal(item.errorType)" v-text="g.getTimeCardTypeByVal(item.errorType)">span>
div>
<div class="div-address"><i class="mui-icon mui-icon-location">i><span v-text="item.address">span>div>
div>
div>
section>
<div v-else>
<div class="emptyinfo">
<span class="mui-icon mui-icon-location">span>
<p>您今天未打卡哦~p>
div>
div>
div>
div>
<script src="../../js/mui.min.js">script>
<script type="text/javascript" src="../../js/libs/vue.min.js">script>
<script type="text/javascript" src="../../js/common/config.js">script>
<script type="text/javascript" src="../../js/common/global.js">script>
<script type="text/javascript" src="../../js/libs/calendar.js">script>
<script type="text/javascript">
var canlendar = null;
mui.init();
mui.ready(function () {
//g.initScroll({ h: '300px' });//区域滚动
});
var app = new Vue({
el: "#app",
data: {
obj: {
attendance: '',
actualAttendance: '',
beLate: '',
leaveEarly: ''
},
appData: {
attendance: [],
actualAttendance: [],
beLate: [],
leaveEarly: []
}, //不同考勤类型数据
monthDay: g.getDatePart('month') + '/' + g.getDatePart('day'), //月/日
week: g.getDatePart('week'), //星期几
list: []
},
mounted: function () {
var _self = this;
eventListener();
initHandleData(_self); //1
canlendar = new Calendar({
parentNode: document.getElementById("container"),
appData: _self.appData,
sltDateFuc:_self.sltDateFuc
}); //2注意顺序
mui.plusReady(function () {
var wv = plus.webview.currentWebview();
_self.userName = wv.userName;
_self.roleName = wv.roleName;
_self.imgUrl = wv.imgUrl;
})
},
methods: {
//回调函数,根据日期获取考勤记录
sltDateFuc: function (date) {
//test
console.log(date);
var sltDate = g.convertDateFromString(date);
this.monthDay = g.getDatePart('month', sltDate) + '/' + g.getDatePart('day', sltDate); //月/日
this.week = g.getDatePart('week', sltDate); //星期几
if (date == '2017-11-11') {
this.list = [{
name: '早班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '08:20',
tag: 1,
errorType: '1'
},
{
name: '早班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '09:20',
tag: 1,
errorType: '2'
},
{
name: '中班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '12:20',
tag: 1,
errorType: ''
}
];
} else if (date == '2017-11-10') {
this.list = [{
name: '晚班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '21:20',
tag: 1,
errorType: '1'
},
{
name: '晚班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '22:20',
tag: 1,
errorType: '2'
},
{
name: '晚班上班',
address: '广东省深圳市南山区学苑大道1001号南山智园C3栋5楼',
time: '23:20',
tag: 1,
errorType: ''
}
];
} else {
this.list = [];
}
},
//切换考勤类型
getDataByType: function (t) {
var result = [];
switch (t) {
case 1: //应出勤
app.appData.attendance = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27];
app.appData.beLate = [];
app.appData.leaveEarly = [];
app.appData.actualAttendance = [];
break;
case 2: //实际出勤
app.appData.actualAttendance = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27];
app.appData.attendance = [];
app.appData.leaveEarly = [];
app.appData.beLate = [];
break;
case 3: //迟到
app.appData.beLate = [3, 5];
app.appData.actualAttendance = [];
app.appData.attendance = [];
app.appData.leaveEarly = [];
break;
default: //早退
app.appData.leaveEarly = [11, 7, 21];
app.appData.attendance = [];
app.appData.actualAttendance = [];
app.appData.beLate = [];
break;
}
app.list = result;
canlendar = new Calendar({
parentNode: document.getElementById("container"),
appData: app.appData,
sltDateFuc: app.sltDateFuc
});
}
}
});
function eventListener() {
//上月
document.getElementById('prePage').addEventListener('tap', function () {
console.log('prePage')
canlendar.turnPre();
var str = canlendar.getPreMonth(g.id("bigTime").innerHTML);
updateMonth(str);
})
//下月
document.getElementById('nextPage').addEventListener('tap', function () {
canlendar.turnNext();
var str = canlendar.getNextMonth(g.id("bigTime").innerHTML);
updateMonth(str);
})
}
function updateMonth(str) {
var year = str.substring(0, 4);
var month = str.substr(5, 2);
var r = year + '年' + month + '月';
g.id("bigTime").innerHTML = str;
g.id("timeNow").innerHTML = r;
}
function initHandleData(app) {
app.obj.attendance = 27;
app.obj.actualAttendance = 26;
app.obj.beLate = 2;
app.obj.leaveEarly = 3;
}
script>
body>
html>
数据我都是模拟的假数据,所以看见显示有问题不要奇怪,实际项目自然是用ajax调用api接口进行数据填充。咋一看就知道这里面用到了日期和垂直时间线。
calendar.js我是在别人的calendar组件基础之上进行修改的,代码如下:
/** * Created by zouqj on 17/11/10 */ var doc = window.document; var Calendar = function (options) { "use strict"; //默认参数 var defaults = { //中文格式内容 appData: { attendance: [], actualAttendance: [], beLate: [], leaveEarly: [] },//界面传过来的数据 sltDateFuc: function (sltDate) { },//选中日期回调函数 monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], dayNames: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'], dayLongNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], holiday: { "1-1": "元旦", "2-2": "湿地日", "2-14": "情人节", "3-8": "妇女节", "3-12": "植树节", "3-15": "消费者权益日", "4-1": "愚人节", "4-22": "地球日", "5-1": "劳动节", "5-4": "青年节", "5-12": "护士节", "5-18": "博物馆日", "6-1": "儿童节", "6-5": "环境日", "6-23": "奥林匹克日", "6-24": "骨质疏松日", "7-1": "建党节", "8-1": "建军节", "9-3": "抗战胜利日", "9-10": "教师节", "10-1": "国庆节", "11-17": "学生日", "12-1": "艾滋病日", "12-24": "平安夜", "12-25": "圣诞节" }, firstDay: 1, // 从周一开始,计算 weekendDays: [0, 6], // 休息日为:周六, 周日 dateFormat: 'yyyy-mm-dd', // 打印格式, formatDate 对应 limitDis: 80, // 拖拽限制距离,当大于该距离时,触发页面切换 weekHandler: "dayThead", // 星期title内容所在类 monthContainer: "dateUl", // 日期内容所在容器类 toolBar: "timeChoose", // 工具条所在类 parentNode: document.body, // 组件插入容器,默认body template: // '' //+ '' //+ '' '' //+ ' ' //+ '' //+ '' //+ '' //+ '' //+ '' //+ '' //+ '' //+ '' + '' }; //参数调整 options = options || {}; for (var prop in defaults) { if (typeof options[prop] === 'undefined') { options[prop] = defaults[prop]; } } this.attrs = options; //触摸起始点记录 this.touchesStart = {}; //初始化内容 this.init(); }; Calendar.prototype = { constructor: Calendar, init: function (id) { //初始化界面 this.render(id); //初始化事件 this.initEvents(); //初始布局页面 this.layout(); /** * 初始化参数 * this.index 切换页面索引 * this._initEd 是否初始化完成 * this._interval 动画切换锁,防止动画重复触发 * this.offsetValue 切换容器宽度,页面resize时,需要更新该值 */ this.index = 0; this._initEd = true; this._interval = true; this.offsetValue = this.monthEle.offsetWidth; //节点操作 this.timeNowEle = doc.querySelector("#timeNow"); this.bigTimeEle = doc.querySelector(".bigTime"); //this.noliDateEle = doc.querySelector(".noliDate"); //this.gooList = doc.querySelector(".gooList"); this.badList = doc.querySelector(".badList"); //this.timePannel = doc.getElementById("timePannel"); this.prevMonthEle = doc.querySelector(".prev-month-html"); this.currMonthEle = doc.querySelector(".current-month-html"); this.nextMonthEle = doc.querySelector(".next-month-html"); //初始化 aside页面 this.setAside(); //默认加载当天的考勤记录 var strDate = g.formatDate(new Date(), 'YMD'); this.attrs.sltDateFuc(strDate); }, render: function () { this.attrs.parentNode.innerHTML = this.attrs.template; }, pad2 : function (n) { return n < 10 ? '0' + n : n }, /** * 设置aside侧边栏内容 * @param date 指定日期 */ setAside: function (date) { date = date || new Date(); var year = date.getFullYear(), month = date.getMonth(), day = date.getDate(); var noli = Util.getLunarCalendar(year, month + 1, day); var suitTaboo = Util.getSuitAndTaboo(year, month + 1, day); //var gooStr = "宜"; //var badStr = "忌"; this.timeNowEle.innerHTML = year + "年" + this.pad2(month + 1) + "月"; //+ "" + this.attrs.dayLongNames[date.getDay()] + ""; this.bigTimeEle.innerHTML = year + '-' + (month + 1) + '-' + day; //this.noliDateEle.innerHTML = "' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' //+ '' + '' + '" + noli["month"] + noli["date"] +"
" // + "" + Util.getSexagenaryCycle(year) + "【" +Util.getZodiac(year) + "】" +"
"; //for(var i = 0, len = suitTaboo["suit"].length; i < len; i++) { // gooStr += "" + suitTaboo["suit"][i] +""; //} //for(var i = 0, len = suitTaboo["taboo"].length; i < len; i++) { // badStr += "" + suitTaboo["taboo"][i] + ""; //} //this.gooList.innerHTML = gooStr; //this.badList.innerHTML = badStr; }, /** * 初始化事件 */ initEvents: function () { var self = this; var monthEle = this.monthEle = doc.querySelector("." + this.attrs.monthContainer); this.timeChooseEle = doc.querySelector(".timeChoose"); this.goPrev = doc.querySelector(".goPrev"); this.goNext = doc.querySelector(".goNext"); monthEle.addEventListener("mousedown", this._handleTouchStart.bind(this), false); monthEle.addEventListener("mousemove", this._handleTouchMove.bind(this), false); monthEle.addEventListener("mouseup", this._handleTouchEnd.bind(this), false); monthEle.addEventListener("touchstart", this._handleTouchStart.bind(this), false); monthEle.addEventListener("touchmove", this._handleTouchMove.bind(this), false); monthEle.addEventListener("touchend", this._handleTouchEnd.bind(this), false); monthEle.addEventListener("tap", this._handleClick.bind(this), false);//click change to tap monthEle.addEventListener("touchstart", this._handleClick.bind(this), false); this.goPrev.addEventListener("tap", function (event) { self.turnPre(); }, false); this.goNext.addEventListener("tap", function (event) { self.turnNext(); }, false); /** * BUG: 问题, bind(this)的事件函数, 无法removeEventListener * 监听动画完成事件 */ monthEle.addEventListener("transitionend", this._transformEnd.bind(this), false); monthEle.addEventListener("webkitTransitionEnd", this._transformEnd.bind(this), false); //操作表操作,事件委托处理 this.timeChooseEle.addEventListener("tap", this._handleTimeChoose.bind(this), false); document.addEventListener("tap", this._handleDocument.bind(this), false); //页面resize监听,函数节流,调整布局 window.addEventListener("resize", this._handleResize.bind(this), false); }, /** * 关闭下拉菜单 * @param event * @private */ _handleDocument: function (event) { var target = event.target, cls = target.className; if (cls !== "pullDown") { var btns = document.querySelectorAll(".buttonGroup"); for (var i = 0, len = btns.length; i < len; i++) { btns[i].classList.remove("open"); } } }, /** * 工具栏事件绑定, 提供日期变更(年+月 ), 返回今天等功能 * @param event * @private */ _handleTimeChoose: function (event) { event.preventDefault(); var target = event.target, parentNode = target.parentNode, cls = target.className; //排除非元素节点 if (target.nodeType !== 1) { return; } if (cls === "pullDown") { var pCls = parentNode.className.split(" "); if (pCls.indexOf("open") === -1) { parentNode.classList.add("open"); } else { parentNode.classList.remove("open"); } } //返回今天 if (cls === "returnToday" || parentNode.className === "returnToday") { this.resetDate(new Date()); } //年份更新 if (cls === "list-year") { var year = parseInt(target.getAttribute("data-year")), month = parseInt(doc.getElementById("op-month-time").textContent) - 1; doc.getElementById("op-year-time").textContent = year + "年"; this.resetDate(new Date(year, month)); } //月份更新 if (cls === "list-month") { var year = parseInt(doc.getElementById("op-year-time").textContent), month = parseInt(target.getAttribute("data-month")); doc.getElementById("op-month-time").textContent = month + "月"; this.resetDate(new Date(year, month)); } }, _handleResize: function (event) { var self = this; //函数节流, 重新调整宽度 self.resizeInterval = setTimeout(function () { self.offsetValue = self.monthEle.offsetWidth; }, 300) }, _handleClick: function (event) { event.preventDefault(); var target = event.target, eleName = target.nodeName; if (eleName === "SPAN" || target.className === "dayTd") { var aim = eleName === "SPAN" ? target.parentNode : target; var year = parseInt(aim.getAttribute("data-year")), month = parseInt(aim.getAttribute("data-month")), day = parseInt(aim.getAttribute("data-day")); if (this._oldEle) { this._oldEle.classList.remove("date-selected"); } aim.classList.add("date-selected"); this._oldEle = aim; this.setAside(new Date(year, month, day)) if (event.type == 'tap') { //根据日期加载打卡记录列表 var sltDate = year + '-' + this.pad2(month + 1) + '-' + day; this.attrs.sltDateFuc(sltDate); //执行回调 } } }, _handleTouchStart: function (event) { event.preventDefault(); //屏蔽多次触发 if (!this._interval) { return; } this.isTouched = true; if (event.type === "touchstart") { this.touchesStart.x = event.targetTouches[0].pageX; } else { this.touchesStart.x = event.pageX; } }, _handleTouchMove: function (event) { event.preventDefault(); if (!this.isTouched) { return; } this.isMoved = true; if (event.type === "touchmove") { var pageX = event.targetTouches[0].pageX; } else { var pageX = event.pageX; } //横向移动距离 this.touchesDiff = pageX - this.touchesStart.x; this.endPos = pageX; //设置样式 this.monthEle.style[Util.prefix + "transition"] = "all 0ms"; this.monthEle.style[Util.prefix + "transform"] = "translate3d(" + (this.index * this.offsetValue + this.touchesDiff) + "px, 0px, 0px)"; }, _handleTouchEnd: function (event) { event.preventDefault(); if (!this.isTouched || !this.isMoved) { this.isTouched = false; this.isMoved = false; return; } var comPos = this.endPos - this.touchesStart.x; //移动距离对比 if (Math.abs(comPos) < this.attrs.limitDis) { this._transformPage(); } else { if (comPos < 0) { this.turnNext(); //下一页 } else { this.turnPre(); //上一页 } } this.isTouched = false; this.isMoved = false; }, turnPre: function () { if (!this._interval) { return; } this.index++; this._isTurnPage = true; this._offset = "prev"; //锁上操作, 防止多次触发 this._interval = false; this._transformPage(); }, turnNext: function () { if (!this._interval) { return; } this.index--; this._isTurnPage = true; this._offset = "next"; //锁上操作, 防止多次触发 this._interval = false; this._transformPage(); }, _transformPage: function () { this.monthEle.style[Util.prefix + "transition"] = "300ms"; this.monthEle.style[Util.prefix + "transform"] = "translate3d(" + (this.index * 100) + "%, 0, 0)"; }, /** * 提示日期tip,仅在移动端有效(宽度 < 425px 触发) * @private */ _tipPannel: function () { var self = this; if (this.offsetValue > 425) { return; } var year = this.value.getFullYear(), month = this.value.getMonth() + 1; this.timePannel.textContent = year + "年" + month + "月"; clearTimeout(this.tipInterval); this.timePannel.style["display"] = "block"; //强制relayout,否则动画无效果 self.timePannel.offsetWidth; this.timePannel.style["opacity"] = 1; this.tipInterval = setTimeout(function () { self.timePannel.style["opacity"] = 0; self.timePannel.style["display"] = "none"; }, 800); }, /** * 翻页结束后触发回调事件 * @private */ _transformEnd: function () { //不是翻页不行 var offset = this._offset; if (!this._isTurnPage) { return; } var date = new Date(this.value); var year = date.getFullYear(), month = date.getMonth(); //下个月 if (offset === 'next') { if (month === 11) { date = new Date(year + 1, 0); } else { date = new Date(year, month + 1, 1); } } //上个月 if (offset === 'prev') { if (month === 0) { date = new Date(year - 1, 11); } else { date = new Date(year, month - 1, 1); } } this.value = date; //this._tipPannel(); this.layout(); // 重定位 var index = this.index * -1; //重新获取 时间容器节点 this.prevMonthEle = doc.querySelector(".prev-month-html"); this.currMonthEle = doc.querySelector(".current-month-html"); this.nextMonthEle = doc.querySelector(".next-month-html"); this.prevMonthEle.style[Util.prefix + "transform"] = "translate3d(" + (index - 1) * 100 + "%, 0px, 0px)"; this.currMonthEle.style[Util.prefix + "transform"] = "translate3d(" + (index) * 100 + "%, 0px, 0px)"; this.nextMonthEle.style[Util.prefix + "transform"] = "translate3d(" + (index + 1) * 100 + "%, 0px, 0px)"; doc.getElementById("op-year-time").textContent = this.value.getFullYear() + "年"; doc.getElementById("op-month-time").textContent = (this.value.getMonth() + 1) + "月"; //恢复锁,可以继续触发 this._interval = true; this._isTurnPage = false; }, /** * 指定时间,重置日期内容 * @param date 时间 */ resetDate: function (date) { this.value = date; this.index = 0; this.monthEle.style[Util.prefix + "transition"] = "all 0ms"; this.monthEle.style[Util.prefix + "transform"] = "translate3d(0 ,0 ,0)"; this.prevMonthEle.style[Util.prefix + "transform"] = "translate3d(-100%, 0px, 0px)"; this.currMonthEle.style[Util.prefix + "transform"] = "translate3d(0%, 0px, 0px)"; this.nextMonthEle.style[Util.prefix + "transform"] = "translate3d(100%, 0px, 0px)"; doc.getElementById("op-year-time").textContent = this.value.getFullYear() + "年"; doc.getElementById("op-month-time").textContent = (this.value.getMonth() + 1) + "月"; this.layout(); }, /** * 布局函数 */ layout: function () { var layoutDate = this.value ? this.value : new Date().setHours(0, 0, 0, 0); this.value = layoutDate; //三个月的 HTML信息 var prevMonthHTML = this.monthHTML(layoutDate, 'prev'); var currentMonthHTML = this.monthHTML(layoutDate); var nextMonthHTML = this.monthHTML(layoutDate, 'next'); var monthHTML = '" + '' + prevMonthHTML + "" + ' " if (!this._initEd) { //初次渲染的时候使用, 否则不使用 //渲染 星期头部 var weekHeaderHTML = []; for (var i = 0; i < 7; i++) { var weekDayIndex = (i + this.attrs.firstDay > 6) ? (i - 7 + this.attrs.firstDay) : (i + this.attrs.firstDay); var dayName = this.attrs.dayNames[weekDayIndex]; if (this.attrs.weekendDays.indexOf(weekDayIndex) !== -1) { //休息日样式 weekHeaderHTML.push('' + currentMonthHTML + "' + dayName + ''); } else { weekHeaderHTML.push('' + dayName + ''); } } var yearSelectHTML = [], monthSelectHTML = []; for (var i = 1900; i <= 2050; i++) { var str = "
- ' + yearSelectHTML.join('') + '
- ' + monthSelectHTML.join('') + '
calendar.css也是一样,我进行了改造,代码如下:
html { font-size: 16px;-ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;/*不用10px 62.5% 因为chrome只支持12px最小*/ } *,*:before,*:after { box-sizing: border-box;-webkit-tap-highlight-color: rgba(0, 0, 0, 0); } [hidden],template {display: none;} a {background-color: transparent;text-decoration: none;} a:active,a:hover {outline: 0;} abbr[title] {border-bottom: 1px dotted;} b,strong {font-weight: bold;} dfn {font-style: italic;} mark {background: #ff0;color: #000;} small{font-size: 80%;} sub,sup {font-size: 75%;line-height: 0;position: relative;vertical-align: baseline;} sup {top: -0.5em;} sub {bottom: -0.25em;} img {border: 0;} svg:not(:root) {overflow: hidden;} figure {margin: 1em 40px;} hr {box-sizing: content-box;height: 0;} pre {overflow: auto;} code,kbd,pre,samp { font-family: monospace, monospace;font-size: 1em; } button,input,optgroup,select,textarea { color: inherit;font: inherit;margin: 0; } a,input,textarea,select,button { outline: 0; } button {overflow: visible;} button,select {text-transform: none;} button,html input[type="button"],input[type="reset"],input[type="submit"] { -webkit-appearance: button;cursor: pointer; } button[disabled],html input[disabled] { cursor: default; } button::-moz-focus-inner,input::-moz-focus-inner { border: 0;padding: 0; } input { line-height: normal; } input[type="checkbox"], input[type="radio"] { box-sizing: border-box;padding: 0; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } input[type="search"] { -webkit-appearance: textfield;box-sizing: content-box; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } fieldset { border: 1px solid #c0c0c0;margin: 0 2px;padding: 0.35em 0.625em 0.75em; } legend {border: 0;padding: 0;} textarea {overflow: auto;} optgroup {font-weight: bold;} .button:active {color: #0a8ddf;border-color: #0a8ddf;} /*p {margin: 1em 0;}*/ body { position: absolute;top: 0;right: 0;bottom: 0;left: 0;overflow: hidden; } @font-face { font-family: "icon-yuan";src: url("../fonts/icomoon.ttf"); } .icon { font-family: icon-yuan !important;font-style: normal;display: inline-block;vertical-align: -2px; } .icon-left:after {content: '\f053';} .icon-right:after {content: '\f054';} html {height: 100%;} .container { width: 100%; display: -webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap;flex-wrap: wrap;height: 100%; } .container > div {height: 100%;} .container .aside { color: #ffffff;width: 35%;padding: 15px 20px; } .container .main {width: 65%;padding-left: 15px;padding-right: 15px;} .timeNow {text-align: center;font-size: 0.85rem;} .timeNow span:first-child {margin-right: 10px;} .operator {position: relative;} .operator .goPrev { position: absolute;top: 0px;left: 0px; } .operator .goNext {position: absolute;top: 0px; right: 0px;} .operator i {color: #e02d2d;line-height: 2.2rem;cursor: pointer;} .operator i:hover {color: #fb0;} .dateContain {position: relative;} .dateContain .bigTime { text-align: center;font-family: arial;font-size: 10rem; } .dateContain .bigTime span { text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1); } .dateContain .noliDate > p { font-size: 0.85rem;text-align: center; } .dateContain .goodBad { border-top: 2px solid #94c9ff;padding: 10px 7px; } .dateContain .goodBad > div i { font-style: normal;text-shadow: 2px 2px 1px rgba(0, 0, 0, 0.1); text-align: center;color: #fff;font-size: 1.1rem; } .dateContain .goodBad > div span { font-size: 0.7rem;margin-left: 8px; } .aside { background: #D2456A;background: -webkit-gradient(linear, 0 0, 0 100%, from(#D2456A), to(#e42355)); } .operator { height: 2.2rem;border-bottom: 2px solid #D2456A; } .datePicker { height: calc(100% - 4.4rem);overflow: hidden; } .dateUl {height: 100%;position: relative;} .dateUl .dateLi {height: 100%;position: absolute; width: 100%;} .dayThead,.dayTbody,.dayTr { display: -webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex; -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap;flex-wrap: wrap; } .dayTbody {height: calc(100% - 40px);} .dayTbody .dayTr { width: 100%;border-top: 1px solid #c8cacc; height: calc(100% / 5); } .dayTbody .dayTr .dayTd { display: -webkit-box; display: -webkit-flex;display: -ms-flexbox;display: flex; -webkit-box-orient: vertical;-webkit-box-direction: normal;-webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column;-webkit-box-pack: center; -webkit-justify-content: center;-ms-flex-pack: center;justify-content: center;width: calc(14.28571429%); } .dayTbody .dayTr .dayTd .almanac { width: 100%;overflow: hidden;text-overflow: ellipsis;white-space: nowrap; } .dayTbody .dayTr .dayTd span.dayNumber { font-family: arial;color: #000;font-size: 1.2rem; line-height: 21px;height: 18px; } .dayTbody .dayTr .dayTd span.almanac { color: #616161;font-size: 0.7rem;line-height: 25px; height: 21px; } .dayTbody .dayTr .dayTd.date-reset span:first-child { color: #7FAEF5; } .dayTbody .dayTr .dayTd.date-holiday span:last-child { color: #7FAEF5; } .dayTbody .dayTr .dayTd.date-prev span:first-child, .dayTbody .dayTr .dayTd.date-next span:first-child, .dayTbody .dayTr .dayTd.date-prev span:last-child, .dayTbody .dayTr .dayTd.date-next span:last-child { color: #bfbfbf; } .dayTbody .dayTr .dayTd.date-selected { background-color: #47D5FF; } .dayTbody .dayTr .dayTd.date-selected span:first-child, .dayTbody .dayTr .dayTd.date-selected span:last-child { color: #fff; } .dayTbody .dayTr .dayTd.date-current { background-color: #fb0; } .dayTbody .dayTr .dayTd.date-current span:first-child, .dayTbody .dayTr .dayTd.date-current span:last-child { color: #ffffff !important; } .prev-month-html { -webkit-transform: translate3d(-100%, 0, 0);transform: translate3d(-100%, 0, 0); } .current-month-html { -webkit-transform: translate3d(0, 0, 0);transform: translate3d(0, 0, 0); } .next-month-html { -webkit-transform: translate3d(100%, 0, 0);transform: translate3d(100%, 0, 0); } .dayThead {height: 40px;} .dayThead .dayTd { line-height: 40px;border: none;color: #2c9bb3; } .dayThead .dayTd.active {color: #e02d2d;} .dayTbody .dayTd:hover {box-shadow: inset 0px 0px 4px #47D5FF;} .dayTd { height: 100%;-webkit-box-flex: 1;-webkit-flex: 1; -ms-flex: 1;flex: 1;text-align: center;cursor: pointer; } .dayTd .currentDay { background-color: #fb0;} .dayTd .restDay {background-color: #fff0f0;} .pannel { position: fixed;width: 100%;top: 30%;left: 0px;text-align: center;line-height: 40px;height: 40px; background-color: rgba(227, 36, 85, 0.86);color: #ffffff;opacity: 0;display: none;-webkit-transition: opacity 300ms;transition: opacity 300ms; } @media only screen and (min-width: 701px) { .container{height:500px;} } @media only screen and (max-width: 700px) { .container { -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -ms-flex-direction: column;flex-direction: column;height:305px; } .container .aside { width: 100%;height: 30%; padding: 5px; } .container .main { width: 100%;height: 100%;} .bigTime {line-height: 1;font-size: 5.3rem !important;} .operator { display: none; } .datePicker { height: calc(100% - 10px);} .noliDate { display: -webkit-box;display: -webkit-flex;display: -ms-flexbox; display: flex; -webkit-box-pack: center;-webkit-justify-content: center;-ms-flex-pack: center;justify-content: center; } .noliDate p {margin: 0 10px;} .goodBad { display: none;} } .dateUlContainer { height: 100%;width: 100%;overflow: hidden; } .yearChoose,.monthChoose,.returnToday { display: inline-block;font-size: 0.7rem;margin-right: 10%; } .returnToday { background-color: #f2f2f2;border: 1px solid #999;padding: 2px 10px;cursor: default; } .timeChoose { width: 100%;display: -webkit-box;display: -webkit-flex;display: -ms-flexbox;display: flex; -webkit-box-pack: center;-webkit-justify-content: center;-ms-flex-pack: center;justify-content: center;padding: 0.5rem 20px; } .chooseContainer { position: relative;font-size: 0.7rem;color: #333; } .chooseContainer .buttonGroup { border: 1px solid #999;border-bottom-color: #d8d8d8;border-right-color: #d8d8d8;padding-left: 5px;line-height: 1; } .chooseContainer .buttonGroup span { display: inline-block; } .chooseContainer .buttonGroup span.yearTime, .chooseContainer .buttonGroup span.monthTime { margin: 0px 8px; } .chooseContainer .buttonGroup span.pullDown { width: 1.2rem;line-height: 1.2rem;cursor: default;text-align: center;border-left: 1px solid #d8d8d8;background-color: #f2f2f2; } .chooseContainer .pullSelect { display: none;width: 100%; position: absolute; z-index: 20; height: 432px;border: 1px solid #bbb;background: #fff;overflow-y: scroll; } .chooseContainer .pullSelect.open { display: block; } .chooseContainer .pullSelect ul { background-color: #fff;list-style: none; } .chooseContainer .pullSelect ul li { padding: 5px 10px; } .chooseContainer .pullSelect ul li:hover { cursor: default;background-color: #f2f2f2; } .buttonGroup.open ~ .pullSelect { display: block; } @media only screen and (min-width: 700px) { html {font-size: 21.33333333px !important;} .dateContain .bigTime { font-size: 9rem;} } @media only screen and (min-width: 800px) { html {font-size: 21.6px !important;} .dateContain .bigTime {font-size: 9rem;} } @media only screen and (min-width: 998px) { html {font-size: 25.6px !important;} } /*page css*/ .div-yearmonth,.div-operate,.ul-operate{background-color: #449DED;text-align: center;color: #FFFFFF;} .div-yearmonth{height: 42px;line-height: 42px;font-size: 16px;} .div-yearmonth .mui-icon{font-size: 20px;} .spn-today{margin: 0 10px;} #timeNow{margin-left: 5px;} .div-operate{height:56px;line-height:56px;} .ul-operate{height:56px;width:100%;} .ul-operate a{height:56px;width:25%;} .ul-operate a p{height:28px;color:#ffffff;line-height:22px;} .ul-operate a p:first-child{line-height:36px;} #container{margin-top:8px;} .mui-segmented-control .mui-control-item.mui-active:after { /*display: block;content: '';width: 70%;border-width: 1px;border-style: solid; border-color: #ffffff;position: relative;bottom: 1px;margin: 0 auto;left: 0%;*/ /*细下划线*/ content: '';width: 0;display: block;height: 0;bottom: 1px;margin: 0 auto; border-right: 8px solid transparent;border-bottom: 8px solid #FFFFFF;border-left: 8px solid transparent;/*三角形*/ } .dayTbody .dayTr .dayTd.attendance{background-color:#F2F9FF;} .dayTbody .dayTr .dayTd.actualAttendance{background-color:#F2F9FF;} .dayTbody .dayTr .dayTd.beLate{background-color:#FFE1E1;} .dayTbody .dayTr .dayTd.leaveEarly{background-color:#FFF1D4;} .dayTbody .dayTr .dayTd.attendance.date-selected,.dayTbody .dayTr .dayTd.actualAttendance.date-selected, .dayTbody .dayTr .dayTd.beLate.date-selected,.dayTbody .dayTr .dayTd.leaveEarly.date-selected { background-color:#47D5FF; } .dayTbody .dayTr .dayTd.attendance.date-current,.dayTbody .dayTr .dayTd.actualAttendance.date-current, .dayTbody .dayTr .dayTd.beLate.date-current,.dayTbody .dayTr .dayTd.leaveEarly.date-current { background-color:#fb0; } .dayTbody .dayTr .dayTd.date-next, .dayTbody .dayTr .dayTd.date-prev /*.dayTbody .dayTr .dayTd.date-next.date-selected, .dayTbody .dayTr .dayTd.date-prev.date-selected*/ { background-color:#fff;color:#bfbfbf; } .dayTbody .dayTr .dayTd.date-next.date-selected, .dayTbody .dayTr .dayTd.date-prev.date-selected { background-color:#47D5FF;color:#fff; } /*.dayTbody .dayTr .dayTd.date-next.date-selected span:first-child,.dayTbody .dayTr .dayTd.date-prev.date-selected span:last-child{ color:#bfbfbf !important; }*/ .date-next .dayNumber i.fl,.date-prev .dayNumber i.fl { display:none; } .dayNumber i.fl{font-size:12px;margin-top: -5px;margin-right: 10px;line-height: 0.8;position: absolute;display: block;} .font-warning{color:#FBD06B;} .font-danger{color:#FA6768;} .mui-scroll-wrapper.timeline{top:505px;} .emptyinfo{margin-top:10px;} .emptyinfo span.mui-icon{margin-bottom: 15px;} @media only screen and (max-height: 568px) { .emptyinfo {margin-top: 0px;} .emptyinfo span.mui-icon{font-size: 36px;margin-bottom: 5px;} }
注意事项
- 忘记所有click的事件,通通采用tap事件代替,因为click会存在300ms的延时。
- 不到万不得已,不要使用jquery,直接写原生js,因为jquery为了兼容各个版本的浏览器,写了许多的代码,而在手机上面的浏览器都是采用的Webkit内核,根本不需要考虑浏览器之间的兼容性问题,而且它能够很好的兼容css3特性。所以自然而然原生js的性能非常高,同时,你也不需要担心写js代码会很麻烦,HBuilder强大的智能提示和快捷键以及代码自动生成能力会让你写js快速如飞。
- 能用vue.js就不要用angular,同vue.js比起来angular实在是太重,而且语法相对来说没那么优雅,学习曲线更是没那么平缓,做app对性能的要求是非常苛刻的。
- 尽量不要使用位图,而是采用矢量图,因为矢量图渲染速度快,而且在不同分辨率下面不失真。
- 矢量图图标,可以去阿里矢量库中查找,如果实在找不到,可以自己用AI做图标,然后生成svg格式,再上传到阿里矢量库中,最后统一打包下载下来,这样的话会把所有的矢量图标合成一张图片,减少文件数,还有,下载下来的文件中只需要保留iconfont.ttf和iconfont.css文件,因为我们只需要在移动端使用,记得修改iconfont.css文件去掉无用的引用。精简、极致。
- HBuilder不要同时打开太多的项目,否则会很卡,而且HBuilder的错误智能感知比较弱,代码格式化也不够优雅,这些缺陷可以同时使用VS来解决(作为一名.net老鸟,吐血推荐的宇宙最强IDE),即同时用HBuilder和VS打开项目,用HBuilder写代码,VS进行格式化代码和语法查错,但是在切换IDE之前要记得保存代码,否则可能会造成代码覆盖,切记!
- 能采用真机调试就不要用模拟器,原因还是速度!高效。
作为一个后台开发人员来说,写js还不是最痛苦的,最痛苦的是调css样式,尤其是还需要兼容iso和android以及不同的移动设备,更要命的是不同的css样式写法,同样的效果,渲染速度还不一样......
项目开发完成之后,我会将前端代码开源,目前还在开发中,敬请关注!
关于更多MUI跨平台开发的资料,可以参考我的上一篇文章:MUI开发大全
其它部分页面参考:2017-11-1 今天完成的界面,包括js交互实现
最后,无可避免的,我花了大半天做的东西不符合客户需求...无语...重新改造...