一套代码小程序&Web&Native运行的探索01

前言

前面我们对微信小程序进行了研究:【微信小程序项目实践总结】30分钟从陌生到熟悉

并且用小程序翻写了之前一个demo:【组件化开发】前端进阶篇之如何编写可维护可升级的代码

之前一直在跟业务方打交道后面研究了下后端,期间还做了一些运营、管理相关工作,哈哈,最近一年工作经历十分丰富啊,生命在于不断的尝试嘛。

当然,不可避免的在前端技术一块也稍微有点落后,对React&Vue没有进行过深入一点的研究,这里得空我们便来一起研究一番(回想起来写代码的日子才是最快乐的??),因为我们现在也慢慢在切React、想尝试下React Native,但是我这边对于到底React还是Vue还是比较疑惑,所以我这里研究下,顺便看看能不能解决小程序一套代码的问题

我们现在就来尝试,是否可以用React或者Vue让一套代码能在三端同时运行

我们这里依旧使用这个我觉得还算复杂的例子,做一个首页一个列表页,后面尝试将这套代码翻译成React Native以及微信小程序,于是便开始我们这次的学习吧

PS:我这里对React&Vue熟悉度一般,文中就是demo研究,有不妥的地方请各位指正

React的开发方式

工欲善其事必先利其器,我们这里依旧先做UI组件,首先我们做一个弹出层提示组件alert,这里我们尽量尝试与小程序开发模式保持一致点

我们这里先来创建一个组件:

 1 class UIAlert  extends React.Component {
 2   propType()  {
 3     //name必须有,并且必须是字符串
 4     name:  React.PropTypes.string.isRequired
 5   }
 6   render()  {
 7     return (
 8       <view>我是{this.props.name}</view>
 9     );
10   }
11 };
12
13 React.render(
14   <UIAlert name="alert"/>,
15   document.getElementById(‘main‘)
16 );
//输出
我是alert

生成的HTML结构为:

1 <view data-reactid=".0">
2   <span data-reactid=".0.0">我是</span><span data-reactid=".0.1">alert</span>
3 </view>

这里view显然不会被识别,我们简单做下处理(这里顺便升级下我们React的版本):

 1 class View extends React.Component {
 2   render() {
 3     return (
 4       <div >{this.props.children}</div>
 5     );
 6   }
 7 }
 8 class UIAlert extends React.Component {
 9   render() {
10     return (
11       <View>我是{this.props.name}</View>
12     );
13   }
14 };
15 ReactDOM.render(
16   <UIAlert name="alert" />,
17   document.getElementById(‘root‘)
18 );

于是我们生成了这个样子的代码,没有额外添加span也没有添加id标识了:

<div id="root"><div>我是alert</div></div>

我们这里依旧以一个实际的例子来说明React的各种细节,这里我们索性来做一个提示框组件吧,这里我们先实现一个遮盖层:

 1 class UIMask extends React.Component {
 2   constructor(props) {
 3     super(props);
 4     this.state = {
 5     };
 6     this.onClick = this.onClick.bind(this);
 7   }
 8   onClick(e) {
 9     if(e.target.dataset.flag !== ‘mask‘) return;
10     this.props.onMaskClick();
11   }
12   render() {
13     return (
14       <div onClick={this.onClick} data-flag="mask" className="cm-overlay" style={{zIndex: this.props.uiIndex, display: this.props.isShow}} >
15         {this.props.children}
16       </div>
17     );
18   }
19 }

这里简单说下React中状态以及属性的区别(我理解下的区别):

React中的属性是只读的,原则上部允许修改自己的属性,他一般作为属性由父组件传入
state作为组件状态机而存在,表示组件处于不同的状态,所以是可变的,state是组件数据基础

这句话说的好像比较抽象,这里具体表达一下是:

① 属性可以从父组件获取,并且父组件赋值是组件的主要使用方式

② 一个组件内部不会有调用setProps类似的方法期望引起属性的变化

③ 总之属性便是组件的固有属性,我们只能像函数一样使用而不是想去改变

④ 如果你想改变一个属性的值,那么说明他该被定义为状态

⑤ 反之如果一个变量可以从父组件中获取,那么他一定不是一个状态

这里以我们这里的遮盖层组件为例说明:

遮盖层的z-index以及是否显示,对于遮盖层来说就是最小原子单元了,而且他们也是父组件通过属性的方式传入的,我们看看这里提示框的代码:

 1 class UIAlert extends React.Component {
 2   constructor(props) {
 3     super(props);
 4     this.state = {
 5       isShow: ‘‘,
 6       uiIndex: 3000,
 7       title: ‘‘,
 8       message: ‘message‘,
 9       btns: [{
10         type: ‘ok‘,
11         name: ‘确定‘
12       }, {
13         type: ‘cancel‘,
14         name: ‘取消‘
15       }]
16     };
17     this.onMaskClick = this.onMaskClick.bind(this);
18
19   }
20
21   onMaskClick() {
22     this.setState({
23       isShow: ‘none‘
24     });
25   }
26
27   render() {
28
29     return (
30 <UIMask onMaskClick={this.onMaskClick} uiIndex={this.state.uiIndex} isShow={this.state.isShow}>
31   <div className="cm-modal cm-modal--alert" style={{zIndex: this.state.uiIndex + 1, display: this.state.isShow}}>
32     <div className="cm-modal-bd">
33         <div className="cm-alert-title">{this.state.title}</div>
34        {this.state.message.length > 10 ? <div className="cm-mutil-lines">{this.state.message}</div> : <div>{this.state.message}</div>}
35     </div>
36     <div className={this.state.btns.length > 2 ? ‘cm-actions  cm-actions--full‘ : ‘cm-actions ‘}>
37       {
38         this.state.btns.map(function(item) {
39           return <span data-type={item.type} className={item.type == ‘ok‘ ? ‘cm-btns-ok cm-actions-btn ‘ : ‘cm-actions-btn cm-btns-cancel‘}>{item.name}</span>
40         })
41       }
42     </div>
43   </div>
44 </UIMask>
45     );
46   }
47 };

