":n=t>e;break;case"gte":case">=":n=t>=e;break;case"eq":case"=":default:n=t===e}return n}function U(t,r){for(var e=this.params.sort,n=e.length,i=void 0;--n>=0;){if(e[n][0]===t){i=n;break}}var a=[t,r];i>-1&&this.params.sort.splice(i,1),this.params.sort.push(a)}function V(){var t=this.target,r=this.params,e=this.queried,n=this.options,i=r.group,a=r.range;if(!e){var s=[],o=Object.create(null),u=i&&i.length,c=a[0],l=a[1],f=-1,p=t.length;if(!m.isUndefined(c)||!m.isUndefined(l)){if(c>l){var h=[l,c];c=h[0],l=h[1]}c>p-1&&(c=p-1),l>p-1&&(l=p-1),p=(t=t.slice(c,l)).length}for(var g=void 0;++f=0&&(this.params.range[0]=t),m.isInteger(r)&&r>=0&&(this.params.range[1]=r),this},N.skip=function(t){return this.params.skip=m.isInteger(t)&&t>0?t:0,this},N.limit=function(t){return this.params.limit=m.isInteger(t)?Math.abs(t):void 0,this},N.where=function(t,r,e,n){if(m.isArray(arguments[0])){for(var i=arguments[0].length,a=-1,s=[];++ac)return"desc"===o?-1:1;if(u 使用格式化函数对字段进行进行格式化
3 |
4 | ### 内置格式化函数
5 | [内置格式化函数](https://github.com/S-mohan/query/blob/master/docs/hooks.md)
6 |
7 | ### use
8 | ```javascript
9 | query.to(field, type, options)
10 | ```
11 | ##### arguments
12 | - `field` : {String} 需要格式化的字段
13 | - `type` : {String} 指定格式化方式(内置的格式化函数或者自定义的格式化函数名称)
14 | - `options`: {Object} 可用的配置项
15 | - `args`: {Array} 传递给格式化函数的除了`value`之外的剩余参数, 如: `[arg1, arg2, [arg3]...]`
16 | - `new`: {Boolean | String} 是否生成新的字段, 默认`false`
17 | - `false`: 不生成新字段,直接使用格式化后的值重置原字段
18 | - `true`: 生成新字段,保留原字段。新字段被标识为`$原字段名`,如 `title` => `$title`
19 | - `string`: 生成新字段,保留原字段。使用指定的名称来生成一个新字段,新字段会被自动添加`$`前缀,如 `customTitle` => `$customTitle`
20 | - `handler`: {Function | null} 局部格式化函数,该格式化函数仅对当前使用有效,缺省值为 `null`
21 |
22 | ##### example
23 |
24 | ```javascript
25 | var sourceData = [
26 | {
27 | title: 'title',
28 | createAt: '2017-09-20T13:14:06.312Z',
29 | views: '322'
30 | }
31 | ]
32 |
33 | var query = new Query(sourceData)
34 |
35 | // 1. 使用内置格式化函数
36 |
37 | // 1.1 生成新字段
38 | query
39 | .to('createAt', 'date', {args: ['yy-MM'], new: 'myDate'})
40 | .find()
41 |
42 | // [
43 | // {
44 | // title: 'title',
45 | // createAt: '2017-09-20T13:14:06.312Z',
46 | // views: '322',
47 | // '$myDate': '2017-09'
48 | // }
49 | // ]
50 |
51 | // 1.2 不生成新字段
52 | query
53 | .to('createAt', 'number', {new: false})
54 | .find()
55 |
56 | // [
57 | // {
58 | // title: 'title',
59 | // createAt: '2017-09-20T13:14:06.312Z',
60 | // views: 322 // 字符串被转换为数字
61 | // }
62 | // ]
63 |
64 | // 2. 使用自定义格式化函数
65 |
66 | // 2.1 注册全局格式化函数
67 | Query.hooks('formatTitle', value => {
68 | return '格式化后的' + value
69 | })
70 |
71 | query
72 | .to('title', 'formatTitle', {new: true})
73 | .find()
74 |
75 | // [
76 | // {
77 | // title: 'test',
78 | // createAt: '2017-09-20T13:14:06.312Z',
79 | // views: '322',
80 | // '$title': '格式化后的title'
81 | // }
82 | // ]
83 |
84 | // 2.2 使用局部格式化函数
85 | query
86 | .to('title', null, {new: true, handler(value) {
87 | return '使用局部格式化函数格式化的' + value
88 | }})
89 | .find()
90 |
91 | // [
92 | // {
93 | // title: 'test',
94 | // createAt: '2017-09-20T13:14:06.312Z',
95 | // views: '322',
96 | // '$title': '使用局部格式化函数格式化的title'
97 | // }
98 | // ]
99 |
100 | ```
101 |
--------------------------------------------------------------------------------
/docs/hooks.md:
--------------------------------------------------------------------------------
1 | ## hooks
2 | > 字段格式化内置函数
3 |
4 | ### date
5 | > 将一个字段的值格式化为指定的日期格式
6 |
7 | ##### example
8 | ```javascript
9 | query.to('createTime', 'date', {args: ['yy-MM-dd']})
10 | ```
11 | ##### args: datetime Type
12 | - `y / yy` : year/fullYear, '18'/'2018'
13 | - `M / MM`: month/fullMonth, '5'/'05'
14 | - `d / dd`: day/fullDay, '9'/'09'
15 | - `h / hh`: hour/fullHour, '9'/'09'
16 | - `m / mm`: minute/fullMinute, '9'/'09'
17 | - `s / ss`: second/fullSecond, '9'/'09'
18 | - `q / qq`: quarter/fullQuarter, '2'/'02'
19 |
20 | ### number
21 | > 将一个字段的值格式化为数字
22 |
23 | ##### example
24 | ```javascript
25 | // views: '30'
26 | query.to('views', 'number')
27 | // views: 30
28 | ```
29 |
30 | ### int
31 | > 将一个字段的值格式化为整数
32 |
33 | ##### example
34 | ```javascript
35 | // price: 30.25
36 | query.to('price', 'int')
37 | // price: 30
38 | ```
39 |
40 | ### zero
41 | > 将一个字段的空值('', null, undefined, false, 0)格式化为 0
42 |
43 | ##### example
44 | ```javascript
45 | // price: null
46 | query.to('price', 'zero')
47 | // price: 0
48 | ```
49 |
50 | ### boolean
51 | > 将一个字段的空值('', null, undefined, false, 0)格式化为 false, 非空值转换为 true
52 |
53 | ##### example
54 | ```javascript
55 | // read: null
56 | // updated: 1
57 | query.to('read', 'boolean')
58 | query.to('updated', 'boolean')
59 | // read: false
60 | // updated: true
61 | ```
62 |
63 | ### string
64 | > 将一个字段的值转化为字符串
65 |
66 | ##### example
67 | ```javascript
68 | // views: 10
69 | query.to('views', 'string')
70 | // views: '10'
71 | ```
72 |
73 | ### lower
74 | > 将一个字段的值转化为小写
75 |
76 | ##### example
77 | ```javascript
78 | // author: 'SMOHAN'
79 | query.to('author', 'lower')
80 | // author: 'smohan'
81 | ```
82 |
83 | ### upper
84 | > 将一个字段的值转化为大写
85 |
86 | ##### example
87 | ```javascript
88 | // author: 'smohan'
89 | query.to('author', 'upper')
90 | // author: 'SMOHAN'
91 | ```
--------------------------------------------------------------------------------
/docs/where.md:
--------------------------------------------------------------------------------
1 | ### where 查询构造器
2 |
3 | ###### Syntax
4 | ```javascript
5 | /**
6 | * @param {String} field
7 | * @param {String} expression
8 | * @param {String | RegExp | Function} condition
9 | * @param {String} relation {and(default) | or}
10 | */
11 | query.where(field, expression, condition, relation)
12 | // array group
13 | query.where([[field, expression, condition, relation], [field, expression, condition, relation]])
14 | ```
15 |
16 | ###### expression
17 |
18 | expression | description | example
19 | ---- | --- | ---
20 | `eq` / `=` | 等于(`===`)| where('age', 'eq', 18)
21 | `neq` / `<>` | 不等于(`!==`) | where('sex', 'neq', 'male')
22 | `gt` / `>` | 大于(`>`) | where('price', 'gt', 17)
23 | `gte` / `>=` | 大于等于 (`>=`) | where('price', 'gte', 17)
24 | `lt` / `<` | 小于(`<`) | where('price', '<', 17)
25 | `lte` / `<=` | 小于等于(`<=`) | where('price', '<=', 17)
26 | `like` | 模糊查询 | where('title', 'like', 'javascript')
27 | `in` | in查询 | where('tag', 'in', ['javascript', 'vue'])
28 | `nin` | 不再in查询 | where('tag', 'nin', ['javascript', 'vue'])
29 | `exists` | 字段是否存在 | where('author', 'exists')
30 |
31 | ###### examples
32 |
33 | - 普通查询
34 | ```javascript
35 | // author === 'smohan' && views > 500
36 | query
37 | .where('author', 'eq', 'smohan')
38 | .where('views', '>', 500)
39 | ```
40 |
41 | - or
42 | ```javascript
43 | // author === 'smohan' || views > 500
44 | query
45 | .where('author', 'eq', 'smohan')
46 | .where('views', '>', 500, 'or')
47 | ```
48 |
49 | - where group
50 | ```javascript
51 |
52 | // author === 'smohan' && (title like javascript || tags like javascript) && comments >= 0
53 | query
54 | .where('author', 'eq', 'smohan')
55 | .where([['title', 'like', 'javascript'], ['tags', 'like', 'javascript', 'or'] ])
56 | .where('comments', 'gt', 0)
57 | ```
58 |
59 | - condition function
60 | ```javascript
61 | query
62 | .where('comments', 'eq', function (value) {
63 | return value >= 3 && views <= 100
64 | })
65 | ```
66 |
67 | - condition regExp
68 | ```javascript
69 | // regExp.test(email value)
70 | query
71 | .where('email', 'eq', /^\w+@\w+\.\w+$/)
72 | ```
73 |
--------------------------------------------------------------------------------
/example/data.js:
--------------------------------------------------------------------------------
1 | // 20180517150016
2 | // https://smohan.net/api/posts/?limit=100
3 |
4 | var postsData = [
5 | {
6 | '_id': '59b2c5e00dcd3220659f1b32',
7 | 'thumbnail': '//img.smohan.net/671633a320fe5048522abe8ca4d3d2dd.jpg',
8 | 'excerpt': '本文着重介绍CSS的伪类和伪选择器,讲解了伪类和伪选择器的区别,用`:valid`和`:invalid`来做表单即时校验,用`:target`来实现折叠面板,用`:not`来排除其他选择器,用`:nth-child(even/odd)`来实现隔行变色,用`::selection`来美化选中文本,用`::placeholder`来美化占位符,用`::first-letter`来实现段落首字下沉,用`::first-line`来特殊标记段落第一行',
9 | 'alias': 'tr6bta',
10 | 'title': '你不知道的CSS(三)',
11 | 'top': false,
12 | 'updateTime': '2017-09-20T13:14:06.312Z',
13 | 'createTime': '2017-09-08T15:26:03.896Z',
14 | 'copyright': {
15 | 'author': 'smohan',
16 | 'source': '//smohan.net/',
17 | 'belong': 'original'
18 | },
19 | 'count': {
20 | 'downloads': 0,
21 | 'praises': 13,
22 | 'comments': 6,
23 | 'views': 991
24 | },
25 | 'tags': [
26 | 'css3',
27 | 'sass',
28 | '伪类',
29 | '伪选择器'
30 | ],
31 | 'category': {
32 | 'name': '前端开发',
33 | 'path': '580e36616dd7c320d45984aa',
34 | 'id': '580e36616dd7c320d45984aa'
35 | }
36 | },
37 | {
38 | '_id': '59b173276de7aa2352949694',
39 | 'thumbnail': '//img.smohan.net/671633a320fe5048522abe8ca4d3d2dd.jpg',
40 | 'excerpt': '这一篇中将主要介绍未知高度容器的多种垂直居中方法,包括伪元素占位法,absolute + transform,table-cell,基于flex的等5个方案;用counter来模拟/装饰有序清单;用table-layout来控制表格单元格宽度;用caret-color来自定义光标的样式;用user-select来禁用文本选中',
41 | 'alias': 'farjdx',
42 | 'title': '你不知道的CSS(二)',
43 | 'top': false,
44 | 'updateTime': '2017-09-15T16:19:28.280Z',
45 | 'createTime': '2017-09-04T13:29:13.956Z',
46 | 'copyright': {
47 | 'author': 'smohan',
48 | 'source': '//smohan.net/',
49 | 'belong': 'original'
50 | },
51 | 'count': {
52 | 'downloads': 0,
53 | 'praises': 17,
54 | 'comments': 3,
55 | 'views': 2105
56 | },
57 | 'tags': [
58 | 'css3',
59 | 'css',
60 | '技巧',
61 | 'scss'
62 | ],
63 | 'category': {
64 | 'name': '前端开发',
65 | 'path': '580e36616dd7c320d45984aa',
66 | 'id': '580e36616dd7c320d45984aa'
67 | }
68 | },
69 | {
70 | '_id': '59831f34e20c3f1374ab9d66',
71 | 'thumbnail': '//img.smohan.net/671633a320fe5048522abe8ca4d3d2dd.jpg',
72 | 'excerpt': 'CSS的世界是神奇的。整理了一些实用的CSS技巧,来解决我们在实际项目开发中遇到的的问题。技巧如:用~ / + 兄弟选择器来美化表单元素;用font-size:0来清除间距;用 overflow 來清除浮动;用border来绘制三角形;用垂直方向的padding来实现等比缩放的盒子;用pointer-event来禁用事件;用max-width来防止图片撑破容器;用伪类来显示打印时a标签的链接',
73 | 'alias': '6gr77h',
74 | 'title': '你不知道的CSS(一)',
75 | 'top': false,
76 | 'updateTime': '2017-09-14T13:13:47.159Z',
77 | 'createTime': '2017-08-03T12:59:01.809Z',
78 | 'copyright': {
79 | 'author': 'smohan',
80 | 'source': '//smohan.net/',
81 | 'belong': 'original'
82 | },
83 | 'count': {
84 | 'downloads': 0,
85 | 'praises': 43,
86 | 'comments': 0,
87 | 'views': 4202
88 | },
89 | 'tags': [
90 | 'css3',
91 | 'sass',
92 | 'css技巧'
93 | ],
94 | 'category': {
95 | 'name': '前端开发',
96 | 'path': '580e36616dd7c320d45984aa',
97 | 'id': '580e36616dd7c320d45984aa'
98 | }
99 | },
100 | {
101 | '_id': '5aca29dfc62051368fc1d00a',
102 | 'title': '前端魔法堂——异常不仅仅是try/catch',
103 | 'alias': 'kr6yu1',
104 | 'excerpt': '异常还是错误?它会如何影响我们的代码?内置异常类型有哪些?捕获“同步代码”中的"运行时异常",用try/catch就够了。"万能"异常捕获者window.onerror,真的万能吗?Promise.reject也抛异常,怎么办?404等网络请求异常真心要后之后觉吗?',
105 | 'thumbnail': '//img.smohan.net/c7923a14c1475f2a0880c690eaf3dc29.jpg',
106 | 'top': false,
107 | 'updateTime': '2018-04-08T15:21:44.567Z',
108 | 'createTime': '2018-04-08T14:40:31.698Z',
109 | 'copyright': {
110 | 'author': '肥仔John',
111 | 'source': 'https://www.cnblogs.com/fsjohnhuang/p/7685144.html',
112 | 'belong': 'reprint'
113 | },
114 | 'count': {
115 | 'downloads': 0,
116 | 'praises': 4,
117 | 'comments': 0,
118 | 'views': 41
119 | },
120 | 'tags': [
121 | 'java',
122 | '异常',
123 | 'error',
124 | 'try-catch'
125 | ],
126 | 'category': {
127 | 'name': '他山之石',
128 | 'path': '580e36a66dd7c320d45984b0',
129 | 'id': '580e36a66dd7c320d45984b0'
130 | }
131 | },
132 | {
133 | '_id': '5a9554c6c62051368fc1cfde',
134 | 'title': '在原生CSS中使用变量',
135 | 'alias': 'w0incg',
136 | 'excerpt': 'CSS 变量是由CSS作者定义的实体,其中包含要在整个文档中重复使用的特定值。使用自定义属性来设置变量名,并使用特定的 var() 来访问。css变量使用--开头;css变量分为全局变量和局部变量',
137 | 'thumbnail': '//img.smohan.net/a713d933ff8bd8f28794e237c3d920ec.jpg',
138 | 'top': false,
139 | 'updateTime': '2018-04-08T15:28:47.421Z',
140 | 'createTime': '2018-02-27T12:53:26.342Z',
141 | 'copyright': {
142 | 'author': 'smohan',
143 | 'source': 'https://smohan.net',
144 | 'belong': 'original'
145 | },
146 | 'count': {
147 | 'downloads': 0,
148 | 'praises': 2,
149 | 'comments': 0,
150 | 'views': 300
151 | },
152 | 'tags': [
153 | 'css3',
154 | 'var',
155 | 'variable'
156 | ],
157 | 'category': {
158 | 'name': '前端文档',
159 | 'path': '580e36616dd7c320d45984aa#580e368b6dd7c320d45984ad',
160 | 'id': '580e368b6dd7c320d45984ad'
161 | }
162 | },
163 | {
164 | '_id': '585561405684002ec0a0920d',
165 | 'thumbnail': '//img.smohan.net/article/4b2344f9ab77bf4da1d48c2b2c6a0dd6.png',
166 | 'excerpt': 'Vue2.0利用vue-resource上传文件到七牛',
167 | 'alias': 'ygbey7',
168 | 'title': 'Vue2.0利用vue-resource上传文件到七牛',
169 | 'top': false,
170 | 'updateTime': '2017-08-23T16:22:02.931Z',
171 | 'createTime': '2016-12-13T15:57:23.970Z',
172 | 'copyright': {
173 | 'source': '//smohan.net',
174 | 'author': 'smohan',
175 | 'belong': 'original'
176 | },
177 | 'count': {
178 | 'downloads': 0,
179 | 'praises': 27,
180 | 'comments': 3,
181 | 'views': 5634
182 | },
183 | 'tags': [
184 | 'vue',
185 | 'javascript',
186 | 'webpack'
187 | ],
188 | 'category': {
189 | 'name': '学习笔记',
190 | 'path': '580e36986dd7c320d45984ae',
191 | 'id': '580e36986dd7c320d45984ae'
192 | }
193 | },
194 | {
195 | '_id': '5895773f8b462909388085ef',
196 | 'thumbnail': '//img.smohan.net/article/93b1b5505ab2ae192b9544ec6c0f2c51.jpg',
197 | 'excerpt': 'Mongoose是在`node.js`环境下对`mongodb`进行便捷操作的对象模型工具。本文总结了mongoose简要的增删改查api',
198 | 'alias': 'b9rmng',
199 | 'title': 'Mongoose简要API',
200 | 'top': false,
201 | 'updateTime': '2017-08-22T16:09:27.868Z',
202 | 'createTime': '2017-02-04T06:35:02.116Z',
203 | 'copyright': {
204 | 'author': 'smohan',
205 | 'source': '//smohan.net',
206 | 'belong': 'original'
207 | },
208 | 'count': {
209 | 'downloads': 0,
210 | 'praises': 11,
211 | 'comments': 4,
212 | 'views': 568
213 | },
214 | 'tags': [
215 | 'mongodb',
216 | 'mongoose',
217 | 'nodejs',
218 | 'nginx'
219 | ],
220 | 'category': {
221 | 'name': '且行且冥',
222 | 'path': '580e369f6dd7c320d45984af',
223 | 'id': '580e369f6dd7c320d45984af'
224 | }
225 | },
226 | {
227 | '_id': '5884cdfc0bd6381f9cfd38ed',
228 | 'thumbnail': '//img.smohan.net/project/5807b0d7d9fbb8ff71199154ecf12854.jpg',
229 | 'excerpt': '一款基于HTML5以及CSS3的列表式音乐播放器,实现了音量控制、播放进度、播放时间以及播放模式的选择,上一曲、下一曲的控制。',
230 | 'alias': 'u3zxq1',
231 | 'title': 'HTML5音乐列表播放器SMusic开发总结',
232 | 'top': false,
233 | 'updateTime': '2017-08-22T16:10:35.874Z',
234 | 'createTime': '2015-05-17T15:10:32.833Z',
235 | 'copyright': {
236 | 'author': 'smohan',
237 | 'source': '//smohan.net',
238 | 'belong': 'original'
239 | },
240 | 'count': {
241 | 'downloads': 0,
242 | 'praises': 134,
243 | 'comments': 30,
244 | 'views': 16198
245 | },
246 | 'tags': [
247 | 'audio',
248 | 'javascript',
249 | 'html5',
250 | 'smusic',
251 | 'css3'
252 | ],
253 | 'category': {
254 | 'name': '前端分享',
255 | 'path': '580e36616dd7c320d45984aa#580e36836dd7c320d45984ac',
256 | 'id': '580e36836dd7c320d45984ac'
257 | }
258 | },
259 | {
260 | '_id': '581b7fd48e2ca73f4c5623bc',
261 | 'thumbnail': '//img.smohan.net/article/52f1f7fdcefa844bbd5d9a2b96bf03ef.jpg',
262 | 'excerpt': 'CSS3 Filter是W3C CSS filter Effect 1.0中定义的滤镜,一个使用CSS来改变图片和HTML的模糊度、亮度、对比度、饱和度等等效果的过滤器。',
263 | 'alias': '5zls13',
264 | 'title': '巧用CSS3滤镜实现图片不同渲染效果',
265 | 'top': false,
266 | 'updateTime': '2017-08-22T16:13:52.810Z',
267 | 'createTime': '2015-12-25T18:10:25.063Z',
268 | 'copyright': {
269 | 'author': 'smohan',
270 | 'source': '//smohan.net/',
271 | 'belong': 'original'
272 | },
273 | 'count': {
274 | 'downloads': 0,
275 | 'praises': 16,
276 | 'comments': 1,
277 | 'views': 3580
278 | },
279 | 'tags': [
280 | 'css3',
281 | 'filter',
282 | '滤镜'
283 | ],
284 | 'category': {
285 | 'name': '前端开发',
286 | 'path': '580e36616dd7c320d45984aa',
287 | 'id': '580e36616dd7c320d45984aa'
288 | }
289 | },
290 | {
291 | '_id': '5aae6c7ec62051368fc1cff6',
292 | 'title': '这几年记在有道云笔记上的前端知识',
293 | 'alias': 'jc6gyl',
294 | 'excerpt': '知识需要积累。\n打开有道云笔记,在前端目录中已经有约30多篇来自工作中,项目中或者书本中的知识点总结,大概看了一些,大部分都是JavaScript相关的知识点,css不多,这里筛选出一些来,按照时间顺序汇总分享出来。\n文章没有具体内容,也没有章节顺序,仅仅是一些知识点的碎片或者理论化的论点。\n',
295 | 'thumbnail': '',
296 | 'top': false,
297 | 'updateTime': '2018-04-08T15:25:49.410Z',
298 | 'createTime': '2018-03-18T13:41:18.632Z',
299 | 'copyright': {
300 | 'author': 'smohan',
301 | 'source': 'https://smohan.net',
302 | 'belong': 'original'
303 | },
304 | 'count': {
305 | 'downloads': 0,
306 | 'praises': 3,
307 | 'comments': 1,
308 | 'views': 221
309 | },
310 | 'tags': [
311 | 'JavaScript',
312 | '有道云笔记',
313 | '前端'
314 | ],
315 | 'category': {
316 | 'name': '学习笔记',
317 | 'path': '580e36986dd7c320d45984ae',
318 | 'id': '580e36986dd7c320d45984ae'
319 | }
320 | },
321 | {
322 | '_id': '5a808c5bbbd5dc179f47c890',
323 | 'title': '升级总结:phantomjs在Centos上的安装过程',
324 | 'alias': 'me7esu',
325 | 'excerpt': '在centos7上安装prerender-spa-plugin时遇到phantomjs的诸多问题导致npm run build 失败,经过不断尝试,得出解决方案;可作为webpack 预编译模块prerender-spa-plugin的安装参考',
326 | 'thumbnail': '//img.smohan.net/442e99750d2ca18f5876a3fa83f3a8a9.png',
327 | 'top': false,
328 | 'updateTime': '2018-03-18T14:34:33.734Z',
329 | 'createTime': '2018-02-11T17:57:23.269Z',
330 | 'copyright': {
331 | 'source': 'https://smohan.net',
332 | 'author': 'smohan',
333 | 'belong': 'original'
334 | },
335 | 'count': {
336 | 'downloads': 0,
337 | 'praises': 2,
338 | 'comments': 0,
339 | 'views': 230
340 | },
341 | 'tags': [
342 | 'centos',
343 | 'nodejs',
344 | 'linux',
345 | 'phantomjs',
346 | 'prerender-spa-plugin'
347 | ],
348 | 'category': {
349 | 'name': '且行且冥',
350 | 'path': '580e369f6dd7c320d45984af',
351 | 'id': '580e369f6dd7c320d45984af'
352 | }
353 | },
354 | {
355 | '_id': '599047704637b02c48ed4f5e',
356 | 'thumbnail': '//img.smohan.net/c74232fa0d2667cce77d73acf68ff918.jpg',
357 | 'excerpt': 'clip-path直译过来就是裁剪路径,使用SVG或形状定义一个HTML元素的可见区域的方法。clip-path属性代替了现在已经弃用的剪切 clip属性,是SVG clip-path属性的延伸',
358 | 'alias': 'eutcdc',
359 | 'title': '不可思议的CSS之clip-path',
360 | 'top': false,
361 | 'updateTime': '2018-04-08T15:02:40.723Z',
362 | 'createTime': '2017-08-13T06:22:33.846Z',
363 | 'copyright': {
364 | 'source': '//smohan.net/',
365 | 'author': 'smohan',
366 | 'belong': 'original'
367 | },
368 | 'count': {
369 | 'downloads': 0,
370 | 'praises': 3,
371 | 'comments': 0,
372 | 'views': 1150
373 | },
374 | 'tags': [
375 | 'css3',
376 | 'clip-path',
377 | 'clip',
378 | 'svg',
379 | 'scss'
380 | ],
381 | 'category': {
382 | 'name': '学习笔记',
383 | 'path': '580e36986dd7c320d45984ae',
384 | 'id': '580e36986dd7c320d45984ae'
385 | }
386 | },
387 | {
388 | '_id': '590d648ee43c6b4a6e71bc16',
389 | 'thumbnail': '//img.smohan.net/article/7c15c2b8ea5af0a0ac5f76f5374b3c94.jpg',
390 | 'excerpt': 'Javascript中的强制类型转换总是返回标量基本类型值(String, Boolean, Number, Undefined, Null)。直白点就是Object.toString()或者Object.valueOf()的返回值。',
391 | 'alias': 'a8kngd',
392 | 'title': 'JAVASCRIPT学习笔记之强制类型转换',
393 | 'top': false,
394 | 'updateTime': '2018-04-08T15:03:44.148Z',
395 | 'createTime': '2017-05-06T05:13:12.237Z',
396 | 'copyright': {
397 | 'source': '//smohan.net',
398 | 'author': 'smohan',
399 | 'belong': 'original'
400 | },
401 | 'count': {
402 | 'downloads': 0,
403 | 'praises': 7,
404 | 'comments': 4,
405 | 'views': 548
406 | },
407 | 'tags': [
408 | 'javascript',
409 | '类型转换',
410 | '==',
411 | '==='
412 | ],
413 | 'category': {
414 | 'name': '学习笔记',
415 | 'path': '580e36986dd7c320d45984ae',
416 | 'id': '580e36986dd7c320d45984ae'
417 | }
418 | },
419 | {
420 | '_id': '58bed88513cffc3af3a3dded',
421 | 'thumbnail': 'https://img.smohan.net/article/4b2344f9ab77bf4da1d48c2b2c6a0dd6.png',
422 | 'excerpt': '分页是WEB开发中很常用的功能,尤其是在各种前后端分离的今天,后端API返回数据,前端计算分页页码并渲染到页面上已经是一个很普通很常见的功能了。这里使用Vue2来实现一个数据分页的组件',
423 | 'alias': 'pgk1qr',
424 | 'title': 'Vue实现一个分页组件',
425 | 'top': false,
426 | 'updateTime': '2018-04-08T15:03:17.501Z',
427 | 'createTime': '2017-02-26T15:45:03.268Z',
428 | 'copyright': {
429 | 'author': 'smohan',
430 | 'source': '//smohan.net',
431 | 'belong': 'original'
432 | },
433 | 'count': {
434 | 'downloads': 0,
435 | 'praises': 14,
436 | 'comments': 12,
437 | 'views': 1208
438 | },
439 | 'tags': [
440 | 'vue',
441 | 'javascript',
442 | '分页'
443 | ],
444 | 'category': {
445 | 'name': '学习笔记',
446 | 'path': '580e36986dd7c320d45984ae',
447 | 'id': '580e36986dd7c320d45984ae'
448 | }
449 | },
450 | {
451 | '_id': '589f07d73e2b8708305cc7bf',
452 | 'thumbnail': '//img.smohan.net/article/3923ee9db046cbd1d93698ab0aacf651.jpg',
453 | 'excerpt': '各个角度讲解webpack2。如分割webpack配置文件的多种方法,开发环境下的自动刷新,环境变量的设置,打包文件分割,chunk type 块的类型大揭秘等',
454 | 'alias': 'bhcly1',
455 | 'title': '看懂前端脚手架你需要这篇webpack',
456 | 'top': false,
457 | 'updateTime': '2017-08-22T16:08:11.169Z',
458 | 'createTime': '2017-02-11T10:24:36.826Z',
459 | 'copyright': {
460 | 'source': 'https://gold.xitu.io/post/586ddb8ab123db005d0b65cb',
461 | 'author': '二口南洋',
462 | 'belong': 'reprint'
463 | },
464 | 'count': {
465 | 'downloads': 0,
466 | 'praises': 4,
467 | 'comments': 0,
468 | 'views': 393
469 | },
470 | 'tags': [
471 | 'javascript',
472 | 'webpack',
473 | 'npm',
474 | 'nodejs'
475 | ],
476 | 'category': {
477 | 'name': '他山之石',
478 | 'path': '580e36a66dd7c320d45984b0',
479 | 'id': '580e36a66dd7c320d45984b0'
480 | }
481 | },
482 | {
483 | '_id': '587f75da8111c622304cb750',
484 | 'thumbnail': '//img.smohan.net/article/93b1b5505ab2ae192b9544ec6c0f2c51.jpg',
485 | 'excerpt': '在mongoose中使用query.$or和query.$regex实现多条件模糊搜索功能',
486 | 'alias': 'qz1etc',
487 | 'title': 'Mongoose 多条件模糊查询的实现',
488 | 'top': false,
489 | 'updateTime': '2017-08-22T16:11:10.420Z',
490 | 'createTime': '2016-05-08T13:47:49.133Z',
491 | 'copyright': {
492 | 'source': '//smohan.net',
493 | 'author': 'smohan',
494 | 'belong': 'original'
495 | },
496 | 'count': {
497 | 'downloads': 0,
498 | 'praises': 1,
499 | 'comments': 3,
500 | 'views': 509
501 | },
502 | 'tags': [
503 | 'mongodb',
504 | 'mongoose',
505 | 'nodejs',
506 | 'npm',
507 | 'nginx'
508 | ],
509 | 'category': {
510 | 'name': '且行且冥',
511 | 'path': '580e369f6dd7c320d45984af',
512 | 'id': '580e369f6dd7c320d45984af'
513 | }
514 | },
515 | {
516 | '_id': '582313ae8da6d62554be0d5f',
517 | 'thumbnail': '//img.smohan.net/article/7c15c2b8ea5af0a0ac5f76f5374b3c94.jpg',
518 | 'excerpt': 'javascript学习笔记之正则表达式,了解正则表达式语法,在IDE中使用正则表达式,在javascript 中使用正则表达式处理字符串',
519 | 'alias': '3g3lh0',
520 | 'title': 'Javascript学习笔记之正则表达式',
521 | 'top': false,
522 | 'updateTime': '2017-08-22T16:13:17.493Z',
523 | 'createTime': '2016-11-09T12:15:19.582Z',
524 | 'copyright': {
525 | 'author': '水墨寒湘',
526 | 'source': 'https://gold.xitu.io/post/582dfcfda22b9d006b726d11',
527 | 'belong': 'reprint'
528 | },
529 | 'count': {
530 | 'downloads': 0,
531 | 'praises': 2,
532 | 'comments': 0,
533 | 'views': 491
534 | },
535 | 'tags': [
536 | 'javascript',
537 | 'regexp',
538 | '正则表达式'
539 | ],
540 | 'category': {
541 | 'name': '他山之石',
542 | 'path': '580e36a66dd7c320d45984b0',
543 | 'id': '580e36a66dd7c320d45984b0'
544 | }
545 | },
546 | {
547 | '_id': '581b7aeffab9b93648a982d3',
548 | 'thumbnail': '//img.smohan.net/article/7cebc2acc3c6d1897a4de5a42f2d1ab8.jpg',
549 | 'excerpt': '流光效果主要是利用css3的线性渐变(linear-gradient),2D转换(transform)以及倾斜(skew)配合hover来实现',
550 | 'alias': 'eame1m',
551 | 'title': 'CSS3实现京东图片鼠标滑过流光效果',
552 | 'top': false,
553 | 'updateTime': '2017-08-22T16:14:37.213Z',
554 | 'createTime': '2015-08-22T23:18:21.145Z',
555 | 'copyright': {
556 | 'author': 'smohan',
557 | 'source': '//smohan.net/',
558 | 'belong': 'original'
559 | },
560 | 'count': {
561 | 'downloads': 0,
562 | 'praises': 8,
563 | 'comments': 5,
564 | 'views': 3549
565 | },
566 | 'tags': [
567 | 'css3',
568 | 'linear-gradient',
569 | '渐变',
570 | 'transform'
571 | ],
572 | 'category': {
573 | 'name': '前端开发',
574 | 'path': '580e36616dd7c320d45984aa',
575 | 'id': '580e36616dd7c320d45984aa'
576 | }
577 | },
578 | {
579 | '_id': '581b7620fab9b93648a982d2',
580 | 'thumbnail': '//img.smohan.net/article/7c15c2b8ea5af0a0ac5f76f5374b3c94.jpg',
581 | 'excerpt': 'DOM 描绘了一个层次化的节点树。本文从获取节点的名称和类型、获取元素节点的方式、节点指针、节点的操作、DOM操作内容、DOM操作样式、DOM操作位置和大小、常用到的简洁快速的document属性和方法等几个方面总结了JavaScript DOM基础知识',
582 | 'alias': 'vhikuj',
583 | 'title': 'JavaScript学习笔记之Dom知识点总结',
584 | 'top': false,
585 | 'updateTime': '2017-08-22T16:15:17.000Z',
586 | 'createTime': '2015-05-11T23:18:25.145Z',
587 | 'copyright': {
588 | 'author': 'smohan',
589 | 'source': '//smohan.net/',
590 | 'belong': 'original'
591 | },
592 | 'count': {
593 | 'downloads': 0,
594 | 'praises': 3,
595 | 'comments': 0,
596 | 'views': 2928
597 | },
598 | 'tags': [
599 | 'dom',
600 | 'javascript'
601 | ],
602 | 'category': {
603 | 'name': '学习笔记',
604 | 'path': '580e36986dd7c320d45984ae',
605 | 'id': '580e36986dd7c320d45984ae'
606 | }
607 | },
608 | {
609 | '_id': '581b7541fab9b93648a982d1',
610 | 'thumbnail': '//img.smohan.net/article/7c15c2b8ea5af0a0ac5f76f5374b3c94.jpg',
611 | 'excerpt': '数组对象是使用单独的变量名来存储一系列的值;数组可以用一个变量名存储所有的值,并且可以用变量名访问任何一个值;数组中的每个元素都有自己的的ID索引,以便它可以很容易地被访问到;',
612 | 'alias': 'fwnuvr',
613 | 'title': 'JavaScript学习笔记之数组对象知识点总结',
614 | 'top': false,
615 | 'updateTime': '2017-08-22T16:15:42.459Z',
616 | 'createTime': '2015-05-07T17:01:13.145Z',
617 | 'copyright': {
618 | 'author': 'smohan',
619 | 'source': '//smohan.net/',
620 | 'belong': 'original'
621 | },
622 | 'count': {
623 | 'downloads': 0,
624 | 'praises': 2,
625 | 'comments': 0,
626 | 'views': 2137
627 | },
628 | 'tags': [
629 | 'javascript',
630 | 'es6',
631 | 'array'
632 | ],
633 | 'category': {
634 | 'name': '学习笔记',
635 | 'path': '580e36986dd7c320d45984ae',
636 | 'id': '580e36986dd7c320d45984ae'
637 | }
638 | },
639 | {
640 | '_id': '581b7200fab9b93648a982d0',
641 | 'thumbnail': '//img.smohan.net/article/587925f8462f8ca5386a09d89f96c022.jpg',
642 | 'excerpt': '通过微信JS-SDK提供的11类接口集,开发者不仅能够在网页上使用微信本身的拍照、选图、语音、位置等基本能力,还可以直接使用微信分享、扫一扫、卡券、支付等微信特有的能力,为微信用户提供更优质的网页体验。',
643 | 'alias': 'efg5ty',
644 | 'title': '微信公众平台开发 JS-SDK开发(图像接口实例)',
645 | 'top': false,
646 | 'updateTime': '2017-08-22T16:16:41.065Z',
647 | 'createTime': '2015-04-30T15:19:40.145Z',
648 | 'copyright': {
649 | 'author': 'smohan',
650 | 'source': '//smohan.net/',
651 | 'belong': 'original'
652 | },
653 | 'count': {
654 | 'downloads': 0,
655 | 'praises': 21,
656 | 'comments': 26,
657 | 'views': 36256
658 | },
659 | 'tags': [
660 | 'javascript',
661 | '微信',
662 | 'js-sdk'
663 | ],
664 | 'category': {
665 | 'name': '前端文档',
666 | 'path': '580e36616dd7c320d45984aa#580e368b6dd7c320d45984ad',
667 | 'id': '580e368b6dd7c320d45984ad'
668 | }
669 | },
670 | {
671 | '_id': '5815b5ebd6202610dcc1c8a0',
672 | 'thumbnail': '//img.smohan.net/article/d7d7e9d61343f7e900d8079246b5bd86.png',
673 | 'excerpt': '本文结合作者对网易与淘宝移动端首页html元素上的font-size这个属性的思考与学习,讨论html5设计稿尺寸以及前端与设计之间协作流程的问题,内容较多,但对你的技术和工作一定有价值。',
674 | 'alias': 'mggrai',
675 | 'title': '从网易与淘宝的font-size思考前端设计稿与工作流',
676 | 'top': false,
677 | 'updateTime': '2017-08-22T16:17:25.610Z',
678 | 'createTime': '2015-12-02T04:32:40.079Z',
679 | 'copyright': {
680 | 'source': 'http://www.cnblogs.com/lyzg/p/4877277.html',
681 | 'author': '流云诸葛',
682 | 'belong': 'reprint'
683 | },
684 | 'count': {
685 | 'downloads': 0,
686 | 'praises': 1,
687 | 'comments': 7,
688 | 'views': 1979
689 | },
690 | 'tags': [
691 | 'font-size',
692 | '响应式',
693 | '淘宝',
694 | '网易',
695 | 'css3'
696 | ],
697 | 'category': {
698 | 'name': '他山之石',
699 | 'path': '580e36a66dd7c320d45984b0',
700 | 'id': '580e36a66dd7c320d45984b0'
701 | }
702 | },
703 | {
704 | '_id': '5814f4206a434d26c0dc0d3d',
705 | 'thumbnail': '//img.smohan.net/article/6e318baa3070d35a721226dd914787a6.jpg',
706 | 'excerpt': '新浪微博开放平台(http://open.weibo.com/)提供了大量的接口API,如粉丝,微博,评论,用户读取等26个接口。Smohan的博客很多地方都用到这些接口,如留言板中的地理位置,博客右侧的新浪用户卡片……',
707 | 'alias': 'iqdfmw',
708 | 'title': '利用新浪微博接口生成漂亮的微博卡片',
709 | 'top': false,
710 | 'updateTime': '2017-08-22T16:18:04.398Z',
711 | 'createTime': '2015-07-04T18:35:59.620Z',
712 | 'copyright': {
713 | 'author': 'smohan',
714 | 'source': '//smohan.net/',
715 | 'belong': 'original'
716 | },
717 | 'count': {
718 | 'downloads': 0,
719 | 'praises': 7,
720 | 'comments': 5,
721 | 'views': 5768
722 | },
723 | 'tags': [
724 | 'css3',
725 | 'javascript',
726 | '微博',
727 | 'api'
728 | ],
729 | 'category': {
730 | 'name': '前端开发',
731 | 'path': '580e36616dd7c320d45984aa',
732 | 'id': '580e36616dd7c320d45984aa'
733 | }
734 | },
735 | {
736 | '_id': '581232a37ccec437f87ad8c3',
737 | 'thumbnail': '//img.smohan.net/article/37ccd1bbf247121b9229e13c3dc00ef8.jpg',
738 | 'excerpt': '利用CSS3的一些属性,如animate、translate、transform等,以及Html5 Audio属性配合JS实现一个简单的CSS3播放器,为你的博客添加一个简洁而又活波的CSS3音乐播放器',
739 | 'alias': 'tfw78q',
740 | 'title': '为你的博客添加简单的CSS3音乐播放器',
741 | 'top': false,
742 | 'updateTime': '2017-08-22T16:18:34.517Z',
743 | 'createTime': '2015-04-06T16:41:36.140Z',
744 | 'copyright': {
745 | 'author': 'smohan',
746 | 'source': '//smohan.net/',
747 | 'belong': 'original'
748 | },
749 | 'count': {
750 | 'downloads': 0,
751 | 'praises': 13,
752 | 'comments': 3,
753 | 'views': 11931
754 | },
755 | 'tags': [
756 | 'css3',
757 | 'audio',
758 | 'smusic',
759 | 'javascript'
760 | ],
761 | 'category': {
762 | 'name': '前端分享',
763 | 'path': '580e36616dd7c320d45984aa#580e36836dd7c320d45984ac',
764 | 'id': '580e36836dd7c320d45984ac'
765 | }
766 | },
767 | {
768 | '_id': '58122f9c7ccec437f87ad8c2',
769 | 'thumbnail': '//img.smohan.net/article/5aabd44a04fff4a0b75420c43e130434.jpg',
770 | 'excerpt': '经常会遇到一些可编辑文本内容中显示红色下划波浪线,类似于word文档中错误单词的拼写检查,使得该标签内容很不美观,那么如何关闭textarea等可编辑文本字段的拼写检查呢?我们可以通过一个HTML 5 全局属性spellcheck来解决禁用拼写检查的功能',
771 | 'alias': 'srnyl4',
772 | 'title': '使用spellcheck属性禁用输入框拼写检查',
773 | 'top': false,
774 | 'updateTime': '2017-08-22T16:19:31.447Z',
775 | 'createTime': '2015-01-25T16:41:36.140Z',
776 | 'copyright': {
777 | 'author': 'smohan',
778 | 'source': '//smohan.net/',
779 | 'belong': 'original'
780 | },
781 | 'count': {
782 | 'downloads': 0,
783 | 'praises': 5,
784 | 'comments': 1,
785 | 'views': 3378
786 | },
787 | 'tags': [
788 | 'html5',
789 | 'spellcheck',
790 | '拼写检查'
791 | ],
792 | 'category': {
793 | 'name': '前端开发',
794 | 'path': '580e36616dd7c320d45984aa',
795 | 'id': '580e36616dd7c320d45984aa'
796 | }
797 | },
798 | {
799 | '_id': '58122dafd5a20d37400b0e79',
800 | 'thumbnail': '//img.smohan.net/article/626bf6defa53ec230dcd3dc480af43eb.jpg',
801 | 'excerpt': '没有感情终归是假的,继续呆着又不免矫情,怎么说呢,一切都好,只是不适合我,我还年轻,还有一些想法没有实现,还有很多明知不可为而为之的梦想要去追寻,我可不想把太多的时间浪费在扯淡的IE6、7、8上',
802 | 'alias': 'bmjclw',
803 | 'title': '从这几个月的一点感悟谈改版',
804 | 'top': false,
805 | 'updateTime': '2016-10-30T10:18:46.607Z',
806 | 'createTime': '2014-07-24T16:22:22.652Z',
807 | 'copyright': {
808 | 'author': 'smohan',
809 | 'source': '//smohan.net/',
810 | 'belong': 'original'
811 | },
812 | 'count': {
813 | 'downloads': 0,
814 | 'praises': 2,
815 | 'comments': 7,
816 | 'views': 4969
817 | },
818 | 'tags': [
819 | '改版',
820 | '前端',
821 | '离职',
822 | '成都'
823 | ],
824 | 'category': {
825 | 'name': '且行且冥',
826 | 'path': '580e369f6dd7c320d45984af',
827 | 'id': '580e369f6dd7c320d45984af'
828 | }
829 | }
830 | ]
831 |
--------------------------------------------------------------------------------
/example/full.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Query Example
8 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 | id |
141 | 标题 |
142 | 作者 |
143 | 标签 |
144 | 阅读数 |
145 | 创建时间 |
146 |
147 |
148 |
149 | |
150 |
151 |
152 |
156 |
157 |
158 |
168 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
--------------------------------------------------------------------------------
/example/full.js:
--------------------------------------------------------------------------------
1 | var sourceData = window.postsData
2 |
3 |
4 | var $keyword = document.getElementById('keyword')
5 | var $searchBtn = document.getElementById('search')
6 | var $list = document.getElementById('list')
7 | var $sort = document.getElementById('sort')
8 | var $pagination = document.getElementById('pagination')
9 | var $paginationInfo = document.getElementById('paginationInfo')
10 | var $listTPL = document.getElementById('tpl-list')
11 | var $pageTPL = document.getElementById('tpl-page')
12 |
13 | var params = {
14 | page: 1,
15 | limit: 5,
16 | sort: ''
17 | }
18 |
19 |
20 | var quertInstance = new Query(sourceData)
21 |
22 | /**
23 | * 简单模板渲染
24 | * @param {Element} $tpl
25 | * @param {Object} data
26 | * @returns {String}
27 | */
28 | function render($tpl, data) {
29 | return $tpl.innerHTML.replace(/{(\w+)}/g, function (a, b) {
30 | return data[b]
31 | })
32 | }
33 |
34 |
35 | function renderList (list) {
36 | $list.innerHTML = ''
37 | if (list && list.length) {
38 | list.forEach(item => {
39 | $list.innerHTML += render($listTPL, {
40 | id: item._id,
41 | title: item.title,
42 | author: item.copyright.author,
43 | tags: item.tags,
44 | views: item.count.views,
45 | createTime: item.createTime.replace(/T/g, ' ').replace(/\.\d{3}Z/, '')
46 | })
47 | })
48 | } else {
49 | $list.innerHTML = '暂无记录 |
'
50 | }
51 | }
52 |
53 | // 渲染分页
54 | function renderPager (pages, count) {
55 | if (count === 0) {
56 | $pagination.style.display = 'none';
57 | $paginationInfo.style.display = 'none';
58 | } else {
59 | $pagination.style.display = 'block';
60 | $paginationInfo.style.display = 'block';
61 | }
62 | $pagination.innerHTML = ''
63 | for (var i = 1; i <= pages; i++) {
64 | $pagination.innerHTML += render($pageTPL, {
65 | page: i,
66 | className: i === params.page ? 'active' : ''
67 | })
68 | }
69 | $paginationInfo.innerHTML = '共 ' + count + ' 条记录,当前第 ' + params.page + ' / ' + pages + ' 页'
70 | }
71 |
72 |
73 | function getList(page) {
74 | if (page) {
75 | params.page = page
76 | }
77 | var query = quertInstance.reset()
78 | var kewyord = $keyword.value.trim()
79 | if (kewyord) {
80 | query.where([
81 | ['title', 'like', kewyord],
82 | ['tags', 'like', kewyord, 'or'],
83 | ['copyright.author', 'like', kewyord, 'or']
84 | ])
85 | }
86 | if (params.sort) {
87 | query.sort(params.sort, 'desc')
88 | }
89 | var count = query.count()
90 | var pages = Math.ceil(count / params.limit)
91 | if (params.page > pages) {
92 | params.page = 1
93 | }
94 | query
95 | .skip(params.limit * (params.page - 1))
96 | .limit(params.limit)
97 |
98 | var list = query.find()
99 | renderList(list)
100 | renderPager(pages, count)
101 | }
102 |
103 | getList()
104 |
105 | $pagination.addEventListener('click', function (e) {
106 | e = e || window.e
107 | if (e.target.nodeName === 'LI') {
108 | e.stopPropagation()
109 | var page = e.target.getAttribute('data-page')
110 | page = Number(page)
111 | if (page !== params.page) {
112 | getList(page)
113 | }
114 | }
115 | })
116 |
117 |
118 | $sort.addEventListener('click', function (e) {
119 | e = e || window.e
120 | if (e.target.nodeName === 'BUTTON') {
121 | e.stopPropagation()
122 | var sort = e.target.getAttribute('data-value')
123 | if (sort !== params.sort) {
124 | params.sort = sort
125 | Array.prototype.slice.call($sort.querySelectorAll('button')).forEach(function(button) {
126 | if (button === e.target) {
127 | button.classList.add('active')
128 | } else {
129 | button.classList.remove('active')
130 | }
131 | })
132 | getList()
133 | }
134 | }
135 | })
136 |
137 |
138 | $searchBtn.addEventListener('click', function() {
139 | getList(1)
140 | })
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Query Example
9 |
121 |
122 |
123 |
124 |
125 |
126 |
132 |
136 |
198 |
199 |
200 |
201 |
214 |
215 |
216 |
217 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
--------------------------------------------------------------------------------
/example/index.js:
--------------------------------------------------------------------------------
1 |
2 | var sourceData = window.postsData
3 | var dataLength = sourceData.length
4 |
5 | var $search = document.getElementById('search')
6 | var $reset = document.getElementById('reset')
7 | var $code = document.getElementById('code')
8 | var $result = document.getElementById('result')
9 |
10 |
11 | var $where = document.getElementsByName('where')
12 | var $sort = document.getElementsByName('sort')
13 | var $skip = document.getElementsByName('skip')
14 | var $limit = document.getElementsByName('limit')
15 | var $group = document.getElementsByName('group')
16 |
17 | var $tplPost = document.getElementById('tpl-post')
18 | var $tplGroup = document.getElementById('tpl-group')
19 |
20 | // 实例化句柄
21 | var quertInstance = new Query(sourceData)
22 |
23 | // let res = quertInstance
24 | // .where('count.comments', 'gt', 0)
25 | // .where([
26 | // ['title', 'like', 'javascript'],
27 | // ['tags', 'like', 'javascript', 'or'],
28 | // ])
29 | // .find()
30 | // console.log(res)
31 |
32 |
33 | function init() {
34 | $skip[0].value = 0
35 | $skip[0].max = dataLength
36 | $limit[0].value = dataLength
37 | $search.click()
38 | }
39 |
40 |
41 | var isGroup = false
42 |
43 |
44 | /**
45 | * 拼接sql
46 | * @returns {String}
47 | */
48 | function getQuery() {
49 | var queries = Object.create(null)
50 |
51 | $where.forEach(function (where) {
52 | if (where.checked) {
53 | if (!queries.where) {
54 | queries.where = []
55 | }
56 | queries.where.push(where.value)
57 | }
58 | })
59 |
60 | $sort.forEach(function (sort) {
61 | if (sort.checked) {
62 | queries.sort = sort.value
63 | return false
64 | }
65 | })
66 |
67 | if ($skip[0].value >= 0) {
68 | queries.skip = +$skip[0].value
69 | }
70 |
71 | if ($limit[0].value >= 1) {
72 | queries.limit = +$limit[0].value
73 | }
74 |
75 |
76 | $group.forEach(function (group) {
77 | if (group.checked) {
78 | queries.group = group.value
79 | return false
80 | }
81 | })
82 |
83 | var queryString = ''
84 |
85 | if (queries.where && queries.where.length) {
86 | queryString += queries.where.join('')
87 | }
88 | if (queries.group) {
89 | isGroup = true
90 | queryString += queries.group
91 | } else {
92 | isGroup = false
93 | }
94 | if (queries.sort) {
95 | queryString += queries.sort
96 | }
97 | if (queries.skip >= 0) {
98 | queryString += '.skip(' + queries.skip + ')'
99 | }
100 | if (queries.limit >= 1) {
101 | queryString += '.limit(' + queries.limit + ')'
102 | }
103 | return queryString
104 | }
105 |
106 |
107 | /**
108 | * 简单模板渲染
109 | * @param {Element} $tpl
110 | * @param {Object} data
111 | * @returns {String}
112 | */
113 | function render($tpl, data) {
114 | return $tpl.innerHTML.replace(/{(\w+)}/g, function (a, b) {
115 | return data[b]
116 | })
117 | }
118 |
119 |
120 | /**
121 | * 执行SQL
122 | * @param {String} sql
123 | */
124 | function exec(sql) {
125 | try {
126 | var res = new Function('return ' + sql)()
127 | $result.innerHTML = ''
128 | if (!res || !res.length) {
129 | $result.innerHTML = 'no records'
130 | return
131 | }
132 | res.forEach(function (item) {
133 | if (!isGroup) {
134 | $result.innerHTML += render($tplPost, {
135 | title: item.title,
136 | createTime: item.createTime,
137 | author: item.copyright.author,
138 | comments: item.count.comments,
139 | tags: item.tags.join(',')
140 | })
141 | } else {
142 | $result.innerHTML += render($tplGroup, {
143 | id: item._id,
144 | count: item.count
145 | })
146 | }
147 | })
148 | } catch (e) {
149 | console.log(e)
150 | }
151 | }
152 |
153 |
154 | $search.addEventListener('click', function () {
155 | var queries = getQuery()
156 | var sql = 'quertInstance.reset()' + queries + '.find()'
157 | $code.innerHTML = sql.replace(/\)\./gm, ')\n.')
158 | exec(sql)
159 | })
160 | //
161 |
162 | $reset.addEventListener('click', function () {
163 | setTimeout(init, 0)
164 | })
165 |
166 |
167 |
168 | init()
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "query",
3 | "version": "1.0.2",
4 | "description": "SQL like data query",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack",
9 | "lint": "eslint --ext .js src/",
10 | "lint-fix": "eslint --fix --ext .js src/"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/S-mohan/query.git"
15 | },
16 | "keywords": [
17 | "sql",
18 | "query",
19 | "data"
20 | ],
21 | "author": "smohan ",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/S-mohan/query/issues"
25 | },
26 | "homepage": "https://github.com/S-mohan/query#readme",
27 | "devDependencies": {
28 | "babel-core": "^6.26.3",
29 | "babel-loader": "^7.1.4",
30 | "babel-preset-env": "^1.6.1",
31 | "babel-preset-stage-2": "^6.24.1",
32 | "eslint": "^4.19.1",
33 | "eslint-config-standard": "^12.0.0-alpha.0",
34 | "eslint-plugin-html": "^4.0.3",
35 | "eslint-plugin-import": "^2.11.0",
36 | "eslint-plugin-node": "^6.0.1",
37 | "eslint-plugin-promise": "^3.7.0",
38 | "eslint-plugin-standard": "^3.1.0",
39 | "string-replace-loader": "^2.1.1",
40 | "webpack": "^4.8.1",
41 | "webpack-cli": "^2.1.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/hooks.js:
--------------------------------------------------------------------------------
1 | import _ from './utils'
2 |
3 | /**
4 | * 将一个日期(字符串)转换为真实日期
5 | * @param {Any} date
6 | * @return {Date}
7 | */
8 | const parseDate = date => {
9 | // Date Object
10 | if (date instanceof Date) {
11 | return date
12 | }
13 | // Number
14 | if (/^\d+$/.test(date)) {
15 | return new Date(parseInt(date, 10))
16 | }
17 | // String
18 | date = (date || '').trim()
19 | .replace(/\.\d+/, '') // remove milliseconds
20 | .replace(/-/g, '/') // fix safair
21 | .replace(/T/, ' ')
22 | .replace(/Z/, ' UTC')
23 | /* eslint-disable */
24 | .replace(/([\+\-]\d\d)\:?(\d\d)/, ' $1$2') //fix time
25 | return new Date(date)
26 | }
27 |
28 |
29 | /**
30 | * 将一个日期转换为一个日期对象
31 | * @param {Date} date
32 | * @returns {Object}
33 | */
34 | const getDateMap = date => {
35 | date = parseDate(date)
36 | return {
37 | y: date.getFullYear(),
38 | M: date.getMonth() + 1,
39 | d: date.getDate(),
40 | h: date.getHours(),
41 | m: date.getMinutes(),
42 | s: date.getSeconds(),
43 | q: Math.floor((date.getMonth() + 3) / 3) // 季度
44 | }
45 | }
46 |
47 |
48 | /**
49 | * 格式化日期
50 | * @param {Date} date
51 | * @param {String} format
52 | * @return {String}
53 | */
54 | const formatDate = (date, format) => {
55 | date = date || new Date()
56 | format = format || 'yy-MM-dd hh:mm:ss'
57 | const map = getDateMap(date)
58 | format = format.replace(/([yMdhmsqS])+/g, (all, t) => {
59 | let v = map[t]
60 | if (v !== void 0) {
61 | v = v.toString()
62 | if (t !== 'y' && all.length > 1) {
63 | v = '0' + v
64 | v = v.substr(v.length - 2)
65 | } else if (t === 'y' && all.length === 1) {
66 | v = v.substr(2)
67 | }
68 | }
69 | return v
70 | })
71 |
72 | return format
73 | }
74 |
75 |
76 | /**
77 | * 转换成数字
78 | * @param {String} value
79 | * @returns {Number}
80 | */
81 | const toNumber = value => {
82 | if (_.isNumber(value)) {
83 | return value
84 | }
85 | return +value
86 | }
87 |
88 |
89 | /**
90 | * 转换为整数
91 | * @param {String} value
92 | * @returns {Number}
93 | */
94 | const toInteger = value => {
95 | let _value = toNumber(value)
96 | return isNaN(_value) ? value : parseInt(value, 10)
97 | }
98 |
99 |
100 | /**
101 | * 将空字符串,null, undefined 等转换为 0
102 | * @param {any} value
103 | * @returns {0 | any}
104 | */
105 | const toZero = value => {
106 | if (!value) {
107 | return 0
108 | }
109 | return value
110 | }
111 |
112 |
113 | /**
114 | * 将空字符串,null, undefined 等转换为 false,
115 | * 其他值转换为TRUE
116 | * @param {any} value
117 | * @returns {Boolean}
118 | */
119 | const toBoolean = value => {
120 | if (!value) {
121 | return false
122 | }
123 | return true
124 | }
125 |
126 |
127 | /**
128 | * 转换为字符串
129 | * @param {any} value
130 | * @returns {String}
131 | */
132 | const toString = value => {
133 | if (_.isString(value)) {
134 | return value
135 | }
136 | return value + ''
137 | }
138 |
139 |
140 | /**
141 | * 转换为小写
142 | * @param {String} value
143 | * @returns {String}
144 | */
145 | const toLower = value => toString(value).toLocaleLowerCase()
146 |
147 |
148 | /**
149 | * 转换为大写
150 | * @param {String} value
151 | * @returns {String}
152 | */
153 | const toUpper = value => toString(value).toLocaleUpperCase()
154 |
155 |
156 |
157 | export default {
158 | date: formatDate,
159 | number: toNumber,
160 | int: toInteger,
161 | zero: toZero,
162 | boolean: toBoolean,
163 | string: toString,
164 | lower: toLower,
165 | upper: toUpper
166 | }
167 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default } from './query'
2 |
--------------------------------------------------------------------------------
/src/query.js:
--------------------------------------------------------------------------------
1 | import _ from './utils'
2 | import hooks from './hooks'
3 |
4 | // 查询表达式
5 | const EXPRESSIONS = ['eq', '=', 'neq', '<>', 'gt', '>', 'gte', '>=', 'lt', '<', 'lte', '<=', 'like', 'in', 'nin', 'exists']
6 |
7 | // 排序
8 | const SORTS = ['asc', 'desc']
9 |
10 | const RELATIONS = ['and', 'or']
11 |
12 | // 内置格式化钩子函数
13 | const BUILT_IN_HOOKS = Object.keys(hooks)
14 |
15 | // 格式化钩子函数字典(私有)
16 | const FORMATS_HOOKS = Object.create(null)
17 |
18 | // 内置钩子函数初始化
19 | BUILT_IN_HOOKS.forEach(hook => {
20 | FORMATS_HOOKS[hook] = function (value) {
21 | return _.apply(hooks[hook], value, ...(_.toArray(arguments)))
22 | }
23 | })
24 |
25 |
26 | /**
27 | * 对象拷贝
28 | * @param {Object} obj
29 | * @returns {Object}
30 | */
31 | const clone = obj => JSON.parse(JSON.stringify(obj))
32 |
33 |
34 | // 默认可配置项
35 | const QUERY_DEFAULTS = {
36 | // 索引
37 | index: [],
38 | // 用于多个group后 key值得分割符
39 | // eg. smohan$smohan@163.com: []
40 | groupKeySeparator: '$',
41 | // 基于老字段生成的新字段前缀
42 | newFieldNamePrefix: '$'
43 | }
44 |
45 |
46 | /**
47 | * @constructor
48 | * 源数据
49 | * @param {Array} data
50 | * 可选配置
51 | * @param {Object} options
52 | * @returns {Object}
53 | */
54 | function Query (data, options) {
55 | if (!(this instanceof Query)) {
56 | return new Query(data, options)
57 | }
58 | if (!_.isArray(data)) {
59 | log('data must be an array')
60 | data = []
61 | }
62 | // source data
63 | this.source = data
64 | // options
65 | this.options = Object.assign(Object.create(null), QUERY_DEFAULTS, options)
66 | this.reset()
67 | }
68 |
69 |
70 | // version
71 | Query.version = '__VERSION__'
72 |
73 |
74 | /**
75 | * register hooks
76 | * @param {String} name
77 | * @param {function} handler
78 | */
79 | Query.hooks = function (name, handler) {
80 | if (!!~BUILT_IN_HOOKS.indexOf(name) === false && _.isFunction(handler)) {
81 | FORMATS_HOOKS[name] = function (value) {
82 | return _.apply(handler, value, ...(_.toArray(arguments)))
83 | }
84 | }
85 | }
86 |
87 |
88 | // prototype
89 | const QP = Query.prototype
90 |
91 |
92 | // 格式化默认配置
93 | const FORMATS_DEFAULTS = {
94 | // 传递给钩子函数的剩余参数
95 | // 参数如果是个数组,将会展开每一项依次作为参数传递
96 | // eg.
97 | // args: [1, [1, 2], true] => func(value, 1, [1, 2], true)
98 | // args: 'smohan' => func(value, 'smohan')
99 | args: null,
100 | // 是否生成新字段
101 | // 该值如果是字符串,将会作为新字段的名称
102 | // new: true => $name
103 | // new: 'newName' => $newName
104 | new: false,
105 |
106 | // 私有格式化钩子
107 | handler: null
108 | }
109 |
110 |
111 | /**
112 | * 字段格式化
113 | * @public
114 | * @param {String} field
115 | * @param {String} type
116 | * @param {Object} options
117 | */
118 | QP.to = QP.format = function (field, type, options) {
119 | options = Object.assign(Object.create(null), FORMATS_DEFAULTS, options)
120 | let handler
121 | // 如果自己提供了钩子函数
122 | if (options.handler && _.isFunction(options.handler)) {
123 | handler = options.handler
124 | } else if (_.hasKey(type, FORMATS_HOOKS)) {
125 | handler = FORMATS_HOOKS[type]
126 | }
127 | if (handler) {
128 | let args = []
129 | if (!_.isUndefined(options.args)) {
130 | if (_.isArray(options.args)) {
131 | options.args.forEach(arg => args.push(arg))
132 | } else {
133 | args.push(options.args)
134 | }
135 | }
136 | this.params.format[field] = {
137 | args,
138 | handler,
139 | new: options.new
140 | }
141 | }
142 | return this
143 | }
144 |
145 |
146 | /**
147 | * 在一个区间内取值
148 | * @public
149 | * @param {Number} start
150 | * @param {Number} end
151 | */
152 | QP.range = function (start, end) {
153 | // todo
154 | if (_.isInteger(start) && start >= 0) {
155 | this.params.range[0] = start
156 | }
157 |
158 | if (_.isInteger(end) && end >= 0) {
159 | this.params.range[1] = end
160 | }
161 |
162 | return this
163 | }
164 |
165 |
166 | /**
167 | * 取值起始位置
168 | * @public
169 | * @param {Number} skip
170 | */
171 | QP.skip = function (skip) {
172 | this.params.skip = (_.isInteger(skip) && skip > 0) ? skip : 0
173 | return this
174 | }
175 |
176 |
177 | /**
178 | * 每次取值个数
179 | * @public
180 | * @param {Number} limit
181 | */
182 | QP.limit = function (limit) {
183 | this.params.limit = _.isInteger(limit) ? Math.abs(limit) : void 0
184 | return this
185 | }
186 |
187 |
188 | /**
189 | * where
190 | * @public
191 | * @param {String} field
192 | * @param {String} expression
193 | * @param {String | Function} condition
194 | * @param {String} relation {and(default) | or}
195 | * @example
196 | *
197 | * 1. 普通
198 | * author === 'smohan'
199 | * .where('author', 'eq', 'smohan')
200 | *
201 | * 2. or
202 | * author === 'smohan' || 'count.comments > 0'
203 | * .where('author', 'eq', 'smohan')
204 | * .where('count.comments', 'gt', 0, 'or')
205 | *
206 | * 3. 条件分组, 使用数组把多个条件分组,相当于把一组表达式用括号括起来作为一个整体
207 | * author === 'smohan' && (title like 'javascript' || tags like 'javascript') && 'count.comments > 0'
208 | * .where('author', 'eq', 'smohan')
209 | * .where([['title', 'like', 'javascript'], ['tags', 'like', 'javascript', 'or'] ])
210 | * .where('count.comments', 'gt', 0)
211 | *
212 | * 4. 函数表达式
213 | * .where('author', 'eq', function (value) {
214 | * return value === 'smohan' || value === '水墨寒'
215 | * })
216 | * .where('count.comments', 'eq', function (value) {
217 | * return value > 3 && value < 10
218 | * }, 'or')
219 | *
220 | * ...
221 | */
222 | QP.where = function (field, expression, condition, relation) {
223 | // 如果是数组,其他参数将会被忽略
224 | if (_.isArray(arguments[0])) {
225 | // [[field, expression, condition, relation], [field, expression, condition, relation], ...]
226 | let len = arguments[0].length
227 | let i = -1
228 | let whereGroups = []
229 | while (++i < len) {
230 | let where = arguments[0][i]
231 | if (!_.isArray(where)) {
232 | continue
233 | }
234 | let query = _adapterWhere.call(this, where[0], where[1], where[2], where[3])
235 | if (query) {
236 | whereGroups.push(query)
237 | }
238 | }
239 | if (whereGroups.length) {
240 | this.params.query.push(whereGroups)
241 | }
242 | } else {
243 | let query = _adapterWhere.call(this, field, expression, condition, relation)
244 | if (query) {
245 | this.params.query.push(query)
246 | }
247 | }
248 | return this
249 | }
250 |
251 |
252 | /**
253 | * 排序
254 | * @public
255 | * @param {String | Object} field
256 | * @param {String | void} type
257 | * @example
258 | * single
259 | * 可以保证优先级
260 | * query.sort('create_time', 'asc')
261 | *
262 | * multiple
263 | * 优先级无法保证
264 | * query.sort({
265 | * create_time: 'desc',
266 | * id: 'desc',
267 | * name: 'asc'
268 | * })
269 | */
270 | QP.sort = function (field, type) {
271 | let sorts = Object.create(null)
272 | if (arguments.length === 1) {
273 | if (_.isPlainObject(field)) {
274 | sorts = field
275 | } else {
276 | sorts[field] = 'desc'
277 | }
278 | } else {
279 | sorts[field] = type
280 | }
281 |
282 | for (let k in sorts) {
283 | let sortType = sorts[k].toLocaleLowerCase()
284 | if (_.isString(k) && !!~SORTS.indexOf(sortType)) {
285 | _addSort.call(this, k, sortType)
286 | }
287 | }
288 |
289 | return this
290 | }
291 |
292 |
293 | /**
294 | * 获取一个匹配结果集的数量
295 | * @public
296 | * @returns {Number}
297 | * @example
298 | * let query = new Query(data)
299 | *
300 | * query
301 | * .where('name', 'eq', 'smohan')
302 | * .count()
303 | */
304 | QP.count = function () {
305 | _query.call(this)
306 | return this.target.length
307 | }
308 |
309 |
310 | /**
311 | * 获取一个匹配结果集的集合
312 | * @public
313 | * @returns {Array}
314 | * @example
315 | * let query = new Query(data)
316 | *
317 | * query.where('name', 'eq', 'smohan')
318 | * .skip(5)
319 | * .limit(10)
320 | * .find()
321 | */
322 | QP.find = function () {
323 | _query.call(this)
324 | let result = this.target
325 | // sort
326 | let sorts = this.params.sort.length
327 | let { skip, limit } = this.params
328 |
329 | if (sorts) {
330 | _parseSort.call(this, result)
331 | }
332 |
333 | // 通过skip和limit计算起止截取位置
334 | let size = result.length
335 |
336 | if (size === 0) {
337 | this.target = []
338 | return
339 | }
340 |
341 | let start = skip
342 | let end
343 | if (start === void 0) {
344 | start = 0
345 | }
346 |
347 | // 这地方应该是size而不是size-1, 因为起始值一旦超过总数,就应该返回空
348 | start = Math.min(start, size)
349 |
350 | if (limit === void 0) {
351 | end = size
352 | } else {
353 | end = start + limit
354 | }
355 |
356 | end = Math.min(end, size)
357 |
358 | result = result.slice(start, end)
359 |
360 | // 将当前target指向查询结果,
361 | // 下次查询如果不经过reset方法,将会在该结果集中继续查询
362 | return result
363 | }
364 |
365 |
366 | /**
367 | * 分组
368 | * @public
369 | * @param {String} field
370 | */
371 | QP.group = function (field) {
372 | if (!!~this.params.group.indexOf(field) === false) {
373 | this.params.group.push(field)
374 | }
375 | return this
376 | }
377 |
378 |
379 | /**
380 | * 重置target和查询条件
381 | * 就是数据恢复到初始化状态
382 | * 可以从头开始操作源数据
383 | * @public
384 | * let query = new Query(data)
385 | *
386 | * let target = query.where('name', 'eq', 'smohan')
387 | * .skip(5)
388 | * .limit(10)
389 | * .find()
390 | *
391 | * query
392 | * .reset()
393 | * .where('name','like', 'smohan')
394 | * ...
395 | */
396 | QP.reset = function () {
397 | const { source } = this
398 | this.target = clone(source)
399 | // params
400 | this.params = Object.create(null)
401 | // 查询条件
402 | this.params.query = []
403 | // sort涉及到规则的先后顺序,因此使用数组存储
404 | this.params.sort = []
405 | // 需要输出的字段
406 | this.params.field = Object.create(null)
407 | // group 字段
408 | this.params.group = []
409 | // 格式化 字段
410 | this.params.format = Object.create(null)
411 | // range
412 | this.params.range = []
413 | // unlock
414 | this.queried = false
415 | this.indexes = Object.create(null)
416 |
417 | // 创建索引
418 | // todo 还没想好索引怎么处理
419 | // if (options.index && options.index.length) {
420 | // let i = -1
421 | // let len = this.target.length
422 |
423 | // while (++i < len) {
424 | // let item = this.target[i]
425 | // // 仅对第一级字段做索引,深层次不做
426 | // for (let k in item) {
427 | // if (!!~options.index.indexOf(k) === false) {
428 | // continue
429 | // }
430 | // if (!this.indexes[k]) {
431 | // this.indexes[k] = Object.create(null)
432 | // }
433 | // this.indexes[k][item[k]] = i
434 | // }
435 | // }
436 | // }
437 |
438 | return this
439 | }
440 |
441 |
442 | /**
443 | * destroy
444 | * @public
445 | */
446 | QP.destroy = function () {
447 | this.target = null
448 | this.params = null
449 | }
450 |
451 |
452 | /**
453 | * where参数适配器
454 | * 返回一个可用的where语句
455 | * @private
456 | * @param {String} field
457 | * @param {String} expression
458 | * @param {String | Function | RegExp} condition
459 | * @param {String} relation
460 | * @returns {Object | undefined}
461 | */
462 | function _adapterWhere (field, expression, condition, relation) {
463 | if (!_.isString(field) || _.isEmpty(field) || !!~EXPRESSIONS.indexOf(expression) === false) {
464 | return
465 | }
466 |
467 | // 如果condition是个正则表达式,则将它包装成函数
468 | if (condition && _.isRegexp(condition)) {
469 | let reg = condition
470 | condition = function (value) {
471 | return !!reg.test(value)
472 | }
473 | }
474 |
475 | // 如果condition是个函数的话,自动将expression转换成等号表达式
476 | if (_.isFunction(condition)) {
477 | expression = 'eq'
478 | }
479 |
480 | relation = ~RELATIONS.indexOf(relation) ? relation : 'and'
481 | const query = {
482 | _f: field,
483 | _c: condition,
484 | _e: expression,
485 | _r: relation
486 | }
487 | const queries = JSON.stringify(this.params.query)
488 | if (!~queries.indexOf(JSON.stringify(query))) {
489 | return query
490 | }
491 | }
492 |
493 |
494 | /**
495 | * 解析where语句
496 | * 返回传入数据是否匹配where规则的结果
497 | * @private
498 | * @param {Object} data
499 | * @returns {Boolean}
500 | */
501 | function _parseWhere (data) {
502 | let { query: queries } = this.params
503 | let len = queries.length
504 | if (len === 0) {
505 | return true
506 | }
507 | let result = true
508 | let where
509 | let i = 0
510 | for (; i < len; i++) {
511 | where = queries[i]
512 | let whereGroup
513 | // 将每一条where语句组装成一个where group
514 | if (!_.isArray(where)) {
515 | whereGroup = [where]
516 | } else {
517 | whereGroup = where
518 | }
519 | let res = getWhereGroupResult(whereGroup, data)
520 | let rel = whereGroup[0]._r
521 | // 上一个group的结果跟当前结果的逻辑关系
522 | result = (rel === 'or') ? (result || res) : (result && res)
523 | }
524 |
525 | return result
526 | }
527 |
528 |
529 | /**
530 | * 获取每一个whereGroup的结果
531 | * @private
532 | * @param {Array} whereGroup
533 | * @param {Object} data
534 | * @returns {Boolean}
535 | */
536 | function getWhereGroupResult (whereGroup, data) {
537 | let result = true
538 | let i = -1
539 | let len = whereGroup.length
540 | while (++i < len) {
541 | let where = whereGroup[i]
542 | let { _f: field, _c: cond, _e: exp, _r: rel } = where
543 | let res
544 | // field exists
545 | if (exp === 'exists') {
546 | res = _.objKeyIsExists(field, data)
547 | } else {
548 | let value = _.getObjectValue(field, data)
549 | if (_.isFunction(cond)) {
550 | /* eslint-disable no-useless-call */
551 | res = cond.call(null, value)
552 | } else {
553 | res = matchWhere(value, exp, cond)
554 | }
555 | }
556 | // 上一个where的结果跟当前结果的逻辑关系
557 | result = (rel === 'or') ? (result || res) : (result && res)
558 | }
559 | return result
560 | }
561 |
562 |
563 | /**
564 | * 解析where条件的各种情况
565 | * @private
566 | * @param {Any} value
567 | * @param {String} expression
568 | * @param {Primitive} condition
569 | * @returns {Boolean}
570 | */
571 | function matchWhere (value, expression, condition) {
572 | let res = true
573 | switch (expression) {
574 | case 'like':
575 | let keyword = new RegExp(_.escapeKeyword(condition), 'i')
576 | res = !!((value || '').toString().match(keyword))
577 | break
578 | case 'in':
579 | if (_.isPrimitive(condition)) {
580 | condition = [condition]
581 | }
582 | if (_.isArray(condition)) {
583 | res = !!~condition.indexOf(value)
584 | } else {
585 | res = false
586 | }
587 | break
588 | case 'nin':
589 | if (_.isPrimitive(condition)) {
590 | condition = [condition]
591 | }
592 | if (_.isArray(condition)) {
593 | res = (!!~condition.indexOf(value)) === false
594 | }
595 | break
596 | case 'neq':
597 | case '<>':
598 | res = (value !== condition)
599 | break
600 | case 'lt':
601 | case '<':
602 | res = (value < condition)
603 | break
604 | case 'lte':
605 | case '<=':
606 | res = (value <= condition)
607 | break
608 | case 'gt':
609 | case '>':
610 | res = (value > condition)
611 | break
612 | case 'gte':
613 | case '>=':
614 | res = (value >= condition)
615 | break
616 | case 'eq':
617 | case '=':
618 | default:
619 | res = (value === condition)
620 | }
621 | return res
622 | }
623 |
624 |
625 | /**
626 | * 添加排序规则
627 | * @private
628 | * @param {String} field
629 | * @param {String} type
630 | */
631 | function _addSort (field, type) {
632 | const { sort: sorts } = this.params
633 | let len = sorts.length
634 | let idx
635 | while (--len >= 0) {
636 | let sort = sorts[len]
637 | // 已经存在当前字段的排序
638 | if (sort[0] === field) {
639 | idx = len
640 | break
641 | }
642 | }
643 |
644 | let sort = [field, type]
645 |
646 | // 有则覆盖,无则添加
647 | if (idx > -1) {
648 | this.params.sort.splice(idx, 1)
649 | }
650 | this.params.sort.push(sort)
651 | }
652 |
653 |
654 | /**
655 | * 对数据根据排序规则进行排序
656 | * 如果第一条规则未区分出大小,则使用第二条规则
657 | * 否则一旦区分出大小,后面的规则将不再继续
658 | * @private
659 | * @param {Array} data
660 | */
661 | function _parseSort (data) {
662 | const { sort: sorts } = this.params
663 | data.sort((a, b) => {
664 | let i = -1
665 | let len = sorts.length
666 | let sort
667 | while (++i < len) {
668 | sort = sorts[i]
669 | let field = sort[0]
670 | let type = sort[1]
671 | let valueA = _.getObjectValue(field, a)
672 | let valueB = _.getObjectValue(field, b)
673 | if (valueA > valueB) {
674 | return type === 'desc' ? -1 : 1
675 | } else if (valueA < valueB) {
676 | return type === 'asc' ? -1 : 1
677 | }
678 | }
679 | })
680 | }
681 |
682 |
683 | /**
684 | * 处理并返回结果集
685 | * where -> sort -> pagination
686 | * @private
687 | */
688 | function _query () {
689 | let { target, params, queried, options } = this
690 | let { group, range } = params
691 | // 一条SQL语句查询完后将结果集保存在target中,
692 | // 如不经过reset方法,下次查询将会在当前基础上查询
693 | // 主要是为了一条where语句既可以查询count,又可以
694 | // 经过分页后查询list
695 |
696 | if (queried) {
697 | return
698 | }
699 |
700 | let result = []
701 | let groups = Object.create(null)
702 | // 是否需要分组
703 | const groupLen = group && group.length
704 |
705 | // range
706 | let rangeStart = range[0]
707 | let rangeEnd = range[1]
708 | let i = -1
709 | let len = target.length
710 |
711 | if (!_.isUndefined(rangeStart) || !_.isUndefined(rangeEnd)) {
712 | if (rangeStart > rangeEnd) {
713 | [rangeStart, rangeEnd] = [rangeEnd, rangeStart]
714 | }
715 | if (rangeStart > len - 1) {
716 | rangeStart = len - 1
717 | }
718 | if (rangeEnd > len - 1) {
719 | rangeEnd = len - 1
720 | }
721 | target = target.slice(rangeStart, rangeEnd)
722 | len = target.length
723 | }
724 |
725 | // match where
726 |
727 | let item
728 | while (++i < len) {
729 | item = _format.call(this, target[i])
730 | let res = _parseWhere.call(this, item)
731 | if (res) {
732 | // group by
733 | if (groupLen) {
734 | let values = []
735 | for (let j = 0; j < groupLen; j++) {
736 | let field = group[j]
737 | let value = _.getObjectValue(field, item)
738 | if (value !== void 0) {
739 | values.push(value)
740 | }
741 | }
742 | let groupKey = values.join(options.groupKeySeparator)
743 | if (!groups[groupKey]) {
744 | groups[groupKey] = []
745 | }
746 | groups[groupKey].push(item)
747 | } else {
748 | result.push(item)
749 | }
750 | }
751 | }
752 |
753 | if (groupLen) {
754 | result.length = 0
755 | for (let key in groups) {
756 | let list = groups[key]
757 | result.push({
758 | _id: key,
759 | list,
760 | count: list.length
761 | })
762 | }
763 | }
764 |
765 | // queried
766 | this.queried = true
767 | this.target = result
768 | }
769 |
770 |
771 | /**
772 | * 对指定字段格式化
773 | * @private
774 | * @param {Object} data
775 | * @returns {Object}
776 | */
777 | function _format (data) {
778 | let { params, options } = this
779 | if (params.format) {
780 | try {
781 | for (let field in params.format) {
782 | let format = params.format[field]
783 | let value = _.getObjectValue(field, data)
784 | let newValue = format.handler(value, ...format.args)
785 | let parts = field.split('.')
786 | let parentKey = (parts.slice(0, parts.length - 1)).join('.')
787 | let curKey = parts[parts.length - 1]
788 | let parentObj = data
789 | if (parentKey) {
790 | let parent = _.getObjectValue(parentKey, data)
791 | if (_.isPlainObject(parent) || _.isArray(parent)) {
792 | parentObj = parent
793 | }
794 | }
795 | if (format.new) {
796 | let newKey = options.newFieldNamePrefix + (_.isString(format.new) ? format.new : curKey)
797 | parentObj[newKey] = newValue
798 | } else {
799 | parentObj[curKey] = newValue
800 | }
801 | }
802 | } catch (error) {
803 | log(error)
804 | }
805 | }
806 | return data
807 | }
808 |
809 |
810 | function log (error) {
811 | console.log('[QUERY ERROR]:', error)
812 | }
813 |
814 |
815 | export default Query
816 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | // 缓存 Object.prototype.toString
2 | const TOSTRING = Object.prototype.toString
3 |
4 | // hasOwnProperty
5 | const HAS_OWN = Object.prototype.hasOwnProperty
6 |
7 | // array.slice
8 | const ARRAY_SLICE = Array.prototype.slice
9 |
10 | // primitive values
11 | const PRIMITIVE_VALUES = ['string', 'number', 'boolean', 'symbol']
12 |
13 |
14 | /**
15 | * 正则对象检测
16 | * @param {Any} value
17 | * @returns {Boolean}
18 | */
19 | const isRegexp = value => TOSTRING.call(value) === '[object RegExp]'
20 |
21 |
22 | /**
23 | * String
24 | * @param {Any} value
25 | * @returns {Boolean}
26 | */
27 | const isString = value => typeof value === 'string'
28 |
29 |
30 | /**
31 | * undefined
32 | * @param {Any} value
33 | * @returns {Boolean}
34 | */
35 | const isUndefined = value => value === void 0
36 |
37 |
38 | /**
39 | * null
40 | * @param {Any} value
41 | * @returns {Boolean}
42 | */
43 | const isNull = value => value === null
44 |
45 |
46 | /**
47 | * null | undefined | empty string
48 | * @param {Any} value
49 | * @returns {Boolean}
50 | */
51 | const isEmpty = value => !!(isUndefined(value) || isNull(value) || (isString(value) && value.trim().length === 0))
52 |
53 |
54 | /**
55 | * object
56 | * @param {Any} value
57 | * @returns {Boolean}
58 | */
59 | const isObject = value => !isNull(value) && (typeof value === 'object')
60 |
61 |
62 | /**
63 | * plain object
64 | * @param {Any} value
65 | * @returns {Boolean}
66 | */
67 | const isPlainObject = value => TOSTRING.call(value) === '[object Object]'
68 |
69 |
70 | /**
71 | * array
72 | * @param {Any} value
73 | * @returns {Boolean}
74 | */
75 | const isArray = value => Array.isArray.call(null, value)
76 |
77 |
78 | /**
79 | * primitive types
80 | * without null && undefined
81 | * @param {Any} value
82 | * @returns {Boolean}
83 | */
84 | const isPrimitive = value => !!~PRIMITIVE_VALUES.indexOf((typeof value))
85 |
86 |
87 | /**
88 | * primitive types
89 | * @param {Any} value
90 | * @returns {Boolean}
91 | */
92 | const isFullPrimitive = value => isNull(value) || isUndefined(value) || isPrimitive(value)
93 |
94 |
95 | /**
96 | * function
97 | * @param {Any} value
98 | * @returns {Boolean}
99 | */
100 | const isFunction = value => typeof value === 'function'
101 |
102 |
103 | /**
104 | * number
105 | * @param {Any} value
106 | * @returns {Boolean}
107 | */
108 | const isNumber = value => typeof value === 'number'
109 |
110 |
111 | /**
112 | * integer
113 | * @param {Any} value
114 | * @returns {Boolean}
115 | */
116 | const isInteger = value => isNumber(value) && (value % 1 === 0)
117 |
118 |
119 | /**
120 | * float
121 | * @param {Any} value
122 | * @returns {Boolean}
123 | */
124 | const isFloat = value => +value && (value !== (value | 0))
125 |
126 |
127 | /**
128 | * boolean
129 | * @param {Any} value
130 | * @returns {Boolean}
131 | */
132 | const isBoolean = value => typeof value === 'boolean'
133 |
134 |
135 | // 模糊搜索中需要转义的特殊字符
136 | const SPAN_CHAR_REG = /(\^|\.|\[|\$|\(|\)|\||\*|\+|\?|\{|\\)/g
137 |
138 |
139 | /**
140 | * 将传入的搜索关键词转义
141 | * @param {String} keyword
142 | * @returns {String}
143 | */
144 | const escapeKeyword = keyword => (keyword || '').toString().replace(SPAN_CHAR_REG, '\\$1')
145 |
146 |
147 | /**
148 | * 根据path路径从object中取值
149 | * eg.
150 | * let obj = {
151 | * a : {
152 | * b : {
153 | * c : 1
154 | * }
155 | * },
156 | * d : [{c : 2}]
157 | * }
158 | * getObjectValue('a.b.c', obj) => 1
159 | * getObjectValue('a.d.0.c', obj) => 2
160 | * getObjectValue('a.d.0', obj) => {c: 2}
161 | *
162 | * @param name
163 | * @param object
164 | * @returns {Any}
165 | */
166 | function getObjectValue (name, object) {
167 | if (isEmpty(name)) { return void 0 }
168 |
169 | let paths = name.split('.')
170 |
171 | while (paths.length) {
172 | let k = paths.shift()
173 | object = object[k]
174 | if (!isPlainObject(object) && !isArray(object)) {
175 | break
176 | }
177 | }
178 |
179 | return object
180 | }
181 |
182 |
183 | /**
184 | * 检测对象中是否存在key值
185 | * @param {String} key
186 | * @param {Object} object
187 | * @returns {Boolean}
188 | */
189 | const hasKey = (key, object) => HAS_OWN.call(object, key)
190 |
191 |
192 | /**
193 | * 验证对象中是否存在某个key
194 | * @param {String} name
195 | * @param {Object} object
196 | * @returns {Boolean}
197 | */
198 | function objKeyIsExists (name, object) {
199 | if (isEmpty(name)) { return false }
200 |
201 | let paths = name.split('.')
202 |
203 | while (paths.length) {
204 | let k = paths.shift()
205 | if (!hasKey(k, object)) {
206 | return false
207 | }
208 | object = object[k]
209 | }
210 |
211 | return true
212 | }
213 |
214 |
215 | /**
216 | * 将类数组转换为数组
217 | * @param {ArrayLike} array
218 | * @returns {Array}
219 | */
220 | const toArray = array => ARRAY_SLICE.call(array)
221 |
222 |
223 | /**
224 | * 优化后的apply
225 | * @param {Function} func
226 | * @param {Object} context
227 | */
228 | function apply (func, context) {
229 | const args = ARRAY_SLICE.call(arguments, 2)
230 | switch (args.length) {
231 | case 0:
232 | case 1:
233 | return func.call(context, args[0])
234 | case 2:
235 | return func.call(context, args[0], args[1])
236 | case 3:
237 | return func.call(context, args[0], args[1], args[2])
238 | default:
239 | return func.apply(context, args)
240 | }
241 | }
242 |
243 |
244 |
245 | export default {
246 | isRegexp,
247 | isString,
248 | isUndefined,
249 | isNull,
250 | isEmpty,
251 | isObject,
252 | isPlainObject,
253 | isArray,
254 | isPrimitive,
255 | isFullPrimitive,
256 | isFunction,
257 | isNumber,
258 | isInteger,
259 | isFloat,
260 | isBoolean,
261 | escapeKeyword,
262 | getObjectValue,
263 | objKeyIsExists,
264 | hasKey,
265 | apply,
266 | toArray
267 | }
268 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const pkj = require('./package.json')
2 | const webpack = require('webpack')
3 | const path = require('path')
4 | module.exports = {
5 | entry: './src/index.js',
6 | output: {
7 | filename: 'query.js',
8 | path: path.resolve(__dirname, './build'),
9 | library: 'Query',
10 | libraryTarget: 'umd',
11 | libraryExport: 'default',
12 | umdNamedDefine: true,
13 | globalObject: 'this'
14 | },
15 | mode: 'production',
16 | module: {
17 | rules: [{
18 | test: /\.js$/,
19 | exclude: /node_modules/,
20 | use: [
21 | {
22 | loader: 'babel-loader'
23 | },
24 | {
25 | loader: 'string-replace-loader',
26 | options: {
27 | search: '__VERSION__',
28 | replace: pkj.version,
29 | }
30 | }
31 | ],
32 | exclude: /node_modules/
33 | }
34 | ]
35 | },
36 | plugins: [
37 | new webpack.BannerPlugin(`${pkj.name} ${pkj.version}\n(c) 2018 Smohan\nReleased under the ${pkj.license} License.`)
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------