(function (Vue, axios, window) { "use strict"; // local preserved data object - cache var db = { collections: [], // ["abc", "bbc", "cba"] colorMap: {}, // {"abc": "red", "bbc": "blue", ...} documents: {}, randomColor: function () { return "hsl(" + Math.floor(Math.random() * 360) + ",85%,55%)"; }, _initialGetUrl: "", initialGetUrl: function (v) { this._initialGetUrl = v; return this; }, _subsequentGetUrlFn: function (name) { return console.error( "请先设置subsequentGetUrlFn函数!! \n" + "该函数接收一个collectionName参数\n" + "通过这个参数返回获取documents列表的url地址。" ); }, subsequentGetUrlFn: function (fn) { this._subsequentGetUrlFn = fn; return this; }, // _failedFn will be excuted on intialGetUrl or subsequentGetUrl failed. _failedFn: function (error) { console.log(error); }, failedFn: function (fn) { this._failedFn = fn; return this; }, _successFn: function () { console.error("请配置成功返回函数,先。"); }, successFn: function (fn) { this._successFn = fn; return this; }, run: function () { var self = this; axios .get(this._initialGetUrl) .then(function (raw) { var data = raw.data; self.collections = data.collections; data.collections.forEach(function (name, index) { self.colorMap[name] = data.colors[index] || self.randomColor(); }); self.documents[data.doc.name] = { items: data.doc.items, querys: [] }; self._successFn(data); }) .catch(this._failedFn); } }; // ------------- 高级检索初始化参数.xlsx ------------- // typeMap shared by queryGroup component and Vue instance as computer property var typeMap = (function () { // var maps = [ {value: ">", text: ">"}, {value: "<", text: "<"}, {value: "=", text: "="}, {value: ">=", text: ">="}, {value: "<=", text: "<="} ]; var textMaps = [ {value: "同义包含", text: "同义包含"}, {value: "绝对包含", text: "绝对包含"}, {value: "以XXX开头", text: "以XXX开头"}, {value: "以XXX结尾", text: "以XXX结尾"}, {value: "等于", text: "等于"} ]; function checkFn (reg) { return function (text) { text = text.trim(); if (!text.length) { return false; } return !!reg.exec(text); }; } function checkEmpty (text) { text = text.trim(); return !!text.length; } // function getPlaceholder (type) { switch (type) { case "0": return "整型: 123"; case "1": return "浮点型: 0.23"; case "3": return "日期: 2019-08-01"; } return "字符串: xxx"; } // function getClass (type) { switch (type) { case "0": return {"bg-integer": true}; case "1": return {"bg-float": true}; //case "3": return {"bg-date": true}; case "2": case "4": case "9": return {"bg-string": true}; } return {}; } return { "0": { // 整形 maps: maps, relation: "and", /* default relation value */ logic: ">", /* default logic value */ check: checkFn(/^[+-]?[1-9]+\d?$/) }, "1": { // 浮点型 maps: maps, relation: "and", logic: ">", check: checkFn(/^[+-]?\d+(\.\d+)?$/) }, "3": { // 日期: 2019-10-21 maps: maps, relation: "and", logic: ">", check: checkFn(/^(19|20)\d\d-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/) }, "2": { maps: textMaps, relation: "and", logic: "同义包含", check: checkEmpty }, "4": { maps: textMaps.slice(1), relation: "and", logic: "绝对包含", check: checkEmpty }, "9": { maps: textMaps.slice(1), relation: "and", logic: "绝对包含", check: checkEmpty }, getPlaceholder: getPlaceholder, getClass: getClass }; })(); var uuid = (function () { var i = 0; return function () { return "i" + i++; }; })(); Vue.config.devtools = false; var collectionGroup = { props: ["options"], template: `
` }; var documentGroup = { props: ["documents"], data: function () { return { filterString: "" }; }, watch: { documents: function () { this.filterString = ""; } }, computed: { filteredOptions: function () { var f = this.filterString.trim().toUpperCase(); if (!f.length) { return this.documents; } return this.documents.filter(function (d) { return d.name.toUpperCase().indexOf(f) > -1; }); } }, methods: { onClick: function (name, type) { this.$emit("add-query", name, type); } }, template: `
` }; var queryGroup = { props: ["items"], data: function () { return { dy: 0, // offset y between mouse and tRow on mousedown event rowTop: 0, // tRow initial y position rowHeight: 0, // tRow height // used for tedecting if the dragNode touch ceilling or floor // if it does readjust scroll property minY: 0, maxY: 0, // rowIndex on dragBefore and dragAfter indexBefore: 0, indexAfter: 0, // table row node dragNode: null, // max scollable value if overflowed scrollSapn: 0 }; }, computed: { map: function () { return typeMap; } }, methods: { onChange: function (id, key, $event) { // for select controls this.$emit("update-query", id, key, $event.target.value); }, onTextChange: function (id, key, type, $event) { // for input controls var value = $event.target.value.trim(); $event.target.value = value; this.$emit("update-query", id, key, value); if (this.map[type].check(value)) { $event.target.classList.remove("border-danger"); } else { $event.target.classList.add("border-danger"); } }, // following mouse event only fired on dragging behaviour onMousedown: function (q, $event) { // div.fix-table>table>tbody>tr>td>span.fix-table__handler var row = $event.target.parentElement.parentElement, div = this.$el, //row.closest(".fix-table"), rowBBox = row.getBoundingClientRect(), divBBox = div.getBoundingClientRect(); this.dragNode = row; this.indexBefore = row.rowIndex; this.indexAfter = row.rowIndex; // Geometry calculation this.rowTop = rowBBox.top; this.rowHeight = rowBBox.height; this.dy = $event.clientY - rowBBox.top; this.minY = divBBox.top + div.querySelector("thead").getBoundingClientRect().height; this.maxY = divBBox.bottom; this.scrollSapn = div.scrollHeight - div.clientHeight; row.style.backgroundColor = "lightgreen"; window.addEventListener("mousemove", this.onMousemove); window.addEventListener("mouseup", this.onMouseup); }, onMousemove: function (evt) { evt.preventDefault(); var py = evt.clientY - this.dy; var pre = this.dragNode.previousElementSibling, next = this.dragNode.nextElementSibling; if (pre && pre.getBoundingClientRect().top > py) { pre.insertAdjacentElement("beforebegin", this.dragNode); this.rowTop = this.dragNode.getBoundingClientRect().top; } else if (next && next.getBoundingClientRect().top < py) { next.insertAdjacentElement("afterend", this.dragNode); this.rowTop = this.dragNode.getBoundingClientRect().top; } py = evt.clientY - this.dy; this.indexAfter = this.dragNode.rowIndex; this.dragNode.style.transform = "translate(0px," + (py - this.rowTop) + "px)"; // auto scroll behaviour if (py < this.minY && this.$el.scrollTop > 0) { this.$el.scrollTop -= 10; } if ((py + this.rowHeight) > this.maxY && this.$el.scrollTop < this.scrollSapn) { this.$el.scrollTop += 10; } }, onMouseup: function (evt) { this.dragNode.style= ""; this.dragNode = null; window.removeEventListener("mousemove", this.onMousemove); window.removeEventListener("mouseup", this.onMouseup); if (this.indexBefore !== this.indexAfter) { this.$emit("move-query", this.indexBefore - 1, this.indexAfter - 1); } }, // this method is fired by parent Vue instance validate: function () { var evt = document.createEvent("HTMLEvents"); evt.initEvent("change", false, true); // Input controls validate one by one, // "change" event refer to component template var inputs = this.$el.querySelectorAll("input"); for (var i = 0, len = inputs.length; i < len; i++) { // invalid input will give a border-danger class on change event inputs[i].dispatchEvent(evt); } // invalide input will be fenced with red border if any. // Find one of them and shake this component to alert user. var invalid = this.$el.querySelector(".border-danger"); if (invalid) { invalid.scrollIntoView({ behavior: "smooth", block: "center" }); this.$el.classList.add("animated", "shake"); return false; } // all input control validate checking passed return true; } }, template: `
关系类型 字段 运算符 检索值
 
{{q.name}}
删 除
` }; var vm = new Vue({ // customed properties confirmCallbackFn: null, data: { currentCollection: "", // EXPORT collections: [], documents: [], queryItems: [], errorMsg: "", state: "insert" // insert/update "插入节点" | "更新节点" }, computed: { map: function () { return typeMap; }, db: function () { return db; } }, watch: { currentCollection: function (newColl, oldColl) { var doc = this.db.documents, self = this; // currentCollectin is switch to newColl this.preservDataToDB(oldColl); if (doc[newColl]) { this.$set(this.$data, "documents", doc[newColl].items); this.$set(this.$data, "queryItems", doc[newColl].querys); } else { this.$set(this.$data, "documents", []); this.$set(this.$data, "queryItems", []); axios.get(this.db._subsequentGetUrlFn(newColl)) .then(function (d) { doc[d.data.name] = { items: d.data.items, querys: [] }; self.$set(self.$data, "documents", doc[newColl].items); self.$set(self.$data, "queryItems", doc[newColl].querys); }) .catch(this.db._failedFn); } } }, methods: { addQuery: function (name, type) { this.queryItems.unshift({ id: this.uuid(), type: type, name: name, relation: this.map[type].relation, logic: this.map[type].logic, query: "" }); }, findQuery: function (id) { return this.queryItems.findIndex(function (item) { return item.id == id; }); }, updateQuery: function (id, key, value) { var index = this.findQuery(id); this.queryItems[index][key] = value; }, removeQuery: function (id) { var index = this.findQuery(id); this.queryItems.splice(index, 1); }, moveQuery: function (fromIndex, toIndex) { var i = fromIndex, j = toIndex, node = this.queryItems[fromIndex]; if (fromIndex < toIndex) { while (i < j) { this.queryItems[i] = this.queryItems[i + 1]; i++; } } else { while (i > j) { this.queryItems[i] = this.queryItems[i - 1]; i--; } } // Trigger reactive feature and place the item at right index this.queryItems.splice(toIndex, 1, node); //console.log(this.queryItems.map(d => d.name)); }, uuid: uuid, toExport: function (collectionName) { if (!collectionName) { collectionName = this.currentCollection; } return { collection: this.currentCollection, color: this.db.colorMap[collectionName], querys: this.queryItems.map(function (d) { return { id : d.id, name : d.name, type : d.type, relation: d.relation, logic : d.logic, query : d.query }; }) }; }, // preserve data action only when close dialoge or collection switch preservDataToDB: function (collectionName) { if (!this.queryItems.length) { return; } if (!collectionName) { collectionName = this.currentCollection; } this.db.documents[collectionName].querys = this.toExport(collectionName).querys; }, // Ref: _animation.scss onAnimationEnd: function (evt) { evt.target.classList.remove("animated", "shake", "bounceInDown", "bounceInUp", "bounceOutDown"); }, onEscPressed: function (evt) { if (evt.key.toUpperCase() === "ESCAPE") { this.close(); } }, show: function (callbackFn, bind) { window.addEventListener("keydown", this.onEscPressed); this.errorMsg = ""; this.state = "insert"; if (callbackFn) { this.confirmCallbackFn = callbackFn; } if (bind) { this.currentCollection = bind.collection; this.$set(this.$data, "queryItems", bind.querys); this.state = "update"; } this.$el.style.display = "block"; document.body.classList.add("modal-open"); this.$el.querySelector(".modal-dialog") .classList.add("animated", "bounceInDown"); }, close: function () { window.removeEventListener("keydown", this.onEscPressed); this.$el.style.display = "none"; document.body.classList.remove("modal-open"); this.confirmCallbackFn = null; this.preservDataToDB(); // finally preserve data }, confirm: function () { this.errorMsg = ""; // 1) filter items must not less than 1 if (!this.queryItems.length) { this.errorMsg = "请点击左侧列表,添加筛选条件到右侧。"; this.$refs.query.$el.classList.add("animated", "shake"); return; } // 2) query string is required if (!this.$refs.query.validate()) { this.errorMsg = "输入不得为空,且必须按照指定数据类型输入数据。"; return; } // 3) get all information then fire callback if any if (this.confirmCallbackFn) { this.confirmCallbackFn(this.toExport()); } // 4) close dialoge finnaly this.close(); } }, components: { collectionGroup: collectionGroup, documentGroup: documentGroup, queryGroup: queryGroup }, template: ` ` }); window.vm = vm; })(Vue, axios, window);