为了方便调试,我们这里给提示框组件定义了很多“状态”,而这里面的状态可能有很多是“不合适”的,可以看到我们遮盖层UIMask使用的几个属性全部是这里传入的,然后我们这里想象下真实使用场景,肯定是全局有一个变量,或者按钮控制显示隐藏,所以我们这个组件进行一轮改造:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4   <meta charset="UTF-8">
  5   <title>Title</title>
  6   <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
  7   <script  src="./libs/react.development.js"></script>
  8   <script  src="./libs/react-dom.development.js"></script>
  9   <script src="./libs/JSXTransformer.js"></script>
 10 </head>
 11 <body>
 12
 13 <div id="root">ddd</div>
 14
 15
 16 <script type="text/jsx">
 17
 18
 19
 20 class UIMask extends React.Component {
 21   constructor(props) {
 22     super(props);
 23     this.state = {
 24     };
 25     this.onClick = this.onClick.bind(this);
 26   }
 27   onClick(e) {
 28     if(e.target.dataset.flag !== ‘mask‘) return;
 29     this.props.onMaskClick();
 30   }
 31   render() {
 32     return (
 33       <div onClick={this.onClick} data-flag="mask" className="cm-overlay" style={{zIndex: this.props.uiIndex, display: this.props.isShow}} >
 34         {this.props.children}
 35       </div>
 36     );
 37   }
 38 }
 39
 40 class UIAlert extends React.Component {
 41   constructor(props) {
 42     super(props);
 43     this.state = {
 44       uiIndex: 3000
 45     };
 46     this.onMaskClick = this.onMaskClick.bind(this);
 47     this.onBtnClick = this.onBtnClick.bind(this);
 48
 49   }
 50   onMaskClick() {
 51     this.props.hideMessage();
 52   }
 53   onBtnClick(e) {
 54     let index = e.target.dataset.index;
 55     this.props.btns[index].callback();
 56   }
 57   render() {
 58     let scope = this;
 59     return (
 60 <UIMask onMaskClick={this.onMaskClick} uiIndex={this.state.uiIndex} isShow={this.props.isShow}>
 61   <div className="cm-modal cm-modal--alert" style={{zIndex: this.state.uiIndex + 1, display: this.props.isShow}}>
 62     <div className="cm-modal-bd">
 63         <div className="cm-alert-title">{this.props.title}</div>
 64        {this.props.message.length > 10 ? <div className="cm-mutil-lines">{this.props.message}</div> : <div>{this.props.message}</div>}
 65     </div>
 66     <div className={this.props.btns.length > 2 ? ‘cm-actions  cm-actions--full‘ : ‘cm-actions ‘}>
 67       {
 68         this.props.btns.map(function(item, index) {
 69           return <span onClick={scope.onBtnClick} data-index={index}  data-type={item.type} className={item.type == ‘ok‘ ? ‘cm-btns-ok cm-actions-btn ‘ : ‘cm-actions-btn cm-btns-cancel‘}>{item.name}</span>
 70         })
 71       }
 72     </div>
 73   </div>
 74 </UIMask>
 75     );
 76   }
 77 };
 78
 79
 80
 81
 82 //这里是真正的调用者,页面级别控制器
 83
 84 class MainPage extends React.Component {
 85   constructor(props) {
 86     super(props);
 87     this.showMessage = this.showMessage.bind(this);
 88     this.hideMessage = this.hideMessage.bind(this);
 89     let scope = this;
 90
 91     this.state = {
 92       alertShow: ‘none‘,
 93       btns: [{
 94         type: ‘ok‘,
 95         name: ‘确定‘,
 96         callback: function() {
 97           scope.hideMessage();
 98           console.log(‘成功‘);
 99         }
100       }, {
101         type: ‘cancel‘,
102         name: ‘取消‘,
103         callback: function() {
104           scope.hideMessage();
105           console.log(‘取消‘);
106         }
107       }]
108     };
109
110   }
111   showMessage() {
112     this.setState({
113       alertShow: ‘‘
114     })
115   }
116   hideMessage() {
117     this.setState({
118       alertShow: ‘none‘
119     })
120   }
121   render() {
122     return (
123       <div>
124         <input type="button" value="我是一个一般的按钮" onClick={this.showMessage} />
125         <UIAlert
126         title="title"
127         message="点点滴滴"
128         btns={this.state.btns}
129         showMessage={this.showMessage}
130         hideMessage={this.hideMessage}
131         isShow={this.state.alertShow}
132         name="alert"
133         uiIndex="333"
134         />
135       </div>
136     );
137   }
138 }
139
140
141 ReactDOM.render(
142   <MainPage />,
143   document.getElementById(‘root‘)
144 );
145
146 </script>
147
148 </body>
149 </html>

如此一来,我们这个组件便基本结束了,可以看到,事实上我们页面组件所有的状态全部汇集到了顶层组件也就是页面层级来了,在页面层级依旧需要分块处理,否则代码依旧可能会很乱

阶段总结

我们这里回顾小程序中的弹出层组件是这样写的:

 1 <view class="cm-modal cm-modal--alert" style="z-index: {{uiIndex}}; display: {{isShow}}; ">
 2   <view class="cm-modal-bd">
 3     <block wx:if="{{title}}">
 4       <view class="cm-alert-title">{{title}}</view>
 5     </block>
 6     <block wx:if="{{message.length > 20}}">
 7       <view class="cm-mutil-lines">{{message}}</view>
 8     </block>
 9     <block wx:else>
