首次提交

This commit is contained in:
2026-08-20 18:29:49 +08:00
commit 76827aedfe
77 changed files with 24937 additions and 0 deletions
+193
View File
@@ -0,0 +1,193 @@
## 为减小组件包的大小,默认组件包中不包含编辑、latex 公式等扩展功能,需要使用扩展功能的请参考下方的 插件扩展 栏的说明
## 功能介绍
- 全端支持(含 `v3、NVUE`
- 支持丰富的标签(包括 `table``video``svg` 等)
- 支持丰富的事件效果(自动预览图片、链接处理等)
- 支持设置占位图(加载中、出错时、预览时)
- 支持锚点跳转、长按复制等丰富功能
- 支持大部分 *html* 实体
- 丰富的插件(关键词搜索、内容编辑、`latex` 公式等)
- 效率高、容错性强且轻量化
查看 [功能介绍](https://jin-yufeng.gitee.io/mp-html/#/overview/feature) 了解更多
## 使用方法
- `uni_modules` 方式
1. 点击右上角的 `使用 HBuilder X 导入插件` 按钮直接导入项目或点击 `下载插件 ZIP` 按钮下载插件包并解压到项目的 `uni_modules/mp-html` 目录下
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<!-- 不需要引入,可直接使用 -->
<mp-html :content="html" />
```
```javascript
export default {
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时在 `HBuilder X` 中右键 `uni_modules/mp-html` 目录选择 `从插件市场更新` 即可
- 源码方式
1. 从 [github](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 或 [gitee](https://gitee.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 下载源码
插件市场的 **非 uni_modules 版本** 无法更新,不建议从插件市场获取
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from '@/components/mp-html/mp-html'
export default {
// HBuilderX 2.5.5+ 可以通过 easycom 自动引入
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
- npm 方式
1. 在项目根目录下执行
```bash
npm install mp-html
```
2. 在需要使用页面的 `(n)vue` 文件中添加
```html
<mp-html :content="html" />
```
```javascript
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
export default {
// 不可省略
components: {
mpHtml
},
data() {
return {
html: '<div>Hello World!</div>'
}
}
}
```
3. 需要更新版本时执行以下命令即可
```bash
npm update mp-html
```
使用 *cli* 方式运行的项目,通过 *npm* 方式引入时,需要在 *vue.config.js* 中配置 *transpileDependencies*,详情可见 [#330](https://github.com/jin-yufeng/mp-html/issues/330#issuecomment-913617687)
如果在 **nvue** 中使用还要将 `dist/uni-app/static` 目录下的内容拷贝到项目的 `static` 目录下,否则无法运行
查看 [快速开始](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart) 了解更多
## 组件属性
| 属性 | 类型 | 默认值 | 说明 |
|:---:|:---:|:---:|---|
| container-style | String | | 容器的样式([2.1.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v210) |
| content | String | | 用于渲染的 html 字符串 |
| copy-link | Boolean | true | 是否允许外部链接被点击时自动复制 |
| domain | String | | 主域名(用于链接拼接) |
| error-img | String | | 图片出错时的占位图链接 |
| lazy-load | Boolean | false | 是否开启图片懒加载 |
| loading-img | String | | 图片加载过程中的占位图链接 |
| pause-video | Boolean | true | 是否在播放一个视频时自动暂停其他视频 |
| preview-img | Boolean | true | 是否允许图片被点击时自动预览 |
| scroll-table | Boolean | false | 是否给每个表格添加一个滚动层使其能单独横向滚动 |
| selectable | Boolean | false | 是否开启文本长按复制 |
| set-title | Boolean | true | 是否将 title 标签的内容设置到页面标题 |
| show-img-menu | Boolean | true | 是否允许图片被长按时显示菜单 |
| tag-style | Object | | 设置标签的默认样式 |
| use-anchor | Boolean | false | 是否使用锚点链接 |
查看 [属性](https://jin-yufeng.gitee.io/mp-html/#/basic/prop) 了解更多
## 组件事件
| 名称 | 触发时机 |
|:---:|---|
| load | dom 树加载完毕时 |
| ready | 图片加载完毕时 |
| error | 发生渲染错误时 |
| imgtap | 图片被点击时 |
| linktap | 链接被点击时 |
| play | 音视频播放时 |
查看 [事件](https://jin-yufeng.gitee.io/mp-html/#/basic/event) 了解更多
## api
组件实例上提供了一些 `api` 方法可供调用
| 名称 | 作用 |
|:---:|---|
| in | 将锚点跳转的范围限定在一个 scroll-view 内 |
| navigateTo | 锚点跳转 |
| getText | 获取文本内容 |
| getRect | 获取富文本内容的位置和大小 |
| setContent | 设置富文本内容 |
| imgList | 获取所有图片的数组 |
| pauseMedia | 暂停播放音视频([2.2.2+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v222) |
| setPlaybackRate | 设置音视频播放速率([2.4.0+](https://jin-yufeng.gitee.io/mp-html/#/changelog/changelog#v240) |
查看 [api](https://jin-yufeng.gitee.io/mp-html/#/advanced/api) 了解更多
## 插件扩展
除基本功能外,本组件还提供了丰富的扩展,可按照需要选用
| 名称 | 作用 |
|:---:|---|
| audio | 音乐播放器 |
| editable | 富文本 **编辑**[示例项目](https://mp-html.oss-cn-hangzhou.aliyuncs.com/editable.zip) |
| emoji | 解析 emoji |
| highlight | 代码块高亮显示 |
| markdown | 渲染 markdown |
| search | 关键词搜索 |
| style | 匹配 style 标签中的样式 |
| txv-video | 使用腾讯视频 |
| img-cache | 图片缓存 by [@PentaTea](https://github.com/PentaTea) |
| latex | 渲染 latex 公式 by [@Zeng-J](https://github.com/Zeng-J) |
从插件市场导入的包中 **不含有** 扩展插件,使用插件需通过微信小程序 `富文本插件` 获取或参考以下方法进行打包:
1. 获取完整组件包
```bash
npm install mp-html
```
2. 编辑 `tools/config.js` 中的 `plugins` 项,选择需要的插件
3. 生成新的组件包
在 `node_modules/mp-html` 目录下执行
```bash
npm install
npm run build:uni-app
```
4. 拷贝 `dist/uni-app` 中的内容到项目根目录
查看 [插件](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin) 了解更多
## 关于 nvue
`nvue` 使用原生渲染,不支持部分 `css` 样式,为实现和 `html` 相同的效果,组件内部通过 `web-view` 进行渲染,性能上差于原生,根据 `weex` 官方建议,`web` 标签仅应用在非常规的降级场景。因此,如果通过原生的方式(如 `richtext`)能够满足需要,则不建议使用本组件,如果有较多的富文本内容,则可以直接使用 `vue` 页面
由于渲染方式与其他端不同,有以下限制:
1. 不支持 `lazy-load` 属性
2. 视频不支持全屏播放
3. 如果在 `flex-direction: row` 的容器中使用,需要给组件设置宽度或设置 `flex: 1` 占满剩余宽度
纯 `nvue` 模式下,[此问题](https://ask.dcloud.net.cn/question/119678) 修复前,不支持通过 `uni_modules` 引入,需要本地引入(将 [dist/uni-app](https://github.com/jin-yufeng/mp-html/tree/master/dist/uni-app) 中的内容拷贝到项目根目录下)
## 立即体验
![富文本插件](https://mp-html.oss-cn-hangzhou.aliyuncs.com/qrcode.jpg)
## 问题反馈
遇到问题时,请先查阅 [常见问题](https://jin-yufeng.gitee.io/mp-html/#/question/faq) 和 [issue](https://github.com/jin-yufeng/mp-html/issues) 中是否已有相同的问题
可通过 [issue](https://github.com/jin-yufeng/mp-html/issues/new/choose) 、插件问答或发送邮件到 [mp_html@126.com](mailto:mp_html@126.com) 提问,不建议在评论区提问(不方便回复)
提问请严格按照 [issue 模板](https://github.com/jin-yufeng/mp-html/issues/new/choose) ,描述清楚使用环境、`html` 内容或可复现的 `demo` 项目以及复现方式,对于 **描述不清**、**无法复现** 或重复的问题将不予回复
欢迎加入 `QQ` 交流群:
群1(已满):`699734691`
群2`778239129`
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多
+129
View File
@@ -0,0 +1,129 @@
## v2.4.22023-05-14
1. `A` `editable` 插件支持修改文字颜色 [详细](https://github.com/jin-yufeng/mp-html/issues/254)
2. `F` 修复了 `svg` 中有 `style` 不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/505)
3. `F` 修复了使用旧版编译器可能报错 `Bad attr nodes` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/472)
4. `F` 修复了 `app` 端可能出现无法读取 `lazyLoad` 的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/513)
5. `F` 修复了 `editable` 插件在点击换图时未拼接 `domain` 的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/497) by [@TwoKe945](https://github.com/TwoKe945)
6. `F` 修复了 `latex` 插件部分情况下不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/515)
7. `F` 修复了 `editable` 插件点击音视频时其他标签框不消失的问题
## v2.4.12022-12-25
1. `F` 修复了没有图片时 `ready` 事件可能不触发的问题
2. `F` 修复了加载过程中可能出现 `Root label not found` 错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/470)
3. `F` 修复了 `audio` 插件退出页面可能会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/457)
4. `F` 修复了 `vue3` 运行到 `app``HBuilder X 3.6.10` 以上报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/480)
5. `F` 修复了 `nvue` 端链接中包含 `%22` 时可能无法显示的问题
6. `F` 修复了 `vue3` 使用 `highlight` 插件可能报错的问题
## v2.4.02022-08-27
1. `A` 增加了 [setPlaybackRate](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#setPlaybackRate) 的 `api`,可以设置音视频的播放速率 [详细](https://github.com/jin-yufeng/mp-html/issues/452)
2. `A` 示例小程序代码开源 [详细](https://github.com/jin-yufeng/mp-html-demo)
3. `U` 优化 `ready` 事件触发时机,未设置懒加载的情况下基本可以准确触发 [详细](https://github.com/jin-yufeng/mp-html/issues/195)
4. `U` `highlight` 插件在编辑状态下不进行高亮处理,便于编辑
5. `F` 修复了 `flex` 布局下图片大小可能不正确的问题
6. `F` 修复了 `selectable` 属性没有设置 `force` 也可能出现渲染异常的问题
7. `F` 修复了表格中的图片大小可能不正确的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/448)
8. `F` 修复了含有合并单元格的表格可能无法设置竖直对齐的问题
9. `F` 修复了 `editable` 插件在 `scroll-view` 中使用时工具条位置可能不正确的问题
10. `F` 修复了 `vue3` 使用 [search](advanced/plugin#search) 插件可能导致错误换行的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/449)
## v2.3.22022-08-13
1. `A` 增加 [latex](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#latex) 插件,可以渲染数学公式 [详细](https://github.com/jin-yufeng/mp-html/pull/447) by [@Zeng-J](https://github.com/Zeng-J)
2. `U` 优化根节点下有很多标签的长内容渲染速度
3. `U` `highlight` 插件适配 `lang-xxx` 格式
4. `F` 修复了 `table` 标签设置 `border` 属性后可能无法修改边框样式的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/439) by [@zouxingjie](https://github.com/zouxingjie)
5. `F` 修复了 `editable` 插件输入连续空格无效的问题
6. `F` 修复了 `vue3` 图片设置 `inline` 会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/438)
7. `F` 修复了 `vue3` 使用 `table` 可能报错的问题
## v2.3.12022-05-20
1. `U` `app` 端支持使用本地图片
2. `U` 优化了微信小程序 `selectable` 属性在 `ios` 端的处理 [详细](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable)
3. `F` 修复了 `editable` 插件不在顶部时 `tooltip` 位置可能错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/430)
4. `F` 修复了 `vue3` 运行到微信小程序可能报错丢失内容的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/414)
5. `F` 修复了 `vue3` 部分标签可能被错误换行的问题
6. `F` 修复了 `editable` 插件 `app` 端插入视频无法预览的问题
## v2.3.02022-04-01
1. `A` 增加了 `play` 事件,音视频播放时触发,可用于与页面其他音视频进行互斥播放 [详细](basic/event#play)
2. `U` `show-img-menu` 属性支持控制预览时是否长按弹出菜单
3. `U` 优化 `wxs` 处理,提高渲染性能 [详细](https://developers.weixin.qq.com/community/develop/article/doc/0006cc2b204740f601bd43fa25a413)
4. `U` `video` 标签支持 `object-fit` 属性
5. `U` 增加支持一些常用实体编码 [详细](https://github.com/jin-yufeng/mp-html/issues/418)
6. `F` 修复了图片仅设置高度可能不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/410)
7. `F` 修复了 `video` 标签高度设置为 `auto` 不显示的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/411)
8. `F` 修复了使用 `grid` 布局时可能样式错误的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/413)
9. `F` 修复了含有合并单元格的表格部分情况下显示异常的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/417)
10. `F` 修复了 `editable` 插件连续插入内容时顺序不正确的问题
11. `F` 修复了 `uni-app``vue3` 使用 `audio` 插件报错的问题
12. `F` 修复了 `uni-app``highlight` 插件使用自定义的 `prism.min.js` 报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/416)
## v2.2.22022-02-26
1. `A` 增加了 [pauseMedia](https://jin-yufeng.gitee.io/mp-html/#/advanced/api#pauseMedia) 的 `api`,可用于暂停播放音视频 [详细](https://github.com/jin-yufeng/mp-html/issues/317)
2. `U` 优化了长内容的加载速度
3. `U` 适配 `vue3` [#389](https://github.com/jin-yufeng/mp-html/issues/389)、[#398](https://github.com/jin-yufeng/mp-html/pull/398) by [@zhouhuafei](https://github.com/zhouhuafei)、[#400](https://github.com/jin-yufeng/mp-html/issues/400)
4. `F` 修复了小程序端图片高度设置为百分比时可能不显示的问题
5. `F` 修复了 `highlight` 插件部分情况下可能显示不完整的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/403)
## v2.2.12021-12-24
1. `A` `editable` 插件增加上下移动标签功能
2. `U` `editable` 插件支持在文本中间光标处插入内容
3. `F` 修复了 `nvue` 端设置 `margin` 后可能导致高度不正确的问题
4. `F` 修复了 `highlight` 插件使用压缩版的 `prism.css` 可能导致背景失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/367)
5. `F` 修复了编辑状态下使用 `emoji` 插件内容为空时可能报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/371)
6. `F` 修复了使用 `editable` 插件后将 `selectable` 属性设置为 `force` 不生效的问题
## v2.2.02021-10-12
1. `A` 增加 `customElements` 配置项,便于添加自定义功能性标签 [详细](https://github.com/jin-yufeng/mp-html/issues/350)
2. `A` `editable` 插件增加切换音视频自动播放状态的功能 [详细](https://github.com/jin-yufeng/mp-html/pull/341) by [@leeseett](https://github.com/leeseett)
3. `A` `editable` 插件删除媒体标签时触发 `remove` 事件,便于删除已上传的文件
4. `U` `editable` 插件 `insertImg` 方法支持同时插入多张图片 [详细](https://github.com/jin-yufeng/mp-html/issues/342)
5. `U` `editable` 插入图片和音视频时支持拼接 `domian` 主域名
6. `F` 修复了内部链接参数中包含 `://` 时被认为是外部链接的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/356)
7. `F` 修复了部分 `svg` 标签名或属性名大小写不正确时不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/351)
8. `F` 修复了 `nvue` 页面运行到非 `app` 平台时可能样式错误的问题
## v2.1.52021-08-13
1. `A` 增加支持标签的 `dir` 属性
2. `F` 修复了 `ruby` 标签文字与拼音没有居中对齐的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/325)
3. `F` 修复了音视频标签内有 `a` 标签时可能无法播放的问题
4. `F` 修复了 `externStyle` 中的 `class` 名包含下划线或数字时可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
5. `F` 修复了 `h5` 端引入 `externStyle` 可能不生效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/326)
## v2.1.42021-07-14
1. `F` 修复了 `rt` 标签无法设置样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/318)
2. `F` 修复了表格中有单元格同时合并行和列时可能显示不正确的问题
3. `F` 修复了 `app` 端无法关闭图片长按菜单的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/322)
4. `F` 修复了 `editable` 插件只能添加图片链接不能修改的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/312) by [@leeseett](https://github.com/leeseett)
## v2.1.32021-06-12
1. `A` `editable` 插件增加 `insertTable` 方法
2. `U` `editable` 插件支持编辑表格中的空白单元格 [详细](https://github.com/jin-yufeng/mp-html/issues/310)
3. `F` 修复了 `externStyle` 中使用伪类可能失效的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/298)
4. `F` 修复了多个组件同时使用时 `tag-style` 属性时可能互相影响的问题 [详细](https://github.com/jin-yufeng/mp-html/pull/305) by [@woodguoyu](https://github.com/woodguoyu)
5. `F` 修复了包含 `linearGradient``svg` 可能无法显示的问题
6. `F` 修复了编译到头条小程序时可能报错的问题
7. `F` 修复了 `nvue` 端不触发 `click` 事件的问题
8. `F` 修复了 `editable` 插件尾部插入时无法撤销的问题
9. `F` 修复了 `editable` 插件的 `insertHtml` 方法只能在末尾插入的问题
10. `F` 修复了 `editable` 插件插入音频不显示的问题
## v2.1.22021-04-24
1. `A` 增加了 [img-cache](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#img-cache) 插件,可以在 `app` 端缓存图片 [详细](https://github.com/jin-yufeng/mp-html/issues/292) by [@PentaTea](https://github.com/PentaTea)
2. `U` 支持通过 `container-style` 属性设置 `white-space` 来保留连续空格和换行符 [详细](https://jin-yufeng.gitee.io/mp-html/#/question/faq#space)
3. `U` 代码风格符合 [standard](https://standardjs.com) 标准
4. `U` `editable` 插件编辑状态下支持预览视频 [详细](https://github.com/jin-yufeng/mp-html/issues/286)
5. `F` 修复了 `svg` 标签内嵌 `svg` 时无法显示的问题
6. `F` 修复了编译到支付宝和头条小程序时部分区域不可复制的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/291)
## v2.1.12021-04-09
1. 修复了对 `p` 标签设置 `tag-style` 可能不生效的问题
2. 修复了 `svg` 标签中的文本无法显示的问题
3. 修复了使用 `editable` 插件编辑表格时可能报错的问题
4. 修复了使用 `highlight` 插件运行到头条小程序时可能没有样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/280)
5. 修复了使用 `editable` 插件 `editable` 属性为 `false` 时会报错的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/284)
6. 修复了 `style` 插件连续子选择器失效的问题
7. 修复了 `editable` 插件无法修改图片和字体大小的问题
## v2.1.0.22021-03-21
修复了 `nvue` 端使用可能报错的问题
## v2.1.02021-03-20
1. `A` 增加了 [container-style](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#container-style) 属性 [详细](https://gitee.com/jin-yufeng/mp-html/pulls/1)
2. `A` 增加支持 `strike` 标签
3. `A` `editable` 插件增加 `placeholder` 属性 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
4. `A` `editable` 插件增加 `insertHtml` 方法 [详细](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#editable)
5. `U` 外部样式支持标签名选择器 [详细](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart#setting)
6. `F` 修复了 `nvue` 端部分情况下可能不显示的问题
## v2.0.52021-03-12
1. `U` [linktap](https://jin-yufeng.gitee.io/mp-html/#/basic/event#linktap) 事件增加返回内部文本内容 `innerText` [详细](https://github.com/jin-yufeng/mp-html/issues/271)
2. `U` [selectable](https://jin-yufeng.gitee.io/mp-html/#/basic/prop#selectable) 属性设置为 `force` 时能够在微信 `iOS` 端生效(文本块会变成 `inline-block` [详细](https://github.com/jin-yufeng/mp-html/issues/267)
3. `F` 修复了部分情况下竖向无法滚动的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/182)
4. `F` 修复了多次修改富文本数据时部分内容可能不显示的问题
5. `F` 修复了 [腾讯视频](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#txv-video) 插件可能无法播放的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/265)
6. `F` 修复了 [highlight](https://jin-yufeng.gitee.io/mp-html/#/advanced/plugin#highlight) 插件没有设置高亮语言时没有应用默认样式的问题 [详细](https://github.com/jin-yufeng/mp-html/issues/276) by [@fuzui](https://github.com/fuzui)
@@ -0,0 +1,498 @@
<template>
<view id="_root" :class="(selectable?'_select ':'')+'_root'" :style="containerStyle">
<slot v-if="!nodes[0]" />
<!-- #ifndef APP-PLUS-NVUE -->
<node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]" name="span" />
<!-- #endif -->
<!-- #ifdef APP-PLUS-NVUE -->
<web-view ref="web" src="/uni_modules/mp-html/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
<!-- #endif -->
</view>
</template>
<script>
/**
* mp-html v2.4.2
* @description 富文本组件
* @tutorial https://github.com/jin-yufeng/mp-html
* @property {String} container-style 容器的样式
* @property {String} content 用于渲染的 html 字符串
* @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
* @property {String} domain 主域名,用于拼接链接
* @property {String} error-img 图片出错时的占位图链接
* @property {Boolean} lazy-load 是否开启图片懒加载
* @property {string} loading-img 图片加载过程中的占位图链接
* @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
* @property {Boolean} preview-img 是否允许图片被点击时自动预览
* @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
* @property {Boolean | String} selectable 是否开启长按复制
* @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
* @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
* @property {Object} tag-style 标签的默认样式
* @property {Boolean | Number} use-anchor 是否使用锚点链接
* @event {Function} load dom 结构加载完毕时触发
* @event {Function} ready 所有图片加载完毕时触发
* @event {Function} imgtap 图片被点击时触发
* @event {Function} linktap 链接被点击时触发
* @event {Function} play 音视频播放时触发
* @event {Function} error 媒体加载出错时触发
*/
// #ifndef APP-PLUS-NVUE
import node from './node/node'
// #endif
import Parser from './parser'
const plugins=[]
// #ifdef APP-PLUS-NVUE
const dom = weex.requireModule('dom')
// #endif
export default {
name: 'mp-html',
data () {
return {
nodes: [],
// #ifdef APP-PLUS-NVUE
height: 3
// #endif
}
},
props: {
containerStyle: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
copyLink: {
type: [Boolean, String],
default: true
},
domain: String,
errorImg: {
type: String,
default: ''
},
lazyLoad: {
type: [Boolean, String],
default: false
},
loadingImg: {
type: String,
default: ''
},
pauseVideo: {
type: [Boolean, String],
default: true
},
previewImg: {
type: [Boolean, String],
default: true
},
scrollTable: [Boolean, String],
selectable: [Boolean, String],
setTitle: {
type: [Boolean, String],
default: true
},
showImgMenu: {
type: [Boolean, String],
default: true
},
tagStyle: Object,
useAnchor: [Boolean, Number]
},
// #ifdef VUE3
emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
// #endif
// #ifndef APP-PLUS-NVUE
components: {
node
},
// #endif
watch: {
content (content) {
this.setContent(content)
}
},
created () {
this.plugins = []
for (let i = plugins.length; i--;) {
this.plugins.push(new plugins[i](this))
}
},
mounted () {
if (this.content && !this.nodes.length) {
this.setContent(this.content)
}
},
beforeDestroy () {
this._hook('onDetached')
},
methods: {
/**
* @description 将锚点跳转的范围限定在一个 scroll-view 内
* @param {Object} page scroll-view 所在页面的示例
* @param {String} selector scroll-view 的选择器
* @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
*/
in (page, selector, scrollTop) {
// #ifndef APP-PLUS-NVUE
if (page && selector && scrollTop) {
this._in = {
page,
selector,
scrollTop
}
}
// #endif
},
/**
* @description 锚点跳转
* @param {String} id 要跳转的锚点 id
* @param {Number} offset 跳转位置的偏移量
* @returns {Promise}
*/
navigateTo (id, offset) {
return new Promise((resolve, reject) => {
if (!this.useAnchor) {
reject(Error('Anchor is disabled'))
return
}
offset = offset || parseInt(this.useAnchor) || 0
// #ifdef APP-PLUS-NVUE
if (!id) {
dom.scrollToElement(this.$refs.web, {
offset
})
resolve()
} else {
this._navigateTo = {
resolve,
reject,
offset
}
this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
}
// #endif
// #ifndef APP-PLUS-NVUE
let deep = ' '
// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
deep = '>>>'
// #endif
const selector = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this._in ? this._in.page : this)
// #endif
.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
if (this._in) {
selector.select(this._in.selector).scrollOffset()
.select(this._in.selector).boundingClientRect()
} else {
// 获取 scroll-view 的位置和滚动距离
selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
}
selector.exec(res => {
if (!res[0]) {
reject(Error('Label not found'))
return
}
const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
if (this._in) {
// scroll-view 跳转
this._in.page[this._in.scrollTop] = scrollTop
} else {
// 页面跳转
uni.pageScrollTo({
scrollTop,
duration: 300
})
}
resolve()
})
// #endif
})
},
/**
* @description 获取文本内容
* @return {String}
*/
getText (nodes) {
let text = '';
(function traversal (nodes) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
if (node.type === 'text') {
text += node.text.replace(/&amp;/g, '&')
} else if (node.name === 'br') {
text += '\n'
} else {
// 块级标签前后加换行
const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
if (isBlock && text && text[text.length - 1] !== '\n') {
text += '\n'
}
// 递归获取子节点的文本
if (node.children) {
traversal(node.children)
}
if (isBlock && text[text.length - 1] !== '\n') {
text += '\n'
} else if (node.name === 'td' || node.name === 'th') {
text += '\t'
}
}
}
})(nodes || this.nodes)
return text
},
/**
* @description 获取内容大小和位置
* @return {Promise}
*/
getRect () {
return new Promise((resolve, reject) => {
uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this)
// #endif
.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
})
},
/**
* @description 暂停播放媒体
*/
pauseMedia () {
for (let i = (this._videos || []).length; i--;) {
this._videos[i].pause()
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置媒体播放速率
* @param {Number} rate 播放速率
*/
setPlaybackRate (rate) {
this.playbackRate = rate
for (let i = (this._videos || []).length; i--;) {
this._videos[i].playbackRate(rate)
}
// #ifdef APP-PLUS
const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
// #ifndef APP-PLUS-NVUE
let page = this.$parent
while (!page.$scope) page = page.$parent
page.$scope.$getAppWebview().evalJS(command)
// #endif
// #ifdef APP-PLUS-NVUE
this.$refs.web.evalJs(command)
// #endif
// #endif
},
/**
* @description 设置内容
* @param {String} content html 内容
* @param {Boolean} append 是否在尾部追加
*/
setContent (content, append) {
if (!append || !this.imgList) {
this.imgList = []
}
const nodes = new Parser(this).parse(content)
// #ifdef APP-PLUS-NVUE
if (this._ready) {
this._set(nodes, append)
}
// #endif
this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
// #ifndef APP-PLUS-NVUE
this._videos = []
this.$nextTick(() => {
this._hook('onLoad')
this.$emit('load')
})
if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
let height = 0
const callback = rect => {
if (!rect || !rect.height) rect = {}
// 350ms 总高度无变化就触发 ready 事件
if (rect.height === height) {
this.$emit('ready', rect)
} else {
height = rect.height
setTimeout(() => {
this.getRect().then(callback).catch(callback)
}, 350)
}
}
this.getRect().then(callback).catch(callback)
} else {
// 未设置懒加载,等待所有图片加载完毕
if (!this.imgList._unloadimgs) {
this.getRect().then(rect => {
this.$emit('ready', rect)
}).catch(() => {
this.$emit('ready', {})
})
}
}
// #endif
},
/**
* @description 调用插件钩子函数
*/
_hook (name) {
for (let i = plugins.length; i--;) {
if (this.plugins[i][name]) {
this.plugins[i][name]()
}
}
},
// #ifdef APP-PLUS-NVUE
/**
* @description 设置内容
*/
_set (nodes, append) {
this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
},
/**
* @description 接收到 web-view 消息
*/
_onMessage (e) {
const message = e.detail.data[0]
switch (message.action) {
// web-view 初始化完毕
case 'onJSBridgeReady':
this._ready = true
if (this.nodes) {
this._set(this.nodes)
}
break
// 内容 dom 加载完毕
case 'onLoad':
this.height = message.height
this._hook('onLoad')
this.$emit('load')
break
// 所有图片加载完毕
case 'onReady':
this.getRect().then(res => {
this.$emit('ready', res)
}).catch(() => {
this.$emit('ready', {})
})
break
// 总高度发生变化
case 'onHeightChange':
this.height = message.height
break
// 图片点击
case 'onImgTap':
this.$emit('imgtap', message.attrs)
if (this.previewImg) {
uni.previewImage({
current: parseInt(message.attrs.i),
urls: this.imgList
})
}
break
// 链接点击
case 'onLinkTap': {
const href = message.attrs.href
this.$emit('linktap', message.attrs)
if (href) {
// 锚点跳转
if (href[0] === '#') {
if (this.useAnchor) {
dom.scrollToElement(this.$refs.web, {
offset: message.offset
})
}
} else if (href.includes('://')) {
// 打开外链
if (this.copyLink) {
plus.runtime.openWeb(href)
}
} else {
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href
})
}
})
}
}
break
}
case 'onPlay':
this.$emit('play')
break
// 获取到锚点的偏移量
case 'getOffset':
if (typeof message.offset === 'number') {
dom.scrollToElement(this.$refs.web, {
offset: message.offset + this._navigateTo.offset
})
this._navigateTo.resolve()
} else {
this._navigateTo.reject(Error('Label not found'))
}
break
// 点击
case 'onClick':
this.$emit('tap')
this.$emit('click')
break
// 出错
case 'onError':
this.$emit('error', {
source: message.source,
attrs: message.attrs
})
}
}
// #endif
}
}
</script>
<style>
/* #ifndef APP-PLUS-NVUE */
/* 根节点样式 */
._root {
padding: 1px 0;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
}
/* 长按复制 */
._select {
user-select: text;
}
/* #endif */
</style>
@@ -0,0 +1,576 @@
<template>
<view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
<block v-for="(n, i) in childs" v-bind:key="i">
<!-- 图片 -->
<!-- 占位图 -->
<image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
<!-- 显示图片 -->
<!-- #ifdef H5 || (APP-PLUS && VUE2) -->
<img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifndef H5 || (APP-PLUS && VUE2) -->
<!-- 表格中的图片使用 rich-text 防止大小不正确 -->
<rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]" :data-i="i" @tap.stop="imgTap" />
<!-- #endif -->
<!-- #ifndef H5 || APP-PLUS -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- #ifdef APP-PLUS && VUE3 -->
<image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
<!-- #endif -->
<!-- 文本 -->
<!-- #ifdef MP-WEIXIN -->
<text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
<text v-else-if="n.text" decode>{{n.text}}</text>
<!-- #endif -->
<text v-else-if="n.name==='br'">\n</text>
<!-- 链接 -->
<view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
<node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
</view>
<!-- 视频 -->
<!-- #ifdef APP-PLUS -->
<view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
<!-- #endif -->
<!-- #ifndef APP-PLUS -->
<video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<!-- #ifdef H5 || APP-PLUS -->
<iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
<embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
<!-- #endif -->
<!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
<!-- 音频 -->
<audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
<!-- #endif -->
<view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
<node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
<view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
<node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
<view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<node :childs="tr.children" :opts="opts" />
</view>
<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class" :style="td.attrs.style">
<node :childs="td.children" :opts="opts" />
</view>
</view>
</block>
</view>
</view>
<!-- 富文本 -->
<!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="'display:inline;'+n.f" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
<!-- #endif -->
<!-- 继续递归 -->
<view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
</view>
<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
</block>
</view>
</template>
<script module="handler" lang="wxs">
// 行内标签列表
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
i: true,
ins: true,
label: true,
q: true,
small: true,
span: true,
strong: true,
sub: true,
sup: true
}
/**
* @description 判断是否为行内标签
*/
module.exports = {
isInline: function (tagName, style) {
return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
}
}
</script>
<script>
import node from './node'
export default {
name: 'node',
options: {
// #ifdef MP-WEIXIN
virtualHost: true,
// #endif
// #ifdef MP-TOUTIAO
addGlobalClass: false
// #endif
},
data () {
return {
ctrl: {},
// #ifdef MP-WEIXIN
isiOS: uni.getSystemInfoSync().system.includes('iOS')
// #endif
}
},
props: {
name: String,
attrs: {
type: Object,
default () {
return {}
}
},
childs: Array,
opts: Array
},
components: {
// #ifndef (H5 || APP-PLUS) && VUE3
node
// #endif
},
mounted () {
this.$nextTick(() => {
for (this.root = this.$parent; this.root.$options.name !== 'mp-html'; this.root = this.root.$parent);
})
// #ifdef H5 || APP-PLUS
if (this.opts[0]) {
let i
for (i = this.childs.length; i--;) {
if (this.childs[i].name === 'img') break
}
if (i !== -1) {
this.observer = uni.createIntersectionObserver(this).relativeToViewport({
top: 500,
bottom: 500
})
this.observer.observe('._img', res => {
if (res.intersectionRatio) {
this.$set(this.ctrl, 'load', 1)
this.observer.disconnect()
}
})
}
}
// #endif
},
beforeDestroy () {
// #ifdef H5 || APP-PLUS
if (this.observer) {
this.observer.disconnect()
}
// #endif
},
methods:{
// #ifdef MP-WEIXIN
toJSON () { return this },
// #endif
/**
* @description 播放视频事件
* @param {Event} e
*/
play (e) {
this.root.$emit('play')
// #ifndef APP-PLUS
if (this.root.pauseVideo) {
let flag = false
const id = e.target.id
for (let i = this.root._videos.length; i--;) {
if (this.root._videos[i].id === id) {
flag = true
} else {
this.root._videos[i].pause() // 自动暂停其他视频
}
}
// 将自己加入列表
if (!flag) {
const ctx = uni.createVideoContext(id
// #ifndef MP-BAIDU
, this
// #endif
)
ctx.id = id
if (this.root.playbackRate) {
ctx.playbackRate(this.root.playbackRate)
}
this.root._videos.push(ctx)
}
}
// #endif
},
/**
* @description 图片点击事件
* @param {Event} e
*/
imgTap (e) {
const node = this.childs[e.currentTarget.dataset.i]
if (node.a) {
this.linkTap(node.a)
return
}
if (node.attrs.ignore) return
// #ifdef H5 || APP-PLUS
node.attrs.src = node.attrs.src || node.attrs['data-src']
// #endif
this.root.$emit('imgtap', node.attrs)
// 自动预览图片
if (this.root.previewImg) {
uni.previewImage({
// #ifdef MP-WEIXIN
showmenu: this.root.showImgMenu,
// #endif
// #ifdef MP-ALIPAY
enablesavephoto: this.root.showImgMenu,
enableShowPhotoDownload: this.root.showImgMenu,
// #endif
current: parseInt(node.attrs.i),
urls: this.root.imgList
})
}
},
/**
* @description 图片长按
*/
imgLongTap (e) {
// #ifdef APP-PLUS
const attrs = this.childs[e.currentTarget.dataset.i].attrs
if (this.opts[3] && !attrs.ignore) {
uni.showActionSheet({
itemList: ['保存图片'],
success: () => {
const save = path => {
uni.saveImageToPhotosAlbum({
filePath: path,
success () {
uni.showToast({
title: '保存成功'
})
}
})
}
if (this.root.imgList[attrs.i].startsWith('http')) {
uni.downloadFile({
url: this.root.imgList[attrs.i],
success: res => save(res.tempFilePath)
})
} else {
save(this.root.imgList[attrs.i])
}
}
})
}
// #endif
},
/**
* @description 图片加载完成事件
* @param {Event} e
*/
imgLoad (e) {
const i = e.currentTarget.dataset.i
/* #ifndef H5 || (APP-PLUS && VUE2) */
if (!this.childs[i].w) {
// 设置原宽度
this.$set(this.ctrl, i, e.detail.width)
} else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
// 加载完毕,取消加载中占位图
this.$set(this.ctrl, i, 1)
}
this.checkReady()
},
/**
* @description 检查是否所有图片加载完毕
*/
checkReady () {
if (this.root && !this.root.lazyLoad) {
this.root._unloadimgs -= 1
if (!this.root._unloadimgs) {
setTimeout(() => {
this.root.getRect().then(rect => {
this.root.$emit('ready', rect)
}).catch(() => {
this.root.$emit('ready', {})
})
}, 350)
}
}
},
/**
* @description 链接点击事件
* @param {Event} e
*/
linkTap (e) {
const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
const attrs = node.attrs || e
const href = attrs.href
this.root.$emit('linktap', Object.assign({
innerText: this.root.getText(node.children || []) // 链接内的文本内容
}, attrs))
if (href) {
if (href[0] === '#') {
// 跳转锚点
this.root.navigateTo(href.substring(1)).catch(() => { })
} else if (href.split('?')[0].includes('://')) {
// 复制外部链接
if (this.root.copyLink) {
// #ifdef H5
window.open(href)
// #endif
// #ifdef MP
uni.setClipboardData({
data: href,
success: () =>
uni.showToast({
title: '链接已复制'
})
})
// #endif
// #ifdef APP-PLUS
plus.runtime.openWeb(href)
// #endif
}
} else {
// 跳转页面
uni.navigateTo({
url: href,
fail () {
uni.switchTab({
url: href,
fail () { }
})
}
})
}
}
},
/**
* @description 错误事件
* @param {Event} e
*/
mediaError (e) {
const i = e.currentTarget.dataset.i
const node = this.childs[i]
// 加载其他源
if (node.name === 'video' || node.name === 'audio') {
let index = (this.ctrl[i] || 0) + 1
if (index > node.src.length) {
index = 0
}
if (index < node.src.length) {
this.$set(this.ctrl, i, index)
return
}
} else if (node.name === 'img') {
// #ifdef H5 && VUE3
if (this.opts[0] && !this.ctrl.load) return
// #endif
// 显示错误占位图
if (this.opts[2]) {
this.$set(this.ctrl, i, -1)
}
this.checkReady()
}
if (this.root) {
this.root.$emit('error', {
source: node.name,
attrs: node.attrs,
// #ifndef H5 && VUE3
errMsg: e.detail.errMsg
// #endif
})
}
}
}
}
</script>
<style>
/* a 标签默认效果 */
._a {
padding: 1.5px 0 1.5px 0;
color: #366092;
word-break: break-all;
}
/* a 标签点击态效果 */
._hover {
text-decoration: underline;
opacity: 0.7;
}
/* 图片默认效果 */
._img {
max-width: 100%;
-webkit-touch-callout: none;
}
/* 内部样式 */
._block {
display: block;
}
._b,
._strong {
font-weight: bold;
}
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.83em;
}
._h6 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
display: block;
font-weight: bold;
}
._image {
height: 1px;
}
._ins {
text-decoration: underline;
}
._li {
display: list-item;
}
._ol {
list-style-type: decimal;
}
._ol,
._ul {
display: block;
padding-left: 40px;
margin: 1em 0;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._sub {
font-size: smaller;
vertical-align: sub;
}
._sup {
font-size: smaller;
vertical-align: super;
}
._thead,
._tbody,
._tfoot {
display: table-row-group;
}
._tr {
display: table-row;
}
._td,
._th {
display: table-cell;
vertical-align: middle;
}
._th {
font-weight: bold;
text-align: center;
}
._ul {
list-style-type: disc;
}
._ul ._ul {
margin: 0;
list-style-type: circle;
}
._ul ._ul ._ul {
list-style-type: square;
}
._abbr,
._b,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong,
._sub,
._sup {
display: inline;
}
/* #ifdef APP-PLUS */
._video {
width: 300px;
height: 225px;
}
/* #endif */
</style>
File diff suppressed because it is too large Load Diff
+76
View File
@@ -0,0 +1,76 @@
{
"id": "mp-html",
"displayName": "mp-html 富文本组件【全端支持,支持编辑、latex等扩展】",
"version": "v2.4.2",
"description": "一个强大的富文本组件,高效轻量,功能丰富",
"keywords": [
"富文本",
"编辑器",
"html",
"rich-text",
"editor"
],
"repository": "https://github.com/jin-yufeng/mp-html",
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mp-html",
"type": "component-vue"
},
"uni_modules": {
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
},
"Vue": {
"vue2": "y",
"vue3": "y"
}
}
}
}
}
@@ -0,0 +1 @@
"use strict";function t(t){for(var e=Object.create(null),n=t.attributes.length;n--;)e[t.attributes[n].name]=t.attributes[n].value;return e}function e(){a[1]&&(this.src=a[1],this.onerror=null),this.onclick=null,this.ontouchstart=null,uni.postMessage({data:{action:"onError",source:"img",attrs:t(this)}})}function n(){window.unloadimgs-=1,0===window.unloadimgs&&uni.postMessage({data:{action:"onReady"}})}function o(r,s,c){for(var d=0;d<r.length;d++)!function(d){var u=r[d],l=void 0;if(u.type&&"node"!==u.type)l=document.createTextNode(u.text.replace(/&amp;/g,"&"));else{var g=u.name;"svg"===g&&(c="http://www.w3.org/2000/svg"),"html"!==g&&"body"!==g||(g="div"),l=c?document.createElementNS(c,g):document.createElement(g);for(var p in u.attrs)l.setAttribute(p,u.attrs[p]);if(u.children&&o(u.children,l,c),"img"===g){if(window.unloadimgs+=1,l.onload=n,l.onerror=n,!l.src&&l.getAttribute("data-src")&&(l.src=l.getAttribute("data-src")),u.attrs.ignore||(l.onclick=function(e){e.stopPropagation(),uni.postMessage({data:{action:"onImgTap",attrs:t(this)}})}),a[2]){var h=new Image;h.src=l.src,l.src=a[2],h.onload=function(){l.src=this.src},h.onerror=function(){l.onerror()}}l.onerror=e}else if("a"===g)l.addEventListener("click",function(e){e.stopPropagation(),e.preventDefault();var n,o=this.getAttribute("href");o&&"#"===o[0]&&(n=(document.getElementById(o.substr(1))||{}).offsetTop),uni.postMessage({data:{action:"onLinkTap",attrs:t(this),offset:n}})},!0);else if("video"===g||"audio"===g)i.push(l),u.attrs.autoplay||u.attrs.controls||l.setAttribute("controls","true"),l.onplay=function(){if(uni.postMessage({data:{action:"onPlay"}}),a[3])for(var t=0;t<i.length;t++)i[t]!==this&&i[t].pause()},l.onerror=function(){uni.postMessage({data:{action:"onError",source:g,attrs:t(this)}})};else if("table"===g&&a[4]&&!l.style.cssText.includes("inline")){var f=document.createElement("div");f.style.overflow="auto",f.appendChild(l),l=f}else"svg"===g&&(c=void 0)}s.appendChild(l)}(d)}document.addEventListener("UniAppJSBridgeReady",function(){document.body.onclick=function(){return uni.postMessage({data:{action:"onClick"}})},uni.postMessage({data:{action:"onJSBridgeReady"}})});var a,i=[];window.setContent=function(t,e,n){var r=document.getElementById("content");e[0]&&(document.body.style.cssText=e[0]),e[5]||(r.style.userSelect="none"),n||(r.innerHTML="",i=[]),a=e,window.unloadimgs=0;var s=document.createDocumentFragment();o(t,s),r.appendChild(s);var c=r.scrollHeight;uni.postMessage({data:{action:"onLoad",height:c}}),window.unloadimgs||uni.postMessage({data:{action:"onReady",height:c}}),clearInterval(window.timer),window.timer=setInterval(function(){r.scrollHeight!==c&&(c=r.scrollHeight,uni.postMessage({data:{action:"onHeightChange",height:c}}))},350)},window.onunload=function(){clearInterval(window.timer)};
@@ -0,0 +1 @@
!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n():"function"==typeof define&&define.amd?define(n):(e=e||self).uni=n()}(this,(function(){"use strict";try{var e={};Object.defineProperty(e,"passive",{get:function(){!0}}),window.addEventListener("test-passive",null,e)}catch(e){}var n=Object.prototype.hasOwnProperty;function t(e,t){return n.call(e,t)}var i=[],a=function(e,n){var t={options:{timestamp:+new Date},name:e,arg:n};if(window.__dcloud_weex_postMessage||window.__dcloud_weex_){if("postMessage"===e){var a={data:[n]};return window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessage(a):window.__dcloud_weex_.postMessage(JSON.stringify(a))}var o={type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}};window.__dcloud_weex_postMessage?window.__dcloud_weex_postMessageToService(o):window.__dcloud_weex_.postMessageToService(JSON.stringify(o))}if(!window.plus)return window.parent.postMessage({type:"WEB_INVOKE_APPSERVICE",data:t,pageId:""},"*");if(0===i.length){var r=plus.webview.currentWebview();if(!r)throw new Error("plus.webview.currentWebview() is undefined");var d=r.parent(),s="";s=d?d.id:r.id,i.push(s)}if(plus.webview.getWebviewById("__uniapp__service"))plus.webview.postMessageToUniNView({type:"WEB_INVOKE_APPSERVICE",args:{data:t,webviewIds:i}},"__uniapp__service");else{var w=JSON.stringify(t);plus.webview.getLaunchWebview().evalJS('UniPlusBridge.subscribeHandler("'.concat("WEB_INVOKE_APPSERVICE",'",').concat(w,",").concat(JSON.stringify(i),");"))}},o={navigateTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("navigateTo",{url:encodeURI(n)})},navigateBack:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.delta;a("navigateBack",{delta:parseInt(n)||1})},switchTab:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("switchTab",{url:encodeURI(n)})},reLaunch:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("reLaunch",{url:encodeURI(n)})},redirectTo:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=e.url;a("redirectTo",{url:encodeURI(n)})},getEnv:function(e){window.plus?e({plus:!0}):e({h5:!0})},postMessage:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};a("postMessage",e.data||{})}},r=/uni-app/i.test(navigator.userAgent),d=/Html5Plus/i.test(navigator.userAgent),s=/complete|loaded|interactive/;var w=window.my&&navigator.userAgent.indexOf("AlipayClient")>-1;var u=window.swan&&window.swan.webView&&/swan/i.test(navigator.userAgent);var c=window.qq&&window.qq.miniProgram&&/QQ/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var g=window.tt&&window.tt.miniProgram&&/toutiaomicroapp/i.test(navigator.userAgent);var v=window.wx&&window.wx.miniProgram&&/micromessenger/i.test(navigator.userAgent)&&/miniProgram/i.test(navigator.userAgent);var p=window.qa&&/quickapp/i.test(navigator.userAgent);for(var l,_=function(){window.UniAppJSBridge=!0,document.dispatchEvent(new CustomEvent("UniAppJSBridgeReady",{bubbles:!0,cancelable:!0}))},f=[function(e){if(r||d)return window.__dcloud_weex_postMessage||window.__dcloud_weex_?document.addEventListener("DOMContentLoaded",e):window.plus&&s.test(document.readyState)?setTimeout(e,0):document.addEventListener("plusready",e),o},function(e){if(v)return window.WeixinJSBridge&&window.WeixinJSBridge.invoke?setTimeout(e,0):document.addEventListener("WeixinJSBridgeReady",e),window.wx.miniProgram},function(e){if(c)return window.QQJSBridge&&window.QQJSBridge.invoke?setTimeout(e,0):document.addEventListener("QQJSBridgeReady",e),window.qq.miniProgram},function(e){if(w){document.addEventListener("DOMContentLoaded",e);var n=window.my;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){if(u)return document.addEventListener("DOMContentLoaded",e),window.swan.webView},function(e){if(g)return document.addEventListener("DOMContentLoaded",e),window.tt.miniProgram},function(e){if(p){window.QaJSBridge&&window.QaJSBridge.invoke?setTimeout(e,0):document.addEventListener("QaJSBridgeReady",e);var n=window.qa;return{navigateTo:n.navigateTo,navigateBack:n.navigateBack,switchTab:n.switchTab,reLaunch:n.reLaunch,redirectTo:n.redirectTo,postMessage:n.postMessage,getEnv:n.getEnv}}},function(e){return document.addEventListener("DOMContentLoaded",e),o}],m=0;m<f.length&&!(l=f[m](_));m++);l||(l={});var E="undefined"!=typeof uni?uni:{};if(!E.navigateTo)for(var b in l)t(l,b)&&(E[b]=l[b]);return E.webView=l,E}));
@@ -0,0 +1 @@
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow-x:scroll;overflow-y:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}</style></head><body><div id="content" style="overflow:hidden"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>
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"
}
}
+14
View File
@@ -0,0 +1,14 @@
## 1.0.62023-04-12
为防冲突,给selectComponent两个函数添加前缀zp
## 1.0.52023-03-28
修复ts项目找不到setData的问题
## 1.0.42023-03-07
修复小程序报错
## 1.0.32023-02-26
修复pageLifetimes
## 1.0.22023-01-06
增加使用文档
## 1.0.12022-12-07
更新文档
## 1.0.02022-12-07
init
+52
View File
@@ -0,0 +1,52 @@
//lifetimes
import { pageLifetimes } from './lifecycle/pageLifetimes';
//methods
import { clone } from './methods/clone';
import { handleDataset } from './methods/dataset';
import { escape2Html, html2Escape } from './methods/escape';
import { parseEventDynamicCode } from './methods/event';
import { getTabBar } from './methods/getTabBar';
import { getRelationNodes } from './methods/relation';
import { selectComponent as zpSelectComponent,
selectAllComponents as zpSelectAllComponents } from './methods/selectComponent';
import { setData } from './methods/setData';
export default {
// #ifndef VUE3
install(Vue, option) {
Vue.mixin({
...pageLifetimes,
methods: {
clone,
handleDataset,
escape2Html,
html2Escape,
parseEventDynamicCode,
getTabBar,
getRelationNodes,
zpSelectComponent,
zpSelectAllComponents,
setData
}
})
}
// #endif
// #ifdef VUE3
...pageLifetimes,
methods: {
clone,
handleDataset,
escape2Html,
html2Escape,
parseEventDynamicCode,
getTabBar,
getRelationNodes,
zpSelectComponent,
zpSelectAllComponents,
setData
}
// #endif
}
+31
View File
@@ -0,0 +1,31 @@
import Vue from 'vue';
//lifetimes
import { pageLifetimes } from './lifecycle/pageLifetimes';
//methods
import { clone } from './methods/clone';
import { handleDataset } from './methods/dataset';
import { escape2Html, html2Escape } from './methods/escape';
import { parseEventDynamicCode } from './methods/event';
import { getTabBar } from './methods/getTabBar';
import { getRelationNodes } from './methods/relation';
import { selectComponent as zpSelectComponent, selectAllComponents as zpSelectAllComponents } from './methods/selectComponent';
import { setData } from './methods/setData';
// console.log("lifecycle", lifecycle)
export default Vue.extend({
...pageLifetimes,
methods: {
clone,
handleDataset,
escape2Html,
html2Escape,
parseEventDynamicCode,
getTabBar,
getRelationNodes,
zpSelectComponent,
zpSelectAllComponents,
setData
}
});
+2
View File
@@ -0,0 +1,2 @@
export * from './pageLifetimes'
@@ -0,0 +1,39 @@
/**
* 组件pageLifetimes处理,需在页面生命周期里调用
* @param {Object} node
* @param {Object} lifeName
*/
function handlePageLifetime(node, lifeName) {
node.$children.map(child => {
if (typeof child[lifeName] == 'function') child[lifeName]()
handlePageLifetime(child, lifeName)
})
}
export const pageLifetimes = {
onLoad() {
// uni.onWindowResize(CALLBACK) 监听窗口尺寸变化事件
// 平台差异说明
// App H5 微信小程序 支付宝小程序 百度小程序 抖音小程序 飞书小程序 QQ小程序
// √ √ √ x x x √ √
// #ifdef H5 || MP-LARK || MP-QQ
uni.onWindowResize((res) => {
handlePageLifetime(this, "handlePageResize")
})
// #endif
},
onShow() {
handlePageLifetime(this, "handlePageShow")
},
onHide() {
handlePageLifetime(this, "handlePageHide")
},
onResize() {
//onResize 监听窗口尺寸变化 App、微信小程序、快手小程序
// #ifdef APP || MP-WEIXIN || MP-KUAISHOU
handlePageLifetime(this, "handlePageResize")
// #endif
}
};
+10
View File
@@ -0,0 +1,10 @@
/**
* 用于处理对props进行赋值的情况
* //简单处理一下就行了
*
* @param {*} target
* @returns
*/
export function clone(target) {
return JSON.parse(JSON.stringify(target))
}
+21
View File
@@ -0,0 +1,21 @@
/**
* 用于处理dataset
* 自定义组件的事件里,是获取不到e.currentTarget.dataset的
* 因此收集data-参数,手动传进去
*
* @param {*} event
* @param {*} dataSet
*/
export function handleDataset(event, dataSet = {}) {
if (event && !event.currentTarget) {
if (dataSet.tagId) {
event.currentTarget = {
id: dataSet.tagId
}
} else {
event.currentTarget = {
dataset: dataSet
}
}
}
}
+36
View File
@@ -0,0 +1,36 @@
/**
* 转义符换成普通字符
* @param {*} str
* @returns
*/
export function escape2Html(str) {
if (!str) return str
var arrEntities = {
'lt': '<',
'gt': '>',
'nbsp': ' ',
'amp': '&',
'quot': '"'
}
return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function(all, t) {
return arrEntities[t]
})
}
/**
* 普通字符转换成转义符
* @param {*} sHtml
* @returns
*/
export function html2Escape(sHtml) {
if (!sHtml) return sHtml
return sHtml.replace(/[<>&"]/g, function(c) {
return {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;'
} [c]
})
}
+10
View File
@@ -0,0 +1,10 @@
/**
* 解析事件里的动态函数名,这种没有()的函数名,在uniapp不被执行
* 比如:<view bindtap="{{openId==undefined?'denglu':'hy_to'}}">立即</view>
* @param {*} exp
*/
export function parseEventDynamicCode(e, exp) {
if (typeof(this[exp]) === 'function') {
this[exp](e)
}
}
@@ -0,0 +1,19 @@
/**
* 接管getTabBar函数,默认uni-app是没有这个函数的
* 适用于使用custom-tab-bar自定义导航栏的小程序项目
* 需注意:
* 1.custom-tab-bar下面仍是小程序文件
* 2.pages.json里面需使用条件编译区分好小程序和非小程序的tabBar配置
*/
export function getTabBar() {
return {
setData(obj) {
if (typeof this.$mp?.page?.getTabBar === 'function' &&
this.$mp?.page?.getTabBar()) {
this.$mp.page.getTabBar().setData(obj)
} else {
console.log("当前平台不支持getTabBar(),已稍作处理,详细请参见相关文档。")
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
export * from './clone'
export * from './dataset'
export * from './escape'
export * from './event'
export * from './getTabBar'
export * from './relation'
export * from './selectComponent'
export * from './setData'
+10
View File
@@ -0,0 +1,10 @@
/**
* 组件间关系
* 注意:须与p-f-unicom配合使用!!!
* @param {*} name
* @returns
*/
export function getRelationNodes(name) {
if(!this.$unicom) throw "this.getRelationNodes()需与p-f-unicom配合使用!"
return this.$unicom('@' + name)
}
@@ -0,0 +1,196 @@
const createTraverse = () => {
let stop = false;
return function traverse(root, callback) {
if (!stop && typeof callback === 'function') {
let children = root.$children;
for (let index = 0; !stop && index < children.length; index++) {
let element = children[index];
stop = callback(element) === true;
traverse(element, callback);
}
}
};
};
/**
* 安全的JSON.stringify
* @param {Object} node
*/
function safeStringify(node) {
var cache = [];
var str = JSON.stringify(node, function(key, value) {
if (typeof value === 'object' && value !== null) {
if (cache.indexOf(value) !== -1) {
// 移除
return;
}
// 收集所有的值
cache.push(value);
}
return value;
});
cache = null; // 清空变量,便于垃圾回收机制回收
return str
}
const match = (node, selector) => {
var vnode = node._vnode;
//好家伙,在微信小程序里,node里面根本找不到class,因此这种方式没法搞了
//关键之处!
// console.log("attrs", (vnode.context.$vnode.data));
vnode = vnode?.context?.$vnode ?? ""
//console.log(vnode.data) --> [Object] {"staticClass":"bar","attrs":{"_i":0}} at selectComponent.js:72
if (!vnode || !vnode.data) {
return false
}
let attrs = vnode.data.attrs || {};
let staticClass = vnode.data.staticClass || '';
const id = attrs.id || '';
if (selector[0] === '#') {
return selector.substr(1) === id;
} else {
staticClass = staticClass.trim().split(' ');
selector = selector.substr(1).split('.');
return selector.reduce((a, c) => a && staticClass.includes(c), true);
}
};
const selectorBuilder = (selector) => {
selector = selector.replace(/>>>/g, '>');
selector = selector.split('>').map(s => {
return s.trim().split(' ').join(`').descendant('`);
}).join(`').child('`);
// 替换掉new Function方式,因为小程序不支持new Function和eval
//return new Function('Selector', 'node', 'all', `return new Selector(node, all).descendant('` + selector + `')`);
return function(Selector, node, all) {
return new Selector(node, all).descendant(selector)
}
};
class Selector {
constructor(node, all = false) {
this.nodes = [node];
this.all = all;
}
child(selector) {
let matches = [];
if (this.all) {
this.nodes.forEach(node => {
matches.push(...node.$children.filter(node => match(node, selector)));
});
} else {
if (this.nodes.length > 0) {
let node = this.nodes[0].$children.find(node => match(node, selector));
matches = node ? [node] : [];
}
}
this.nodes = matches;
return this;
}
descendant(selector) {
let matches = [];
this.nodes.forEach(root => {
createTraverse()(root, (node) => {
if (match(node, selector)) {
matches.push(node);
return !this.all;
}
});
});
this.nodes = matches;
return this;
}
}
////////////////////////////////////////////selectComponent//////////////////////////////////////////////////
/**
* 其他平台,如APP
* @param {Object} selector
*/
function selectComponentOther(selector) {
const selectors = selector.split(',').map(s => s.trim());
if (!selectors[0]) {
return null;
}
const querySelector = selectorBuilder(selectors[0]);
return querySelector(Selector, this, false, selector).nodes[0];
}
/**
* 还是用这个微信小程序的实现吧
* @param {Object} selector
*/
var selectComponentWeiXin2 = function(selector) {
console.log(".$scope",this.$scope.selectComponent(selector))
return this.$scope.selectComponent(selector)?.data || undefined
}
/**
* selectComponent
* @param {Object} args
*/
export function selectComponent(args) {
// console.log(".$scope",this.$scope)
// #ifdef MP
//H5和小程序能正常使用这个函数
//重写selectComponent函数,因为默认会多一层$vm
return selectComponentWeiXin2.call(this, args)
// #endif
// #ifndef MP
// 因App的结构略有差异,此函数无法正常使用
// function(e){return function e(t,n){if(n(t.$vnode||t._vnode))return t;for(var r=t.$children,i=0;i<r.length;i++){var o=e(r[i],n);if(o)return o}}(this,ov(e))}
// return selectComponentOther(args)
return selectComponentOther.call(this, args)
// #endif
}
////////////////////////////////////////////selectAllComponents//////////////////////////////////////////////////
/**
* 其他平台,如APP
* @param {Object} selector
*/
function selectAllComponentsOther(selector) {
const selectors = selector.split(',').map(s => s.trim());
let selected = [];
selectors.forEach(selector => {
const querySelector = selectorBuilder(selector);
selected = selected.concat(querySelector(Selector, this, true, selector).nodes);
});
return selected;
}
/**
* 还是用这个微信小程序的实现吧
* @param {Object} selector
*/
var selectAllComponentsWeiXin2 = function(selector) {
var list = this.$scope.selectAllComponents(selector) || []
list = list.map(item => item.data)
return list
}
/**
* selectAllComponents
* @param {Object} args
*/
export function selectAllComponents(args) {
// #ifdef MP
//H5和小程序能正常使用这个函数
//重写selectComponent函数,因为默认会多一层$vm
return selectAllComponentsWeiXin2.call(this, args)
// #endif
// #ifndef MP
// 因App的结构略有差异,此函数无法正常使用
return selectAllComponentsOther.call(this, args)
// #endif
}
+90
View File
@@ -0,0 +1,90 @@
import _set from '../utils/_set'
import debounce from '../utils/debounce'
/**
* 老setData polyfill
* 用于转换后的uniapp的项目能直接使用this.setData()函数
* @param {*} obj
* @param {*} callback
*/
function oldSetData (obj, callback) {
let that = this
const handleData = (tepData, tepKey, afterKey) => {
var tepData2 = tepData
tepKey = tepKey.split('.')
tepKey.forEach(item => {
if (tepData[item] === null || tepData[item] === undefined) {
let reg = /^[0-9]+$/
tepData[item] = reg.test(afterKey) ? [] : {}
tepData2 = tepData[item]
} else {
tepData2 = tepData[item]
}
})
return tepData2
}
const isFn = function (value) {
return typeof value == 'function' || false
}
Object.keys(obj).forEach(function (key) {
let val = obj[key]
key = key.replace(/\]/g, '').replace(/\[/g, '.')
let front, after
let index_after = key.lastIndexOf('.')
if (index_after != -1) {
after = key.slice(index_after + 1)
front = handleData(that, key.slice(0, index_after), after)
} else {
after = key
front = that
}
if (front.$data && front.$data[after] === undefined) {
Object.defineProperty(front, after, {
get () {
return front.$data[after]
},
set (newValue) {
front.$data[after] = newValue
that.hasOwnProperty("$forceUpdate") && that.$forceUpdate()
},
enumerable: true,
configurable: true
})
front[after] = val
} else {
that.$set(front, after, val)
}
})
// this.$forceUpdate();
isFn(callback) && this.$nextTick(callback)
}
/**
* 变量名正则
*/
const variableNameReg = /^([^\x00-\xff]|[a-zA-Z_$])([^\x00-\xff]|[a-zA-Z0-9_$])*$/
/**
* 2022-10-31 重写setData
* 2023-05-08 增加微信“简易双向绑定”支持
* 用于转换后的uniapp的项目能直接使用this.setData()函数
* @param {Object} obj
* @param {Object} callback
*/
export function setData (obj, callback = null) {
Object.keys(obj).forEach((key) => {
_set(this, key, obj[key])
//处理微信“简易双向绑定”
if (variableNameReg.test(key) && key.endsWith("Clone")) {
let propName = key.replace(/Clone$/, "")
if (this.$options && this.$options.propsData[propName]) {
this.$emit(`update:${propName}`, obj[key])
}
}
})
this.$forceUpdate();
if (typeof callback == 'function') this.$nextTick(callback)
}
+86
View File
@@ -0,0 +1,86 @@
{
"id": "zp-mixins",
"displayName": "zp-mixins",
"version": "1.0.6",
"description": "用于minipropgram-to-uniapp的mixins",
"keywords": [
"mixins",
"小程序",
"代码转换"
],
"repository": "https://github.com/zhangdaren/zp-mixins",
"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": "y",
"vue3": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y",
"钉钉": "y",
"快手": "y",
"飞书": "y",
"京东": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
},
"dependencies": {
"tern": "^0.24.3"
}
}
+107
View File
@@ -0,0 +1,107 @@
# zp-mixins
本插件,基本无法单独使用。
为方便miniprogram-to-uniapp(小程序转uniapp项目)工具后续升级,特单独摘出做成插件,便于后续更新。
注意:
1.本插件仅限于miniprogram-to-uniapp(小程序转uni-app项目)转换工具使用,
2.如果是新项目或uni-app项目,建议还是使用vue/uni-app它自身支持的方式实现,效率更高些。
3.目前仅支持vue2/vue3的options写法。至于setup,因其天生弱化mixin,也无this,建议直接import导入按需使用吧~
# 安装
点击右侧 “使用 HBuilderX 导入插件” 按钮,并选择对应项目。
# 使用
在main.js文件里面增加引用,即可全局使用这些函数,如下所示:
```
import App from './App'
// 添加zpMixins
import zpMixins from '@/uni_modules/zp-mixins/index.js'
// #ifndef VUE3
import Vue from 'vue'
Vue.use(zpMixins)
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
// #endif
// #ifdef VUE3
import { createSSRApp } from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.mixin(zpMixins)
return {
app
}
}
// #endif
```
注意:vue3项目仅支持Options API方式(setup API写法有点不同,不支持这种方式使用,建议直接通过import引入使用)。
# 功能介绍
## pageLifetimes(组件所在页面的生命周期)
处理组件里的pageLifetimes
## clone(深拷贝)
深拷贝的简单版本,未处理循环引用。
## handleDataset
用于处理dataset
在自定义组件的事件里,是获取不到e.currentTarget.dataset的
因此收集data-参数,手动传进去
## html2Escape 普通字符转换成转义符
用于替换wxParse为mp-html时使用
## escape2Html 转义符换成普通字符
暂未用上
## parseEventDynamicCode
解析事件里的动态函数名,这种没有()的函数名,在uniapp不被执行
比如:<view bindtap="{{openId==undefined?'denglu':'hy_to'}}">立即</view>
## getTabBar
实现小程序自定义组件的this.getTabBar().setData()函数
## getRelationNodes
获取组件间关系this.getRelationNodes()
注意:
1.须与p-f-unicom配合使用!
2.并不能与小程序的getRelationNodes相提并论,因为底层实现不一样
## selectComponent
## selectAllComponents
抹平各平台差异,实现类似于小程序的selectComponent和selectAllComponents函数。
使用方式与小程序一致。
注:新项目建议还是使用ref这种方式,获取组件实例。
## setData
1.实现setData函数,让uni-app也能支持使用setData函数。
2.实现微信“简易双向绑定”
注意:本函数仅为小程序转换uniapp项目所作的支撑,如uni-app项目里,最好还是使用this.xx这种方式。
+26
View File
@@ -0,0 +1,26 @@
/**
* lodash set
* @param {*} obj
* @param {*} path
* @param {*} value
* @returns
*/
function _set(obj, path, value) {
if (Object(obj) !== obj) return obj // When obj is not an object
// If not yet an array, get the keys from the string-path
if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []
path.slice(0, -1).reduce((a, c, i) => // Iterate all of them except the last one
Object(a[c]) === a[c] // Does the key exist and is its value an object?
// Yes: then follow that path
?
a[c]
// No: create the key. Is the next key a potential array-index?
:
a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] // Yes: assign a new array object
:
{}, // No: assign a new plain object
obj)[path[path.length - 1]] = value // Finally assign the value to the last key
return obj // Return the top-level object to allow chaining
}
export default _set
+17
View File
@@ -0,0 +1,17 @@
/**
* 防抖
* @param {Object} scope //引用的this,发现不显式传this,拿不到。
* @param {Object} fn
* @param {Object} delay
*/
let t = null
const debounce = function(scope, fn, delay) {
if (t !== null) {
clearTimeout(t)
}
t = setTimeout(() => {
scope[fn]()
}, delay)
}
export default debounce