首次提交

This commit is contained in:
2026-08-20 18:29:49 +08:00
commit 76827aedfe
77 changed files with 24937 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

+188
View File
@@ -0,0 +1,188 @@
## p-f-unicom 跨层级通信插件,解决非父子通信的痛点
> 前言:我们在使用`Vue`进行开发时如何能够解决非父子组件之间的一个通信问题,那如果碰到这样一个问题其实按照一般的开发步骤解决起来是非常繁琐的可能各种的`parent`、`children`或者有人可能会直接定义在全局环境上那这样的操作会使得你的项目在越来越复杂的业务开发中变得非常的脆弱。
##### `p-f-unicom` 自定义插件:
1. 它是`Vue.js`的一个自定义插件,解决了`Vue`中非父子组件通讯的痛点。
2. 利用订阅者和发布者模式来管理消息。
##### 功能:
1. 任意一个`Vue`组件向其他所有组件发送指令。
2. 任意一个`Vue`组件向某组`Vue`组件发送指令。
3. 任意一个`Vue`组件向特定`unicom-id`组件发送消息。
4. 在任意一个`Vue`组件内获取某组组件列表。
5. 在任意一个`Vue`组件内获取特定`unciom-id`组件。
6. 发送指令到还没初始化的`unicom-id`组件。
7. 发送指令到还没初始化的`unicom-group`分组组件。
8. 组件销毁时自动销毁绑定的订阅事件。
##### 使用:
`main.js` 注册`Unicom`插件
`unicom`是插件内置的名称,如果需要修改可以在`Vue.use`时自定义。
```javascript
// 导入
import unicom from './plugin/p-f-unicom.js'
// 使用
Vue.use(unicom, {
name: 'unicom',
idName: 'unicomId',
groupName: 'unicomGroup'
})
```
##### Vue组件内部使用
child.vue (订阅)
```javascript
<template>
<div>
// ......
</div>
</template>
<script>
export default {
// 将这个组件归到 group 分组,必须是数组
unicomGroup: ['group'],
unicom: {
// message 通讯指令名称和 this.$unicom 时对应
/**
* @desc 注册接收指令
* sender: 发送指令者(建议只能读区不能修改)
* args:指令发出者附带参数
*/
message (sender, ...args){
// 订阅消息 (名称为 `message`
}
}
}
</script>
```
index.vue (发布)
```javascript
<template>
<div>
// unicom-id 不能和其它组件所定的 unicom-id 相同
// unicom-group 定义组的名称数组,会和组件内的 `unicomGroup` 合并并去重 ,不定义默认使用 child 组件内的 `unicomGroup`
<child unicom-id="child-id" :unicom-group="aUnicomGroupList"></child>
</div>
</template>
<script>
import Child from './child.vue'
export default {
components: {
Child
},
data(){
return {
// 名字不能叫 unicomGroup,因为已经被 unicom 插件占用自动放到了组件的props对象中所以名字会重名冲突
aUnicomGroupList: ['group', 'group1']
}
},
methods: {
doExec () {
// 发布订阅消息,会自动调用 child 组件内订阅的 `message` 指令
this.$unicom("message", arg1, arg2, ...)
// 获取被命名为 child-id 的组件引用
const child = this.$unicom("#child-id")
// 获取分组为 group 的所有 vue 组件
const childs = this.$unicom("@group")
}
}
}
</script>
```
#### 发布的使用模式:
##### 组件已经存在
instruct1 是指令也就是你在 vue 组件中定义的接收消息的事件名称:
```javascript
unicom: {
instruct1(sender, oQueryParams, aYearList) {
// 查询栏消息
console.log('订阅消息', JSON.stringify(oQueryParams), aYearList);
}
}
```
1. instruct1@group (发送到指定分组)
2. instruct1#id1 (发送到指定组件)
3. @group (获取指定分组组件)
4. #id1 (获取指定组件)
##### 组件还未创建,延迟发送指令(一次性指令)
指令使用 ~ 打头
1. ~instruct1 (指令延迟发送,直到包含有 `instruct1` 指令的组件出现)
2. ~instruct1@group (指令延迟发送,直到出现分组命名`group`的组件)
3. ~instruct1#id1 (指令延迟发送,直到出现命名`id1`的组件)
如果你的组件是通过点击按钮这样通过判断来渲染出来的,那么事件的执行可以按下面的示例:
```javascript
this.$unicom('~onQuery111@industrialAnalyseFilterGroup', 'hello'); // 这个是发送组件未创建时的指令,指令将被缓存等待组件创建后触发onQuery111指令方法
this.$unicom('onQuery111@industrialAnalyseFilterGroup', 'hello111'); // 第二次向已经创建完成的组件发送指令onQuery111需要使用不带~号的形式
```
对应的组件
```javascript
<template>
<div>我不是初始化时就渲染</div>
</template>
<script>
export default {
unicomGroup: ['industrialAnalyseFilterGroup'],
// unicomId: 'aaaa',
unicom: {
// onQuery(sender, args) {
// console.log(sender, args, 11111111111);
// },
onQuery111(sender, p) {
console.log('hello: ', p);
}
},
data(){
return {
}
},
created(){}
}
</script>
```
##### 组件监听
组件监听使用,指令使用 ~ 打头, 第二个参数为 `callback` 回调
```
methods: {
doExec () {
this.$unicom('~#child-id', function(child){
// `child`组件创建完成,从 `child` 组件的`created`函数触发出来,所以请不要操作 `child`组件的 dom 元素,组件挂载请监听 this.$nextTick
})
}
}
```
1. ~@group (监听分组命名group的组件出现)
2. ~#id1 (监听命名id1的组件出现)
具体的使用 demo 请参考 v-demo 目录。
*****
注:
1.此插件原始地址:[vue-plugins](https://gitee.com/zhangh-design/vue-plugins)。
2.版权归原作者所有,仅为了方便,挂上插件市场。
3.对原作者表示感谢!
+2
View File
@@ -0,0 +1,2 @@
## 1.0.02023-02-02
init
+369
View File
@@ -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
}
}
}
+84
View File
@@ -0,0 +1,84 @@
{
"id": "p-f-unicom",
"displayName": "p-f-unicom",
"version": "1.0.0",
"description": "p-f-unicom",
"keywords": [
"p-f-unicom"
],
"repository": "https://gitee.com/zhangh-design/vue-plugins",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"type": "sdk-js",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": ""
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"Vue": {
"vue2": "u",
"vue3": "u"
},
"App": {
"app-vue": "u",
"app-nvue": "u"
},
"H5-mobile": {
"Safari": "u",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "u",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
},
"dependencies": {
"lodash": "^4.17.21"
}
}