10       <view>{{message}}</view>
11     </block>
12   </view>
13   <view class="cm-actions  {{ btns.length > 2 ? ‘cm-actions--full‘ : ‘‘ }}">
14     <block wx:for="{{btns}}" wx:key="{{k}}">
15       <text bindtap="onBtnEvent" data-type="{{item.type}}" class="{{item.type == ‘ok‘ ? ‘cm-btns-ok‘ : ‘cm-btns-cancel‘}} cm-actions-btn">{{item.name}}</text>
16     </block>
17
18   </view>
19 </view>
20 <view class="cm-overlay" bindtap="onMaskEvent" style="z-index: {{maskzIndex}}; display: {{isShow}}">
21 </view>

简单研究到这里,感觉要使用React完成一套代码三端运行有点困难,小程序中的模板全部不能直接调用就是,除非是有wxs,而React支持的就是模板中写很多js,除非给React做很多的规则限制,或者由React编译为小程序识别的代码,否则暂时是不大可能的,所以我们现在研究下Vue是不是合适

Vue的开发方式

这里先不考虑mpvue框架,否则一点神秘感都没有了,我们依旧使用弹出层组件为例,用纯纯的Vue代码尝试是否做得到,首先Vue会将代码与模板做一个绑定,大概可以这样:

 1 <div id="app">
 2   <p>{{ foo }}</p>
 3 </div>
 4 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 5 <script type="text/javascript">
 6   var obj = {
 7     foo: ‘bar‘
 8   }
 9   new Vue({
10     el: ‘#app‘,
11     data: obj
12   })
13 </script>

这块与小程序还比较类似,于是我们来完成我们的提示框组件:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4   <meta charset="UTF-8">
  5   <title>Title</title>
  6   <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
  7 </head>
  8 <body>
  9
 10 <div id="app">
 11   <input type="button" value="我是一个一般的按钮" v-on:click="showMessage" />
 12   <ui-mask  v-bind:style="{zIndex: uiIndex, display: isShow}">
 13     <ui-alert :title="title"
 14               :message="message"
 15               v-bind:style="{zIndex: uiIndex + 1, display: isShow}"
 16               :btns="btns" ></ui-alert>
 17   </ui-mask>
 18 </div>
 19
 20
 21 <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 22 <script type="text/javascript">
 23
 24 Vue.component(‘ui-mask‘, {
 25   methods:{
 26     onClick: function (e) {
 27       if(e.target.dataset.flag !== ‘mask‘) return;
 28       this.$parent.hideMessage();
 29     }
 30   },
 31   template: `
 32 <div v-on:click="onClick" data-flag="mask" class="cm-overlay" >
 33   <slot></slot>
 34 </div>
 35   `
 36 });
 37
 38   Vue.component(‘ui-alert‘, {
 39     props: {
 40       title: {
 41         type: String
 42       },
 43       message: {
 44         type: String
 45       },
 46       uiIndex: {
 47         type: String
 48       },
 49       isShow: {
 50         type: String
 51       },
 52       btns: Array
 53     },
 54      methods: {
 55         onBtnEvent: function (e) {
 56           let index = e.target.dataset.index;
 57           this.btns[index].callback.call(this.$parent.$parent);
 58         }
 59      },
 60       template: `
 61 <div class="cm-modal cm-modal--alert">
 62   <div class="cm-modal-bd">
 63     <template v-if="title">
 64       <div class="cm-alert-title">{{title}}</div>
 65     </template>
 66     <template v-if="message.length > 20">
 67       <div class="cm-mutil-lines">{{message}}</div>
 68     </template>
 69     <template v-else>
 70       <div>{{message}}</div>
 71     </template>
 72   </div>
 73   <div class="cm-actions" v-bind:class="[btns.length > 2 ? ‘cm-actions--full‘ : ‘‘]">
 74     <template v-for="(item, index) in btns">
 75       <span v-on:click="onBtnEvent"  :data-index="index" class="cm-actions-btn" v-bind:class="[item.type == ‘ok‘ ? ‘cm-btns-ok‘ : ‘cm-btns-cancel‘]">{{item.name}}</span>
 76     </template>
 77   </div>
 78 </div>
 79   `
 80   })
 81   new Vue({
 82     methods: {
 83       showMessage: function() {
 84         this.isShow = ‘‘;
 85       },
 86       hideMessage: function() {
 87         this.isShow = ‘none‘;
 88       }
 89     },
 90     data: function() {
 91       return {
 92         title: ‘title1‘,
 93         message: ‘message1‘,
 94         uiIndex: 3000,
 95         isShow: ‘none‘,
 96         btns: [{
 97           type: ‘ok‘,
 98           name: ‘确定‘,
 99           callback: function() {
100             this.hideMessage();
101             console.log(‘成功‘);
102           }
103         }, {
104           type: ‘cancel‘,
105           name: ‘取消‘,
106           callback: function() {
107             this.hideMessage();
108             console.log(‘取消‘);
109           }
110         }]
111       };
112     } ,
113     el: ‘#app‘
114   })
115
116 </script>
117 </body>
118 </html>

从相似度上来说比React高得多,但是仍然有很多区别:

① class&style一块的处理

② 属性的传递

③ setData....

这些等我们后续代码写完看看如何处理之,能不能达到一套代码三端运行,这里做首页的简单实现。

首页逻辑的实现

这里简单的组织了下目录结构,做了最简单的实现,这里大家注意我这里对Vue不是很熟悉,不会遵循Vue的最佳实践,我这里是在探索能不能借助Vue让一套代码三端运行,所以这里写法会尽量靠近小程序写法:

 1 <html lang="en">
 2 <head>
 3   <meta charset="UTF-8">
 4   <title>Title</title>
 5   <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
 6   <link href="./static/css/index.css" rel="stylesheet" type="text/css"/>
 7   <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 8 </head>
 9 <body>
