diff --git a/package.json b/package.json index 08ce0971..8beb0480 100644 --- a/package.json +++ b/package.json @@ -16,24 +16,26 @@ "jquery": "^3.5.1", "laravel-mix": "^6.0.6", "lodash": "^4.17.19", + "postcss": "^8.1.14", + "resolve-url-loader": "^3.1.3", + "sass": "^1.34.0", + "sass-loader": "^11.1.1", "stylus": "^0.54.8", "stylus-loader": "^3.0.2", "vue": "^2.6.12", + "vue-loader": "^15.9.7", "vue-router": "^3.4.2", - "vue-template-compiler": "^2.6.11", - "postcss": "^8.1.14" + "vue-template-compiler": "^2.6.11" }, "dependencies": { - "@chenfengyuan/vue-qrcode": "^1.0.2", - "echarts": "^5.0.0", - "internal-ip": "^6.1.0", - "tinymce": "^5.6.2", + "echarts": "^5.1.1", + "tinymce": "^5.8.1", "view-design-hi": "^4.5.0-4", "vue-clipboard2": "^0.3.1", "vue-emoji-picker": "^1.0.1", "vue-kityminder-gg": "^1.3.6", "vue-resize-observer": "^1.0.37", "vuedraggable": "^2.24.3", - "xlsx": "^0.16.9" + "xlsx": "^0.17.0" } } diff --git a/resources/css/app.css b/resources/css/app.css deleted file mode 100644 index e69de29b..00000000 diff --git a/resources/js/App.vue b/resources/js/App.vue new file mode 100755 index 00000000..62c9008f --- /dev/null +++ b/resources/js/App.vue @@ -0,0 +1,212 @@ + + + + + + + diff --git a/resources/js/app.js b/resources/js/app.js index 40c55f65..da57313f 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1 +1,81 @@ -require('./bootstrap'); +import Vue from 'vue' +import App from './App.vue' +import routes from './routes' +import VueRouter from 'vue-router' +import ViewUI from 'view-design-hi'; +import Language from './common/language/index' +import Mixins from './common/mixins/index' + +import './common/functions/index' +import './main' + +Vue.use(VueRouter); +Vue.use(ViewUI); +Vue.use(Language); +Vue.use(Mixins); + +import PageTitle from './common/components/PageTitle.vue' +import Loading from './common/components/Loading.vue' +import AutoTip from './common/components/AutoTip.vue' +import TableAction from './common/components/TableAction.vue' + +Vue.component('PageTitle', PageTitle); +Vue.component('Loading', Loading); +Vue.component('AutoTip', AutoTip); +Vue.component('TableAction', TableAction); + + +const originalPush = VueRouter.prototype.push +VueRouter.prototype.push = function push(location) { + return originalPush.call(this, location).catch(err => err) +} +const router = new VueRouter({ + mode: 'history', + routes +}); + +//进度条配置 +ViewUI.LoadingBar.config({ + color: '#3fcc25', + failedColor: '#ff0000' +}); +router.beforeEach((to, from, next) => { + ViewUI.LoadingBar.start(); + next(); +}); +router.afterEach((to, from, next) => { + ViewUI.LoadingBar.finish(); +}); + +//加载函数 +Vue.prototype.goForward = function(location, isReplace) { + if (typeof location === 'string') location = {name: location}; + if (isReplace === true) { + this.$router.replace(location); + }else{ + this.$router.push(location); + } +}; + +//返回函数 +Vue.prototype.goBack = function (number) { + let history = $A.jsonParse(window.sessionStorage['__history__'] || '{}'); + if ($A.runNum(history['::count']) > 2) { + this.$router.go(typeof number === 'number' ? number : -1); + } else { + this.$router.replace(typeof number === "object" ? number : {path: '/'}); + } +}; + +Vue.prototype.$A = $A; + +Vue.config.productionTip = false; + +const app = new Vue({ + el: '#app', + router, + template: '', + components: { App } +}); + +$A.app = app; diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js deleted file mode 100644 index 69225776..00000000 --- a/resources/js/bootstrap.js +++ /dev/null @@ -1,28 +0,0 @@ -window._ = require('lodash'); - -/** - * We'll load the axios HTTP library which allows us to easily issue requests - * to our Laravel back-end. This library automatically handles sending the - * CSRF token as a header based on the value of the "XSRF" token cookie. - */ - -window.axios = require('axios'); - -window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; - -/** - * Echo exposes an expressive API for subscribing to channels and listening - * for events that are broadcast by Laravel. Echo and event broadcasting - * allows your team to easily build robust real-time web applications. - */ - -// import Echo from 'laravel-echo'; - -// window.Pusher = require('pusher-js'); - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: process.env.MIX_PUSHER_APP_KEY, -// cluster: process.env.MIX_PUSHER_APP_CLUSTER, -// forceTLS: true -// }); diff --git a/resources/js/common/components/AutoTip.vue b/resources/js/common/components/AutoTip.vue new file mode 100644 index 00000000..86012f2c --- /dev/null +++ b/resources/js/common/components/AutoTip.vue @@ -0,0 +1,85 @@ + + + diff --git a/resources/js/common/components/ImgUpload.vue b/resources/js/common/components/ImgUpload.vue new file mode 100755 index 00000000..adb423d3 --- /dev/null +++ b/resources/js/common/components/ImgUpload.vue @@ -0,0 +1,594 @@ + + + + diff --git a/resources/js/common/components/Loading.vue b/resources/js/common/components/Loading.vue new file mode 100644 index 00000000..b3e67673 --- /dev/null +++ b/resources/js/common/components/Loading.vue @@ -0,0 +1,108 @@ + + + + diff --git a/resources/js/common/components/PageTitle.vue b/resources/js/common/components/PageTitle.vue new file mode 100755 index 00000000..3375a9b2 --- /dev/null +++ b/resources/js/common/components/PageTitle.vue @@ -0,0 +1,64 @@ + + + diff --git a/resources/js/common/components/Spinner.vue b/resources/js/common/components/Spinner.vue new file mode 100644 index 00000000..f41c5a45 --- /dev/null +++ b/resources/js/common/components/Spinner.vue @@ -0,0 +1,32 @@ + + + + diff --git a/resources/js/common/components/TEditor.vue b/resources/js/common/components/TEditor.vue new file mode 100755 index 00000000..cd5c4095 --- /dev/null +++ b/resources/js/common/components/TEditor.vue @@ -0,0 +1,545 @@ + + + + + + diff --git a/resources/js/common/components/TableAction.vue b/resources/js/common/components/TableAction.vue new file mode 100644 index 00000000..8b61e31c --- /dev/null +++ b/resources/js/common/components/TableAction.vue @@ -0,0 +1,84 @@ + + + diff --git a/resources/js/common/components/TagInput.vue b/resources/js/common/components/TagInput.vue new file mode 100755 index 00000000..208e52c6 --- /dev/null +++ b/resources/js/common/components/TagInput.vue @@ -0,0 +1,222 @@ + + + + diff --git a/resources/js/common/directives/clickoutside.js b/resources/js/common/directives/clickoutside.js new file mode 100755 index 00000000..6d75fc3d --- /dev/null +++ b/resources/js/common/directives/clickoutside.js @@ -0,0 +1,21 @@ +export default { + bind (el, binding, vnode) { + function documentHandler (e) { + if (el.contains(e.target)) { + return false; + } + if (binding.expression) { + binding.value(e); + } + } + el.__vueClickOutside__ = documentHandler; + document.addEventListener('click', documentHandler); + }, + update () { + + }, + unbind (el, binding) { + document.removeEventListener('click', el.__vueClickOutside__); + delete el.__vueClickOutside__; + } +}; \ No newline at end of file diff --git a/resources/js/common/directives/popper-novalue.js b/resources/js/common/directives/popper-novalue.js new file mode 100755 index 00000000..b4b83f1b --- /dev/null +++ b/resources/js/common/directives/popper-novalue.js @@ -0,0 +1,108 @@ +/** + * https://github.com/freeze-component/vue-popper + * */ +import Vue from 'vue'; +const isServer = Vue.prototype.$isServer; +const Popper = isServer ? function() {} : require('popper.js/dist/umd/popper.js'); // eslint-disable-line + +export default { + props: { + placement: { + type: String, + default: 'bottom' + }, + boundariesPadding: { + type: Number, + default: 5 + }, + reference: Object, + popper: Object, + offset: { + default: 0 + }, + transition: String, + options: { + type: Object, + default () { + return { + modifiers: { + computeStyle:{ + gpuAcceleration: false, + }, + preventOverflow :{ + boundariesElement: 'window' + } + } + }; + } + } + }, + data () { + return { + visible: false + }; + }, + watch: { + visible(val) { + if (val) { + if (this.handleIndexIncrease) this.handleIndexIncrease(); // just use for Poptip + this.updatePopper(); + this.$emit('on-popper-show'); + } else { + this.$emit('on-popper-hide'); + } + } + }, + methods: { + createPopper() { + if (isServer) return; + if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(this.placement)) { + return; + } + + const options = this.options; + const popper = this.popper || this.$refs.popper; + const reference = this.reference || this.$refs.reference; + + if (!popper || !reference) return; + + if (this.popperJS && this.popperJS.hasOwnProperty('destroy')) { + this.popperJS.destroy(); + } + + options.placement = this.placement; + + if (!options.modifiers.offset) { + options.modifiers.offset = {}; + } + options.modifiers.offset.offset = this.offset; + options.onCreate =()=>{ + this.$nextTick(this.updatePopper); + this.$emit('created', this); + }; + + this.popperJS = new Popper(reference, popper, options); + + }, + updatePopper() { + if (isServer) return; + this.popperJS ? this.popperJS.update() : this.createPopper(); + }, + doDestroy() { + if (isServer) return; + if (this.visible) return; + this.popperJS.destroy(); + this.popperJS = null; + } + }, + updated (){ + this.$nextTick(()=>this.updatePopper()); + + }, + beforeDestroy() { + if (isServer) return; + if (this.popperJS) { + this.popperJS.destroy(); + } + } +}; diff --git a/resources/js/common/directives/transfer-dom.js b/resources/js/common/directives/transfer-dom.js new file mode 100644 index 00000000..6c2a0cf0 --- /dev/null +++ b/resources/js/common/directives/transfer-dom.js @@ -0,0 +1,77 @@ +// Thanks to: https://github.com/airyland/vux/blob/v2/src/directives/transfer-dom/index.js +// Thanks to: https://github.com/calebroseland/vue-dom-portal + +/** + * Get target DOM Node + * @param {(Node|string|Boolean)} [node=document.body] DOM Node, CSS selector, or Boolean + * @return {Node} The target that the el will be appended to + */ +function getTarget (node) { + if (node === void 0) { + node = document.body + } + if (node === true) { return document.body } + return node instanceof window.Node ? node : document.querySelector(node) +} + +const directive = { + inserted (el, { value }, vnode) { + if ( el.dataset && el.dataset.transfer !== 'true') return false; + el.className = el.className ? el.className + ' v-transfer-dom' : 'v-transfer-dom'; + const parentNode = el.parentNode; + if (!parentNode) return; + const home = document.createComment(''); + let hasMovedOut = false; + + if (value !== false) { + parentNode.replaceChild(home, el); // moving out, el is no longer in the document + getTarget(value).appendChild(el); // moving into new place + hasMovedOut = true + } + if (!el.__transferDomData) { + el.__transferDomData = { + parentNode: parentNode, + home: home, + target: getTarget(value), + hasMovedOut: hasMovedOut + } + } + }, + componentUpdated (el, { value }) { + if ( el.dataset && el.dataset.transfer !== 'true') return false; + // need to make sure children are done updating (vs. `update`) + const ref$1 = el.__transferDomData; + if (!ref$1) return; + // homes.get(el) + const parentNode = ref$1.parentNode; + const home = ref$1.home; + const hasMovedOut = ref$1.hasMovedOut; // recall where home is + + if (!hasMovedOut && value) { + // remove from document and leave placeholder + parentNode.replaceChild(home, el); + // append to target + getTarget(value).appendChild(el); + el.__transferDomData = Object.assign({}, el.__transferDomData, { hasMovedOut: true, target: getTarget(value) }); + } else if (hasMovedOut && value === false) { + // previously moved, coming back home + parentNode.replaceChild(el, home); + el.__transferDomData = Object.assign({}, el.__transferDomData, { hasMovedOut: false, target: getTarget(value) }); + } else if (value) { + // already moved, going somewhere else + getTarget(value).appendChild(el); + } + }, + unbind (el) { + if (el.dataset && el.dataset.transfer !== 'true') return false; + el.className = el.className.replace('v-transfer-dom', ''); + const ref$1 = el.__transferDomData; + if (!ref$1) return; + if (el.__transferDomData.hasMovedOut === true) { + el.__transferDomData.parentNode && el.__transferDomData.parentNode.appendChild(el) + } + el.__transferDomData = null + } +}; + +export default directive; \ No newline at end of file diff --git a/resources/js/common/directives/v-click-outside-x.js b/resources/js/common/directives/v-click-outside-x.js new file mode 100644 index 00000000..12505f82 --- /dev/null +++ b/resources/js/common/directives/v-click-outside-x.js @@ -0,0 +1,219 @@ +const CLICK = 'click'; +const captureInstances = Object.create(null); +const nonCaptureInstances = Object.create(null); +const instancesList = [captureInstances, nonCaptureInstances]; + +/** + * The common event handler for bot capture and non-capture events. + * + * @param {!Object} context - The event context. + * @param {!Object} instances - The capture or non-capture registered instances. + * @param {Event} event - The event object. + * @returns {undefined} Default. + */ +const commonHandler = function _onCommonEvent(context, instances, event) { + const {target} = event; + + const itemIteratee = function _itemIteratee(item) { + const {el} = item; + + if (el !== target && !el.contains(target)) { + const {binding} = item; + + if (binding.modifiers.stop) { + event.stopPropagation(); + } + + if (binding.modifiers.prevent) { + event.preventDefault(); + } + + binding.value.call(context, event); + } + }; + + const keysIteratee = function _keysIteratee(eventName) { + return instances[eventName].forEach(itemIteratee); + }; + + Object.keys(instances).forEach(keysIteratee); +}; + +/** + * Event handler for capture events. + * + * @param {Event} event - The event object. + */ +const captureEventHandler = function onCaptureEvent(event) { + /* eslint-disable-next-line babel/no-invalid-this */ + commonHandler(this, captureInstances, event); +}; + +/** + * Event handler for non-capture events. + * + * @param {Event} event - The event object. + */ +const nonCaptureEventHandler = function onNonCaptureEvent(event) { + /* eslint-disable-next-line babel/no-invalid-this */ + commonHandler(this, nonCaptureInstances, event); +}; + +/** + * Get the correct event handler: Capture or non-capture. + * + * @param {boolean} useCapture - Indicate which handler to use; 'true' to use + * capture handler or 'false' for non-capture. + * @returns {Function} - The event handler. + */ +const getEventHandler = function _getEventHandler(useCapture) { + return useCapture ? captureEventHandler : nonCaptureEventHandler; +}; + +/** + * The directive definition. + * {@link https://vuejs.org/v2/guide/custom-directive.html|Custom directive} + * + * @namespace + * @property {!Object} $_captureInstances - Registered capture instances. + * @property {!Object} $_nonCaptureInstances - Registered non-capture instances. + * @property {Function} $_onCaptureEvent - Event handler for capture events. + * @property {Function} $_onNonCaptureEvent - Event handler for non-capture events. + * @property {Function} bind - Called only once, when the directive is first + * bound to the element. + * @property {Function} unbind - Called only once, when the directive is unbound + * from the element. + * @property {string} version - The version number of this release. + */ +export const directive = Object.defineProperties( + {}, + { + $_captureInstances: { + value: captureInstances, + }, + + $_nonCaptureInstances: { + value: nonCaptureInstances, + }, + + $_onCaptureEvent: { + value: captureEventHandler, + }, + + $_onNonCaptureEvent: { + value: nonCaptureEventHandler, + }, + + /** + * 注意,这里的 arg 修改为 capture,这样可以动态设置,原先的事件作为 modifiers + * */ + bind: { + value: function bind(el, binding) { + if (typeof binding.value !== 'function') { + throw new TypeError('Binding value must be a function.'); + } + + let eventType; + const modifiers = binding.modifiers; + if (modifiers.click) eventType = 'click'; + else if (modifiers.mousedown) eventType = 'mousedown'; + else if (modifiers.touchstart) eventType = 'touchstart'; + else eventType = CLICK; + + const useCapture = binding.arg; + + const normalisedBinding = { + ...binding, + ...{ + modifiers: { + ...{ + capture: false, + prevent: false, + stop: false, + }, + ...binding.modifiers, + }, + }, + }; + + const instances = useCapture ? captureInstances : nonCaptureInstances; + + if (!Array.isArray(instances[eventType])) { + instances[eventType] = []; + } + + if (instances[eventType].push({el, binding: normalisedBinding}) === 1) { + if (typeof document === 'object' && document) { + document.addEventListener( + eventType, + getEventHandler(useCapture), + useCapture, + ); + } + } + }, + }, + + unbind: { + value: function unbind(el) { + const compareElements = function _compareElements(item) { + return item.el !== el; + }; + + const instancesIteratee = function _instancesIteratee(instances) { + const instanceKeys = Object.keys(instances); + + if (instanceKeys.length) { + const useCapture = instances === captureInstances; + + const keysIteratee = function _keysIteratee(eventName) { + const newInstance = instances[eventName].filter(compareElements); + + if (newInstance.length) { + instances[eventName] = newInstance; + } else { + if (typeof document === 'object' && document) { + document.removeEventListener( + eventName, + getEventHandler(useCapture), + useCapture, + ); + } + + delete instances[eventName]; + } + }; + + instanceKeys.forEach(keysIteratee); + } + }; + + instancesList.forEach(instancesIteratee); + }, + }, + + /* Note: This needs to be manually updated to match package.json. */ + version: { + enumerable: true, + value: '3.7.1', + }, + }, +); + +/** + * @typedef {Function} Vue - The constructor. + * @property {Function} directive - You can register a global custom directive + * with the Vue.directive() method, passing in a directiveID followed by a + * definition object. + */ + +/** + * A Vue.js plugin should expose an install method. The method will be called + * with the Vue constructor as the first argument, along with possible options. + * {@link https://vuejs.org/v2/guide/plugins.html#Writing-a-Plugin|Writing a plugin}. + * + * @param {Vue} Vue - The Vue function. + */ +export function install(Vue) { + Vue.directive('click-outside', directive); +} diff --git a/resources/js/common/functions/index.js b/resources/js/common/functions/index.js new file mode 100755 index 00000000..03d41cf1 --- /dev/null +++ b/resources/js/common/functions/index.js @@ -0,0 +1,1549 @@ +/** + * 基础函数 + */ +(function (window, $, undefined) { + + let serverUrl = window.location.origin + '/'; + + /** + * ============================================================================= + * ************************** 基础函数类 ************************** + * ============================================================================= + */ + $.extend({ + /** + * 是否数组 + * @param obj + * @returns {boolean} + */ + isArray(obj) { + return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number"; + }, + + /** + * 是否数组对象 + * @param obj + * @returns {boolean} + */ + isJson(obj) { + return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined"; + }, + + /** + * 身份识别码 + * @param text + * @returns {*|string} + */ + token: function (text) { + let token = this.storage('token') || ''; + if (typeof text === 'string') { + this.storage('token', text); + token = text; + } + return token; + }, + + /** + * 随机获取范围 + * @param Min + * @param Max + * @returns {*} + */ + randNum(Min,Max){ + let Range = Max - Min; + let Rand = Math.random(); + return Min + Math.round(Rand * Range); //四舍五入 + }, + + /** + * 获取数组最后一个值 + * @param array + * @returns {boolean} + */ + last: function (array) { + let str = false; + if (typeof array === 'object' && array.length > 0) { + str = array[array.length - 1]; + } + return str; + }, + + + /** + * 字符串是否包含 + * @param string + * @param find + * @param lower + * @returns {boolean} + */ + strExists: function (string, find, lower = false) { + string += ""; + find += ""; + if (lower !== true) { + string = string.toLowerCase(); + find = find.toLowerCase(); + } + return (string.indexOf(find) !== -1); + }, + + /** + * 字符串是否左边包含 + * @param string + * @param find + * @param lower + * @returns {boolean} + */ + leftExists: function (string, find, lower = false) { + string += ""; + find += ""; + if (lower !== true) { + string = string.toLowerCase(); + find = find.toLowerCase(); + } + return (string.substring(0, find.length) === find); + }, + + /** + * 删除左边字符串 + * @param string + * @param find + * @param lower + * @returns {string} + */ + leftDelete: function (string, find, lower = false) { + string += ""; + find += ""; + if (this.leftExists(string, find, lower)) { + string = string.substring(find.length) + } + return string ? string : ''; + }, + + /** + * 字符串是否右边包含 + * @param string + * @param find + * @param lower + * @returns {boolean} + */ + rightExists: function (string, find, lower = false) { + string += ""; + find += ""; + if (lower !== true) { + string = string.toLowerCase(); + find = find.toLowerCase(); + } + return (string.substring(string.length - find.length) === find); + }, + + /** + * 取字符串中间 + * @param string + * @param start + * @param end + * @returns {*} + */ + getMiddle: function (string, start, end) { + string = string.toString(); + if (this.ishave(start) && this.strExists(string, start)) { + string = string.substring(string.indexOf(start) + start.length); + } + if (this.ishave(end) && this.strExists(string, end)) { + string = string.substring(0, string.indexOf(end)); + } + return string; + }, + + /** + * 截取字符串 + * @param string + * @param start + * @param end + * @returns {string} + */ + subString: function(string, start, end) { + string += ""; + if (!this.ishave(end)) { + end = string.length; + } + return string.substring(start, end); + }, + + /** + * 随机字符 + * @param len + * @returns {string} + */ + randomString: function (len) { + len = len || 32; + let $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1'; + let maxPos = $chars.length; + let pwd = ''; + for (let i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; + }, + + /** + * 判断是否有 + * @param set + * @returns {boolean} + */ + ishave: function (set) { + return !!(set !== null && set !== "null" && set !== undefined && set !== "undefined" && set); + }, + + /** + * 相当于 intval + * @param str + * @param fixed + * @returns {number} + */ + runNum: function (str, fixed) { + let _s = Number(str); + if (_s + "" === "NaN") { + _s = 0; + } + if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) { + _s = _s.toFixed(fixed); + let rs = _s.indexOf('.'); + if (rs < 0) { + _s += "."; + for (let i = 0; i < fixed; i++) { + _s += "0"; + } + } + } + return _s; + }, + + /** + * 服务器地址 + * @param str + * @returns {string} + */ + serverUrl: function (str) { + if (str.substring(0, 2) === "//" || + str.substring(0, 7) === "http://" || + str.substring(0, 8) === "https://" || + str.substring(0, 6) === "ftp://" || + str.substring(0, 1) === "/") { + return str; + } + return serverUrl + str; + }, + + /** + * 获取IP地址详情 + * @param ip + * @param callback + */ + getIpInfo: function(ip, callback) { + if (!this.strExists(ip, ".")) { + return; + } + let keyName = '__ip' + ip.substring(0, 1) + '__'; + let key = this.getMiddle(ip, '', '.'); + let res = this.loadFromlLocal(key, ip, '', keyName); + if (typeof res == "object") { + if (typeof callback == "function") { + callback(res); + } + return; + } + $A.ajaxc({ + url: $A.serverUrl('api/system/get/ipinfo'), + data: { ip: ip }, + timeout: 8000, + success: (res) => { + this.savaToLocal(key, ip, res, keyName); + if (typeof callback == "function") { + callback(res); + } + } + }); + }, + + /** + * 新增&&获取缓存数据 + * @param key + * @param value + * @returns {*} + */ + storage: function(key, value) { + let keyName = 'app'; + switch (window.location.pathname) { + case "/admin": + keyName+= ":" + window.location.pathname.substr(1); + break; + } + if (typeof value === 'undefined') { + return this.loadFromlLocal('__::', key, '', '__' + keyName + '__'); + }else{ + this.savaToLocal('__::', key, value, '__' + keyName + '__'); + } + }, + + /** + * 新增&&修改本地缓存 + * @param {string} id 唯一id + * @param {string} key 标示 + * @param value 新增&修改的值 + * @param keyName 主键名称 + */ + savaToLocal: function(id, key, value, keyName) { + try { + if (typeof keyName === 'undefined') keyName = '__seller__'; + let seller = window.localStorage[keyName]; + if (!seller) { + seller = {}; + seller[id] = {}; + } else { + seller = JSON.parse(seller); + if (!seller[id]) { + seller[id] = {}; + } + } + seller[id][key] = value; + window.localStorage[keyName] = JSON.stringify(seller); + } catch(e) { } + }, + + /** + * 查询本地缓存 + * @param {string} id 唯一id + * @param {string} key 标示 + * @param def 如果查询不到显示的值 + * @param keyName 主键名称 + */ + loadFromlLocal: function(id, key, def, keyName) { + if (typeof keyName === 'undefined') keyName = '__seller__'; + let seller = window.localStorage[keyName]; + if (!seller) { + return def; + } + seller = JSON.parse(seller)[id]; + if (!seller) { + return def; + } + let ret = seller[key]; + return ret || def; + }, + + /** + * 补零 + * @param str + * @param length + * @param after + * @returns {*} + */ + zeroFill: function(str, length, after) { + str+= ""; + if (str.length >= length) { + return str; + } + let _str = '', _ret = ''; + for (let i = 0; i < length; i++) { + _str += '0'; + } + if (after || typeof after === 'undefined') { + _ret = (_str + "" + str).substr(length * -1); + } else { + _ret = (str + "" + _str).substr(0, length); + } + return _ret; + }, + + /** + * 时间戳转时间格式 + * @param format + * @param v + * @returns {string} + */ + formatDate: function(format, v) { + if (format === '') { + format = 'Y-m-d H:i:s'; + } + let dateObj; + if (v instanceof Date) { + dateObj = v; + }else { + if (typeof v === 'undefined') { + v = new Date().getTime(); + }else if (/^(-)?\d{1,10}$/.test(v)) { + v = v * 1000; + } else if (/^(-)?\d{1,13}$/.test(v)) { + v = v * 1000; + } else if (/^(-)?\d{1,14}$/.test(v)) { + v = v * 100; + } else if (/^(-)?\d{1,15}$/.test(v)) { + v = v * 10; + } else if (/^(-)?\d{1,16}$/.test(v)) { + v = v * 1; + } else { + return v; + } + dateObj = new Date(v); + } + // + format = format.replace(/Y/g, dateObj.getFullYear()); + format = format.replace(/m/g, this.zeroFill(dateObj.getMonth() + 1, 2)); + format = format.replace(/d/g, this.zeroFill(dateObj.getDate(), 2)); + format = format.replace(/H/g, this.zeroFill(dateObj.getHours(), 2)); + format = format.replace(/i/g, this.zeroFill(dateObj.getMinutes(), 2)); + format = format.replace(/s/g, this.zeroFill(dateObj.getSeconds(), 2)); + return format; + }, + + /** + * 租用时间差(不够1个小时算一个小时) + * @param s + * @param e + * @returns {*} + */ + timeDiff: function(s, e) { + if (typeof e === 'undefined') { + e = Math.round(new Date().getTime()/1000); + } + let d = e - s; + if (d > 86400) { + let day = Math.floor(d / 86400); + let hour = Math.ceil((d - (day * 86400)) / 3600); + if (hour > 0) { + return day + '天' + hour + '小时'; + } else { + return day + '天'; + } + } else if (d > 3600) { + return Math.ceil(d / 3600) + '小时'; + } else if (d > 60) { + return Math.ceil(d / 60) + '分钟'; + } else if (d > 10) { + return d + '秒'; + } else { + return '刚刚'; + } + }, + + /** + * 检测手机号码格式 + * @param str + * @returns {boolean} + */ + isMobile: function(str) { + return /^1([3456789])\d{9}$/.test(str); + }, + + /** + * 是否手机号码 + * @param phone + * @returns {boolean} + */ + isPhone: function (phone) { + return this.isMobile(phone); + }, + + /** + * 根据两点间的经纬度计算距离 + * @param lng1 + * @param lat1 + * @param lng2 + * @param lat2 + * @returns {string|*} + */ + getDistance: function (lng1, lat1, lng2, lat2) { + let DEF_PI = 3.14159265359; // PI + let DEF_2PI = 6.28318530712; // 2*PI + let DEF_PI180 = 0.01745329252; // PI/180.0 + let DEF_R = 6370693.5; // radius of earth + // + let ew1, ns1, ew2, ns2; + let dx, dy, dew; + let distance; + // 角度转换为弧度 + ew1 = lng1 * DEF_PI180; + ns1 = lat1 * DEF_PI180; + ew2 = lng2 * DEF_PI180; + ns2 = lat2 * DEF_PI180; + // 经度差 + dew = ew1 - ew2; + // 若跨东经和西经180 度,进行调整 + if (dew > DEF_PI) + dew = DEF_2PI - dew; + else if (dew < -DEF_PI) + dew = DEF_2PI + dew; + dx = DEF_R * Math.cos(ns1) * dew; // 东西方向长度(在纬度圈上的投影长度) + dy = DEF_R * (ns1 - ns2); // 南北方向长度(在经度圈上的投影长度) + // 勾股定理求斜边长 + distance = Math.sqrt(dx * dx + dy * dy).toFixed(0); + return distance; + }, + + /** + * 设置网页标题 + * @param title + */ + setTile(title) { + document.title = title; + let mobile = navigator.userAgent.toLowerCase(); + if (/iphone|ipad|ipod/.test(mobile)) { + let iframe = document.createElement('iframe'); + iframe.style.display = 'none'; + iframe.setAttribute('src', '/favicon.ico'); + let iframeCallback = function () { + setTimeout(function () { + iframe.removeEventListener('load', iframeCallback); + document.body.removeChild(iframe) + }, 0) + }; + iframe.addEventListener('load', iframeCallback); + document.body.appendChild(iframe) + } + }, + + /** + * 克隆对象 + * @param myObj + * @returns {*} + */ + cloneData(myObj) { + if(typeof(myObj) !== 'object') return myObj; + if(myObj === null) return myObj; + // + if (typeof myObj.length === 'number') { + let [ ...myNewObj ] = myObj; + return myNewObj; + }else{ + let { ...myNewObj } = myObj; + return myNewObj; + } + }, + + /** + * 克隆对象 + * @param myObj + * @returns {*} + */ + cloneJSON(myObj) { + if(typeof(myObj) !== 'object') return myObj; + if(myObj === null) return myObj; + // + return $A.jsonParse($A.jsonStringify(myObj)) + }, + + /** + * 将一个 JSON 字符串转换为对象(已try) + * @param str + * @param defaultVal + * @returns {*} + */ + jsonParse(str, defaultVal) { + if (str === null) { + return defaultVal ? defaultVal : {}; + } + if (typeof str === "object") { + return str; + } + try { + return JSON.parse(str.replace(/\n/g,"\\n").replace(/\r/g,"\\r")); + } catch (e) { + return defaultVal ? defaultVal : {}; + } + }, + + /** + * 将 JavaScript 值转换为 JSON 字符串(已try) + * @param json + * @param defaultVal + * @returns {string} + */ + jsonStringify(json, defaultVal) { + if (typeof json !== 'object') { + return json; + } + try{ + return JSON.stringify(json); + }catch (e) { + return defaultVal ? defaultVal : ""; + } + }, + + /** + * 监听对象尺寸发生改变 + * @param obj + * @param callback + */ + resize(obj, callback) { + let myObj = $A(obj); + if (myObj.length === 0) return; + let height = parseInt(myObj.outerHeight()), + width = parseInt(myObj.outerWidth()); + let inter = setInterval(()=>{ + if (myObj.length === 0) clearInterval(inter); + let tmpHeight = parseInt(myObj.outerHeight()), + tmpWidth = parseInt(myObj.outerWidth()); + if (height !== tmpHeight || width !== tmpWidth) { + height = tmpHeight; + width = tmpWidth; + console.log(width, height); + if (typeof callback === 'function') callback(); + } + }, 250); + }, + + /** + * 是否IOS + * @returns {boolean|string} + */ + isIos() { + let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase(); + return ua && /iphone|ipad|ipod|ios/.test(ua); + }, + + /** + * 是否安卓 + * @returns {boolean|string} + */ + isAndroid() { + let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase(); + return ua && ua.indexOf('android') > 0; + }, + + /** + * 是否微信 + * @returns {boolean} + */ + isWeixin() { + let ua = typeof window !== 'undefined' && window.navigator.userAgent.toLowerCase(); + return (ua.match(/MicroMessenger/i) + '' === 'micromessenger'); + }, + + /** + * 获取对象 + * @param obj + * @param keys + * @returns {string|*} + */ + getObject(obj, keys) { + let object = obj; + if (this.count(obj) === 0 || this.count(keys) === 0) { + return ""; + } + let arr = keys.replace(/,/g, "|").replace(/\./g, "|").split("|"); + $A.each(arr, (index, key) => { + object = typeof object[key] === "undefined" ? "" : object[key]; + }); + return object; + }, + + /** + * 统计数组或对象长度 + * @param obj + * @returns {number} + */ + count(obj) { + try { + if (typeof obj === "undefined") { + return 0; + } + if (typeof obj === "number") { + obj+= ""; + } + if (typeof obj.length === 'number') { + return obj.length; + } else { + let i = 0, key; + for (key in obj) { + i++; + } + return i; + } + }catch (e) { + return 0; + } + }, + + /** + * 将数组或对象内容部分拼成字符串 + * @param obj + * @returns {string} + */ + objImplode(obj) { + if (obj === null) { + return ""; + } + let str = ""; + $A.each(obj, (key, val) => { + if (val !== null) { + if (typeof val === "object" && this.count(val) > 0) { + str += this.objImplode(val); + } else { + str += String(val); + } + } + }); + return str.replace(/\s/g, "").replace(/undefined/g, ""); + }, + + /** + * 指定键获取url参数 + * @param key + * @returns {*} + */ + urlParameter(key) { + let params = this.urlParameterAll(); + return typeof key === "undefined" ? params : params[key]; + }, + + urlParameterAll() { + let search = window.location.search || ""; + let arr = []; + if (this.strExists(search, "?")) { + arr = this.getMiddle(search, "?").split("&"); + } + let params = {}; + for (let i = 0; i < arr.length; i++) { + let data = arr[i].split("="); + if (data.length === 2) { + params[data[0]] = data[1]; + } + } + return params; + }, + + /** + * 删除地址中的参数 + * @param url + * @param parameter + * @returns {string|*} + */ + removeURLParameter(url, parameter) { + if (parameter instanceof Array) { + parameter.forEach((key) => { + url = $A.removeURLParameter(url, key) + }); + return url; + } + var urlparts = url.split('?'); + if (urlparts.length >= 2) { + //参数名前缀 + var prefix = encodeURIComponent(parameter) + '='; + var pars = urlparts[1].split(/[&;]/g); + + //循环查找匹配参数 + for (var i = pars.length; i-- > 0;) { + if (pars[i].lastIndexOf(prefix, 0) !== -1) { + //存在则删除 + pars.splice(i, 1); + } + } + + return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : ''); + } + return url; + }, + + /** + * 连接加上参数 + * @param url + * @param params + * @returns {*} + */ + urlAddParams(url, params) { + if (typeof params === "object" && params !== null) { + url+= ""; + url+= url.indexOf("?") === -1 ? '?' : ''; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + url+= '&' + key + '=' + params[key]; + } + } + return url.replace("?&", "?"); + }, + + /** + * 链接字符串 + * @param value 第一个参数为连接符 + * @returns {string} + */ + stringConnect(...value) { + let s = null; + let text = ""; + value.forEach((val) => { + if (s === null) { + s = val; + }else if (val){ + if (val && text) text+= s; + text+= val; + } + }); + return text; + }, + + /** + * 判断两个对象是否相等 + * @param x + * @param y + * @returns {boolean} + */ + objEquals(x, y) { + let f1 = x instanceof Object; + let f2 = y instanceof Object; + if (!f1 || !f2) { + return x === y + } + if (Object.keys(x).length !== Object.keys(y).length) { + return false + } + for (let p in x) { + if (x.hasOwnProperty(p)) { + let a = x[p] instanceof Object; + let b = y[p] instanceof Object; + if (a && b) { + if (!this.objEquals(x[p], y[p])) { + return false; + } + } else if (x[p] != y[p]) { + return false; + } + } + } + return true; + }, + + /** + * 输入框内插入文本 + * @param object + * @param content + */ + insert2Input (object, content) { + if (object === null || typeof object !== "object") return; + if (typeof object.length === 'number' && object.length > 0) object = object[0]; + + let ele = typeof object.$el === "object" ? $A(object.$el) : $A(object); + if (ele.length === 0) return; + let eleDom = ele[0]; + + if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") { + if (ele.find("input").length === 0) { + ele = ele.find("textarea"); + }else{ + ele = ele.find("input"); + } + } + if (ele.length === 0) return; + eleDom = ele[0]; + + if (eleDom.tagName != "INPUT" && eleDom.tagName != "TEXTAREA") return; + + let text = ele.val(); + let { selectionStart, selectionEnd } = eleDom; + + ele.val(`${text.substring(0, selectionStart)}${content}${text.substring(selectionEnd, text.length)}`); + eleDom.dispatchEvent(new Event('input')); + + setTimeout(() => { + if (eleDom.setSelectionRange) { + let pos = text.substring(0, selectionStart).length + content.length; + eleDom.focus(); + eleDom.setSelectionRange(pos, pos); + } + }, 10); + }, + + /** + * iOS上虚拟键盘引起的触控错位 + */ + iOSKeyboardFixer() { + if (!this.isIos()) { + return; + } + document.body.scrollTop = document.body.scrollTop + 1; + document.body.scrollTop = document.body.scrollTop - 1; + }, + + autoDevwid(width) { + let _width = width || 640; + new function () { + let _self = this; + _self.width = _width; //设置默认最大宽度 + _self.fontSize = 30; //默认字体大小 + _self.widthProportion = function () { + let p = (document.body && document.body.clientWidth || document.getElementsByTagName("html")[0].offsetWidth) / _self.width; + return p > 1 ? 1 : p < 0.38 ? 0.38 : p; + }; + _self.changePage = function () { + document.getElementsByTagName("html")[0].setAttribute("style", "font-size:" + _self.widthProportion() * _self.fontSize + "px !important"); + }; + _self.changePage(); + window.addEventListener('resize', function () { + _self.changePage(); + }, false); + }; + // + let scale = $A(window).width() / _width; + $A(".__auto").each(function () { + if ($A(this).attr("data-original") !== "1") { + $A(this).attr("data-original-top", parseInt($A(this).css("top"))); + $A(this).attr("data-original-right", parseInt($A(this).css("right"))); + $A(this).attr("data-original-bottom", parseInt($A(this).css("bottom"))); + $A(this).attr("data-original-left", parseInt($A(this).css("left"))); + $A(this).attr("data-original-width", parseInt($A(this).css("width"))); + $A(this).attr("data-original-height", parseInt($A(this).css("height"))); + $A(this).attr("data-original-line-height", parseInt($A(this).css("line-height"))); + $A(this).attr("data-original", "1"); + } + let _t = parseInt($A(this).attr("data-original-top")); + let _r = parseInt($A(this).attr("data-original-right")); + let _b = parseInt($A(this).attr("data-original-bottom")); + let _l = parseInt($A(this).attr("data-original-left")); + let _w = parseInt($A(this).attr("data-original-width")); + let _h = parseInt($A(this).attr("data-original-height")); + let _lh = parseInt($A(this).attr("data-original-line-height")); + // + let _css = {}; + if (_t > 0) _css['top'] = _t * scale; + if (_r > 0) _css['right'] = _r * scale; + if (_b > 0) _css['bottom'] = _b * scale; + if (_l > 0) _css['left'] = _l * scale; + if (_w > 0) _css['width'] = _w * scale; + if (_h > 0) _css['height'] = _h * scale; + if (_lh > 0) _css['line-height'] = (_lh * scale) + 'px'; + $A(this).css(_css); + }); + return scale; + } + + }); + + /** + * ============================================================================= + * **************************** ihttp **************************** + * ============================================================================= + */ + $.extend({ + + serializeObject (obj, parents) { + if (typeof obj === 'string') return obj; + let resultArray = []; + let separator = '&'; + parents = parents || []; + let newParents; + + function var_name(name) { + if (parents.length > 0) { + let _parents = ''; + for (let j = 0; j < parents.length; j++) { + if (j === 0) _parents += parents[j]; + else _parents += '[' + encodeURIComponent(parents[j]) + ']'; + } + return _parents + '[' + encodeURIComponent(name) + ']'; + } + else { + return encodeURIComponent(name); + } + } + + function var_value(value) { + return encodeURIComponent(value); + } + + for (let prop in obj) { + if (obj.hasOwnProperty(prop)) { + let toPush; + if (Array.isArray(obj[prop])) { + toPush = []; + for (let i = 0; i < obj[prop].length; i++) { + if (!Array.isArray(obj[prop][i]) && typeof obj[prop][i] === 'object') { + newParents = parents.slice(); + newParents.push(prop); + newParents.push(i + ''); + toPush.push($.serializeObject(obj[prop][i], newParents)); + } + else { + toPush.push(var_name(prop) + '[]=' + var_value(obj[prop][i])); + } + + } + if (toPush.length > 0) resultArray.push(toPush.join(separator)); + } + else if (obj[prop] === null) { + resultArray.push(var_name(prop) + '='); + } + else if (typeof obj[prop] === 'object') { + // Object, convert to named array + newParents = parents.slice(); + newParents.push(prop); + toPush = $.serializeObject(obj[prop], newParents); + if (toPush !== '') resultArray.push(toPush); + } + else if (typeof obj[prop] !== 'undefined' && obj[prop] !== '') { + // Should be string or plain value + resultArray.push(var_name(prop) + '=' + var_value(obj[prop])); + } + else if (obj[prop] === '') resultArray.push(var_name(prop)); + } + } + return resultArray.join(separator); + }, + + // Global Ajax Setup + globalAjaxOptions: {}, + ajaxSetup (options) { + if (options.type) options.method = options.type; + $.each(options, function (optionName, optionValue) { + $.globalAjaxOptions[optionName] = optionValue; + }); + }, + + // Ajax + _jsonpRequests: 0, + ihttp(options) { + let defaults = { + method: 'GET', + data: false, + async: true, + cache: true, + user: '', + password: '', + headers: {}, + xhrFields: {}, + statusCode: {}, + processData: true, + dataType: 'text', + contentType: 'application/x-www-form-urlencoded', + timeout: 0 + }; + let callbacks = ['beforeSend', 'error', 'complete', 'success', 'statusCode']; + + + //For jQuery guys + if (options.type) options.method = options.type; + + // Merge global and defaults + $.each($.globalAjaxOptions, function (globalOptionName, globalOptionValue) { + if (callbacks.indexOf(globalOptionName) < 0) defaults[globalOptionName] = globalOptionValue; + }); + + // Function to run XHR callbacks and events + function fireAjaxCallback(eventName, eventData, callbackName) { + let a = arguments; + if (eventName) $(document).trigger(eventName, eventData); + if (callbackName) { + // Global callback + if (callbackName in $.globalAjaxOptions) $.globalAjaxOptions[callbackName](a[3], a[4], a[5], a[6]); + // Options callback + if (options[callbackName]) options[callbackName](a[3], a[4], a[5], a[6]); + } + } + + // Merge options and defaults + $.each(defaults, function (prop, defaultValue) { + if (!(prop in options)) options[prop] = defaultValue; + }); + + // Default URL + if (!options.url) { + options.url = window.location.toString(); + } + // Parameters Prefix + let paramsPrefix = options.url.indexOf('?') >= 0 ? '&' : '?'; + + // UC method + let _method = options.method.toUpperCase(); + // Data to modify GET URL + if ((_method === 'GET' || _method === 'HEAD' || _method === 'OPTIONS' || _method === 'DELETE') && options.data) { + let stringData; + if (typeof options.data === 'string') { + // Should be key=value string + if (options.data.indexOf('?') >= 0) stringData = options.data.split('?')[1]; + else stringData = options.data; + } + else { + // Should be key=value object + stringData = $.serializeObject(options.data); + } + if (stringData.length) { + options.url += paramsPrefix + stringData; + if (paramsPrefix === '?') paramsPrefix = '&'; + } + } + // JSONP + if (options.dataType === 'json' && options.url.indexOf('callback=') >= 0) { + + let callbackName = 'f7jsonp_' + Date.now() + ($._jsonpRequests++); + let abortTimeout; + let callbackSplit = options.url.split('callback='); + let requestUrl = callbackSplit[0] + 'callback=' + callbackName; + if (callbackSplit[1].indexOf('&') >= 0) { + let addVars = callbackSplit[1].split('&').filter(function (el) { + return el.indexOf('=') > 0; + }).join('&'); + if (addVars.length > 0) requestUrl += '&' + addVars; + } + + // Create script + let script = document.createElement('script'); + script.type = 'text/javascript'; + script.onerror = function () { + clearTimeout(abortTimeout); + fireAjaxCallback(undefined, undefined, 'error', null, 'scripterror'); + fireAjaxCallback('ajaxComplete ajax:complete', {scripterror: true}, 'complete', null, 'scripterror'); + }; + script.src = requestUrl; + + // Handler + window[callbackName] = function (data) { + clearTimeout(abortTimeout); + fireAjaxCallback(undefined, undefined, 'success', data); + script.parentNode.removeChild(script); + script = null; + delete window[callbackName]; + }; + document.querySelector('head').appendChild(script); + + if (options.timeout > 0) { + abortTimeout = setTimeout(function () { + script.parentNode.removeChild(script); + script = null; + fireAjaxCallback(undefined, undefined, 'error', null, 'timeout'); + }, options.timeout); + } + + return; + } + + // Cache for GET/HEAD requests + if (_method === 'GET' || _method === 'HEAD' || _method === 'OPTIONS' || _method === 'DELETE') { + if (options.cache === false) { + options.url += (paramsPrefix + '_nocache=' + Date.now()); + } + } + + // Create XHR + let xhr = new XMLHttpRequest(); + + // Save Request URL + xhr.requestUrl = options.url; + xhr.requestParameters = options; + + // Open XHR + xhr.open(_method, options.url, options.async, options.user, options.password); + + // Create POST Data + let postData = null; + + if ((_method === 'POST' || _method === 'PUT' || _method === 'PATCH') && options.data) { + if (options.processData) { + let postDataInstances = [ArrayBuffer, Blob, Document, FormData]; + // Post Data + if (postDataInstances.indexOf(options.data.constructor) >= 0) { + postData = options.data; + } + else { + // POST Headers + let boundary = '---------------------------' + Date.now().toString(16); + + if (options.contentType === 'multipart\/form-data') { + xhr.setRequestHeader('Content-Type', 'multipart\/form-data; boundary=' + boundary); + } + else { + xhr.setRequestHeader('Content-Type', options.contentType); + } + postData = ''; + let _data = $.serializeObject(options.data); + if (options.contentType === 'multipart\/form-data') { + boundary = '---------------------------' + Date.now().toString(16); + _data = _data.split('&'); + let _newData = []; + for (let i = 0; i < _data.length; i++) { + _newData.push('Content-Disposition: form-data; name="' + _data[i].split('=')[0] + '"\r\n\r\n' + _data[i].split('=')[1] + '\r\n'); + } + postData = '--' + boundary + '\r\n' + _newData.join('--' + boundary + '\r\n') + '--' + boundary + '--\r\n'; + } + else { + postData = _data; + } + } + } + else { + postData = options.data; + } + + } + + // Additional headers + if (options.headers) { + $.each(options.headers, function (headerName, headerCallback) { + xhr.setRequestHeader(headerName, headerCallback); + }); + } + + // Check for crossDomain + if (typeof options.crossDomain === 'undefined') { + options.crossDomain = /^([\w-]+:)?\/\/([^\/]+)/.test(options.url) && RegExp.$2 !== window.location.host; + } + + if (!options.crossDomain) { + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + } + + if (options.xhrFields) { + $.each(options.xhrFields, function (fieldName, fieldValue) { + xhr[fieldName] = fieldValue; + }); + } + + let xhrTimeout; + // Handle XHR + xhr.onload = function (e) { + if (xhrTimeout) clearTimeout(xhrTimeout); + if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 0) { + let responseData; + if (options.dataType === 'json') { + try { + responseData = JSON.parse(xhr.responseText); + fireAjaxCallback('ajaxSuccess ajax:success', {xhr: xhr}, 'success', responseData, xhr.status, xhr); + } + catch (err) { + fireAjaxCallback('ajaxError ajax:error', { + xhr: xhr, + parseerror: true + }, 'error', xhr, 'parseerror'); + } + } + else { + responseData = xhr.responseType === 'text' || xhr.responseType === '' ? xhr.responseText : xhr.response; + fireAjaxCallback('ajaxSuccess ajax:success', {xhr: xhr}, 'success', responseData, xhr.status, xhr); + } + } + else { + fireAjaxCallback('ajaxError ajax:error', {xhr: xhr}, 'error', xhr, xhr.status); + } + if (options.statusCode) { + if ($.globalAjaxOptions.statusCode && $.globalAjaxOptions.statusCode[xhr.status]) $.globalAjaxOptions.statusCode[xhr.status](xhr); + if (options.statusCode[xhr.status]) options.statusCode[xhr.status](xhr); + } + fireAjaxCallback('ajaxComplete ajax:complete', {xhr: xhr}, 'complete', xhr, xhr.status); + }; + + xhr.onerror = function (e) { + if (xhrTimeout) clearTimeout(xhrTimeout); + fireAjaxCallback('ajaxError ajax:error', {xhr: xhr}, 'error', xhr, xhr.status); + fireAjaxCallback('ajaxComplete ajax:complete', {xhr: xhr, error: true}, 'complete', xhr, 'error'); + }; + + // Ajax start callback + fireAjaxCallback('ajaxStart ajax:start', {xhr: xhr}, 'start', xhr); + fireAjaxCallback(undefined, undefined, 'beforeSend', xhr); + + // Timeout + if (options.timeout > 0) { + xhr.onabort = function () { + if (xhrTimeout) clearTimeout(xhrTimeout); + }; + xhrTimeout = setTimeout(function () { + xhr.abort(); + fireAjaxCallback('ajaxError ajax:error', {xhr: xhr, timeout: true}, 'error', xhr, 'timeout'); + fireAjaxCallback('ajaxComplete ajax:complete', { + xhr: xhr, + timeout: true + }, 'complete', xhr, 'timeout'); + }, options.timeout); + } + + // Send XHR + xhr.send(postData); + + // Return XHR object + return xhr; + } + }); + + /** + * ============================================================================= + * ***************************** ajaxc **************************** + * ============================================================================= + */ + $.extend({ + + ajaxc(params) { + if (!params) return false; + if (typeof params.url === 'undefined') return false; + if (typeof params.data === 'undefined') params.data = {}; + if (typeof params.cache === 'undefined') params.cache = false; + if (typeof params.method === 'undefined') params.method = 'GET'; + if (typeof params.timeout === 'undefined') params.timeout = 30000; + if (typeof params.dataType === 'undefined') params.dataType = 'json'; + if (typeof params.beforeSend === 'undefined') params.beforeSend = () => { }; + if (typeof params.complete === 'undefined') params.complete = () => { }; + if (typeof params.afterComplete === 'undefined') params.afterComplete = () => { }; + if (typeof params.success === 'undefined') params.success = () => { }; + if (typeof params.error === 'undefined') params.error = () => { }; + // + let loadText = "数据加载中....."; + let busyNetwork = "网络繁忙,请稍后再试!"; + if (typeof $A.app === 'object' && typeof $A.app.$L === 'function') { + loadText = $A.app.$L(loadText); + busyNetwork = $A.app.$L(busyNetwork); + } + // + let toastID = null, beforeTitle = '', errorTitle = ''; + if (typeof $A.app === 'object' && typeof $A.app.$Message === 'object') { + if (typeof params.beforeSend === 'string') { + beforeTitle = params.beforeSend; + params.beforeSend = () => { toastID = $A.app.$Message.loading({content:beforeTitle, duration: 0}); }; + }else if (params.beforeSend === true) { + params.beforeSend = () => { toastID = $A.app.$Message.loading({content:loadText, duration: 0}); }; + } + if (typeof params.error === 'string') { + errorTitle = params.error; + params.error = () => { $A.app.$Message.error({content:errorTitle, duration: 5}); }; + }else if (params.error === true) { + params.error = () => { $A.app.$Message.error({content:busyNetwork, duration: 5}); }; + } + if (params.complete === true) { + params.complete = () => { toastID?toastID():'' }; + } + }else{ + if (typeof params.beforeSend === 'string') { + beforeTitle = params.beforeSend; + params.beforeSend = () => { toastID = $A.toast({title:beforeTitle, fixed: true, timeout: false}); }; + }else if (params.beforeSend === true) { + params.beforeSend = () => { toastID = $A.toast({title:loadText, fixed: true, timeout: false}); }; + } + if (typeof params.error === 'string') { + errorTitle = params.error; + params.error = () => { $A.toast(errorTitle, "danger"); }; + }else if (params.error === true) { + params.error = () => { $A.toast(busyNetwork, "danger"); }; + } + if (params.complete === true) { + params.complete = () => { toastID?$A.toast(toastID):'' }; + } + } + // + let language = window.localStorage['__language:type__'] || 'en'; + if (typeof $A.app === 'object') { + language = $A.app.languageType || language; + } + // + if (typeof params.header !== 'object') params.header = {}; + params.header['Content-Type'] = 'application/json'; + params.header['language'] = language; + params.header['token'] = $A.token(); + // + params.data['__Access-Control-Allow-Origin'] = true; + params.beforeSend(); + $A.ihttp({ + url: params.url, + data: params.data, + cache: params.cache, + headers: params.header, + method: params.method.toUpperCase(), + contentType: "OPTIONS", + crossDomain: true, + dataType: params.dataType, + timeout: params.timeout, + success: function(data, status, xhr) { + params.complete(); + params.success(data, status, xhr); + params.afterComplete(true); + }, + error: function(xhr, status) { + params.complete(); + params.error(xhr, status); + params.afterComplete(false); + } + }); + } + }); + + + /** + * ============================================================================= + * ***************************** manage assist **************************** + * ============================================================================= + */ + $.extend({ + + /** + * 对象中有Date格式的转成指定格式 + * @param myObj + * @param format 默认格式:Y-m-d + * @returns {*} + */ + date2string(myObj, format) { + if (myObj === null) { + return myObj; + } + if (typeof format === "undefined") { + format = "Y-m-d"; + } + if (typeof myObj === "object") { + if (myObj instanceof Date) { + return $A.formatDate(format, myObj); + } + $A.each(myObj, (key, val)=>{ + myObj[key] = $A.date2string(val, format); + }); + return myObj; + } + return myObj; + }, + + /** + * 获取一些指定时间 + * @param str + * @param retInt + * @returns {*|string} + */ + getData(str, retInt = false) { + let now = new Date(); //当前日期 + let nowDayOfWeek = now.getDay(); //今天本周的第几天 + let nowDay = now.getDate(); //当前日 + let nowMonth = now.getMonth(); //当前月 + let nowYear = now.getYear(); //当前年 + nowYear += (nowYear < 2000) ? 1900 : 0; + let lastMonthDate = new Date(); //上月日期 + lastMonthDate.setDate(1); + lastMonthDate.setMonth(lastMonthDate.getMonth()-1); + let lastMonth = lastMonthDate.getMonth(); + let getQuarterStartMonth = () => { + let quarterStartMonth = 0; + if(nowMonth < 3) { + quarterStartMonth = 0; + } + if (2 < nowMonth && nowMonth < 6) { + quarterStartMonth = 3; + } + if (5 < nowMonth && nowMonth < 9) { + quarterStartMonth = 6; + } + if (nowMonth > 8) { + quarterStartMonth = 9; + } + return quarterStartMonth; + }; + let getMonthDays = (myMonth) => { + let monthStartDate = new Date(nowYear, myMonth, 1); + let monthEndDate = new Date(nowYear, myMonth + 1, 1); + return (monthEndDate - monthStartDate)/(1000 * 60 * 60 * 24); + }; + // + let time = now.getTime(); + switch (str) { + case '今天': + time = now; + break; + case '昨天': + time = now - 86400000; + break; + case '前天': + time = now - 86400000 * 2; + break; + case '本周': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek); + break; + case '本周结束': + time = new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek)); + break; + case '上周': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 7); + break; + case '上周结束': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 1); + break; + case '本周2': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek + 1); + break; + case '本周结束2': + time = new Date(nowYear, nowMonth, nowDay + (6 - nowDayOfWeek) + 1); + break; + case '上周2': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 7 + 1); + break; + case '上周结束2': + time = new Date(nowYear, nowMonth, nowDay - nowDayOfWeek - 1 + 1); + break; + case '本月': + time = new Date(nowYear, nowMonth, 1); + break; + case '本月结束': + time = new Date(nowYear, nowMonth, getMonthDays(nowMonth)); + break; + case '上个月': + time = new Date(nowYear, lastMonth, 1); + break; + case '上个月结束': + time = new Date(nowYear, lastMonth, getMonthDays(lastMonth)); + break; + case '本季度': + time = new Date(nowYear, getQuarterStartMonth(), 1); + break; + case '本季度结束': + let quarterEndMonth = getQuarterStartMonth() + 2; + time = new Date(nowYear, quarterEndMonth, getMonthDays(quarterEndMonth)); + break; + } + if (retInt === true) { + return time; + } + return $A.formatDate("Y-m-d", parseInt(time / 1000)) + }, + + /** + * 字节转换 + * @param bytes + * @returns {string} + */ + bytesToSize(bytes) { + if (bytes === 0) return '0 B'; + let k = 1024; + let sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + let i = Math.floor(Math.log(bytes) / Math.log(k)); + if (typeof sizes[i] === "undefined") { + return '0 B'; + } + return $A.runNum((bytes / Math.pow(k, i)), 2) + ' ' + sizes[i]; + }, + + /** + * html代码转义 + * @param sHtml + * @returns {*} + */ + html2Escape(sHtml) { + if (!sHtml || sHtml == '') { + return ''; + } + return sHtml.replace(/[<>&"]/g, function (c) { + return {'<': '<', '>': '>', '&': '&', '"': '"'}[c]; + }); + }, + + /** + * 搜索高亮 + * @param string + * @param key + * @returns {string|*} + */ + sreachHighlight(string, key) { + if (!string || string == '') { + return ''; + } + if (!key || key == '') { + return $A.html2Escape(string); + } + string = $A.html2Escape(string.replace(new RegExp(key, "g"), "[highlight]" + key + "[/highlight]")); + string = string.replace(/\[highlight\]/g, ''); + string = string.replace(/\[\/highlight\]/g, ''); + return string; + }, + }); + + window.$A = $; +})(window, window.jQuery); diff --git a/resources/js/common/language/index.js b/resources/js/common/language/index.js new file mode 100644 index 00000000..336ff217 --- /dev/null +++ b/resources/js/common/language/index.js @@ -0,0 +1,233 @@ +const languageTypeLists = { + "EN": "English", + "KM": "ភាសាខ្មែរ", + "TH": "ภาษาไทย", + "KO": "한국어", + "JA": "日本語", + "CN": "中文-简体", + "TC": "中文-繁體", +}; +const languageCachesObjects = {}; +const languageListenerObjects = []; + +export default { + install(Vue) { + Vue.mixin({ + data() { + return { + languageInit: false, + languageData: [], + languageType: window.localStorage['__language:type__'] || this.__getNavigatorLanguage(), + languageList: languageTypeLists, + } + }, + + watch: { + languageType: { + handler(type) { + if (type && typeof this.initLanguage === "function") { + this.initLanguage(); + } + }, + immediate: true + }, + }, + + methods: { + /** + * 获取浏览器默认语言 + * @returns {string} + * @private + */ + __getNavigatorLanguage() { + let lang = 'EN'; + let navLang = (navigator.language || navigator.userLanguage + "").toUpperCase(); + switch (navLang) { + case "EN": + case "KM": + case "TH": + case "KO": + case "JA": + lang = navLang + break; + case "ZH-CN": + case "ZH": + lang = 'CN' + break; + case "ZH-TW": + case "ZH-HK": + lang = 'TC' + break; + } + return window.localStorage['__language:type__'] = lang; + }, + + /** + * 初始化语言数据 + * @private + */ + __initLanguageData() { + if (this.languageInit === false) { + this.languageInit = true; + // + this.addLanguageData(require("../../../lang/language.js").default); + // + languageListenerObjects.push((lang) => { + this.languageType = lang; + }); + } + }, + + /** + * 是否数组 + * @param obj + * @returns {boolean} + * @private + */ + __isArray(obj) { + return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number"; + }, + + /** + * 监听语言变化 + * @param callback + */ + setLanguageListener(callback) { + if (typeof callback === 'function') { + languageListenerObjects.push((lang) => { + callback(lang); + }); + } + }, + + /** + * 语言包数据 + * @param data + */ + addLanguageData(data) { + if (!this.__isArray(data)) { + return; + } + this.__initLanguageData(); + this.languageData.push(...data); + }, + + /** + * 变化语言 + * @param language + */ + setLanguage(language) { + this.__initLanguageData(); + setTimeout(() => { + window.localStorage['__language:type__'] = language; + languageListenerObjects.forEach((call) => { + if (typeof call === 'function') { + call(language); + } + }); + }, 10) + }, + + /** + * 获取语言 + * @returns {*} + */ + getLanguage() { + this.__initLanguageData(); + return this.languageType; + }, + + /** + * 替换%遍历 + * @param text + * @param objects + */ + replaceArgumentsLanguage(text, objects) { + let j = 1; + while (text.indexOf("%") !== -1) { + if (typeof objects[j] === "object") { + text = text.replace("%", ""); + } else { + text = text.replace("%", objects[j]); + } + j++; + } + return text; + }, + + /** + * 译文转义 + * @param val + * @returns {string|*} + */ + replaceEscape(val) { + if (!val || val == '') { + return ''; + } + return val.replace(/%/g, '%').replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + }, + + /** + * 显示语言 + * @return {string} + */ + $L(text) { + if (typeof arguments[1] !== "undefined") { + return this.$L(this.replaceArgumentsLanguage(text, arguments)); + } + if (typeof text !== "string" || !text) { + return text; + } + this.__initLanguageData(); + // + if (typeof languageCachesObjects[text] === "undefined") { + let tmpRege = null; + let tmpData = this.languageData.find((obj) => { + return Object.values(obj).find((val) => { + tmpRege = new RegExp("^" + this.replaceEscape(val).replace(/%/g, "(.*?)") + "$", "g"); + return !!text.match(tmpRege); + }) + }); + languageCachesObjects[text] = { + rege: tmpRege, + data: tmpData, + }; + } + const {rege, data} = languageCachesObjects[text]; + if (data + && typeof data === "object" + && typeof data[this.languageType] !== "undefined" + && data[this.languageType]) { + let index = 0; + let value = data[this.languageType]; + value = value.replace(/%/g, function () { + return "$" + (++index); + }); + return text.replace(rege, value); + } + // + if (this.languageType == "CN") { + try { + let key = '__language:Undefined__'; + let languageTmp = JSON.parse(window.localStorage[key] || '[]'); + if (!this.__isArray(languageTmp)) { + languageTmp = []; + } + let tmpRege = null; + let tmpData = languageTmp.find((val) => { + tmpRege = new RegExp("^" + val.replace(/%/g, "(.*?)") + "$", "g"); + return !!text.match(tmpRege); + }); + if (!tmpData) { + languageTmp.push(text); + window.localStorage[key] = JSON.stringify(languageTmp); + } + } catch (e) { } + } + // + return text; + } + } + }); + } +} diff --git a/resources/js/common/mixins/index.js b/resources/js/common/mixins/index.js new file mode 100644 index 00000000..b84d04d5 --- /dev/null +++ b/resources/js/common/mixins/index.js @@ -0,0 +1,55 @@ +export default { + install(Vue) { + Vue.mixin({ + data() { + return { + mixinId: 0, + //用户信息 + userLogin: false, + userInfo: {}, + userName: '', + userId: 0, + //浏览器宽度≤768返回true + windowMax768: window.innerWidth <= 768, + } + }, + + mounted() { + if (typeof window.__mixinId != "number") window.__mixinId = 0; + this.mixinId = window.__mixinId++; + // + this.userLogin = $A.getToken() !== false; + this.userInfo = $A.getUserInfo(); + this.userName = this.userInfo.username || ''; + this.userId = parseInt(this.userInfo.userid); + $A.setOnUserInfoListener('mixins_' + this.mixinId, (data, isLogin) => { + this.userLogin = isLogin; + this.userInfo = data; + this.userName = this.userInfo.username || ''; + this.userId = parseInt(this.userInfo.userid); + }); + // + window.addEventListener('resize', this.windowMax768Listener); + }, + + beforeDestroy() { + $A.removeUserInfoListener('mixins_' + this.mixinId); + window.removeEventListener('resize', this.windowMax768Listener); + }, + + methods: { + isArray(obj) { + return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == '[object array]' && typeof obj.length == "number"; + }, + + isJson(obj) { + return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && typeof obj.length == "undefined"; + }, + + windowMax768Listener() { + this.windowMax768 = window.innerWidth <= 768 + } + } + }); + } +} diff --git a/resources/js/main.js b/resources/js/main.js new file mode 100755 index 00000000..5e1c3f96 --- /dev/null +++ b/resources/js/main.js @@ -0,0 +1,1027 @@ +/** + * 页面专用 + */ +(function (window) { + + let apiUrl = window.location.origin + '/api/'; + let $ = window.$A; + + $.extend({ + + fillUrl(str) { + if (str.substring(0, 2) === "//" || + str.substring(0, 7) === "http://" || + str.substring(0, 8) === "https://" || + str.substring(0, 6) === "ftp://" || + str.substring(0, 1) === "/") { + return str; + } + return window.location.origin + '/' + str; + }, + + webUrl(str) { + return $A.fillUrl(str || ''); + }, + + apiUrl(str) { + if (str.substring(0, 2) === "//" || + str.substring(0, 7) === "http://" || + str.substring(0, 8) === "https://" || + str.substring(0, 6) === "ftp://" || + str.substring(0, 1) === "/") { + return str; + } + return apiUrl + str; + }, + + apiAjax(params) { + if (typeof params !== 'object') return false; + if (typeof params.success === 'undefined') params.success = () => { }; + if (typeof params.header !== 'object') params.header = {}; + params.url = this.apiUrl(params.url); + // + let beforeCall = params.beforeSend; + params.beforeSend = () => { + $A.aAjaxLoadNum++; + $A(".w-spinner").show(); + // + if (typeof beforeCall == "function") { + beforeCall(); + } + }; + // + let completeCall = params.complete; + params.complete = () => { + $A.aAjaxLoadNum--; + if ($A.aAjaxLoadNum <= 0) { + $A(".w-spinner").hide(); + } + // + if (typeof completeCall == "function") { + completeCall(); + } + }; + // + let callback = params.success; + params.success = (data, status, xhr) => { + if (typeof data === 'object') { + if (data.ret === -1 && params.checkRole !== false) { + //身份丢失 + $A.modalError({ + content: data.msg, + onOk: () => { + $A.userLogout(); + } + }); + return; + } + if (data.ret === -2 && params.role !== false) { + //没有权限 + $A.modalError({ + content: data.msg ? data.msg : "你没有相关的权限查看或编辑!" + }); + } + } + if (typeof callback === "function") { + callback(data, status, xhr); + } + }; + // + if (params.websocket === true || params.ws === true) { + const apiWebsocket = $A.randomString(16); + const apiTimeout = setTimeout(() => { + const WListener = $A.aAjaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); + $A.aAjaxWsListener = $A.aAjaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); + if (WListener) { + WListener.complete(); + WListener.error("timeout"); + WListener.afterComplete(); + } + }, params.timeout || 30000); + $A.aAjaxWsListener.push({ + apiWebsocket: apiWebsocket, + complete: typeof params.complete === "function" ? params.complete : () => { }, + afterComplete: typeof params.afterComplete === "function" ? params.afterComplete : () => { }, + success: typeof params.success === "function" ? params.success : () => { }, + error: typeof params.error === "function" ? params.error : () => { }, + }); + // + params.complete = () => { }; + params.afterComplete = () => { }; + params.success = () => { }; + params.error = () => { }; + params.header['Api-Websocket'] = apiWebsocket; + // + if ($A.aAjaxWsReady === false) { + $A.aAjaxWsReady = true; + $A.WSOB.setOnMsgListener("apiWebsocket", [ + 'apiWebsocket', + ], (msgDetail) => { + switch (msgDetail.messageType) { + case 'apiWebsocket': + clearTimeout(apiTimeout); + const apiWebsocket = msgDetail.apiWebsocket; + const apiSuccess = msgDetail.apiSuccess; + const apiResult = msgDetail.body; + const WListener = $A.aAjaxWsListener.find((item) => item.apiWebsocket == apiWebsocket); + $A.aAjaxWsListener = $A.aAjaxWsListener.filter((item) => item.apiWebsocket != apiWebsocket); + if (WListener) { + WListener.complete(); + if (apiSuccess) { + WListener.success(apiResult); + } else { + WListener.error(apiResult); + } + WListener.afterComplete(); + } + break; + } + }); + } + } + // + $A.ajaxc(params); + }, + aAjaxLoadNum: 0, + aAjaxWsReady: false, + aAjaxWsListener: [], + + /** + * 编辑器参数配置 + * @returns {{modules: {toolbar: *[]}}} + */ + editorOption() { + return { + modules: { + toolbar: [ + ['bold', 'italic'], + [{ 'list': 'ordered'}, { 'list': 'bullet' }], + [{ 'size': ['small', false, 'large', 'huge'] }], + [{ 'header': [1, 2, 3, 4, 5, 6, false] }], + [{ 'color': [] }, { 'background': [] }], + [{ 'align': [] }] + ] + } + }; + }, + + /** + * 获取token + * @returns {boolean} + */ + getToken() { + let token = $A.token(); + return $A.count(token) < 10 ? false : token; + }, + + /** + * 设置token + * @param token + */ + setToken(token) { + $A.token(token); + }, + + /** + * 获取会员ID + * @returns string + */ + getUserId() { + if ($A.getToken() === false) { + return ""; + } + let userInfo = $A.getUserInfo(); + return $A.runNum(userInfo.userid); + }, + + /** + * 获取会员账号 + * @returns string + */ + getUserName() { + if ($A.getToken() === false) { + return ""; + } + let userInfo = $A.getUserInfo(); + return $A.ishave(userInfo.username) ? userInfo.username : ''; + }, + + /** + * 获取会员昵称 + * @param nullName + * @returns {string|*} + */ + getNickName(nullName = true) { + if ($A.getToken() === false) { + return ""; + } + let userInfo = $A.getUserInfo(); + return $A.ishave(userInfo.nickname) ? userInfo.nickname : ''; + }, + + /** + * 获取用户信息(并保存) + * @param callback 网络请求获取到用户信息回调(监听用户信息发生变化) + * @returns Object + */ + getUserInfo(callback) { + if (typeof callback === 'function' || callback === true) { + $A.apiAjax({ + url: 'users/info', + error: () => { + $A.userLogout(); + }, + success: (res) => { + if (res.ret === 1) { + $A.storage("userInfo", res.data); + $A.setToken(res.data.token); + $A.triggerUserInfoListener(res.data); + $A.updateUserBasic({ + username: res.data.username, + nickname: res.data.nickname, + userimg: res.data.userimg, + }); + typeof callback === "function" && callback(res.data, $A.getToken() !== false); + } + }, + }); + } + return $A.jsonParse($A.storage("userInfo")); + }, + + /** + * 根据用户名获取用户基本信息 + * @param params Object{username,callback,listenerName,cacheTime} + */ + getUserBasic(params) { + if (typeof params !== 'object' || params === null) return; + if (typeof params.listenerName === 'undefined') params.listenerName = $A.randomString(16); + if (typeof params.cacheTime === 'undefined') params.cacheTime = 300; + + if (typeof params.callback !== "function") { + return; + } + if (!params.username) { + params.callback({}, false); + return; + } + // + $A.__userBasicFuncUpdate.push({ + listenerName: params.listenerName, + username: params.username, + callback: params.callback + }); + // + let keyName = '__userBasic:' + params.username.substring(0, 1) + '__'; + let localData = $A.jsonParse(window.localStorage[keyName]); + if ($A.getObject(localData, params.username + '.success') === true) { + params.callback(localData[params.username].data, true); + if (localData[params.username].update + params.cacheTime > Math.round(new Date().getTime() / 1000)) { + return; + } + } + // + $A.__userBasicFuncAjax.push({ + username: params.username, + callback: params.callback + }); + // + $A.__userBasicTimeout++; + let timeout = $A.__userBasicTimeout; + setTimeout(() => { + timeout === $A.__userBasicTimeout && $A.__userBasicEvent(); + }, 100); + }, + __userBasicEvent() { + if ($A.__userBasicLoading === true) { + return; + } + $A.__userBasicLoading = true; + // + let userArray = []; + $A.__userBasicFuncAjax.some((item) => { + userArray.push(item.username); + if (userArray.length >= 30) { + return true; + } + }); + // + $A.apiAjax({ + url: 'users/basic', + data: { + username: $A.jsonStringify(userArray), + }, + error: () => { + userArray.forEach((username) => { + let tmpLists = $A.__userBasicFuncAjax.filter((item) => item.username == username); + tmpLists.forEach((item) => { + if (typeof item.callback === "function") { + item.callback({}, false); + item.callback = null; + } + }); + }); + // + $A.__userBasicLoading = false; + $A.__userBasicFuncAjax = $A.__userBasicFuncAjax.filter((item) => typeof item.callback === "function"); + if ($A.__userBasicFuncAjax.length > 0) { + $A.__userBasicEvent(); + } + }, + success: (res) => { + if (res.ret === 1) { + res.data.forEach((data) => { + let keyName = '__userBasic:' + data.username.substring(0, 1) + '__'; + let localData = $A.jsonParse(window.localStorage[keyName]); + localData[data.username] = { + success: true, + update: Math.round(new Date().getTime() / 1000), + data: data + }; + window.localStorage[keyName] = $A.jsonStringify(localData); + }); + } + userArray.forEach((username) => { + let tmpLists = $A.__userBasicFuncAjax.filter((item) => item.username == username); + tmpLists.forEach((item) => { + if (typeof item.callback === "function") { + let info = res.data.filter((data) => data.username == username); + if (info.length === 0) { + item.callback({}, false); + } else { + item.callback(info[0], true); + } + item.callback = null; + } + }); + }); + // + $A.__userBasicLoading = false; + $A.__userBasicFuncAjax = $A.__userBasicFuncAjax.filter((item) => typeof item.callback === "function"); + if ($A.__userBasicFuncAjax.length > 0) { + $A.__userBasicEvent(); + } + } + }); + }, + __userBasicTimeout: 0, + __userBasicLoading: false, + __userBasicFuncAjax: [], + __userBasicFuncUpdate: [], + + /** + * 主动更新缓存 + * @param params Object{username,....} + */ + updateUserBasic(params) { + if (typeof params !== 'object' || params === null) return; + + if (!params.username) { + return; + } + let keyName = '__userBasic:' + params.username.substring(0, 1) + '__'; + let localData = $A.jsonParse(window.localStorage[keyName]); + if ($A.getObject(localData, params.username + '.success') === true) { + localData[params.username].data = Object.assign(localData[params.username].data, params); + window.localStorage[keyName] = $A.jsonStringify(localData); + // + let tmpLists = $A.__userBasicFuncUpdate.filter((item) => item.username == params.username); + tmpLists.forEach((item) => { + if (typeof item.callback === "function") { + item.callback(localData[params.username].data, true); + } + }); + } + }, + + /** + * 销毁监听 + * @param listenerName + */ + destroyUserBasicListener(listenerName) { + $A.__userBasicFuncUpdate = $A.__userBasicFuncUpdate.filter((item) => item.listenerName != listenerName); + }, + + /** + * 打开登录页面 + */ + userLogout() { + $A.token(""); + $A.storage("userInfo", {}); + $A.triggerUserInfoListener({}); + let from = window.location.pathname == '/' ? '' : encodeURIComponent(window.location.href); + if (typeof $A.app === "object") { + $A.app.goForward({path: '/login', query: from ? {from: from} : {}}, true); + } else { + window.location.replace($A.webUrl('login') + (from ? ('?from=' + from) : '')); + } + }, + + /** + * 权限是否通过 + * @param role + * @returns {boolean} + */ + identityCheck(role) { + let userInfo = $A.getUserInfo(); + return $A.identityRaw(role, userInfo.identity); + }, + + /** + * 权限是否通过 + * @param role + * @param identity + * @returns {boolean} + */ + identityRaw(role, identity) { + let isRole = false; + $A.each(identity, (index, res) => { + if (res === role) { + isRole = true; + } + }); + return isRole; + }, + + /** + * 监听用户信息发生变化 + * @param listenerName 监听标识 + * @param callback 监听回调 + */ + setOnUserInfoListener(listenerName, callback) { + if (typeof listenerName != "string") { + return; + } + if (typeof callback === "function") { + $A.__userInfoListenerObject[listenerName] = { + callback: callback, + } + } + }, + removeUserInfoListener(listenerName) { + if (typeof listenerName != "string") { + return; + } + if (typeof $A.__userInfoListenerObject[listenerName] != "undefined") { + delete $A.__userInfoListenerObject[listenerName]; + } + }, + triggerUserInfoListener(userInfo) { + let key, item; + for (key in $A.__userInfoListenerObject) { + if (!$A.__userInfoListenerObject.hasOwnProperty(key)) continue; + item = $A.__userInfoListenerObject[key]; + if (typeof item.callback === "function") { + item.callback(userInfo, $A.getToken() !== false); + } + } + }, + __userInfoListenerObject: {}, + }); + + /** + * ============================================================================= + * **************************** websocket assist *************************** + * ============================================================================= + */ + $.extend({ + /** + * @param config {userid, url, token, logCallback} + */ + WTWS: function (config) { + this.__instance = null; + this.__connected = false; + this.__callbackid = {}; + this.__openNum = 0; + this.__autoNum = 0; + this.__lastSend = 0; + + this.__autoLine = function (timeout) { + var tempNum = this.__autoNum; + var thas = this; + setTimeout(function () { + if (tempNum === thas.__autoNum) { + thas.__autoNum++ + if (!thas.__config.token) { + thas.__log("[WS] No token"); + thas.__autoLine(timeout); + } else { + // 发refresh之前判断10秒内没有使用过sendTo的再发送refresh + if (thas.__lastSend + 10 < Math.round(new Date().getTime() / 1000)) { + thas.sendTo('refresh', function (res) { + thas.__log("[WS] Connection " + (res.status ? 'success' : 'error')); + thas.__autoLine(timeout); + }); + } + } + } + }, Math.min(timeout, 30) * 1000); + } + this.__log = function (text, event) { + typeof this.__config.logCallback === "function" && this.__config.logCallback(text, event); + } + this.__lExists = function (string, find, lower) { + string += ""; + find += ""; + if (lower !== true) { + string = string.toLowerCase(); + find = find.toLowerCase(); + } + return (string.substring(0, find.length) === find); + } + this.__rNum = function (str, fixed) { + var _s = Number(str); + if (_s + "" === "NaN") { + _s = 0; + } + if (/^[0-9]*[1-9][0-9]*$/.test(fixed)) { + _s = _s.toFixed(fixed); + var rs = _s.indexOf('.'); + if (rs < 0) { + _s += "."; + for (var i = 0; i < fixed; i++) { + _s += "0"; + } + } + } + return _s; + } + this.__jParse = function (str, defaultVal) { + if (str === null) { + return defaultVal ? defaultVal : {}; + } + if (typeof str === "object") { + return str; + } + try { + return JSON.parse(str); + } catch (e) { + return defaultVal ? defaultVal : {}; + } + } + this.__randString = function (len) { + len = len || 32; + var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678oOLl9gqVvUuI1'; + var maxPos = $chars.length; + var pwd = ''; + for (var i = 0; i < len; i++) { + pwd += $chars.charAt(Math.floor(Math.random() * maxPos)); + } + return pwd; + } + this.__urlParams = function(url, params) { + if (typeof params === "object" && params !== null) { + url+= ""; + url+= url.indexOf("?") === -1 ? '?' : ''; + for (var key in params) { + if (!params.hasOwnProperty(key)) { + continue; + } + url+= '&' + key + '=' + params[key]; + } + } + return url.replace("?&", "?"); + } + this.__isArr = function (obj){ + return Object.prototype.toString.call(obj)=='[object Array]'; + } + + /** + * 设置参数 + * @param config + */ + this.config = function (config) { + if (typeof config !== "object" || config === null) { + config = {}; + } + config.userid = config.userid || ''; + config.url = config.url || ''; + config.token = config.token || ''; + config.logCallback = config.logCallback || null; + this.__config = config; + return this; + } + + /** + * 连接 + * @param force + */ + this.connection = function (force) { + if (!this.__lExists(this.__config.url, "ws://") && !this.__lExists(this.__config.url, "wss://")) { + this.__log("[WS] No connection address"); + return this; + } + + if (!this.__config.token) { + this.__log("[WS] No connected token"); + return this; + } + + if (this.__instance !== null && force !== true) { + this.__log("[WS] Connection exists"); + return this; + } + + var thas = this; + + // 初始化客户端套接字并建立连接 + this.__instance = new WebSocket(this.__urlParams(this.__config.url, { + mode: 'console', + token: this.__config.token, + })); + + // 连接建立时触发 + this.__instance.onopen = function (event) { + thas.__log("[WS] Connection opened", event); + } + + // 接收到服务端推送时执行 + this.__instance.onmessage = function (event) { + thas.__log("[WS] Message", event); + var msgDetail = thas.__jParse(event.data); + if (msgDetail.messageType === 'open') { + thas.__log("[WS] Connection connected"); + msgDetail.openNum = thas.__openNum; + msgDetail.config = thas.__config; + thas.__openNum++; + thas.__connected = true; + thas.__autoLine(30); + } else if (msgDetail.messageType === 'back') { + typeof thas.__callbackid[msgDetail.messageId] === "function" && thas.__callbackid[msgDetail.messageId](msgDetail.body); + delete thas.__callbackid[msgDetail.messageId]; + return; + } + if (thas.__rNum(msgDetail.contentId) > 0) { + thas.sendTo('roger', msgDetail.contentId); + } + thas.triggerMsgListener(msgDetail); + }; + + // 连接关闭时触发 + this.__instance.onclose = function (event) { + thas.__log("[WS] Connection closed", event); + thas.__connected = false; + thas.__instance = null; + thas.__autoLine(3); + } + + // 连接出错 + this.__instance.onerror = function (event) { + thas.__log("[WS] Connection error", event); + thas.__connected = false; + thas.__instance = null; + thas.__autoLine(3); + } + + return this; + } + + /** + * 添加消息监听 + * @param listenerName + * @param listenerType + * @param callback + */ + this.setOnMsgListener = function (listenerName, listenerType, callback) { + if (typeof listenerName != "string") { + return this; + } + if (typeof listenerType === "function") { + callback = listenerType; + listenerType = []; + } + if (!this.__isArr(listenerType)) { + listenerType = [listenerType]; + } + if (typeof callback === "function") { + window.webSocketConfig.LISTENER[listenerName] = { + callback: callback, + listenerType: listenerType, + } + } + return this; + } + this.triggerMsgListener = function (msgDetail) { + var key, item; + for (key in window.webSocketConfig.LISTENER) { + if (!window.webSocketConfig.LISTENER.hasOwnProperty(key)) { + continue; + } + item = window.webSocketConfig.LISTENER[key]; + if (item.listenerType.length > 0 && item.listenerType.indexOf(msgDetail.messageType) === -1) { + continue; + } + if (typeof item.callback === "function") { + try { + item.callback(msgDetail); + } catch (e) { } + } + } + } + + /** + * 发送消息 + * @param messageType 会话类型 + * - refresh: 刷新 + * @param target 发送目标 + * @param body 发送内容(对象或数组) + * @param callback 发送回调 + * @param againNum + */ + this.sendTo = function (messageType, target, body, callback, againNum = 0) { + if (typeof target === "object" && typeof body === "undefined") { + body = target; + target = null; + } + if (typeof target === "function") { + body = target; + target = null; + } + if (typeof body === "function") { + callback = body; + body = null; + } + if (body === null || typeof body !== "object") { + body = {}; + } + // + var thas = this; + if (this.__instance === null || this.__connected === false) { + if (againNum < 10 && messageType != 'team') { + setTimeout(function () { + thas.sendTo(messageType, target, body, callback, thas.__rNum(againNum) + 1) + }, 600); + if (againNum === 0) { + this.connection(); + } + } else { + if (this.__instance === null) { + this.__log("[WS] Service not connected"); + typeof callback === "function" && callback({status: 0, message: '服务未连接'}); + } else { + this.__log("[WS] Failed connection"); + typeof callback === "function" && callback({status: 0, message: '未连接成功'}); + } + } + return this; + } + if (['refresh', 'notificationStatus'].indexOf(messageType) === -1) { + this.__log("[WS] Wrong message messageType: " + messageType); + typeof callback === "function" && callback({status: 0, message: '错误的消息类型: ' + messageType}); + return this; + } + // + var contentId = 0; + if (messageType === 'roger') { + contentId = target; + target = null; + } + var messageId = ''; + if (typeof callback === "function") { + messageId = this.__randString(16); + this.__callbackid[messageId] = callback; + } + this.__lastSend = Math.round(new Date().getTime()/1000); + this.__instance.send(JSON.stringify({ + messageType: messageType, + messageId: messageId, + contentId: contentId, + userid: this.__config.userid, + target: target, + body: body, + time: this.__lastSend, + })); + return this; + } + + /** + * 关闭连接 + */ + this.close = function () { + if (this.__instance === null) { + this.__log("[WS] Service not connected"); + return this; + } + if (this.__connected === false) { + this.__log("[WS] Failed connection"); + return this; + } + this.__instance.close(); + return this; + } + + return this.config(config); + }, + + WSOB: { + instance: null, + isClose: false, + + /** + * 初始化 + */ + initialize() { + let url = window.webSocketConfig.URL; + if (!url) { + url = window.location.origin; + url = url.replace("https://", "wss://"); + url = url.replace("http://", "ws://"); + url+= "/ws"; + } + let config = { + userid: $A.getUserId(), + url: url, + token: $A.getToken(), + }; + if (window.webSocketConfig.DEBUG) { + config.logCallback = function (msg) { + console.log(msg); + }; + } + if (this.instance === null) { + this.instance = new $A.WTWS(config); + this.instance.connection() + } else { + this.instance.config(config); + if (this.isClose) { + this.isClose = false + this.instance.connection(); + } + } + }, + + /** + * 主动连接 + */ + connection() { + this.initialize(); + this.instance.connection(); + }, + + /** + * 监听消息 + * @param listenerName + * @param listenerType + * @param callback + */ + setOnMsgListener(listenerName, listenerType, callback) { + this.initialize(); + this.instance.setOnMsgListener(listenerName, listenerType, callback); + }, + + /** + * 发送消息 + * @param messageType + * @param target + * @param body + * @param callback + */ + sendTo(messageType, target, body, callback) { + this.initialize(); + this.instance.sendTo(messageType, target, body, callback); + }, + + /** + * 关闭连接 + */ + close() { + if (this.instance === null) { + return; + } + this.isClose = true + this.instance.config(null).close(); + }, + } + }); + + /** + * ============================================================================= + * ***************************** iviewui assist **************************** + * ============================================================================= + */ + $.extend({ + // 弹窗 + modalConfig(config) { + if (typeof config === "string") { + config = { + content: config + }; + } + config.title = $A.app.$L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); + config.content = $A.app.$L(config.content || ''); + config.okText = $A.app.$L(config.okText || '确定'); + config.cancelText = $A.app.$L(config.cancelText || '取消'); + return config; + }, + + modalConfirm(config, millisecond = 0) { + if (millisecond > 0) { + setTimeout(() => { $A.modalConfirm(config) }, millisecond); + return; + } + $A.app.$Modal.confirm($A.modalConfig(config)); + }, + + modalSuccess(config, millisecond = 0) { + if (millisecond > 0) { + setTimeout(() => { $A.modalSuccess(config) }, millisecond); + return; + } + $A.app.$Modal.success($A.modalConfig(config)); + }, + + modalInfo(config, millisecond = 0) { + if (millisecond > 0) { + setTimeout(() => { $A.modalInfo(config) }, millisecond); + return; + } + $A.app.$Modal.info($A.modalConfig(config)); + }, + + modalWarning(config, millisecond = 0) { + if (millisecond > 0) { + setTimeout(() => { $A.modalWarning(config) }, millisecond); + return; + } + $A.app.$Modal.warning($A.modalConfig(config)); + }, + + modalError(config, millisecond = 0) { + if (millisecond > 0) { + setTimeout(() => { $A.modalError(config) }, millisecond); + return; + } + $A.app.$Modal.error($A.modalConfig(config)); + }, + + modalInfoShow(title, data, addConfig) { + let content = ''; + for (let i in data) { + let item = data[i]; + content += ``; + } + let config = { + title: title, + content: content, + okText: $A.app.$L('关闭'), + closable: true + }; + if (typeof addConfig == 'object' && addConfig) { + config = Object.assign(config, addConfig); + } + this.app.$Modal.info(config); + }, + + modalAlert(msg) { + alert($A.app.$L(msg)); + }, + + //提示 + messageSuccess(msg) { + $A.app.$Message.success($A.app.$L(msg)); + }, + + messageWarning(msg) { + $A.app.$Message.warning($A.app.$L(msg)); + }, + + messageError(msg) { + $A.app.$Message.error($A.app.$L(msg)); + }, + + //通知 + noticeConfig(config) { + if (typeof config === "string") { + config = { + desc: config + }; + } + config.title = $A.app.$L(config.title || (typeof config.render === 'undefined' ? '温馨提示' : '')); + config.desc = $A.app.$L(config.desc || ''); + return config; + }, + + noticeSuccess(config) { + $A.app.$Notice.success($A.noticeConfig(config)); + }, + + noticeWarning(config) { + $A.app.$Notice.warning($A.noticeConfig(config)); + }, + + noticeError(config) { + if (typeof config === "string") { + config = { + desc: config, + duration: 6 + }; + } + $A.app.$Notice.error($A.noticeConfig(config)); + }, + }); + + window.$A = $; +})(window); diff --git a/resources/js/pages/404.vue b/resources/js/pages/404.vue new file mode 100644 index 00000000..438f8cbd --- /dev/null +++ b/resources/js/pages/404.vue @@ -0,0 +1,45 @@ + + + diff --git a/resources/js/pages/dashboard/index.vue b/resources/js/pages/dashboard/index.vue new file mode 100644 index 00000000..970698b5 --- /dev/null +++ b/resources/js/pages/dashboard/index.vue @@ -0,0 +1,14 @@ + + + diff --git a/resources/js/pages/index.vue b/resources/js/pages/index.vue new file mode 100644 index 00000000..544abe6e --- /dev/null +++ b/resources/js/pages/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/resources/js/pages/login/index.vue b/resources/js/pages/login/index.vue new file mode 100644 index 00000000..4afd47b0 --- /dev/null +++ b/resources/js/pages/login/index.vue @@ -0,0 +1,25 @@ + + + + + diff --git a/resources/js/routes.js b/resources/js/routes.js new file mode 100755 index 00000000..c95a5c23 --- /dev/null +++ b/resources/js/routes.js @@ -0,0 +1,22 @@ +export default [ + { + path: '/', + name: 'index', + component: resolve => require(['./pages/index.vue'], resolve) + }, + { + path: '*', + name: '404', + component: resolve => require(['./pages/404.vue'], resolve), + }, + { + path: '/login', + name: 'login', + component: resolve => require(['./pages/login/index.vue'], resolve), + }, + { + path: '/dashboard', + name: 'dashboard', + component: resolve => require(['./pages/dashboard/index.vue'], resolve), + }, +] diff --git a/resources/lang/language.js b/resources/lang/language.js new file mode 100644 index 00000000..49e15457 --- /dev/null +++ b/resources/lang/language.js @@ -0,0 +1,2 @@ +exports.default = [ +]; diff --git a/resources/public/js/html2md.js b/resources/public/js/html2md.js deleted file mode 100644 index c7c08c83..00000000 --- a/resources/public/js/html2md.js +++ /dev/null @@ -1,876 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.toMarkdown = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0) { - elem = inqueue.shift() - outqueue.push(elem) - children = elem.childNodes - for (i = children.length - 1; i >= 0; i--) { - if (children[i].nodeType === 1) inqueue.push(children[i]) - } - } - outqueue.shift() - return outqueue - } - - /* - * Contructs a Markdown string of replacement text for a given node - */ - - function getContent (node) { - var text = '' - for (var i = 0; i < node.childNodes.length; i++) { - if (node.childNodes[i].nodeType === 1) { - text += node.childNodes[i]._replacement - } else if (node.childNodes[i].nodeType === 3) { - text += node.childNodes[i].data - } else continue - } - return text - } - - /* - * Returns the HTML string of an element with its contents converted - */ - - function outer (node, content) { - return node.cloneNode(false).outerHTML.replace('><', '>' + content + '<') - } - - function canConvert (node, filter) { - if (typeof filter === 'string') { - return filter === node.nodeName.toLowerCase() - } - if (Array.isArray(filter)) { - return filter.indexOf(node.nodeName.toLowerCase()) !== -1 - } else if (typeof filter === 'function') { - return filter.call(toMarkdown, node) - } else { - throw new TypeError('`filter` needs to be a string, array, or function') - } - } - - function isFlankedByWhitespace (side, node) { - var sibling - var regExp - var isFlanked - - if (side === 'left') { - sibling = node.previousSibling - regExp = / $/ - } else { - sibling = node.nextSibling - regExp = /^ / - } - - if (sibling) { - if (sibling.nodeType === 3) { - isFlanked = regExp.test(sibling.nodeValue) - } else if (sibling.nodeType === 1 && !isBlock(sibling)) { - isFlanked = regExp.test(sibling.textContent) - } - } - return isFlanked - } - - function flankingWhitespace (node, content) { - var leading = '' - var trailing = '' - - if (!isBlock(node)) { - var hasLeading = /^[ \r\n\t]/.test(content) - var hasTrailing = /[ \r\n\t]$/.test(content) - - if (hasLeading && !isFlankedByWhitespace('left', node)) { - leading = ' ' - } - if (hasTrailing && !isFlankedByWhitespace('right', node)) { - trailing = ' ' - } - } - - return { leading: leading, trailing: trailing } - } - - /* - * Finds a Markdown converter, gets the replacement, and sets it on - * `_replacement` - */ - - function process (node) { - var replacement - var content = getContent(node) - - // Remove blank nodes - if (!isVoid(node) && !/A|TH|TD/.test(node.nodeName) && /^\s*$/i.test(content)) { - node._replacement = '' - return - } - - for (var i = 0; i < converters.length; i++) { - var converter = converters[i] - - if (canConvert(node, converter.filter)) { - if (typeof converter.replacement !== 'function') { - throw new TypeError( - '`replacement` needs to be a function that returns a string' - ) - } - - var whitespace = flankingWhitespace(node, content) - - if (whitespace.leading || whitespace.trailing) { - content = content.trim() - } - replacement = whitespace.leading + - converter.replacement.call(toMarkdown, content, node) + - whitespace.trailing - break - } - } - - node._replacement = replacement - } - - toMarkdown = function (input, options) { - options = options || {} - - if (typeof input !== 'string') { - throw new TypeError(input + ' is not a string') - } - - if (input === '') { - return '' - } - - // Escape potential ol triggers - input = input.replace(/(\d+)\. /g, '$1\\. ') - - var clone = htmlToDom(input).body - var nodes = bfsOrder(clone) - var output - - converters = mdConverters.slice(0) - if (options.gfm) { - converters = gfmConverters.concat(converters) - } - - if (options.converters) { - converters = options.converters.concat(converters) - } - - // Process through nodes in reverse (so deepest child elements are first). - for (var i = nodes.length - 1; i >= 0; i--) { - process(nodes[i]) - } - output = getContent(clone) - - return output.replace(/^[\t\r\n]+|[\t\r\n\s]+$/g, '') - .replace(/\n\s+\n/g, '\n\n') - .replace(/\n{3,}/g, '\n\n') - } - - toMarkdown.isBlock = isBlock - toMarkdown.isVoid = isVoid - toMarkdown.outer = outer - - module.exports = toMarkdown - - },{"./lib/gfm-converters":2,"./lib/html-parser":3,"./lib/md-converters":4,"collapse-whitespace":6}],2:[function(require,module,exports){ - 'use strict' - - function cell (content, node) { - var index = Array.prototype.indexOf.call(node.parentNode.childNodes, node) - var prefix = ' ' - if (index === 0) prefix = '| ' - return prefix + content + ' |' - } - - var highlightRegEx = /highlight highlight-(\S+)/ - - module.exports = [ - { - filter: 'br', - replacement: function () { - return '\n' - } - }, - { - filter: ['del', 's', 'strike'], - replacement: function (content) { - return '~~' + content + '~~' - } - }, - - { - filter: function (node) { - return node.type === 'checkbox' && node.parentNode.nodeName === 'LI' - }, - replacement: function (content, node) { - return (node.checked ? '[x]' : '[ ]') + ' ' - } - }, - - { - filter: ['th', 'td'], - replacement: function (content, node) { - return cell(content, node) - } - }, - - { - filter: 'tr', - replacement: function (content, node) { - var borderCells = '' - var alignMap = { left: ':--', right: '--:', center: ':-:' } - - if (node.parentNode.nodeName === 'THEAD') { - for (var i = 0; i < node.childNodes.length; i++) { - var align = node.childNodes[i].attributes.align - var border = '---' - - if (align) border = alignMap[align.value] || border - - borderCells += cell(border, node.childNodes[i]) - } - } - return '\n' + content + (borderCells ? '\n' + borderCells : '') - } - }, - - { - filter: 'table', - replacement: function (content) { - return '\n\n' + content + '\n\n' - } - }, - - { - filter: ['thead', 'tbody', 'tfoot'], - replacement: function (content) { - return content - } - }, - - // Fenced code blocks - { - filter: function (node) { - return node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - }, - replacement: function (content, node) { - return '\n\n```\n' + node.firstChild.textContent + '\n```\n\n' - } - }, - - // Syntax-highlighted code blocks - { - filter: function (node) { - return node.nodeName === 'PRE' && - node.parentNode.nodeName === 'DIV' && - highlightRegEx.test(node.parentNode.className) - }, - replacement: function (content, node) { - var language = node.parentNode.className.match(highlightRegEx)[1] - return '\n\n```' + language + '\n' + node.textContent + '\n```\n\n' - } - }, - - { - filter: function (node) { - return node.nodeName === 'DIV' && - highlightRegEx.test(node.className) - }, - replacement: function (content) { - return '\n\n' + content + '\n\n' - } - } - ] - - },{}],3:[function(require,module,exports){ - /* - * Set up window for Node.js - */ - - var _window = (typeof window !== 'undefined' ? window : this) - - /* - * Parsing HTML strings - */ - - function canParseHtmlNatively () { - var Parser = _window.DOMParser - var canParse = false - - // Adapted from https://gist.github.com/1129031 - // Firefox/Opera/IE throw errors on unsupported types - try { - // WebKit returns null on unsupported types - if (new Parser().parseFromString('', 'text/html')) { - canParse = true - } - } catch (e) {} - - return canParse - } - - function createHtmlParser () { - var Parser = function () {} - - // For Node.js environments - if (typeof document === 'undefined') { - var jsdom = require('jsdom') - Parser.prototype.parseFromString = function (string) { - return jsdom.jsdom(string, { - features: { - FetchExternalResources: [], - ProcessExternalResources: false - } - }) - } - } else { - if (!shouldUseActiveX()) { - Parser.prototype.parseFromString = function (string) { - var doc = document.implementation.createHTMLDocument('') - doc.open() - doc.write(string) - doc.close() - return doc - } - } else { - Parser.prototype.parseFromString = function (string) { - var doc = new window.ActiveXObject('htmlfile') - doc.designMode = 'on' // disable on-page scripts - doc.open() - doc.write(string) - doc.close() - return doc - } - } - } - return Parser - } - - function shouldUseActiveX () { - var useActiveX = false - - try { - document.implementation.createHTMLDocument('').open() - } catch (e) { - if (window.ActiveXObject) useActiveX = true - } - - return useActiveX - } - - module.exports = canParseHtmlNatively() ? _window.DOMParser : createHtmlParser() - - },{"jsdom":8}],4:[function(require,module,exports){ - 'use strict' - module.exports = [ - // P标签处理 - { - filter: 'p', - replacement: function (content, node) { - var attrClass = node.getAttribute('class') || '' - - if (attrClass === 'command') { - if (!content.endsWith('\n')) { - content += '\n' - } - return '\n```\n' + content + '```\n' - } else { - return '\n\n' + content + '\n\n' - } - } - }, - // BR 标签处理 - { - filter: 'br', - replacement: function () { - return ' \n' - } - }, - // H1 处理 - { - filter: 'h1', - replacement: function (content, node) { - if (typeof titleLock === "undefined") { - var titleLock = false; - } - if (!titleLock) { - titleLock = true; - return content + "\n" + "=".repeat(60); - } - else { - return '\n\n' + '# ' + content + '\n\n' - } - } - }, - // H2-H7 标签处理 - { - filter: ['h2', 'h3', 'h4', 'h5', 'h6', 'h7'], - replacement: function (content, node) { - var hLevel = node.nodeName.charAt(1) - var hPrefix = '##' - hLevel = hLevel - 2 - for (var i = 0; i < hLevel; i++) { - hPrefix += '#' - } - return '\n\n' + hPrefix + ' ' + content + '\n\n' - } - }, - // HR 标签处理 - { - filter: 'hr', - replacement: function () { - return '\n\n* * *\n\n' - } - }, - // em i 斜体处理 - { - filter: ['em', 'i'], - replacement: function (content) { - return ' _' + content + '_ ' - } - }, - // Strong b 粗体处理 - { - filter: ['strong', 'b'], - replacement: function (content) { - return '**' + content + '**' - } - }, - // Inline code - { - filter: function (node) { - var hasSiblings = node.previousSibling || node.nextSibling - var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings - - return node.nodeName === 'CODE' && !isCodeBlock - }, - replacement: function (content) { - return '`' + content + '`' - } - }, - // A 标签处理 - { - filter: function (node) { - return node.nodeName === 'A' && node.getAttribute('href') - }, - replacement: function (content, node) { - return '[' + content + '](' + node.getAttribute('href') + ')' - } - }, - // 特殊情况下的A标签处理 - { - filter: function (node) { - return node.nodeName === 'A' && node.getAttribute('style') - }, - replacement: function (content, node) { - return content - } - }, - // IMG 标签处理 - { - filter: 'img', - replacement: function (content, node) { - var alt = node.alt || '' - var src = node.getAttribute('src') || '' - var title = node.title || '' - var titlePart = title ? ' "' + title + '"' : '' - return src ? '\n![' + alt + ']' + '(' + src + titlePart + ')\n' : '' - } - }, - // 代码块处理 - { - filter: 'pre', - replacement: function (content, node) { - let contentText = node.innerText - if (!contentText.endsWith('\n')) { - contentText += '\n' - } - return '\n```\n' + contentText + '```\n' - } - }, - // 行内代码处理 - { - filter: 'code', - replacement: function (content, node) { - return '`' + content + '`' - } - }, - // IFrame 提醒 - { - filter: 'iframe', - replacement: function (content, node) { - console.log(node); - console.log(content); - return '\n ** 此处有iframe,请手动处理 ** \n' - } - }, - // Canvas 提醒 - { - filter: 'canvas', - replacement: function (content, node) { - return '\n ** 此处有Canvas,请手动处理 ** \n' - } - }, - // div 处理 - { - filter: 'div', - replacement: function (content, node) { - var attrClass = node.getAttribute('class') || '' - if (attrClass === 'code') { - if (!content.endsWith('\n')) { - content += '\n' - } - return '\n```\n' + content + '```\n' - } else { - return content - } - } - }, - { - 'filter': 'textarea', - replacement: function (content, node) { - return '' - } - }, - // 直接返回内容的标签 - { - filter: ['figure', 'span', 'small', 'section', 'font', 'asymspc', 'button', 'article', 'figcaption'], - replacement: function (content) { - return content - } - }, - // 引用 - { - filter: 'blockquote', - replacement: function (content) { - content = content.trim() - content = content.replace(/\n{3,}/g, '\n\n') - content = content.replace(/^/gm, '> ') - return '\n\n' + content + '\n\n' - } - }, - // 列表项 - { - filter: 'li', - replacement: function (content, node) { - content = content.replace(/^\s+/, '').replace(/\n/gm, '\n ') - var prefix = '* ' - var parent = node.parentNode - var index = Array.prototype.indexOf.call(parent.children, node) + 1 - - prefix = /ol/i.test(parent.nodeName) ? index + '. ' : '* ' - return prefix + content + '\n' - } - }, - // 有序/无序列表 - { - filter: ['ul', 'ol'], - replacement: function (content, node) { - var strings = [] - for (var i = 0; i < node.childNodes.length; i++) { - strings.push(node.childNodes[i]._replacement) - } - - if (/li/i.test(node.parentNode.nodeName)) { - return '\n' + strings.join('\n') - } - return '\n\n' + strings.join('\n') + '\n\n' - } - }, - // 判断是否是block,如果是block,前后加空行 - { - filter: function (node) { - return this.isBlock(node) - }, - replacement: function (content, node) { - return '\n\n' + this.outer(node, content) + '\n\n' - } - }, - - // Anything else! - { - filter: function () { - return true - }, - replacement: function (content, node) { - return this.outer(node, content) - } - } - ] - - },{}],5:[function(require,module,exports){ - /** - * This file automatically generated from `build.js`. - * Do not manually edit. - */ - - module.exports = [ - "address", - "article", - "aside", - "blockquote", - "canvas", - "dd", - "div", - "dl", - "dt", - "fieldset", - "figcaption", - "figure", - "footer", - "form", - "h1", - "h2", - "h3", - "h4", - "h5", - "h6", - "h7", - "header", - "hgroup", - "hr", - "li", - "main", - "nav", - "noscript", - "ol", - "output", - "p", - "pre", - "section", - "table", - "tfoot", - "ul", - "video" - ]; - - },{}],6:[function(require,module,exports){ - 'use strict'; - - var voidElements = require('void-elements'); - Object.keys(voidElements).forEach(function (name) { - voidElements[name.toUpperCase()] = 1; - }); - - var blockElements = {}; - require('block-elements').forEach(function (name) { - blockElements[name.toUpperCase()] = 1; - }); - - /** - * isBlockElem(node) determines if the given node is a block element. - * - * @param {Node} node - * @return {Boolean} - */ - function isBlockElem(node) { - return !!(node && blockElements[node.nodeName]); - } - - /** - * isVoid(node) determines if the given node is a void element. - * - * @param {Node} node - * @return {Boolean} - */ - function isVoid(node) { - return !!(node && voidElements[node.nodeName]); - } - - /** - * whitespace(elem [, isBlock]) removes extraneous whitespace from an - * the given element. The function isBlock may optionally be passed in - * to determine whether or not an element is a block element; if none - * is provided, defaults to using the list of block elements provided - * by the `block-elements` module. - * - * @param {Node} elem - * @param {Function} blockTest - */ - function collapseWhitespace(elem, isBlock) { - if (!elem.firstChild || elem.nodeName === 'PRE') return; - - if (typeof isBlock !== 'function') { - isBlock = isBlockElem; - } - - var prevText = null; - var prevVoid = false; - - var prev = null; - var node = next(prev, elem); - - while (node !== elem) { - if (node.nodeType === 3) { - // Node.TEXT_NODE - var text = node.data.replace(/[ \r\n\t]+/g, ' '); - - if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') { - text = text.substr(1); - } - - // `text` might be empty at this point. - if (!text) { - node = remove(node); - continue; - } - - node.data = text; - prevText = node; - } else if (node.nodeType === 1) { - // Node.ELEMENT_NODE - if (isBlock(node) || node.nodeName === 'BR') { - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - } - - prevText = null; - prevVoid = false; - } else if (isVoid(node)) { - // Avoid trimming space around non-block, non-BR void elements. - prevText = null; - prevVoid = true; - } - } else { - node = remove(node); - continue; - } - - var nextNode = next(prev, node); - prev = node; - node = nextNode; - } - - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - if (!prevText.data) { - remove(prevText); - } - } - } - - /** - * remove(node) removes the given node from the DOM and returns the - * next node in the sequence. - * - * @param {Node} node - * @return {Node} node - */ - function remove(node) { - var next = node.nextSibling || node.parentNode; - - node.parentNode.removeChild(node); - - return next; - } - - /** - * next(prev, current) returns the next node in the sequence, given the - * current and previous nodes. - * - * @param {Node} prev - * @param {Node} current - * @return {Node} - */ - function next(prev, current) { - if (prev && prev.parentNode === current || current.nodeName === 'PRE') { - return current.nextSibling || current.parentNode; - } - - return current.firstChild || current.nextSibling || current.parentNode; - } - - module.exports = collapseWhitespace; - - },{"block-elements":5,"void-elements":7}],7:[function(require,module,exports){ - /** - * This file automatically generated from `pre-publish.js`. - * Do not manually edit. - */ - - module.exports = { - "area": true, - "base": true, - "br": true, - "col": true, - "embed": true, - "hr": true, - "img": true, - "input": true, - "keygen": true, - "link": true, - "menuitem": true, - "meta": true, - "param": true, - "source": true, - "track": true, - "wbr": true - }; - - },{}],8:[function(require,module,exports){ - - },{}]},{},[1])(1) -}); \ No newline at end of file diff --git a/resources/scss/app.scss b/resources/scss/app.scss new file mode 100644 index 00000000..b20be79a --- /dev/null +++ b/resources/scss/app.scss @@ -0,0 +1,2 @@ +@import "loading"; +@import "main"; diff --git a/resources/scss/loading.scss b/resources/scss/loading.scss new file mode 100755 index 00000000..fb3ca46c --- /dev/null +++ b/resources/scss/loading.scss @@ -0,0 +1,75 @@ +.app-view-loading { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 99999; + background-color: rgba(255, 255, 255, 0.7); + display: flex; + align-items: center; + justify-content: center; + + > div { + text-align: center; + + > div { + color: #ccc; + margin: 0; + font: 11px verdana; + line-height: 16px; + text-transform: uppercase; + letter-spacing: 1px; + } + + > span { + display: inline-block; + vertical-align: middle; + width: 8px; + height: 8px; + margin: 2px; + background: #007DB6; + border-radius: 8px; + animation: app-view-loadanim 1s infinite alternate; + + &:nth-of-type(2) { + background: #008FB2; + animation-delay: 0.2s; + } + + &:nth-of-type(3) { + background: #009B9E; + animation-delay: 0.4s; + } + + &:nth-of-type(4) { + background: #00A77D; + animation-delay: 0.6s; + } + + &:nth-of-type(5) { + background: #00B247; + animation-delay: 0.8s; + } + + &:nth-of-type(6) { + background: #5AB027; + animation-delay: 1.0s; + } + + &:nth-of-type(7) { + background: #A0B61E; + animation-delay: 1.2s; + } + } + + @keyframes app-view-loadanim { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } + } + } +} diff --git a/resources/scss/main.scss b/resources/scss/main.scss new file mode 100755 index 00000000..0b37fb61 --- /dev/null +++ b/resources/scss/main.scss @@ -0,0 +1,285 @@ +*[hidden="hidden"] { + display: none !important; +} + +.icon-loading { + animation: icon-loading-load 0.6s infinite linear; +} + +@keyframes icon-loading-load { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.ivu-modal-wrap { + &.simple-modal { + .ivu-modal { + top: 100px; + padding-bottom: 100px; + @media (max-height: 900px) { + top: 35px; + padding-bottom: 35px; + } + } + .ivu-modal-header, + .ivu-modal-footer { + border-color: transparent; + } + + .form-network-add-tabs { + .ivu-tabs-tabpane { + padding: 10px 6px 0; + } + } + .form-network-add-in-out { + padding: 0 6px; + .select-in-type { + span.ivu-radio { + display: none; + } + .select-in-type-item-checked { + color: #2d8cf0; + border: 1px solid #2d8cf0 !important; + } + .select-in-type-item { + display: flex; + align-items: center; + min-width: 90px; + border: 1px solid #E8EBEE; + border-radius: 4px; + padding: 0 12px; + margin: 0 8px 0 0; + > span { + flex: 1; + } + &:hover { + cursor: pointer; + } + .select-in-type-icon { + margin-left: 12px; + } + } + } + .select-in-node { + margin-top: 18px; + } + } + } +} + +.modal-info-show { + display: flex; + .column { + color: #6D6D6D; + margin: 5px 0; + min-width: 70px; + max-width: 100px; + text-align: right; + } + .value { + flex: 1; + width: 0; + color: #414141; + margin: 5px 0 5px 5px; + word-break: break-all; + } +} + +.ivu-select-dropdown { + max-height: 360px; + &.select-node { + .ivu-select-item { + display: flex; + align-items: center; + justify-content: space-between; + .option-title { + flex: 1; + } + .ivu-tag { + margin-left: 18px; + margin-right: 0; + transform: scale(0.9); + transform-origin: right center; + } + } + } +} + +.ivu-tooltip-popper { + .ivu-tooltip-inner { + white-space: normal; + } +} + +.ivu-table { + table { + width: 100% !important; + .ivu-table-cell { + padding-left: 12px; + padding-right: 12px; + } + thead { + .ivu-table-cell { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + tbody { + .tree-icon-loading { + .ivu-table-cell { + display: flex; + align-items: center; + .ivu-table-cell-tree { + margin-right: 6px; + } + } + } + } + } + .td-ellipsis { + display: flex; + align-items: center; + max-width: 100%; + .remark-text { + overflow: hidden; + text-overflow: ellipsis; + align-items: center; + white-space: nowrap; + height: 20px; + line-height: 20px; + margin-right: 6px; + } + .remark-icon { + display: none; + font-size: 16px; + cursor: pointer; + } + .remark-tag { + display: inline-block; + height: 22px; + line-height: 22px; + margin: 2px 4px 2px 0; + border: 1px solid #e8eaec; + border-radius: 3px; + background: #f7f7f7; + font-size: 12px; + vertical-align: middle; + opacity: 1; + color: #515a6e; + padding: 0 4px; + white-space: nowrap; + overflow: visible; + &.pointer-tag { + cursor: pointer; + color: #1890ff; + background: #e6f7ff; + border-color: #91d5ff; + } + } + .icon-tooltip { + .ivu-tooltip-rel { + display: flex; + align-items: center; + } + } + } + .td-action { + max-width: 100%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + vertical-align: middle; + .td-action-container { + display: inline-block; + a { + font-size: 12px; + padding: 0 5px; + } + } + } + .ivu-table-row-hover { + &:hover { + .td-ellipsis { + .remark-icon { + display: inline-block; + } + } + } + } + &:before { + background-color: #efefef; + } +} +.auto-height-top { + .ivu-table { + .ivu-table-body { + table { + td { + height: auto; + vertical-align: top; + .ivu-table-cell { + display: flex; + align-items: center; + min-height: 48px; + padding: 8px 12px; + .node-line { + max-width: 100%; + .td-ellipsis { + .remark-line { + margin: 2px 6px 2px 0; + &:hover { + border-color: #888; + } + } + .remark-tag { + margin: 0; + transform: scale(0.8); + } + .remark-icon { + cursor: pointer; + &:hover { + color: #2d8cf0; + } + .ivu-icon { + vertical-align: unset; + transform: scale(1.2); + &.ivu-icon-md-remove-circle { + background-image: url("data:image/svg+xml;base64,PHN2ZyB0PSIxNjIxMDAxMDg0MzAxIiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjE4NjUiIHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2Ij48cGF0aCBkPSJNNTEyIDUxMm0tNTEyIDBhNTEyIDUxMiAwIDEgMCAxMDI0IDAgNTEyIDUxMiAwIDEgMC0xMDI0IDBaIiBmaWxsPSIjRkZGRkZGIiBwLWlkPSIxODY2Ij48L3BhdGg+PHBhdGggZD0iTTUxMiAwYTUxMiA1MTIgMCAxIDAgMCAxMDI0QTUxMiA1MTIgMCAwIDAgNTEyIDB6IG0zODQgNTEyYzAgODIuNzM5Mi0yNi40MTkyIDE1OS4zMzQ0LTcxLjA2NTYgMjIyLjEwNTZMMjg5Ljg5NDQgMTk5LjA2NTZBMzgxLjc0NzIgMzgxLjc0NzIgMCAwIDEgNTEyIDEyOCAzODQuNDA5NiAzODQuNDA5NiAwIDAgMSA4OTYgNTEyeiBtLTc2OCAwYzAtODMuMzUzNiAyNi43MjY0LTE2MC40NjA4IDcxLjk4NzItMjIzLjQzNjhsNTM1LjU1MiA1MzUuMzQ3MkEzODEuNzQ3MiAzODEuNzQ3MiAwIDAgMSA1MTIgODk2IDM4NC40MDk2IDM4NC40MDk2IDAgMCAxIDEyOCA1MTJ6IiBmaWxsPSIjZmYwMDAwIiBwLWlkPSIxODY3IiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pMCIgY2xhc3M9InNlbGVjdGVkIj48L3BhdGg+PC9zdmc+"); + background-repeat: no-repeat; + width: 12px; + height: 12px; + background-size: 94%; + background-position: center; + vertical-align: text-top; + &:before { + display: none; + } + } + } + } + .node-signal-box { + margin-left: 1px; + .node-ping-value { + margin-left: 2px; + } + } + .ivu-table-cell-tooltip { + width: auto; + .ivu-tooltip-rel { + max-width: 100%; + } + } + } + } + } + } + } + } + } +} diff --git a/webpack.mix.js b/webpack.mix.js index 5fef3e4f..76b16a17 100644 --- a/webpack.mix.js +++ b/webpack.mix.js @@ -11,8 +11,21 @@ const mix = require('laravel-mix'); | */ +function getFileName(str) { + if (/resources_js_pages_(.*?)_vue/.test(str)) { + return /resources_js_pages_(.*?)_vue/.exec(str)[1]; + } + return str; +} -mix.js('resources/js/app.js', 'public/js') - .postCss('resources/css/app.css', 'public/css', [ - // - ]).copy('resources/public', 'public'); +mix.vue({ version: 2 }) + .copy('resources/public', 'public') + .js('resources/js/app.js', 'public/js') + .sass('resources/scss/app.scss','public/css') + .webpackConfig({ + output: { + chunkFilename: function ({chunk}) { + return `js/build/${ getFileName(chunk.id) }.${ mix.inProduction() ? '[hash:8].' : '' }js` + } + }, + });