├── pages
├── logs
│ ├── logs.json
│ ├── logs.wxss
│ ├── logs.wxml
│ └── logs.js
├── index
│ ├── index.wxss
│ ├── index.wxml
│ └── index.js
└── components
│ └── userinfo
│ ├── index.wxml
│ ├── index.wxss
│ └── index.js
├── app.wxss
├── base.js
├── app.json
├── utils
├── util.js
└── emitter.js
├── app.js
├── README.md
├── page.js
└── component.js
/pages/logs/logs.json:
--------------------------------------------------------------------------------
1 | {
2 | "navigationBarTitleText": "查看启动日志"
3 | }
--------------------------------------------------------------------------------
/pages/index/index.wxss:
--------------------------------------------------------------------------------
1 | /**index.wxss**/
2 | @import '../components/userinfo/index.wxss';
3 |
4 | .usermotto {
5 | margin-top: 200px;
6 | }
7 |
--------------------------------------------------------------------------------
/pages/logs/logs.wxss:
--------------------------------------------------------------------------------
1 | .log-list {
2 | display: flex;
3 | flex-direction: column;
4 | padding: 40rpx;
5 | }
6 | .log-item {
7 | margin: 10rpx;
8 | }
9 |
--------------------------------------------------------------------------------
/pages/logs/logs.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{index + 1}}. {{log}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app.wxss:
--------------------------------------------------------------------------------
1 | /**app.wxss**/
2 | .container {
3 | height: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 | padding: 200rpx 0;
9 | box-sizing: border-box;
10 | }
11 |
--------------------------------------------------------------------------------
/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 基类
3 | * 用于提供一些公共方法
4 | */
5 | class Base {
6 | constructor() {
7 |
8 | }
9 |
10 | /**
11 | * 点击上报
12 | * @param {String} 需要上报的 Key
13 | */
14 | report(key) {
15 | // 数据上报代码
16 | }
17 | }
18 |
19 | module.exports = Base;
20 |
--------------------------------------------------------------------------------
/pages/index/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{motto}}
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "pages":[
3 | "pages/index/index",
4 | "pages/logs/logs"
5 | ],
6 | "window":{
7 | "backgroundTextStyle":"light",
8 | "navigationBarBackgroundColor": "#fff",
9 | "navigationBarTitleText": "WeChat",
10 | "navigationBarTextStyle":"black"
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/pages/components/userinfo/index.wxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{nickName}}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/pages/components/userinfo/index.wxss:
--------------------------------------------------------------------------------
1 | .userinfo {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
7 | .userinfo-avatar {
8 | width: 128rpx;
9 | height: 128rpx;
10 | margin: 20rpx;
11 | border-radius: 50%;
12 | }
13 |
14 | .userinfo-nickname {
15 | color: #aaa;
16 | }
17 |
--------------------------------------------------------------------------------
/pages/index/index.js:
--------------------------------------------------------------------------------
1 | //index.js
2 | //获取应用实例
3 | const JDPage = require('../../page.js');
4 |
5 | const UserInfo = require('../components/userinfo/index.js');
6 |
7 | new JDPage({
8 | components: {
9 | userInfo: UserInfo,
10 | },
11 | data: {
12 | motto: 'Hello World',
13 | },
14 | })
15 |
--------------------------------------------------------------------------------
/pages/logs/logs.js:
--------------------------------------------------------------------------------
1 | //logs.js
2 | const JDPage = require('../../page.js');
3 | const util = require('../../utils/util.js');
4 |
5 | new JDPage({
6 | data: {
7 | logs: []
8 | },
9 | onLoad: function() {
10 | this.setData({
11 | logs: (wx.getStorageSync('logs') || []).map(function(log) {
12 | return util.formatTime(new Date(log))
13 | })
14 | })
15 | }
16 | })
17 |
--------------------------------------------------------------------------------
/utils/util.js:
--------------------------------------------------------------------------------
1 | function formatTime(date) {
2 | var year = date.getFullYear()
3 | var month = date.getMonth() + 1
4 | var day = date.getDate()
5 |
6 | var hour = date.getHours()
7 | var minute = date.getMinutes()
8 | var second = date.getSeconds()
9 |
10 |
11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
12 | }
13 |
14 | function formatNumber(n) {
15 | n = n.toString()
16 | return n[1] ? n : '0' + n
17 | }
18 |
19 | module.exports = {
20 | formatTime: formatTime
21 | }
22 |
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | //app.js
2 | App({
3 | onLaunch: function () {
4 | //调用API从本地缓存中获取数据
5 | var logs = wx.getStorageSync('logs') || []
6 | logs.unshift(Date.now())
7 | wx.setStorageSync('logs', logs)
8 | },
9 | getUserInfo:function(cb){
10 | var that = this
11 | if(this.globalData.userInfo){
12 | typeof cb == "function" && cb(this.globalData.userInfo)
13 | }else{
14 | //调用登录接口
15 | wx.login({
16 | success: function () {
17 | wx.getUserInfo({
18 | success: function (res) {
19 | that.globalData.userInfo = res.userInfo
20 | typeof cb == "function" && cb(that.globalData.userInfo)
21 | }
22 | })
23 | }
24 | })
25 | }
26 | },
27 | globalData:{
28 | userInfo:null
29 | }
30 | })
--------------------------------------------------------------------------------
/pages/components/userinfo/index.js:
--------------------------------------------------------------------------------
1 | const Component = require('../../../component.js');
2 |
3 | const app = getApp();
4 |
5 | class UserInfo extends Component {
6 | defaultData() {
7 | return {
8 | avatarUrl: '',
9 | nickName: 'aotu',
10 | };
11 | }
12 |
13 | constructor() {
14 | super(...arguments);
15 |
16 | this.addFunc('_tapComponent', this.bindViewTap);
17 | }
18 |
19 | onLoad() {
20 | console.log('onLoad');
21 | //调用应用实例的方法获取全局数据
22 | app.getUserInfo(userInfo => {
23 | //更新数据
24 | console.log(userInfo);
25 | this.setData(userInfo);
26 | });
27 | }
28 |
29 | bindViewTap() {
30 | wx.navigateTo({
31 | url: '../logs/logs',
32 | });
33 | }
34 |
35 | }
36 |
37 | module.exports = UserInfo;
38 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wxapp-component-base
2 | 京东微信小程序组件方案 - 组件基类
3 |
4 | 目前组件方案仍然处于探索阶段,欢迎共同探讨
5 |
6 | 主要分为三个文件 `base.js`、`page.js`、`component.js`
7 |
8 | ## base.js
9 |
10 | 页面类 和 组件类 都继承于该公共类,用于挂载公共方法
11 |
12 | ## page.js
13 |
14 | 页面基础类,替代原来 `Page()` 方法
15 |
16 | ```js
17 | const JDPage = require('../../page.js');
18 |
19 | new JDPage({
20 | data: {
21 | logs: []
22 | },
23 | onLoad: function() {
24 | // do something
25 | }
26 | });
27 |
28 | ```
29 |
30 | ## component.js
31 |
32 | 组件基础类,用于创建一个组件
33 |
34 | component.js
35 | ```js
36 | const Component = require('../../../component.js');
37 |
38 | class UserInfo extends Component {
39 | // 默认数据
40 | defaultData() {
41 | return {
42 | avatarUrl: '',
43 | nickName: 'aotu',
44 | };
45 | }
46 |
47 | constructor() {
48 | super(...arguments);
49 |
50 | // 组件页面需要绑定方法时,通过 addFunc() 暴露给页面。
51 | this.addFunc('_tapComponent', this.bindViewTap);
52 | }
53 |
54 | onLoad() {
55 | // do something
56 | }
57 |
58 | bindViewTap() {
59 | // do something
60 | }
61 | }
62 |
63 | module.exports = UserInfo;
64 | ```
65 |
66 | component.wxml
67 | ```html
68 |
69 |
70 |
71 |
72 | {{nickName}}
73 |
74 |
75 | ```
76 |
77 |
78 | ## 引用组件
79 |
80 | page.js
81 | ```js
82 | const UserInfo = require('../components/userinfo/index.js');
83 |
84 | new JDPage({
85 | components: {
86 | userInfo: UserInfo,
87 | },
88 | ...
89 | });
90 | ```
91 |
92 | page.wxml
93 | ```html
94 |
95 |
96 |
97 |
98 |
99 | ```
100 |
101 | page.wxss
102 | ```css
103 | @import '../path/to/component.wxss';
104 |
105 | .usermotto {
106 | margin-top: 200px;
107 | }
108 | ```
109 |
110 | 其他详情见代码...
111 |
--------------------------------------------------------------------------------
/utils/emitter.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Simple event emitter based on component/emitter
3 | *
4 | * @constructor
5 | * @param {Object} ctx - the context to call listeners with
6 | */
7 | function Emitter(ctx) {
8 | this._ctx = ctx || this;
9 | }
10 |
11 | var p = Emitter.prototype;
12 |
13 | p.on = function(event, fn) {
14 | if (typeof fn !== "function") {
15 | console.error('fn must be a function')
16 | return;
17 | }
18 |
19 | this._cbs = this._cbs || {};
20 | if (this._cbs[event] && this._cbs[event].length > 0)
21 | console.warn('重复定义事件', event);
22 | (this._cbs[event] || (this._cbs[event] = [])).push(fn);
23 | return this;
24 | };
25 |
26 | p.once = function(event, fn) {
27 | if (typeof fn !== "function") {
28 | console.error('fn must be a function')
29 | return;
30 | }
31 |
32 | var self = this;
33 | this._cbs = this._cbs || {};
34 |
35 | function on() {
36 | self.off(event, on);
37 | fn.apply(this, arguments);
38 | }
39 |
40 | on.fn = fn;
41 | this.on(event, on);
42 | return this;
43 | };
44 |
45 | p.off = function(event, fn) {
46 | this._cbs = this._cbs || {};
47 |
48 | if (!arguments.length) {
49 | this._cbs = {};
50 | return this;
51 | }
52 |
53 | var callbacks = this._cbs[event];
54 | if (!callbacks) return this;
55 |
56 | if (arguments.length === 1) {
57 | delete this._cbs[event];
58 | return this;
59 | }
60 |
61 | var cb;
62 | for (var i = 0; i < callbacks.length; i++) {
63 | cb = callbacks[i];
64 | if (cb === fn || cb.fn === fn) {
65 | callbacks.splice(i, 1);
66 | break;
67 | }
68 | }
69 | return this;
70 | };
71 |
72 | p.emit = function(event) {
73 | this._cbs = this._cbs || {};
74 | var args = [].slice.call(arguments);
75 | var callbacks = this._cbs[event];
76 |
77 | if (callbacks) {
78 | callbacks = callbacks.slice(0);
79 | args.shift();
80 | for (var i = 0, len = callbacks.length; i < len; i++) {
81 | try {
82 | callbacks[i].apply(this._ctx, args);
83 | } catch(err) {
84 | console.error(err);
85 | }
86 | }
87 | }
88 |
89 | return this;
90 | }
91 |
92 | module.exports = Emitter;
--------------------------------------------------------------------------------
/page.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 页面基类
3 | */
4 |
5 | const Base = require('./base.js');
6 | const Emitter = require('./utils/emitter.js');
7 |
8 | const event = new Emitter();
9 |
10 | class JDPage extends Base {
11 | constructor(obj, ...args) {
12 | super(obj);
13 |
14 | this.obj = obj;
15 |
16 | this.initLifeCircle();
17 | this.initComponents();
18 | this.initPlugins();
19 |
20 | Page(obj, ...args);
21 | }
22 |
23 | /**
24 | * 注入页面的生命周期
25 | */
26 | initLifeCircle() {
27 | const list = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onRouteEnd'];
28 | const _self = this;
29 |
30 | for(let fn of list) {
31 | let tempFn = this.obj[fn];
32 |
33 | this.obj[fn] = function(...args) {
34 | if(_self[fn]) args = _self[fn].apply(this, args) || args;
35 |
36 | if(tempFn) tempFn.apply(this, args);
37 | }
38 | }
39 | }
40 |
41 | /**
42 | * 初始化页面挂载的组件
43 | */
44 | initComponents() {
45 | const {components} = this.obj;
46 |
47 | if(!components) return;
48 |
49 | this.components = {};
50 |
51 | for(let component in components) {
52 | this.components[component] = new components[component](this.obj, component);
53 | }
54 |
55 | this.obj.components = this.components;
56 | }
57 |
58 | /**
59 | * 为页面添加一些常用方法
60 | */
61 | initPlugins() {
62 | this.obj._events = {};
63 |
64 | // 添加方法
65 | let funcList = ['on', 'emit', 'report'];
66 | for(let fn of funcList) {
67 | this.obj[fn] = this[fn];
68 | }
69 | }
70 |
71 | /**
72 | * 页面公共的 onLoad 方法,会在页面 onLoad 前执行
73 | * @param 传入 onLoad 的参数
74 | * @return {Any} 返回值会替换原来的参数成传给单个页面的 onLoad 方法
75 | */
76 | onLoad(options) {
77 | // bind events
78 | const {events} = this;
79 | if(events) {
80 | for(let key in events) {
81 | this.on(key, this[events[key]]);
82 | }
83 | }
84 | }
85 |
86 | /**
87 | * 页面公共的 onUnload 方法
88 | */
89 | onUnload() {
90 | // remove event listener
91 | for(let name in this._events) {
92 | for(let i in this._events[name]) {
93 | let fn = this._events[name][i]
94 | event.off(name, fn);
95 | }
96 |
97 | delete(this._events[name]);
98 | }
99 | }
100 |
101 | /**
102 | * 监听事件
103 | */
104 | on(name, fn) {
105 | if(!this._events[name]) this._events[name] = [];
106 |
107 | fn = fn.bind(this);
108 | this._events[name].push(fn);
109 |
110 | event.on(name, fn);
111 | }
112 |
113 | /**
114 | * 触发事件
115 | */
116 | emit() {
117 | event.emit(...arguments);
118 | }
119 | }
120 |
121 | module.exports = JDPage;
122 |
--------------------------------------------------------------------------------
/component.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 组件基类
3 | */
4 |
5 | const Base = require('./base.js');
6 | const Emitter = require('./utils/emitter.js');
7 |
8 | const event = new Emitter();
9 |
10 | /**
11 | * 解析对象路径
12 | * @param o {Object} 要解析的对象
13 | * @param s {String} 对应的路径,如 'foo.bar[2]'
14 | * @param d {Any} 路径不存在时,设置的默认值
15 | * @return {Any} 对应路径的值
16 | */
17 | function resolvePath(o, s, d) {
18 | s = s.replace(/\[(\w+)\]/g, '.$1'); // convert indexes to properties
19 | s = s.replace(/^\./, ''); // strip a leading dot
20 | var a = s.split('.');
21 | for (var i = 0, n = a.length; i < n; ++i) {
22 | var k = a[i];
23 | if (k in o) {
24 | o = o[k];
25 | } else {
26 | return o[k] = d;
27 | }
28 | }
29 | return o;
30 | };
31 |
32 | /**
33 | * 获取随机 ID
34 | * @return {String} ID 字符串
35 | */
36 | function getID() {
37 | return Math.random().toString(36).substring(2, 15);
38 | }
39 |
40 | class Component extends Base {
41 | /**
42 | * 设置组件默认 data
43 | * @return {Object} 默认数据对象
44 | */
45 | defaultData() {
46 | return {};
47 | }
48 |
49 | /**
50 | * 设置依赖组件
51 | * @return {Object} 依赖组件
52 | */
53 | components() {
54 | return {};
55 | }
56 |
57 | /**
58 | * 初始化方法
59 | * @param target {Object} 组件挂载的 page 对象
60 | * @param ns {String} 组件挂载在页面 data 的对应路径,例如 'top.nav'
61 | */
62 | constructor(target, ns) {
63 | super(target);
64 |
65 | this.target = target;
66 | this.ns = ns;
67 |
68 | this._events = {};
69 | this.data.fn = {};
70 |
71 | if(typeof target.setData === 'function') this.page = target;
72 |
73 |
74 | this._mergeDefaultData();
75 | this._initComponents();
76 |
77 | this._mergeLifeCircle();
78 | }
79 |
80 | /**
81 | * 组件 ID
82 | */
83 | get cid() {
84 | if(!this._cid) this._cid = getID();
85 |
86 | return this._cid;
87 | }
88 |
89 | /**
90 | * 获取当前组件的 data 对象
91 | */
92 | get data() {
93 | let target = this.page || this.target;
94 |
95 | return resolvePath(target.data, this.ns, {});
96 | }
97 |
98 | set data(options) {
99 | console.log("~~~~~~~~~ set: ", options);
100 | // TODO set data
101 | throw Error("cannot set data yet! @JD");
102 | }
103 |
104 | /**
105 | * 合并组件 data
106 | */
107 | _mergeDefaultData() {
108 | let defaults = this.defaultData();
109 |
110 | for(let key in defaults) {
111 | if(typeof this.data[key] == "undefined") this.data[key] = defaults[key];
112 | }
113 |
114 | // update page data if page is already exists
115 | if(this.page) {
116 | this.setData(this.data);
117 | }
118 | }
119 |
120 | _initComponents() {
121 | let components = this.components();
122 |
123 | this.components = {};
124 |
125 | for(let key in components) {
126 | this.components[key] = new components[key](this.target, this.ns + '.' + key);
127 | }
128 | }
129 |
130 | /**
131 | * 合并生命周期
132 | */
133 | _mergeLifeCircle() {
134 | let _self = this;
135 | let list = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload'];
136 |
137 | for(let fn of list) {
138 | if(!this[fn]) continue;
139 |
140 | let tempFn = this.target[fn];
141 |
142 | this.target[fn] = function() {
143 | if(!_self.page) _self.page = this;
144 |
145 | if(tempFn) tempFn.apply(this, arguments);
146 | _self[fn].apply(_self, arguments);
147 | }
148 | }
149 | }
150 |
151 | /**
152 | * 获取真正的函数名称
153 | * @param name {String} 组件内函数名称
154 | * @return {String} 挂载到页面的真正的函数名称
155 | */
156 | _getFuncName(name) {
157 | return `${name}_${this.cid}`;
158 | }
159 |
160 | /**
161 | * 更新组件数据
162 | * @param obj {Object} 更新内容,同微信的 setData
163 | * @param isGlobal {Boolean} 若为 true 时,更新作用域为当前页面的数据,默认 false
164 | */
165 | setData(obj, isGlobal) {
166 | if(!this.page) throw Error("no page object~ @JD");
167 |
168 | let {ns} = this;
169 | let data = {};
170 |
171 | if(isGlobal) {
172 | data = obj;
173 | } else {
174 | for(let key in obj) {
175 | data[ns+'.'+key] = obj[key];
176 | }
177 | }
178 |
179 | this.page.setData(data);
180 | }
181 |
182 | onLoad() {
183 |
184 | }
185 |
186 | /**
187 | * 添加回调方法供模板调用
188 | * @param name {String} 对应模板中调用时的方法名
189 | * @param fn {Function} 执行的回调方法,this 指向当前组件
190 | */
191 | addFunc(name, fn) {
192 | if(this.data[name]) {
193 | throw new Error(`function name ${name} is already exists~ @JD`);
194 | }
195 |
196 | const fnName = this._getFuncName(name);
197 |
198 | // notice to page if page object is already exists
199 | if(this.page) {
200 | this.setData({
201 | [`${name}`]: fnName,
202 | });
203 | } else {
204 | this.data[name] = fnName;
205 | }
206 |
207 | this.target[fnName] = fn.bind(this);
208 | }
209 |
210 | /**
211 | * 监听事件,同 Event 组件
212 | */
213 | on(name, fn) {
214 | if(!this._events[name]) this._events[name] = [];
215 |
216 | fn = fn.bind(this);
217 | this._events[name].push(fn);
218 |
219 | event.on(name, fn);
220 | }
221 |
222 | /**
223 | * 触发事件,同 Event 组件
224 | */
225 | emit() {
226 | event.emit(...arguments);
227 | }
228 | }
229 |
230 | module.exports = Component;
231 |
--------------------------------------------------------------------------------