10 <div id="app"></div>
11 <script type="module">
12   import index from ‘./pages/index/index.js‘
13   Vue.component(index.name, index.data);
14   new Vue({
15     data: function() {
16       return {
17       };
18     } ,
19     template: `<page-index></page-index>`,
20     el: ‘#app‘
21   })
22 </script>
23 </body>
24 </html>
 1 import html from ‘./index.html.js‘
 2
 3 export default {
 4   name: ‘page-index‘,
 5   data: {
 6     template: html,
 7     methods: {
 8         showCitylist: function(e) {
 9             console.log(‘showCitylist‘)
10         },
11         showCalendar: function(e) {
12             console.log(‘showCalendar‘)
13         },
14         goList: function(e) {
15             console.log(‘goList‘)
16         }
17     },
18     data: function() {
19       return {
20           cityStartName: ‘请选择出发地‘,
21         cityArriveName: ‘请选择到达地‘,
22         calendarSelectedDateStr: ‘请选择出发日期‘}
23     }
24   }
25 }

 1 export default
 2 `<div class="container">
 3   <div class="c-row search-line" data-flag="start" @click="showCitylist">
 4     <div class="c-span3">
 5       出发</div>
 6     <div class="c-span9 js-start search-line-txt">
 7       {{cityStartName}}</div>
 8   </div>
 9   <div class="c-row search-line" data-flag="arrive" @click="showCitylist">
10     <div class="c-span3">
11       到达</div>
12     <div class="c-span9 js-arrive search-line-txt">
13       {{cityArriveName}}</div>
14   </div>
15   <div class="c-row search-line" data-flag="arrive" @click="showCalendar">
16     <div class="c-span3">
17       出发日期</div>
18     <div class="c-span9 js-arrive search-line-txt">
19       {{calendarSelectedDateStr}}</div>
20   </div>
21   <div class="c-row " data-flag="arrive">
22     <span class="btn-primary full-width js_search_list" @click="goList" >查询</span>
23   </div>
24 </div>
25 `

index.html.js

如此我们首页页面框架就出来了,后续只需要完成对应的几个组件,如日历组件以及城市列表,这里为了更完整的体验,我们先完成日历组件

日历组件

