├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── build └── query.js ├── docs ├── format.md ├── hooks.md └── where.md ├── example ├── data.js ├── full.html ├── full.js ├── index.html └── index.js ├── package.json ├── src ├── hooks.js ├── index.js ├── query.js └── utils.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "env": { 12 | "test": { 13 | "presets": ["env", "stage-2"], 14 | "plugins": ["istanbul"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | quote_type = single -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "plugins": [ 4 | "html" 5 | ], 6 | "rules": { 7 | "no-console":"off", 8 | "no-debugger":"off", 9 | "no-var":"warn", 10 | "no-multiple-empty-lines":["error", {"max":4}] 11 | } 12 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | /test/unit/coverage/ 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | *.npmrc 16 | 17 | package-lock.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Query 2 | 3 | > 类SQL前端数据查询类库 4 | 5 | ## Example 6 | #### [example 1: SQl组装](https://s-mohan.github.io/demo/query/) 7 | #### [example 2: 数据列表](https://s-mohan.github.io/demo/query/full.html) 8 | 9 | ## 如何使用 10 | ```javascript 11 | // 支持 umd 12 | 13 | var data = [] // 数据 14 | var query = new Query(data) 15 | ``` 16 | 17 | ## 实例方法 18 | 19 | ### range 20 | ###### Description 21 | >`[Parameter Collections]` 从数据中选取一个从开始索引(start)到一个结束索引(end)之间的部分的浅拷贝出来作为目标对象,参数同Array.prototype.slice。多次调用以最后一次收集到的参数为准。 22 | 23 | ###### Syntax 24 | ```javascript 25 | /** 26 | * @param {Number} start 27 | * @param {Number} end 28 | */ 29 | query.range(start, end) 30 | ``` 31 | ###### Example 32 | ```javascript 33 | query.range(0, 10) 34 | ``` 35 | 36 | ### to/format 37 | ###### Description 38 | > `[Parameter Collections]` 通过内置格式化函数(hooks)对字段进行格式化。 39 | 40 | ###### [Document](https://github.com/S-mohan/query/blob/master/docs/format.md) 41 | ###### [内置格式化函数(format hooks)](https://github.com/S-mohan/query/blob/master/docs/hooks.md) 42 | ###### Syntax 43 | ```javascript 44 | /** 45 | * @param {String} field 待格式化字段 46 | * @param {String} type 格式化函数名称 47 | * @param {Object} options 可用配置项 48 | */ 49 | query.to(field, type, options) / query.format(field, type, options) 50 | ``` 51 | ###### Example 52 | ```javascript 53 | // 'createTime': '2017-09-08T15:26:03.896Z', 54 | query.to('createTime', 'date', {args: ['yy-MM-dd'], new: 'date'}) 55 | // $date: '2017-09-08' 56 | ``` 57 | 58 | ### where 59 | ###### Description 60 | > `[Parameter Collections]` 条件查询语句 61 | 62 | ###### [Document](https://github.com/S-mohan/query/blob/master/docs/where.md) 63 | ###### Syntax 64 | ```javascript 65 | /** 66 | * @param {String} field 字段 67 | * @param {String} expression 表达式 68 | * @param {String | Function} condition 条件 69 | * @param {String} relation {and(default) | or} 与上次where结果的关系 70 | */ 71 | query.where(field, expression, condition, relation) 72 | ``` 73 | ###### Example 74 | ```javascript 75 | query 76 | .where('author', 'eq', 'smohan') 77 | .where([['title', 'like', 'javascript'], ['tags', 'like', 'javascript', 'or'] ]) 78 | .where('count.comments', 'gt', 0) 79 | // author === 'smohan' && (title like 'javascript' || tags like 'javascript') && 'count.comments > 0' 80 | ``` 81 | 82 | ### group 83 | ###### Description 84 | > `[Parameter Collections]` 根据字段对结果集分组,返回新的结果集。一个字段只能分组一次。 85 | 86 | ###### Syntax 87 | ```javascript 88 | /** 89 | * @param {String} field 待分组的字段 90 | */ 91 | query.group(field) 92 | ``` 93 | ###### Example 94 | ```javascript 95 | query.group('author') 96 | /** 97 | * result 98 | * [ 99 | * 'smohan': {count: 22, list:[...]}, 100 | * '流云诸葛': {count: 1, list: [...]}, 101 | * ... 102 | * ] 103 | */ 104 | ``` 105 | ### skip 106 | ###### Description 107 | > `[Parameter Collections]` 用于分页时指定开始查询的起始行数。 108 | 109 | ###### Syntax 110 | ```javascript 111 | /** 112 | * @param {Number} skip 指定起始行数 113 | */ 114 | query.skip(skip) 115 | ``` 116 | ###### Example 117 | ```javascript 118 | // 从第0行开始查询 119 | query.skip(0) 120 | ``` 121 | ### limit 122 | ###### Description 123 | > `[Parameter Collections]` 用于分页时指定查询的数量。 124 | 125 | ###### Syntax 126 | ```javascript 127 | /** 128 | * @param {Number} limit 指定查询的数量 129 | */ 130 | query.limit(limit) 131 | ``` 132 | ###### Example 133 | ```javascript 134 | // 查询10条结果 135 | query.limit(10) 136 | // 从第5条开始查询10条结果 137 | query.skip(5).limit(10) 138 | ``` 139 | 140 | ### sort 141 | ###### Description 142 | > `[Parameter Collections]` 对查询结果进行排序,输出排序后的结果。 143 | 144 | ###### Syntax 145 | ```javascript 146 | /** 147 | * @param {String | Object} field 待排序的字段 148 | * @param {String | void} type 排序类型 [asc|desc] 149 | */ 150 | query.sort(field, type) 151 | ``` 152 | ###### Example 153 | ```javascript 154 | // 按order的降序排序,如果order一样,则再按照创建时间的降序排序 155 | query 156 | .sort('order', 'desc') 157 | .sort('createTime', 'desc') 158 | 159 | // 同时指定多个排序方式 160 | query.sort({ 161 | create_time: 'desc', 162 | id: 'desc', 163 | name: 'asc' 164 | }) 165 | ``` 166 | ### count 167 | ###### Description 168 | > `[Export results]` 返回经过查询后的结果的总数,如果需要分页,建议在分页前调用该方法。 169 | 170 | ###### Syntax 171 | ```javascript 172 | /** 173 | * @returns {Number} 174 | */ 175 | query.count() 176 | ``` 177 | ###### Example 178 | ```javascript 179 | query.count() 180 | ``` 181 | ### find 182 | ###### Description 183 | > `[Export results]` 返回经过查询后的结果集。 184 | 185 | ###### Syntax 186 | ```javascript 187 | /** 188 | * @returns {Array} 189 | */ 190 | query.find() 191 | ``` 192 | ###### Example 193 | ```javascript 194 | query.find() 195 | ``` 196 | ### reset 197 | ###### Description 198 | > 对数据集和查询条件进行重置。一旦调用`find()`/`count()`方法后,目标集合将会被改变,此时如果需要对源数据进行重新查询,需要调用该方法。 199 | 200 | ###### Syntax 201 | ```javascript 202 | query.reset() 203 | ``` 204 | ###### Example 205 | ```javascript 206 | query.reset() 207 | //.where() 208 | //... 209 | ``` 210 | 211 | ### destroy 212 | ###### Description 213 | > 销毁实例 214 | 215 | ###### Syntax 216 | ```javascript 217 | query.destroy() 218 | ``` 219 | 220 | ## 静态方法/属性 221 | ### hooks 222 | ###### Description 223 | > `[Static Method]` 添加自定义格式化函数 224 | 225 | ###### [内置格式化函数(format hooks)](https://github.com/S-mohan/query/blob/master/docs/hooks.md) 226 | ###### Syntax 227 | ```javascript 228 | /** 229 | * @param {String} name 钩子名称 230 | * @param {function} handler 钩子方法 231 | */ 232 | Query.hooks(name, handler) 233 | ``` 234 | ###### Example 235 | ```javascript 236 | // 添加一个重置标题的格式化函数 237 | Query.hooks('myTitle', function(value) { 238 | return '我是格式化后的标题:' + value 239 | }) 240 | 241 | // use 242 | query.to('title', 'myTitle', {new: true}) 243 | // result: 244 | // $title: '我是格式化后的标题:title' 245 | ``` 246 | ### version 247 | ###### Description 248 | > `[Static Attribute]` 版本号 249 | 250 | ###### Syntax 251 | ```javascript 252 | Query.version 253 | ``` -------------------------------------------------------------------------------- /build/query.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * query 1.0.2 3 | * (c) 2018 Smohan 4 | * Released under the MIT License. 5 | */ 6 | !function(t,r){"object"==typeof exports&&"object"==typeof module?module.exports=r():"function"==typeof define&&define.amd?define("Query",[],r):"object"==typeof exports?exports.Query=r():t.Query=r()}(this,function(){return function(t){var r={};function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.r=function(t){Object.defineProperty(t,"__esModule",{value:!0})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,r){return Object.prototype.hasOwnProperty.call(t,r)},e.p="",e(e.s=0)}([function(t,r,e){"use strict";e.r(r);var n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},i=Object.prototype.toString,a=Object.prototype.hasOwnProperty,s=Array.prototype.slice,o=["string","number","boolean","symbol"],u=function(t){return"string"==typeof t},c=function(t){return void 0===t},l=function(t){return null===t},f=function(t){return!!(c(t)||l(t)||u(t)&&0===t.trim().length)},p=function(t){return"[object Object]"===i.call(t)},h=function(t){return Array.isArray.call(null,t)},g=function(t){return!!~o.indexOf(void 0===t?"undefined":n(t))},d=function(t){return"number"==typeof t},y=/(\^|\.|\[|\$|\(|\)|\||\*|\+|\?|\{|\\)/g;var v=function(t,r){return a.call(r,t)};var m={isRegexp:function(t){return"[object RegExp]"===i.call(t)},isString:u,isUndefined:c,isNull:l,isEmpty:f,isObject:function(t){return!l(t)&&"object"===(void 0===t?"undefined":n(t))},isPlainObject:p,isArray:h,isPrimitive:g,isFullPrimitive:function(t){return l(t)||c(t)||g(t)},isFunction:function(t){return"function"==typeof t},isNumber:d,isInteger:function(t){return d(t)&&t%1==0},isFloat:function(t){return+t&&t!==(0|t)},isBoolean:function(t){return"boolean"==typeof t},escapeKeyword:function(t){return(t||"").toString().replace(y,"\\$1")},getObjectValue:function(t,r){if(!f(t)){for(var e=t.split(".");e.length&&(r=r[e.shift()],p(r)||h(r)););return r}},objKeyIsExists:function(t,r){if(f(t))return!1;for(var e=t.split(".");e.length;){var n=e.shift();if(!v(n,r))return!1;r=r[n]}return!0},hasKey:v,apply:function(t,r){var e=s.call(arguments,2);switch(e.length){case 0:case 1:return t.call(r,e[0]);case 2:return t.call(r,e[0],e[1]);case 3:return t.call(r,e[0],e[1],e[2]);default:return t.apply(r,e)}},toArray:function(t){return s.call(t)}},b=function(t){return{y:(t=function(t){return t instanceof Date?t:/^\d+$/.test(t)?new Date(parseInt(t,10)):(t=(t||"").trim().replace(/\.\d+/,"").replace(/-/g,"/").replace(/T/," ").replace(/Z/," UTC").replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"),new Date(t))}(t)).getFullYear(),M:t.getMonth()+1,d:t.getDate(),h:t.getHours(),m:t.getMinutes(),s:t.getSeconds(),q:Math.floor((t.getMonth()+3)/3)}},O=function(t){return m.isNumber(t)?t:+t},j=function(t){return m.isString(t)?t:t+""},x={date:function(t,r){t=t||new Date,r=r||"yy-MM-dd hh:mm:ss";var e=b(t);return r=r.replace(/([yMdhmsqS])+/g,function(t,r){var n=e[r];return void 0!==n&&(n=n.toString(),"y"!==r&&t.length>1?n=(n="0"+n).substr(n.length-2):"y"===r&&1===t.length&&(n=n.substr(2))),n})},number:O,int:function(t){var r=O(t);return isNaN(r)?t:parseInt(t,10)},zero:function(t){return t||0},boolean:function(t){return!!t},string:j,lower:function(t){return j(t).toLocaleLowerCase()},upper:function(t){return j(t).toLocaleUpperCase()}};function w(t){if(Array.isArray(t)){for(var r=0,e=Array(t.length);r","gt",">","gte",">=","lt","<","lte","<=","like","in","nin","exists"],A=["asc","desc"],k=["and","or"],q=Object.keys(x),_=Object.create(null);q.forEach(function(t){_[t]=function(r){return m.apply.apply(m,[x[t],r].concat(w(m.toArray(arguments))))}});var M={index:[],groupKeySeparator:"$",newFieldNamePrefix:"$"};function P(t,r){if(!(this instanceof P))return new P(t,r);m.isArray(t)||(D("data must be an array"),t=[]),this.source=t,this.options=Object.assign(Object.create(null),M,r),this.reset()}P.version="1.0.2",P.hooks=function(t,r){!1==!!~q.indexOf(t)&&m.isFunction(r)&&(_[t]=function(t){return m.apply.apply(m,[r,t].concat(w(m.toArray(arguments))))})};var N=P.prototype,E={args:null,new:!1,handler:null};function F(t,r,e,n){if(m.isString(t)&&!m.isEmpty(t)&&!1!=!!~S.indexOf(r)){if(e&&m.isRegexp(e)){var i=e;e=function(t){return!!i.test(t)}}m.isFunction(e)&&(r="eq");var a={_f:t,_c:e,_e:r,_r:n=~k.indexOf(n)?n:"and"};return~JSON.stringify(this.params.query).indexOf(JSON.stringify(a))?void 0:a}}function I(t){var r=this.params.query,e=r.length;if(0===e)return!0;for(var n=!0,i=void 0,a=0;a":n=t!==e;break;case"lt":case"<":n=t":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 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
id标题作者标签阅读数创建时间
152 |
153 |
154 |
    155 |
    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 |
    127 |

    Result

    128 |
      129 | 130 |
    131 |
    132 |
    133 |

    Sql

    134 |
    
    135 |     
    136 |
    137 |

    Query

    138 |
    139 | 142 |
    143 | 146 |
    147 | 149 |
    150 |
    151 |
    sort
    152 | 154 | 156 | 158 | 160 | 162 |
    163 |
    164 |
    skip
    165 | 168 |
    169 |
    170 |
    limit
    171 | 174 |
    175 |
    176 |
    group
    177 | 180 |
    181 | 184 |
    185 | 188 |
    189 | 192 |
    193 |
    194 | 195 | 196 |
    197 |
    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 | --------------------------------------------------------------------------------