|
@@ -0,0 +1,369 @@
|
|
|
+import _get from 'lodash/get'
|
|
|
+import _set from 'lodash/set'
|
|
|
+import _isEmpty from 'lodash/isEmpty'
|
|
|
+import _isEqual from 'lodash/isEqual'
|
|
|
+import _forEach from 'lodash/forEach'
|
|
|
+import _isUndefined from 'lodash/isUndefined'
|
|
|
+import _flattenDeep from 'lodash/flattenDeep'
|
|
|
+import _toString from 'lodash/toString'
|
|
|
+import _has from 'lodash/has'
|
|
|
+import _includes from 'lodash/includes'
|
|
|
+import _keys from 'lodash/keys'
|
|
|
+import _isFunction from 'lodash/isFunction'
|
|
|
+import _uniq from 'lodash/uniq'
|
|
|
+import _flatMap from 'lodash/flatMap'
|
|
|
+import _union from 'lodash/union'
|
|
|
+/**
|
|
|
+ * @desc
|
|
|
+ * Vue 跨组件通信插件
|
|
|
+ */
|
|
|
+let unicomIdName = ''
|
|
|
+let unicomGroupName = ''
|
|
|
+// vm容器、分组、事件、命名 唯一、推迟触发的事件
|
|
|
+const [vmMap, groupForVm, events, idForVm, sendDefer] = [new Map(), {}, {}, {}, []]
|
|
|
+/**
|
|
|
+ * @desc 触发执行事件
|
|
|
+ * @param {string} method - 指令的名称
|
|
|
+ * @param {string} toKey - 目标组件的 unicomId 或者 unicomGroup
|
|
|
+ * @param {string} aim - 标识 (#)
|
|
|
+ * @param {Array} args - 参数
|
|
|
+ */
|
|
|
+const emitEvent = function (method, toKey, aim, args) {
|
|
|
+ const evs = _get(events, method, [])
|
|
|
+ let evLen = 0
|
|
|
+ const len = evs.length
|
|
|
+ // 循环已经注册的指令
|
|
|
+ for (evLen; evLen < len; evLen++) {
|
|
|
+ // 存储的数据
|
|
|
+ const { fn, scope } = evs[evLen]
|
|
|
+ if (_isEqual(aim, '#')) {
|
|
|
+ // id
|
|
|
+ if (scope[unicomIdName] !== toKey) {
|
|
|
+ // 目标不存在
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ } else if (_isEqual(aim, '@')) {
|
|
|
+ // 分组
|
|
|
+ const group = _get(vmMap.get(scope), 'group', [])
|
|
|
+ if (_isEmpty(group) || _isEqual(_includes(group, toKey), false)) {
|
|
|
+ // 目标不存在
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+ _isEqual(_isUndefined(scope), false) && fn.apply(scope, args)
|
|
|
+ }
|
|
|
+ // 返回被触发的指令
|
|
|
+ return evLen
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @desc 发送容器 或者 获得 目标容器
|
|
|
+ * @param {string} query - 指令名称 (~instruct1#id1)
|
|
|
+ * @param {...any} args - 可变参数
|
|
|
+ */
|
|
|
+const unicomQuery = function (query, ...args) {
|
|
|
+ let [toKey, aim, defer, eventIndex] = ['', '', false, -1]
|
|
|
+ // query=instruct1#id1
|
|
|
+ const method = query
|
|
|
+ .replace(/^([`~])/, function (s0, s1) {
|
|
|
+ // query=~instruct1#id1
|
|
|
+ if (_isEqual(s1, '~')) {
|
|
|
+ defer = true
|
|
|
+ }
|
|
|
+ return ''
|
|
|
+ })
|
|
|
+ .replace(/([@#])([^@#]*)$/, function (s0, s1, s2) {
|
|
|
+ // query=instruct1@child-a
|
|
|
+ // s0=@child-a s1=@ s2=child-a
|
|
|
+ toKey = s2
|
|
|
+ aim = s1
|
|
|
+ return ''
|
|
|
+ })
|
|
|
+ // method=instruct1
|
|
|
+ if (defer) {
|
|
|
+ sendDefer.push([method, toKey, aim, args, this])
|
|
|
+ return this
|
|
|
+ }
|
|
|
+ if (_isEqual(_isEqual(method, ''), false)) {
|
|
|
+ args.unshift(this)
|
|
|
+ eventIndex = emitEvent(method, toKey, aim, args)
|
|
|
+ }
|
|
|
+ // 获取目标 vm
|
|
|
+ switch (aim) {
|
|
|
+ case '#':
|
|
|
+ return _get(idForVm, toKey, null)
|
|
|
+ case '@':
|
|
|
+ return _get(groupForVm, toKey, [])
|
|
|
+ }
|
|
|
+ return eventIndex
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @desc 更新分组
|
|
|
+ * @param {Object} scope - 组件的实例
|
|
|
+ * @param {string[]} nv - 指令 unicom-group 传入的组数据 (新)
|
|
|
+ * @param {string[]} [ov] - 指令 unicom-group 传入的组数据 (旧)
|
|
|
+ */
|
|
|
+const updateName = function (scope, nv, ov) {
|
|
|
+ // 实例上设置分组
|
|
|
+ const vmData = vmMap.get(scope) || {}
|
|
|
+ const group = _get(vmData, 'group', [])
|
|
|
+ // 删除旧的 vm
|
|
|
+ if (_isEqual(_isUndefined(ov), false)) {
|
|
|
+ _uniq(_flattenDeep(_flatMap(ov))).forEach(function (key) {
|
|
|
+ if (_includes(group, key)) {
|
|
|
+ const vms = _get(groupForVm, key, [])
|
|
|
+ _isEqual(_isEmpty(vms), false) && vms.splice(vms.indexOf(scope), 1)
|
|
|
+ if (_isEmpty(vms)) {
|
|
|
+ group.splice(group.indexOf(key), 1)
|
|
|
+ Reflect.deleteProperty(groupForVm, key)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 增加新的
|
|
|
+ if (_isEqual(_isUndefined(nv), false)) {
|
|
|
+ _uniq(_flattenDeep(_flatMap(nv))).forEach(function (key) {
|
|
|
+ const vms = _get(groupForVm, key, [])
|
|
|
+ if (_isEmpty(vms)) {
|
|
|
+ _set(groupForVm, key, vms)
|
|
|
+ }
|
|
|
+ // 新添加 组件 到组里面
|
|
|
+ if (_isEqual(_includes(vms, scope), false)) {
|
|
|
+ vms.push(scope)
|
|
|
+ }
|
|
|
+ if (_isEqual(_includes(group, key), false)) {
|
|
|
+ group.push(key)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @desc 更新 unicomId
|
|
|
+ * @param {Object} scope - 组件的实例
|
|
|
+ * @param {string} newValue - 指令 unicom-id 传入的id数据 (新)
|
|
|
+ * @param {string} [oldValue] - 指令 unicom-id 传入的id数据 (旧)
|
|
|
+ */
|
|
|
+const updateId = function (scope, newValue, oldValue) {
|
|
|
+ if (_isEqual(newValue, oldValue)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (_isEqual(_isUndefined(oldValue), false) && _isEqual(_get(idForVm, oldValue), scope)) {
|
|
|
+ // watch 监测值修改需要删除,组件销毁时需要删除
|
|
|
+ Reflect.deleteProperty(idForVm, oldValue)
|
|
|
+ }
|
|
|
+ if (_isEqual(_isUndefined(newValue), false) && _isEqual(_has(idForVm, newValue), false)) {
|
|
|
+ _set(idForVm, newValue, scope)
|
|
|
+ } else if (_isEqual(_isUndefined(newValue), false) && _has(idForVm, newValue)) {
|
|
|
+ console.warn(`${unicomIdName}='${newValue}'的组件已经定义并存在。`)
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @desc 添加事件
|
|
|
+ * @param {string} method - 指令的名称
|
|
|
+ * @param {function} fn - 指令名称对应的函数
|
|
|
+ * @param {Object} scope - 指令名称对应函数所运行的作用域
|
|
|
+ */
|
|
|
+const appendEvent = function (method, fn, scope) {
|
|
|
+ if (_isEqual(_has(events, method), false)) {
|
|
|
+ events[method] = []
|
|
|
+ }
|
|
|
+ if (_isFunction(fn)) {
|
|
|
+ events[method].push({ fn, scope, method })
|
|
|
+ }
|
|
|
+}
|
|
|
+/**
|
|
|
+ * @desc 移除事件
|
|
|
+ * @param {string} method - 指令的名称
|
|
|
+ * @param {Object} scope - 指令名称对应函数所运行的作用域
|
|
|
+ */
|
|
|
+const removeEvent = function (method, scope) {
|
|
|
+ const evs = _get(events, method, [])
|
|
|
+ if (_isEqual(_isEmpty(evs), false)) {
|
|
|
+ for (let i = 0; i < evs.length; i++) {
|
|
|
+ if (_isEqual(scope, evs[i].scope)) {
|
|
|
+ evs.splice(i, 1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+export default {
|
|
|
+ install (Vue, { name = 'unicom', idName = `${name}Id`, groupName = `${name}Group` } = {}) {
|
|
|
+ // 添加原型方法 (unicomQuery 函数放在 install 外部不然无法获取调用函数的 this 对象)
|
|
|
+ Vue.prototype['$' + name] = unicomQuery
|
|
|
+ // unicom-id
|
|
|
+ unicomIdName = idName
|
|
|
+ // 分组 unicom-group
|
|
|
+ unicomGroupName = groupName
|
|
|
+ // 全局混入
|
|
|
+ Vue.mixin({
|
|
|
+ props: {
|
|
|
+ // 命名 unicom-id="id1"
|
|
|
+ [idName]: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ // 分组(纯字符串类数组不能是真实的数组) unicom-group="['child-a', 'child-b']"
|
|
|
+ [groupName]: {
|
|
|
+ type: Array,
|
|
|
+ default: () => []
|
|
|
+ }
|
|
|
+ },
|
|
|
+ watch: {
|
|
|
+ [idName] (nv, ov) {
|
|
|
+ updateId(this, nv, ov)
|
|
|
+ },
|
|
|
+ [groupName]: {
|
|
|
+ deep: true,
|
|
|
+ handler (nv, ov) {
|
|
|
+ updateName(this, nv, ov);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 创建的时候加入事件机制
|
|
|
+ beforeCreate () {
|
|
|
+ const opt = this.$options
|
|
|
+ const vmData = {}
|
|
|
+ const [us, uni, group] = [
|
|
|
+ _get(opt, name, {}),
|
|
|
+ (vmData.uni = {}),
|
|
|
+ (vmData.group = [])
|
|
|
+ ]
|
|
|
+ if (_isEqual(_isEmpty(us), false)) {
|
|
|
+ _forEach(us, opt => {
|
|
|
+ if (_isEmpty(opt)) {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ _forEach(opt, (handler, key) => {
|
|
|
+ if (_isEqual(_isUndefined(handler), false)) {
|
|
|
+ _isUndefined(_get(uni, key)) && _set(uni, key, [])
|
|
|
+ uni[key].push(handler)
|
|
|
+ // 添加事件
|
|
|
+ appendEvent(key, handler, this)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ // 命名分组
|
|
|
+ const groupNameOpt = _get(opt, groupName, [])
|
|
|
+ if (_isEqual(_isEmpty(groupNameOpt), false)) {
|
|
|
+ _forEach(_uniq(_flattenDeep(_flatMap(groupNameOpt))), item => {
|
|
|
+ const key = _toString(item)
|
|
|
+ _isUndefined(_get(groupForVm, key)) && (groupForVm[key] = [])
|
|
|
+ group.push(key)
|
|
|
+ groupForVm[key].push(this)
|
|
|
+ })
|
|
|
+ }
|
|
|
+ if (_isEqual(_isEmpty(group), false) || _isEqual(_isEmpty(uni), false)) {
|
|
|
+ vmMap.set(this, vmData)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ created () {
|
|
|
+ // 实例命名 唯一
|
|
|
+ const uId = this[idName]
|
|
|
+ const uGroupName = this[groupName]
|
|
|
+ _isEqual(_isEqual(uId, ''), false) && updateId(this, uId)
|
|
|
+ _isEqual(_isEmpty(uGroupName), false) && updateName(this, uGroupName)
|
|
|
+ // 实例上设置分组
|
|
|
+ const vmData = vmMap.get(this) || {}
|
|
|
+ for (let i = 0; i < sendDefer.length; i++) {
|
|
|
+ const [method, toKey, aim, args, scope] = sendDefer[i]
|
|
|
+ let flag = false
|
|
|
+ if (_isEqual(aim, '#')) {
|
|
|
+ if (_isEqual(toKey, uId)) {
|
|
|
+ flag = true
|
|
|
+ }
|
|
|
+ } else if (_isEqual(aim, '@')) {
|
|
|
+ if (
|
|
|
+ _isEqual(_isUndefined(_get(vmData, 'group')), false) &&
|
|
|
+ _includes(_get(vmData, 'group'), toKey)
|
|
|
+ ) {
|
|
|
+ flag = true
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (
|
|
|
+ _isEqual(method, '') ||
|
|
|
+ _includes(_keys(_get(vmData, 'uni', {})), method)
|
|
|
+ ) {
|
|
|
+ flag = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (flag) {
|
|
|
+ // 延后,并且方法为空
|
|
|
+ if (_isEqual(method, '')) {
|
|
|
+ /**
|
|
|
+ * @desc 监听组件事件
|
|
|
+ * @example
|
|
|
+ * this.$unicom('~#id1', function (childScope) {// unicomId=id1的组件创建完成})
|
|
|
+ */
|
|
|
+ if (_isFunction(args[0])) {
|
|
|
+ args[0](this)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sendDefer.splice(i--, 1)
|
|
|
+ args.unshift(scope)
|
|
|
+ emitEvent(method, toKey, aim, args)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ // 全局混合,销毁实例的时候销毁事件
|
|
|
+ destroyed () {
|
|
|
+ // 移除唯一ID
|
|
|
+ const uId = this[idName]
|
|
|
+ if (_isEqual(_isEqual(uId, ''), false)) {
|
|
|
+ updateId(this, undefined, uId)
|
|
|
+ }
|
|
|
+ // 移除 命名分组、实例命名
|
|
|
+ const uGroupName = _get(this, groupName, [])
|
|
|
+ const optGroupName = _get(this.$options, groupName, [])
|
|
|
+ if (_isEqual(_isEmpty(uGroupName), false) || _isEqual(_isEmpty(optGroupName), false)) {
|
|
|
+ updateName(this, undefined, _union(uGroupName, optGroupName))
|
|
|
+ }
|
|
|
+
|
|
|
+ const vmData = vmMap.get(this)
|
|
|
+ if (_isUndefined(vmData)) {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ vmMap.delete(this)
|
|
|
+
|
|
|
+ const uni = _get(vmData, 'uni', {})
|
|
|
+ // 移除事件
|
|
|
+ for (const key in uni) {
|
|
|
+ removeEvent(key, this)
|
|
|
+ }
|
|
|
+ // 分组,一对多, 单个vm可以多个分组名称 组件命名
|
|
|
+ const group = _get(vmData, 'group', [])
|
|
|
+ _forEach(group, function (name) {
|
|
|
+ const gs = groupForVm[name]
|
|
|
+ if (_isEqual(_isUndefined(gs), false)) {
|
|
|
+ const index = gs.indexOf(this)
|
|
|
+ if (index > -1) {
|
|
|
+ gs.splice(index, 1)
|
|
|
+ }
|
|
|
+ if (gs.length === 0) {
|
|
|
+ delete groupForVm[name]
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 监控销毁 method为空
|
|
|
+ for (let i = 0; i < sendDefer.length;) {
|
|
|
+ const pms = sendDefer[i]
|
|
|
+ if (_isEqual(pms[0], '') && _isEqual(pms[4], this)) {
|
|
|
+ sendDefer.splice(i, 1)
|
|
|
+ } else {
|
|
|
+ i += 1
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 自定义属性合并策略
|
|
|
+ // 混入(mixins)时不是简单的覆盖而是追加
|
|
|
+ const merge = _get(Vue, 'config.optionMergeStrategies', {})
|
|
|
+ merge[name] = merge[unicomGroupName] = function (parentVal, childVal) {
|
|
|
+ const p = parentVal || []
|
|
|
+ if (_isEqual(_isUndefined(childVal), false)) {
|
|
|
+ p.push(childVal)
|
|
|
+ }
|
|
|
+ return p
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|