之前我们做日历组件的时候做的比较复杂,这里可以看到小程序中里面的模板代码:

  1 <wxs module="dateUtil">
  2   var isDate = function(date) {
  3     return date && date.getMonth;
  4   };
  5
  6   var isLeapYear = function(year) {
  7     //传入为时间格式需要处理
  8     if (isDate(year)) year = year.getFullYear()
  9     if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true;
 10     return false;
 11   };
 12
 13   var getDaysOfMonth = function(date) {
 14     var month = date.getMonth(); //注意此处月份要加1,所以我们要减一
 15     var year = date.getFullYear();
 16     return [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
 17   }
 18
 19   var getBeginDayOfMouth = function(date) {
 20     var month = date.getMonth();
 21     var year = date.getFullYear();
 22     var d = getDate(year, month, 1);
 23     return d.getDay();
 24   }
 25
 26   var getDisplayInfo = function(date) {
 27
 28     if (!isDate(date)) {
 29       date = getDate(date)
 30     }
 31     var year = date.getFullYear();
 32
 33     var month = date.getMonth();
 34     var d = getDate(year, month);
 35
 36
 37     //这个月一共多少天
 38     var days = getDaysOfMonth(d);
 39
 40     //这个月是星期几开始的
 41     var beginWeek = getBeginDayOfMouth(d);
 42
 43     return {
 44       year: year,
 45       month: month,
 46       days: days,
 47       beginWeek: beginWeek
 48     }
 49   };
 50
 51   //分割月之间的展示
 52   var monthClapFn = function(year, month) {
 53     month = month + 1;
 54     return year + ‘年‘ + (month) + ‘月‘;
 55   };
 56
 57   var getChangedDate = function(date, m) {
 58
 59     if (!isDate(date)) {
 60       date = getDate(date)
 61     }
 62     var year = date.getFullYear();
 63
 64     var month = date.getMonth();
 65     var changedMonth = month + m;
 66     var yyy = parseInt((month + m) / 12);
 67     if (changedMonth > 11) {
 68       changedMonth = changedMonth - 12 * yyy;
 69     }
 70     changedYear = year + yyy;
 71
 72     return {
 73       str_month: monthClapFn(changedYear, changedMonth)
 74       date: getDate(changedYear, changedMonth),
 75       year: changedYear,
 76       month: changedMonth
 77     };
 78   };
 79
 80   var isSelected = function(date, year, month, day) {
 81     if (!isDate(date)) {
 82       date = getDate(date);
 83     }
 84
 85     if (date.getFullYear() == year && date.getMonth() == month && date.getDate() == day) return ‘active‘;
 86     return ‘‘;
 87
 88   };
 89
 90   var formatNum = function(n) {
 91     if (n < 10) return ‘0‘ + n;
 92     return n;
 93   };
 94
 95   var getDayName = function(dayMap, month, day) {
 96
 97     if (!dayMap) {
 98       dayMap = {
 99         ‘0101‘: ‘元旦节‘,
100         ‘0214‘: ‘情人节‘,
101         ‘0501‘: ‘劳动节‘,
102         ‘0601‘: ‘儿童节‘,
103         ‘0910‘: ‘教师节‘,
104         ‘1001‘: ‘国庆节‘,
105         ‘1225‘: ‘圣诞节‘
106       };
107     }
108
109     var name = formatNum(parseInt(month) + 1) + formatNum(day);
110
111     return dayMap[name] || day;
112   };
113
114   module.exports = {
115         test: function (zzz) {
116               console.log(‘test‘, zzz)
117         },
118     getDipalyInfo: getDisplayInfo,
119     getChangedDate: getChangedDate,
120     isSelected: isSelected,
121     getDayName: getDayName
122   }
123 </wxs>
124 <view class="cm-calendar" style="display: {{isShow}};">
125   <view class="cm-calendar-hd ">
126     <block wx:for="{{weekDayArr}}" wx:key="weekDayKey">
127       <view class="item">{{item}}</view>
128     </block>i
129   </view>
130
131   <block wx:for="{{displayMonthNum}}" wx:for-index="i" wx:key="t">
132
133     <view class="cm-calendar-bd ">
134       <view class="cm-month ex-class">
135         {{dateUtil.getChangedDate(displayTime, i).str_month }}
136       </view>
137       <view class="cm-day-list">
138
139         <block wx:key="tt" wx:for="{{dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).days + dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek}}" wx:for-index="index">
140
141           <view wx:if="{{index < dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek }}" class="item "></view>
142           <view bindtap="onDayTap" wx:else data-year="{{dateUtil.getChangedDate(displayTime, i).year}}" data-month="{{dateUtil.getChangedDate(displayTime, i).month}}" data-day="{{index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek}}" class="item {{dateUtil.isSelected(selectedDate, dateUtil.getChangedDate(displayTime, i).year, dateUtil.getChangedDate(displayTime, i).month, index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek)}}">
143             <view class="cm-field-title">
144               {{dateUtil.getDayName(dayMap, dateUtil.getChangedDate(displayTime, i).month, index + 1 - dateUtil.getDipalyInfo(dateUtil.getChangedDate(displayTime, i).date).beginWeek) }} {{}}
145             </view>
146           </view>
147
148         </block>
149
150         <view class="   cm-item--disabled " data-cndate="" data-date="">
151         </view>
152
153       </view>
154     </view>
155
156   </block>
157
158 </view>

小程序模板代码

我思考了下,应该还是尽量少在模板里面调用js方法,所以我们这里将粒度再拆细一点,实现一个单日的表格组件,所以我们这里的日历由两部分组成:

① 日历-月组件

② 日历-天组件,被月所包裹

所以我们这里先来实现一个天的组件,对于日历来说,他会拥有以下特性:

① 是否过期,这个会影响显示效果

② 是否特殊节日,如中秋等,也会影响显示

③ 点击事件响应......

④ 是不是今天

好像也没多少东西...

这里先简单些个demo将日历单元格展示出来:

<ui-calendar year="2018" month="8" day="8" ></ui-calendar>
 1 import data from ‘./ui-calendar-day.js‘
 2
 3 Vue.component(data.name, data.data);
 4
 5 export default {
 6   name: ‘ui-calendar‘,
 7   data: {
 8     methods: {
 9     },
10     data: function() {
11       return {
12       }
13     },
14     template:
15 `
16 <ul>
17 <template v-for="num in 30" >
18   <ui-calendar-day year="2018" month="8" v-bind:day="num" ></ui-calendar-day>
19 </template>
20 </ul>
21 `
22   }
23 }

 1 //公共方法抽离,输入年月日判断在今天前还是今天后
 2 function isOverdue(year, month, day) {
 3   let date = new Date(year, month, day);
 4   let now = new Date();
 5   now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 6   return date.getTime() < now.getTime();
 7 }
 8
 9 function isToday(year, month, day) {
10   let date = new Date(year, month, day);
11   let now = new Date();
12   now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
13   return date.getTime() === now.getTime();
14 }
15
16 export default {
17   name: ‘ui-calendar-day‘,
18   data: {
19     props: {
20       year: {
21         type: String
22       },
23       month: {
24         type: String
25       },
26       day: {
27         type: Number
28       }
29     },
30     methods: {
31     },
32     data: function() {
33       //是否过期了
34       let klass = isOverdue(this.year, this.month, this.day) ? ‘cm-item--disabled‘ : ‘‘;
35       if(isToday(this.year, this.month, this.day)) klass += ‘active‘
36
37       return {
38         klass: klass
39       }
40     },
41     template:
42     `
43 <li v-bind:class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day">
44     {{day}}
45 </li>
46     `
47   }
48 }

ui-calendar-day

形成的html结构如下:

 1 <ul year="2018" month="8" day="8"><li data-year="2018" data-month="8" data-day="1" class="cm-item--disabled">
 2     1
 3 </li><li data-year="2018" data-month="8" data-day="2" class="cm-item--disabled">
 4     2
 5 </li><li data-year="2018" data-month="8" data-day="3" class="cm-item--disabled">
 6     3
 7 </li><li data-year="2018" data-month="8" data-day="4" class="cm-item--disabled">
 8     4
 9 </li><li data-year="2018" data-month="8" data-day="5" class="cm-item--disabled">
10     5
11 </li><li data-year="2018" data-month="8" data-day="6" class="cm-item--disabled">
12     6
13 </li><li data-year="2018" data-month="8" data-day="7" class="cm-item--disabled">
14     7
15 </li><li data-year="2018" data-month="8" data-day="8" class="active">
16     8
17 </li><li data-year="2018" data-month="8" data-day="9" class="">
18     9
19 </li><li data-year="2018" data-month="8" data-day="10" class="">
20     10
21 </li><li data-year="2018" data-month="8" data-day="11" class="">
22     11
23 </li><li data-year="2018" data-month="8" data-day="12" class="">
24     12
25 </li><li data-year="2018" data-month="8" data-day="13" class="">
26     13
27 </li><li data-year="2018" data-month="8" data-day="14" class="">
28     14
29 </li><li data-year="2018" data-month="8" data-day="15" class="">
30     15
31 </li><li data-year="2018" data-month="8" data-day="16" class="">
32     16
33 </li><li data-year="2018" data-month="8" data-day="17" class="">
34     17
35 </li><li data-year="2018" data-month="8" data-day="18" class="">
36     18
37 </li><li data-year="2018" data-month="8" data-day="19" class="">
38     19
39 </li><li data-year="2018" data-month="8" data-day="20" class="">
40     20
41 </li><li data-year="2018" data-month="8" data-day="21" class="">
42     21
43 </li><li data-year="2018" data-month="8" data-day="22" class="">
44     22
45 </li><li data-year="2018" data-month="8" data-day="23" class="">
46     23
47 </li><li data-year="2018" data-month="8" data-day="24" class="">
48     24
49 </li><li data-year="2018" data-month="8" data-day="25" class="">
50     25
51 </li><li data-year="2018" data-month="8" data-day="26" class="">
52     26
53 </li><li data-year="2018" data-month="8" data-day="27" class="">
54     27
55 </li><li data-year="2018" data-month="8" data-day="28" class="">
56     28
57 </li><li data-year="2018" data-month="8" data-day="29" class="">
58     29
59 </li><li data-year="2018" data-month="8" data-day="30" class="">
60     30
61 </li></ul>

简单来说,关于天的处理似乎处理完成,我们现在来开始月的处理,于是日历雏形就已经出来了:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4   <meta charset="UTF-8">
 5   <title>Title</title>
 6   <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
 7   <link href="./static/css/index.css" rel="stylesheet" type="text/css"/>
 8   <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 9 </head>
10 <body>
11 <div id="app"></div>
12 <script type="module">
13   import index from ‘./pages/index/index.js‘
14   import data from ‘./components/ui-calendar.js‘
15   Vue.component(index.name, index.data);
16   Vue.component(data.name, data.data);
17   new Vue({
18     data: function() {
19       return {
20       };
21     } ,
22     // template: `<page-index></page-index>`,
23     template: `<ui-calendar year="2018" month="8" day="8" ></ui-calendar>`,
24     el: ‘#app‘
25   })
26 </script>
27 </body>
28 </html>

入口

 1 import data from ‘./ui-calendar-month.js‘
 2
 3 Vue.component(data.name, data.data);
 4
 5 export default {
 6   name: ‘ui-calendar‘,
 7   data: {
 8     methods: {
 9     },
10     data: function() {
11       return {
12         weekDayArr: [‘日‘, ‘一‘, ‘二‘, ‘三‘, ‘四‘, ‘五‘, ‘六‘]
13       }
14     },
15     template:
16 `
17 <ul class="cm-calendar ">
18   <ul class="cm-calendar-hd">
19     <template v-for="i in 7" >
20       <li class="cm-item--disabled">{{weekDayArr[i-1]}}</li>
21     </template>
22   </ul>
23   <ui-calendar-month year="2018" month="8" ></ui-calendar-month>
24 </ul>
25 `
26   }
27 }

ui-calendar

 1 import data from ‘./ui-calendar-day.js‘
 2
 3 Vue.component(data.name, data.data);
 4
 5 //公共方法抽离,输入年月日判断在今天前还是今天后
 6 function isLeapYear(year) {
 7     if ((typeof year == ‘object‘) && (year instanceof Date)) year = year.getFullYear()
 8     if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true;
 9     return false;
10 }
11
12 // @description 获取一个月份1号是星期几,注意此时保留开发习惯,月份传入时需要自主减一
13 // @param year {num} 可能是年份或者为一个date时间
14 // @param year {num} 月份
15 // @return {num} 当月一号为星期几0-6
16 function getBeginDayOfMouth(year, month) {
17   //自动减一以便操作
18   month--;
19   if ((typeof year == ‘object‘) && (year instanceof Date)) {
20     month = year.getMonth();
21     year = year.getFullYear();
22   }
23   var d = new Date(year, month, 1);
24   return d.getDay();
25 }
26
27 export default {
28   name: ‘ui-calendar-month‘,
29   data: {
30     props: {
31       year: {
32         type: String
33       },
34       month: {
35         type: String
36       }
37     },
38     methods: {
39     },
40     data: function() {
41       let days = [31, isLeapYear(this.year) ? 29 : 28,
42         31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
43       //本月从哪天开始
44       let beforeDays = getBeginDayOfMouth(this.year, parseInt(this.month) + 1);
45
46       return {
47         days: days[this.month],
48         beforeDays: beforeDays
49       }
50     },
51     template:
52     `
53 <ul class="cm-calendar-bd ">
54   <h3 class="cm-month js_month">{{year + ‘-‘ + month}}</h3>
55   <ul class="cm-day-list">
56   <template v-for="n in beforeDays" >
57     <li class="cm-item--disabled"></li>
58   </template>
59   <template v-for="num in days" >
60     <ui-calendar-day :year="year" :month="month" v-bind:day="num" ></ui-calendar-day>
61   </template>
62   </ul>
63 </ul>
64     `
65   }
66 }

ui-calendar-month

 1 //公共方法抽离,输入年月日判断在今天前还是今天后
 2 function isOverdue(year, month, day) {
 3   let date = new Date(year, month, day);
 4   let now = new Date();
 5   now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 6   return date.getTime() < now.getTime();
 7 }
 8
 9 function isToday(year, month, day) {
10   let date = new Date(year, month, day);
11   let now = new Date();
12   now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
13   return date.getTime() === now.getTime();
14 }
15
16 export default {
17   name: ‘ui-calendar-day‘,
18   data: {
19     props: {
20       year: {
21         type: String
22       },
23       month: {
24         type: String
25       },
26       day: {
27         type: Number
28       }
29     },
30     methods: {
31     },
32     data: function() {
33       //是否过期了
34       let klass = isOverdue(this.year, this.month, this.day) ? ‘cm-item--disabled‘ : ‘‘;
35       if(isToday(this.year, this.month, this.day)) klass += ‘active‘
36       return {
37         klass: klass
38       }
39     },
40     template:
41     `
42 <li v-bind:class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day">
43     <div class="cm-field-wrapper "><div class="cm-field-title">{{day}}</div></div>
44 </li>
45     `
46   }
47 }

ui-calendar-day

这样分解下来,似乎代码变得更加简单了,接下来我们花点功夫完善这个组件,最后形成了这样的代码:

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4   <meta charset="UTF-8">
 5   <title>Title</title>
 6   <link href="./static/css/global.css" rel="stylesheet" type="text/css"/>
 7   <link href="./static/css/index.css" rel="stylesheet" type="text/css"/>
 8   <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
 9 </head>
10 <body>
11 <div id="app"></div>
12 <script type="module">
13 //  console.error = function () {
14 //  }
15   import index from ‘./pages/index/index.js‘
16   import data from ‘./components/ui-calendar.js‘
17   Vue.component(index.name, index.data);
18   Vue.component(data.name, data.data);
19   new Vue({
20     methods: {
21       onDayClick: function (data) {
22         this.selectedDate = new Date(data.year, data.month, data.day).getTime() + ‘‘;
23 //        debugger;
24       }
25     },
26     data: function() {
27
28
29       return {
30         displayTime: new Date().getTime(),
31         selectedDate: new Date(2018, 8, 13).getTime(),
32         displayMonthNum: 2
33       }
34     },
35     // template: `<page-index></page-index>`,
36     template: `
37       <ui-calendar  v-on:dayclick="onDayClick" :selectedDate="selectedDate" :displayMonthNum="displayMonthNum" :displayTime="displayTime"  ></ui-calendar>
38     `,
39     el: ‘#app‘
40   })
41 </script>
42 </body>
43 </html>

入口

 1 import data from ‘./ui-calendar-month.js‘
 2
 3 Vue.component(data.name, data.data);
 4
 5 export default {
 6   name: ‘ui-calendar‘,
 7   data: {
 8     props: {
 9       displayMonthNum: {
10         type: Number
11       },
12       displayTime: {
13         type: Number
14       },
15       selectedDate: {
16         type: Number
17       }
18     },
19     methods: {
20     },
21     data: function() {
22       //要求传入的当前显示时间必须是时间戳
23       let date = new Date(this.displayTime);
24
25       return {
26         year: date.getFullYear(),
27         month: date.getMonth(),
28         weekDayArr: [‘日‘, ‘一‘, ‘二‘, ‘三‘, ‘四‘, ‘五‘, ‘六‘]
29       }
30     },
31     template:
32 `
33 <ul class="cm-calendar ">
34   <ul class="cm-calendar-hd">
35     <template v-for="i in 7" >
36       <li class="cm-item--disabled">{{weekDayArr[i-1]}}</li>
37     </template>
38   </ul>
39     <template v-for="m in displayMonthNum" >
40       <ui-calendar-month :selectedDate="selectedDate" :year="year" :month="month+m-1" ></ui-calendar-month>
41     </template>
42 </ul>
43 `
44   }
45 }

日历

 1 import data from ‘./ui-calendar-day.js‘
 2
 3 Vue.component(data.name, data.data);
 4
 5 //公共方法抽离,输入年月日判断在今天前还是今天后
 6 function isLeapYear(year) {
 7     if ((typeof year == ‘object‘) && (year instanceof Date)) year = year.getFullYear()
 8     if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return true;
 9     return false;
10 }
11
12 // @description 获取一个月份1号是星期几,注意此时保留开发习惯,月份传入时需要自主减一
13 // @param year {num} 可能是年份或者为一个date时间
14 // @param year {num} 月份
15 // @return {num} 当月一号为星期几0-6
16 function getBeginDayOfMouth(year, month) {
17   //自动减一以便操作
18   month--;
19   if ((typeof year == ‘object‘) && (year instanceof Date)) {
20     month = year.getMonth();
21     year = year.getFullYear();
22   }
23   var d = new Date(year, month, 1);
24   return d.getDay();
25 }
26
27 export default {
28   name: ‘ui-calendar-month‘,
29   data: {
30     props: {
31       year: {
32         type: Number
33       },
34       month: {
35         type: Number
36       },
37       selectedDate: {
38         type: Number
39       }
40     },
41     methods: {
42
43     },
44     data: function() {
45       let days = [31, isLeapYear(this.year) ? 29 : 28,
46         31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
47       //本月从哪天开始
48       let beforeDays = getBeginDayOfMouth(this.year, parseInt(this.month) + 1);
49
50       return {
51         days: days[this.month],
52         beforeDays: beforeDays
53       }
54     },
55     template:
56     `
57 <ul class="cm-calendar-bd ">
58   <h3  class="cm-month js_month">{{year + ‘-‘ + month}}</h3>
59   <ul class="cm-day-list">
60   <template v-for="n in beforeDays" >
61     <li class="cm-item--disabled"></li>
62   </template>
63   <template v-for="num in days" >
64     <ui-calendar-day :selectedDate="selectedDate" :year="year" :month="month" v-bind:day="num" ></ui-calendar-day>
65   </template>
66   </ul>
67 </ul>
68     `
69   }
70 }

日历-月

 1 //公共方法抽离,输入年月日判断在今天前还是今天后
 2 function isOverdue(year, month, day) {
 3   let date = new Date(year, month, day);
 4   let now = new Date();
 5   now = new Date(now.getFullYear(), now.getMonth(), now.getDate());
 6   return date.getTime() < now.getTime();
 7 }
 8
 9 function isToday(year, month, day, selectedDate) {
10   let date = new Date(year, month, day);
11   return date.getTime() == selectedDate;
12 }
13
14 export default {
15   name: ‘ui-calendar-day‘,
16   data: {
17     props: {
18       year: {
19         type: Number
20       },
21       month: {
22         type: Number
23       },
24       day: {
25         type: Number
26       },
27       selectedDate: {
28         type: Number
29       }
30     },
31     methods: {
32         onDayClick: function (e) {
33           let data = e.currentTarget.dataset;
34           this.$parent.$parent.$emit(‘dayclick‘, data);
35         }
36     },
37     //引入计算属性概念
38     computed: {
39         // 计算属性的 getter
40         klass: function () {
41            //是否过期了
42           let klass = isOverdue(this.year, this.month, this.day) ? ‘cm-item--disabled‘ : ‘‘;
43
44           if(isToday(this.year, this.month, this.day, this.selectedDate)) klass += ‘active‘
45           return klass;
46         }
47     },
48     data: function() {
49       return {}
50     },
51     template:
52     `
53 <li :selectedDate="selectedDate" @click="onDayClick" :class="klass" v-bind:data-year="year" v-bind:data-month="month" v-bind:data-day="day">
54     <div class="cm-field-wrapper "><div class="cm-field-title">{{day}}</div></div>
55 </li>
56     `
57   }
58 }

日历-天

至此虽然,我们这块代码简陋,却完成了一个简单日历组件,至此我们第一阶段的探索结束,明天继续研究

原文地址:https://www.cnblogs.com/yexiaochai/p/9530725.html

时间: 2024-11-07 07:14:26

一套代码小程序&Web&Native运行的探索01的相关文章

一套代码小程序&amp;Web&amp;Native运行的探索05——snabbdom

接上文:一套代码小程序&Web&Native运行的探索04——数据更新 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvm 参考: https://github.com/fastCreator/MVVM(极度参考,十分感谢该作者,直接看Vue会比较吃力的,但是看完这个作者的代码便会轻易很多,可惜这个作者没有对应博客说明,不然就爽了) https://www.tangshuang.net/3756.html ht

一套代码小程序&amp;Web&amp;Native运行的探索06——组件系统

接上文:一套代码小程序&Web&Native运行的探索05——snabbdom 对应Git代码地址请见:https://github.com/yexiaochai/wxdemo/tree/master/mvvm 参考: https://github.com/fastCreator/MVVM(极度参考,十分感谢该作者,直接看Vue会比较吃力的,但是看完这个作者的代码便会轻易很多,可惜这个作者没有对应博客说明,不然就爽了) https://www.tangshuang.net/3756.htm

原创:微信小程序+WEB使用JS实现注册【60s】倒计时功能

1.效果图: 2.页面仅仅利用了JS的相关功能,包含:wxml.js.wxss 2.1wxml页面代码: <text>绑定手机</text> <form bindsubmit="bindMobile"> <view class="form_group"> <text>手 机:</text> <input type="number" placeholder="请

微信小程序获取今日天气预报代码 小程序获取七日天气

代码是天气api的小程序demo, 粘贴上js和wxml就可以运行看效果了, 有问题的加我qq 445899710, 可提供源代码, 效果如图 如果是测试, 请勾选配置 不校验合法域名.web-view(业务域名).TLS 版本以及 HTTPS 证书 如果正式使用, 请添加安全域名两个 (ip.tianqiapi.com 和 tianqiapi.com) index.js代码 //index.js //获取应用实例 const app = getApp() Page({ data: { weat

微信小程序是怎么运行的?

微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地. 紧接着通过 app.json 的 pages 字段就可以知道你当前小程序的所有页面路径 而写在 pages 字段的第一个页面就是这个小程序的首页(打开小程序看到的第一个页面). 于是微信客户端就把首页的代码装载进来,通过小程序底层的一些机制,就可以渲染出这个首页. 小程序启动之后,在 app.js 定义的 App 实例的 onLaunch 回调会被执行 整个小程序只有一个 App 实例,是全部页面共享的 --------------

从一个猜单词的小程序开始---征服OOP的思维方式01

记得刚开始学Java的时候看到MOOC上有个老师写了一个猜数字的游戏,出于兴趣,小风就写了一个猜单词的小游戏来描述 OOP编程的思维方式.PS:OOP(Object Oriented Programing,面向对象程序设计)可以理解为计算机编程的一种架构. 好了,不BB了.开始了! 首先我们按照常规的面向过程的思维方式来分析这个程序.仅仅定义Demo一个类,该类中存放所有的逻辑代码 由于便于理解,程序先成员的位置上定义两个字符串数组english和chinese.即一个用来存储英文单词,一个用来

微信小程序WEB工具安装

1.下载工具链接:http://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html?t=201715 2.安装完成后图片样式如图: 3.新建项目  说明:选择目录的时候尽量新建微信独立的工作空间[个人习惯] 4.打开项目效果图如下

如何快速开发一套微信商城小程序?

小程序的价值相信已经不用我多说,未来大部分应用场景都将使用微信小程序进行研发.开发一套商城小程序需要哪些步骤,怎么开通?快搞定小编来为大家解疑. 第一步:确定商城小程序产品功能.UI风格 在设计小程序的时候一定要符合"轻便.即用即走"的定位,小程序只是场景化的产品,功能不宜过多,更多的是起到平台覆盖和完善用户使用场景的作用. 第二步:注册微信小程序并申请微信支付 进入微信公众平台mp.weixin.qq.com,按提示注册即可.需注意的是,个人暂时不能注册小程序,注册时必须提供企业营业

微信小程序:微信web开发阶段性学习总结

小程序运行机制 前台/后台状态 小程序启动后,界面被展示给用户,此时小程序处于前台状态. 当用户点击右上角胶囊按钮关闭小程序,或者按了设备 Home 键离开微信时,小程序并没有完全终止运行,而是进入了后台状态,小程序还可以运行一小段时间. 当用户再次进入微信或再次打开小程序,小程序又会从后台进入前台.但如果用户很久没有再进入小程序,或者系统资源紧张,小程序可能被销毁,即完全终止运行. 小程序启动 这样,小程序启动可以分为两种情况,一种是冷启动,一种是热启动. 冷启动:如果用户首次打开,或小程序销