commit
1cebaa25e9
11 changed files with 2806 additions and 8 deletions
@ -0,0 +1,191 @@ |
|||||||
|
## 为减小组件包的大小,默认组件包中不包含编辑、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` 交流群:`699734691` |
||||||
|
|
||||||
|
查看 [问题反馈](https://jin-yufeng.gitee.io/mp-html/#/question/feedback) 了解更多 |
@ -0,0 +1,121 @@ |
|||||||
|
## v2.4.1(2022-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.0(2022-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.2(2022-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.1(2022-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.0(2022-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.2(2022-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.1(2021-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.0(2021-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.5(2021-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.4(2021-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.3(2021-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.2(2021-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.1(2021-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.2(2021-03-21) |
||||||
|
修复了 `nvue` 端使用可能报错的问题 |
||||||
|
## v2.1.0(2021-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.5(2021-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.1 |
||||||
|
* @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(/&/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="'<img class=\'_img\' style=\''+n.attrs.style+'\' src=\''+n.attrs.src+'\'>'" :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="n.f+';display:inline'" :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.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
@ -0,0 +1,76 @@ |
|||||||
|
{ |
||||||
|
"id": "mp-html", |
||||||
|
"displayName": "mp-html 富文本组件【全端支持,支持编辑、latex等扩展】", |
||||||
|
"version": "v2.4.1", |
||||||
|
"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(/&/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> |
Loading…
Reference in new issue