12 changed files with 2581 additions and 0 deletions
@ -0,0 +1,131 @@ |
|||||
|
## 1.5.0(2024-11-01) |
||||
|
1. 更新弹窗中使用的示例 |
||||
|
## 1.4.9(2024-08-30) |
||||
|
1. 修复在app端和小程序端插入超链接可能会回显出部分特殊标识的bug |
||||
|
## 1.4.8(2024-08-16) |
||||
|
1. 有群友反馈,通过wangEditor添加的含有图片的富文本的宽高是在style内联样式中的,但是uni-editor不能解析img标签的内联宽高,因此我封装一个工具方法convertImgStylesToAttributes,处理一下富文本字符串即可,详见示例一 |
||||
|
## 1.4.7(2024-08-12) |
||||
|
1. 新增标题、字体、字体大小、字间距段前后距等工具子级悬浮工具栏 |
||||
|
2. 对于字体样式:可在组件的data中的fabTools.fontFamily中自行引入添加你本地的自定义字体(app中对于部分css自带的字体例如微软雅黑等不支持,因为app中不自带这些字体,你可能需要自行引入字体文件) |
||||
|
## 1.4.6(2024-08-08) |
||||
|
1. 修复字体工具栏无法隐藏的bug |
||||
|
## 1.4.5(2024-07-21) |
||||
|
1. 解决video图标冲突问题 |
||||
|
## 1.4.4(2024-07-20) |
||||
|
1. 新增视频插入功能 |
||||
|
2. 更新示例一(关于视频插入参考请见示例一) |
||||
|
## 1.4.3(2024-07-16) |
||||
|
1. 更新示例工程 |
||||
|
## 1.4.2(2024-07-16) |
||||
|
1. 更新示例工程 |
||||
|
## 1.4.1(2024-06-14) |
||||
|
1. 更新示例工程 |
||||
|
## 1.4.0(2024-05-31) |
||||
|
1. 更新示例工程 |
||||
|
## 1.3.9(2024-05-31) |
||||
|
1. 修复调色板无法正常选色的问题 |
||||
|
## 1.3.8(2024-05-13) |
||||
|
1. 更新示例三(微信小程序上使用setContents造成聚焦滚动的处理) |
||||
|
## 1.3.7(2024-05-10) |
||||
|
1. 修复添加超链接后,不触发input更新当前最新内容的bug |
||||
|
## 1.3.6(2024-05-09) |
||||
|
1. 文档迁移 |
||||
|
## 1.3.5(2024-05-09) |
||||
|
1. 所有事件携带编辑器id参数,以便循环时能区分处理 |
||||
|
2. 更新示例工程 |
||||
|
## 1.3.4(2024-05-08) |
||||
|
1. 更新示例工程 |
||||
|
2. 新增editorId参数 |
||||
|
## 1.3.3(2024-03-22) |
||||
|
1. 修复微信小程序长按无法粘贴的问题 |
||||
|
## 1.3.2(2024-03-14) |
||||
|
1. 更新了toolbar样式与配置,见文档 |
||||
|
2. 更新示例工程,媒体查询响应式写法 |
||||
|
3. 优化了只读模式效果,开启只读模式后,文章内容的超链接可正常点击并跳转 |
||||
|
## 1.3.1(2024-03-14) |
||||
|
1. 优化了只读功能,开启只读后自动隐藏工具栏 |
||||
|
2. 更新示例工程 |
||||
|
## 1.3.0(2024-03-07) |
||||
|
1. 新增addLink的emit事件 |
||||
|
## 1.2.9(2024-02-23) |
||||
|
1. 更新文档 |
||||
|
## 1.2.8(2024-02-23) |
||||
|
1. 新增了添加超链接的工具,toolbar中link字段,默认开启 |
||||
|
2. 优化了部分逻辑 |
||||
|
3. 更新文档、更新示例工程 |
||||
|
## 1.2.7(2024-02-23) |
||||
|
1. 更新文档,更新示例工程 |
||||
|
2. 添加toolbar中图标字体大小可配置项 |
||||
|
## 1.2.6(2024-02-22) |
||||
|
1. 添加导出工具按钮,可将当前已编辑的html导出至页面解析 |
||||
|
2. 超链接工具按钮正在尝试开发中(貌似目前官方不支持) |
||||
|
## 1.2.5(2024-02-19) |
||||
|
1. 更新示例工程(吸顶写法) |
||||
|
2. 完善调色板功能 |
||||
|
## 1.2.4(2024-02-18) |
||||
|
1. 修复工具栏颜色按钮底色动态切换问题 |
||||
|
## 1.2.3(2024-02-18) |
||||
|
1. 更新示例工程 |
||||
|
## 1.2.2(2024-02-18) |
||||
|
1. 删除log调试打印 |
||||
|
## 1.2.1(2024-02-18) |
||||
|
1. 修复了颜色图标不会动态切换的问题 |
||||
|
## 1.2.0(2024-02-18) |
||||
|
1. 修复选择颜色时会将所选文字删除的bug |
||||
|
## 1.1.9(2024-02-04) |
||||
|
1. 更新示例工程 |
||||
|
## 1.1.8(2024-02-04) |
||||
|
1. 文档修改 |
||||
|
## 1.1.7(2024-02-04) |
||||
|
1. 新增toolbar配置项,可自由配置工具栏工具列表 |
||||
|
2. 移除组件内原templates属性,默认初始化编辑器内容请看文档使用方式示例 |
||||
|
3. 更新文档 |
||||
|
## 1.1.6(2024-01-31) |
||||
|
1. 更好的兼容vue2了,修复在vue2下高度可能超出的问题 |
||||
|
2. 示例工程兼容vue2 |
||||
|
## 1.1.5(2024-01-30) |
||||
|
1. 修复工具栏字体按钮无效的问题 |
||||
|
## 1.1.4(2024-01-30) |
||||
|
1. 解决默认初始化内容时前缀空格或缩进无效的问题 |
||||
|
2. 解决点击工具栏高亮状态后输入内容时便失去高亮的bug |
||||
|
3. 更新示例工程 |
||||
|
## 1.1.3(2024-01-23) |
||||
|
1. 重写高度动态计算逻辑,现在对不同屏幕尺寸的适应性更强了 |
||||
|
## 1.1.2(2024-01-17) |
||||
|
1. 修复分割线会生成多条的问题 |
||||
|
## 1.1.1(2024-01-15) |
||||
|
1. 更新文档 |
||||
|
## 1.1.0(2024-01-15) |
||||
|
1. insertText方法在插入内容的时候会移动光标聚焦,导致焦点回滚到视口处 |
||||
|
2. 更新示例工程 |
||||
|
## 1.0.9(2024-01-04) |
||||
|
1. 更新文档 |
||||
|
## 1.0.8(2024-01-04) |
||||
|
1. 修复h5端官方cdn请求失败的问题,详见问答贴:https://ask.dcloud.net.cn/article/40900 |
||||
|
## 1.0.7(2024-01-03) |
||||
|
1. 移除v-bind="$attrs",该写法在微信小程序不支持 |
||||
|
## 1.0.6(2023-12-29) |
||||
|
1. 更新文档 |
||||
|
## 1.0.5(2023-12-29) |
||||
|
1. 更新了init方法,可以使用返回的editor实例尽情自定义 |
||||
|
2. 组件在<editor>上添加v-bind="$attrs"属性穿透,可以使用原editor参数,官方文档:https://uniapp.dcloud.net.cn/component/editor.html |
||||
|
## 1.0.4(2023-12-29) |
||||
|
1. 优化了切换文字和背景颜色是,可能会导致切换后不生效的问题 |
||||
|
2. 修复在部分设备上的微信小程序中可能会存在颜色版无法正常滑动的问题 |
||||
|
3. 更友好的交互体验:添加图标悬停字样描述、添加格式化文本弹窗确认 |
||||
|
4. 有插入视频的需求,暂时可能无法实现,官方给予的回复是:目前各端的eidtor组件都不能直接插入视频,编辑时可以采用视频封面占位,并在图片中保存视频信息,在预览时再还原为视频。 |
||||
|
## 1.0.3(2023-10-13) |
||||
|
1. 更新readme文档 |
||||
|
2. 更新调整组件示例项目,添加插件代码中部分注释 |
||||
|
## 1.0.2(2023-10-13) |
||||
|
1. 更新uni_modules规范,可一键导入组件 |
||||
|
2. 更新组件示例项目(包括使用uniCloud.uploadFile多选上传图片示例方法) |
||||
|
## 1.0.1(2023-10-12) |
||||
|
1. 修复小程序中自动聚焦滚动到富文本组件区域的bug |
||||
|
2. 略微调整了富文本上方toolbar工具栏中按钮的大小尺寸 |
||||
|
## 1.0.0(2023-9-19) |
||||
|
1. 新增字体与背景颜色板 |
||||
|
2. 可自定义预设内容模板 |
||||
|
3. 解决官方样例在小程序和app部分报错不兼容的问题 |
||||
|
4. 可配合云存储上传富文本中插入的图片 本质上是基于官方内置富文本editor组件改版封装,所以官方有的功能都有,官方能兼容的也都兼容 |
||||
|
|
||||
@ -0,0 +1,825 @@ |
|||||
|
<template> |
||||
|
<view v-if="show" class="t-wrapper" @touchmove.stop.prevent="moveHandle"> |
||||
|
<view class="t-mask" :class="{ active: active }" @click.stop="close"></view> |
||||
|
<view class="t-box" :class="{ active: active }"> |
||||
|
<view class="t-header"> |
||||
|
<view class="t-header-button" @click="close">取消</view> |
||||
|
<view class="t-header-button" @click="confirm">确认</view> |
||||
|
</view> |
||||
|
<view class="t-color__box" :style="{ background: 'rgb(' + bgcolor.r + ',' + bgcolor.g + ',' + bgcolor.b + ')' }"> |
||||
|
<view |
||||
|
class="t-background boxs" |
||||
|
@touchstart="touchstart($event, 0)" |
||||
|
@touchmove="touchmove($event, 0)" |
||||
|
@touchend="touchend($event, 0)" |
||||
|
> |
||||
|
<view class="t-color-mask"></view> |
||||
|
<view class="t-pointer" :style="{ top: site[0].top - 8 + 'px', left: site[0].left - 8 + 'px' }"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="t-control__box"> |
||||
|
<view class="t-control__color"> |
||||
|
<view |
||||
|
class="t-control__color-content" |
||||
|
:style="{ background: 'rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + rgba.a + ')' }" |
||||
|
></view> |
||||
|
</view> |
||||
|
<view class="t-control-box__item"> |
||||
|
<view |
||||
|
class="t-controller boxs" |
||||
|
@touchstart="touchstart($event, 1)" |
||||
|
@touchmove="touchmove($event, 1)" |
||||
|
@touchend="touchend($event, 1)" |
||||
|
> |
||||
|
<view class="t-hue"> |
||||
|
<view class="t-circle" :style="{ left: site[1].left - 12 + 'px' }"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view |
||||
|
class="t-controller boxs" |
||||
|
@touchstart="touchstart($event, 2)" |
||||
|
@touchmove="touchmove($event, 2)" |
||||
|
@touchend="touchend($event, 2)" |
||||
|
> |
||||
|
<view class="t-transparency"> |
||||
|
<view class="t-circle" :style="{ left: site[2].left - 12 + 'px' }"></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="t-result__box"> |
||||
|
<view v-if="mode" class="t-result__item"> |
||||
|
<view class="t-result__box-input">{{ hex }}</view> |
||||
|
<view class="t-result__box-text">HEX</view> |
||||
|
</view> |
||||
|
<template v-else> |
||||
|
<view class="t-result__item"> |
||||
|
<view class="t-result__box-input">{{ rgba.r }}</view> |
||||
|
<view class="t-result__box-text">R</view> |
||||
|
</view> |
||||
|
<view class="t-result__item"> |
||||
|
<view class="t-result__box-input">{{ rgba.g }}</view> |
||||
|
<view class="t-result__box-text">G</view> |
||||
|
</view> |
||||
|
<view class="t-result__item"> |
||||
|
<view class="t-result__box-input">{{ rgba.b }}</view> |
||||
|
<view class="t-result__box-text">B</view> |
||||
|
</view> |
||||
|
<view class="t-result__item"> |
||||
|
<view class="t-result__box-input">{{ rgba.a }}</view> |
||||
|
<view class="t-result__box-text">A</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<view class="t-result__item t-select" @click="select"> |
||||
|
<view class="t-result__box-input"> |
||||
|
<view>切换</view> |
||||
|
<view>模式</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="t-alternative"> |
||||
|
<view class="t-alternative__item" v-for="(item, index) in colorList" :key="index"> |
||||
|
<view |
||||
|
class="t-alternative__item-content" |
||||
|
:style="{ background: 'rgba(' + item.r + ',' + item.g + ',' + item.b + ',' + item.a + ')' }" |
||||
|
@click="selectColor(item)" |
||||
|
></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { |
||||
|
color: { |
||||
|
type: Object, |
||||
|
default: () => { |
||||
|
return { |
||||
|
r: 0, |
||||
|
g: 0, |
||||
|
b: 0, |
||||
|
a: 0 |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
spareColor: { |
||||
|
type: Array, |
||||
|
default() { |
||||
|
return [] |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
show: false, |
||||
|
active: false, |
||||
|
// rgba 颜色 |
||||
|
rgba: { |
||||
|
r: 0, |
||||
|
g: 0, |
||||
|
b: 0, |
||||
|
a: 1 |
||||
|
}, |
||||
|
// hsb 颜色 |
||||
|
hsb: { |
||||
|
h: 0, |
||||
|
s: 0, |
||||
|
b: 0 |
||||
|
}, |
||||
|
site: [ |
||||
|
{ |
||||
|
top: 0, |
||||
|
left: 0 |
||||
|
}, |
||||
|
{ |
||||
|
left: 0 |
||||
|
}, |
||||
|
{ |
||||
|
left: 0 |
||||
|
} |
||||
|
], |
||||
|
index: 0, |
||||
|
bgcolor: { |
||||
|
r: 255, |
||||
|
g: 0, |
||||
|
b: 0, |
||||
|
a: 1 |
||||
|
}, |
||||
|
hex: '#000000', |
||||
|
mode: true, |
||||
|
colorList: [ |
||||
|
{ |
||||
|
r: 244, |
||||
|
g: 67, |
||||
|
b: 54, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 233, |
||||
|
g: 30, |
||||
|
b: 99, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 156, |
||||
|
g: 39, |
||||
|
b: 176, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 103, |
||||
|
g: 58, |
||||
|
b: 183, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 63, |
||||
|
g: 81, |
||||
|
b: 181, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 33, |
||||
|
g: 150, |
||||
|
b: 243, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 3, |
||||
|
g: 169, |
||||
|
b: 244, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 0, |
||||
|
g: 188, |
||||
|
b: 212, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 0, |
||||
|
g: 150, |
||||
|
b: 136, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 76, |
||||
|
g: 175, |
||||
|
b: 80, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 139, |
||||
|
g: 195, |
||||
|
b: 74, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 205, |
||||
|
g: 220, |
||||
|
b: 57, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 255, |
||||
|
g: 235, |
||||
|
b: 59, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 255, |
||||
|
g: 193, |
||||
|
b: 7, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 255, |
||||
|
g: 152, |
||||
|
b: 0, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 255, |
||||
|
g: 87, |
||||
|
b: 34, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 121, |
||||
|
g: 85, |
||||
|
b: 72, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 158, |
||||
|
g: 158, |
||||
|
b: 158, |
||||
|
a: 1 |
||||
|
}, |
||||
|
{ |
||||
|
r: 0, |
||||
|
g: 0, |
||||
|
b: 0, |
||||
|
a: 0.5 |
||||
|
}, |
||||
|
{ |
||||
|
r: 0, |
||||
|
g: 0, |
||||
|
b: 0, |
||||
|
a: 0 |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
}, |
||||
|
created() { |
||||
|
this.ready() |
||||
|
}, |
||||
|
methods: { |
||||
|
ready() { |
||||
|
this.rgba = this.color |
||||
|
if (this.spareColor.length !== 0) { |
||||
|
this.colorList = this.spareColor |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 初始化 |
||||
|
*/ |
||||
|
init() { |
||||
|
// hsb 颜色 |
||||
|
this.hsb = this.rgbToHex(this.rgba) |
||||
|
// this.setColor(); |
||||
|
this.setValue(this.rgba) |
||||
|
}, |
||||
|
moveHandle() {}, |
||||
|
open() { |
||||
|
this.show = true |
||||
|
this.$nextTick(() => { |
||||
|
this.init() |
||||
|
setTimeout(() => { |
||||
|
this.active = true |
||||
|
setTimeout(() => { |
||||
|
this.getSelectorQuery() |
||||
|
}, 350) |
||||
|
}, 50) |
||||
|
}) |
||||
|
}, |
||||
|
close() { |
||||
|
this.active = false |
||||
|
this.$nextTick(() => { |
||||
|
setTimeout(() => { |
||||
|
this.show = false |
||||
|
}, 500) |
||||
|
}) |
||||
|
}, |
||||
|
confirm() { |
||||
|
this.close() |
||||
|
this.$emit('confirm', { |
||||
|
rgba: this.rgba, |
||||
|
hex: this.hex |
||||
|
}) |
||||
|
}, |
||||
|
// 选择模式 |
||||
|
select() { |
||||
|
this.mode = !this.mode |
||||
|
}, |
||||
|
// 常用颜色选择 |
||||
|
selectColor(item) { |
||||
|
this.setColorBySelect(item) |
||||
|
}, |
||||
|
touchstart(e, index) { |
||||
|
const { pageX, pageY, clientX, clientY } = e.touches[0] |
||||
|
// 部分机型可能没有pageX或clientX,因此此处需要做兼容 |
||||
|
this.moveX = clientX || pageX |
||||
|
this.moveY = clientY || pageY |
||||
|
this.setPosition(this.moveX, this.moveY, index) |
||||
|
}, |
||||
|
touchmove(e, index) { |
||||
|
const { pageX, pageY, clientX, clientY } = e.touches[0] |
||||
|
this.moveX = clientX || pageX |
||||
|
this.moveY = clientY || pageY |
||||
|
this.setPosition(this.moveX, this.moveY, index) |
||||
|
}, |
||||
|
touchend(e, index) {}, |
||||
|
/** |
||||
|
* 设置位置 |
||||
|
*/ |
||||
|
setPosition(x, y, index) { |
||||
|
this.index = index |
||||
|
const { top, left, width, height } = this.position[index] |
||||
|
// 设置最大最小值 |
||||
|
|
||||
|
this.site[index].left = Math.max(0, Math.min(parseInt(x - left), width)) |
||||
|
if (index === 0) { |
||||
|
this.site[index].top = Math.max(0, Math.min(parseInt(y - top), height)) |
||||
|
// 设置颜色 |
||||
|
this.hsb.s = parseInt((100 * this.site[index].left) / width) |
||||
|
this.hsb.b = parseInt(100 - (100 * this.site[index].top) / height) |
||||
|
this.setColor() |
||||
|
this.setValue(this.rgba) |
||||
|
} else { |
||||
|
this.setControl(index, this.site[index].left) |
||||
|
} |
||||
|
}, |
||||
|
/** |
||||
|
* 设置 rgb 颜色 |
||||
|
*/ |
||||
|
setColor() { |
||||
|
const rgb = this.HSBToRGB(this.hsb) |
||||
|
this.rgba.r = rgb.r |
||||
|
this.rgba.g = rgb.g |
||||
|
this.rgba.b = rgb.b |
||||
|
}, |
||||
|
/** |
||||
|
* 设置二进制颜色 |
||||
|
* @param {Object} rgb |
||||
|
*/ |
||||
|
setValue(rgb) { |
||||
|
this.hex = '#' + this.rgbToHex(rgb) |
||||
|
}, |
||||
|
setControl(index, x) { |
||||
|
const { top, left, width, height } = this.position[index] |
||||
|
|
||||
|
if (index === 1) { |
||||
|
this.hsb.h = parseInt((360 * x) / width) |
||||
|
this.bgcolor = this.HSBToRGB({ |
||||
|
h: this.hsb.h, |
||||
|
s: 100, |
||||
|
b: 100 |
||||
|
}) |
||||
|
this.setColor() |
||||
|
} else { |
||||
|
this.rgba.a = (x / width).toFixed(1) |
||||
|
} |
||||
|
this.setValue(this.rgba) |
||||
|
}, |
||||
|
/** |
||||
|
* rgb 转 二进制 hex |
||||
|
* @param {Object} rgb |
||||
|
*/ |
||||
|
rgbToHex(rgb) { |
||||
|
let hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)] |
||||
|
hex.map(function (str, i) { |
||||
|
if (str.length == 1) { |
||||
|
hex[i] = '0' + str |
||||
|
} |
||||
|
}) |
||||
|
return hex.join('') |
||||
|
}, |
||||
|
setColorBySelect(getrgb) { |
||||
|
const { r, g, b, a } = getrgb |
||||
|
let rgb = {} |
||||
|
rgb = { |
||||
|
r: r ? parseInt(r) : 0, |
||||
|
g: g ? parseInt(g) : 0, |
||||
|
b: b ? parseInt(b) : 0, |
||||
|
a: a ? a : 0 |
||||
|
} |
||||
|
this.rgba = rgb |
||||
|
this.hsb = this.rgbToHsb(rgb) |
||||
|
this.changeViewByHsb() |
||||
|
}, |
||||
|
changeViewByHsb() { |
||||
|
const [a, b, c] = this.position |
||||
|
this.site[0].left = parseInt((this.hsb.s * a.width) / 100) |
||||
|
this.site[0].top = parseInt(((100 - this.hsb.b) * a.height) / 100) |
||||
|
this.setColor(this.hsb.h) |
||||
|
this.setValue(this.rgba) |
||||
|
this.bgcolor = this.HSBToRGB({ |
||||
|
h: this.hsb.h, |
||||
|
s: 100, |
||||
|
b: 100 |
||||
|
}) |
||||
|
|
||||
|
this.site[1].left = (this.hsb.h / 360) * b.width |
||||
|
this.site[2].left = this.rgba.a * c.width |
||||
|
}, |
||||
|
/** |
||||
|
* hsb 转 rgb |
||||
|
* @param {Object} 颜色模式 H(hues)表示色相,S(saturation)表示饱和度,B(brightness)表示亮度 |
||||
|
*/ |
||||
|
HSBToRGB(hsb) { |
||||
|
let rgb = {} |
||||
|
let h = Math.round(hsb.h) |
||||
|
let s = Math.round((hsb.s * 255) / 100) |
||||
|
let v = Math.round((hsb.b * 255) / 100) |
||||
|
if (s == 0) { |
||||
|
rgb.r = rgb.g = rgb.b = v |
||||
|
} else { |
||||
|
let t1 = v |
||||
|
let t2 = ((255 - s) * v) / 255 |
||||
|
let t3 = ((t1 - t2) * (h % 60)) / 60 |
||||
|
if (h == 360) h = 0 |
||||
|
if (h < 60) { |
||||
|
rgb.r = t1 |
||||
|
rgb.b = t2 |
||||
|
rgb.g = t2 + t3 |
||||
|
} else if (h < 120) { |
||||
|
rgb.g = t1 |
||||
|
rgb.b = t2 |
||||
|
rgb.r = t1 - t3 |
||||
|
} else if (h < 180) { |
||||
|
rgb.g = t1 |
||||
|
rgb.r = t2 |
||||
|
rgb.b = t2 + t3 |
||||
|
} else if (h < 240) { |
||||
|
rgb.b = t1 |
||||
|
rgb.r = t2 |
||||
|
rgb.g = t1 - t3 |
||||
|
} else if (h < 300) { |
||||
|
rgb.b = t1 |
||||
|
rgb.g = t2 |
||||
|
rgb.r = t2 + t3 |
||||
|
} else if (h < 360) { |
||||
|
rgb.r = t1 |
||||
|
rgb.g = t2 |
||||
|
rgb.b = t1 - t3 |
||||
|
} else { |
||||
|
rgb.r = 0 |
||||
|
rgb.g = 0 |
||||
|
rgb.b = 0 |
||||
|
} |
||||
|
} |
||||
|
return { |
||||
|
r: Math.round(rgb.r), |
||||
|
g: Math.round(rgb.g), |
||||
|
b: Math.round(rgb.b) |
||||
|
} |
||||
|
}, |
||||
|
rgbToHsb(rgb) { |
||||
|
let hsb = { |
||||
|
h: 0, |
||||
|
s: 0, |
||||
|
b: 0 |
||||
|
} |
||||
|
let min = Math.min(rgb.r, rgb.g, rgb.b) |
||||
|
let max = Math.max(rgb.r, rgb.g, rgb.b) |
||||
|
let delta = max - min |
||||
|
hsb.b = max |
||||
|
hsb.s = max != 0 ? (255 * delta) / max : 0 |
||||
|
if (hsb.s != 0) { |
||||
|
if (rgb.r == max) hsb.h = (rgb.g - rgb.b) / delta |
||||
|
else if (rgb.g == max) hsb.h = 2 + (rgb.b - rgb.r) / delta |
||||
|
else hsb.h = 4 + (rgb.r - rgb.g) / delta |
||||
|
} else hsb.h = -1 |
||||
|
hsb.h *= 60 |
||||
|
if (hsb.h < 0) hsb.h = 0 |
||||
|
hsb.s *= 100 / 255 |
||||
|
hsb.b *= 100 / 255 |
||||
|
return hsb |
||||
|
}, |
||||
|
getSelectorQuery() { |
||||
|
const views = uni.createSelectorQuery().in(this) |
||||
|
views |
||||
|
.selectAll('.boxs') |
||||
|
.boundingClientRect((data) => { |
||||
|
if (!data || data.length === 0) { |
||||
|
setTimeout(() => this.getSelectorQuery(), 20) |
||||
|
return |
||||
|
} |
||||
|
this.position = data |
||||
|
// this.site[0].top = data[0].height; |
||||
|
// this.site[0].left = 0; |
||||
|
// this.site[1].left = data[1].width; |
||||
|
// this.site[2].left = data[2].width; |
||||
|
this.setColorBySelect(this.rgba) |
||||
|
}) |
||||
|
.exec() |
||||
|
}, |
||||
|
hex2Rgb(hexColor, alpha = 1) { |
||||
|
const color = hexColor.slice(1) |
||||
|
const r = parseInt(color.slice(0, 2), 16) |
||||
|
const g = parseInt(color.slice(2, 4), 16) |
||||
|
const b = parseInt(color.slice(4, 6), 16) |
||||
|
return { |
||||
|
r: r, |
||||
|
g: g, |
||||
|
b: b, |
||||
|
a: alpha |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
spareColor(newVal) { |
||||
|
this.colorList = newVal |
||||
|
}, |
||||
|
color(newVal) { |
||||
|
this.ready() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.t-wrapper { |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.t-box { |
||||
|
width: 100%; |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
padding: 30upx 0; |
||||
|
padding-top: 0; |
||||
|
background: #fff; |
||||
|
transition: all 0.3s; |
||||
|
transform: translateY(100%); |
||||
|
} |
||||
|
|
||||
|
.t-box.active { |
||||
|
transform: translateY(0%); |
||||
|
} |
||||
|
|
||||
|
.t-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
height: 100upx; |
||||
|
border-bottom: 1px #eee solid; |
||||
|
box-shadow: 1px 0 2px rgba(0, 0, 0, 0.1); |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.t-header-button { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 150upx; |
||||
|
height: 100upx; |
||||
|
font-size: 30upx; |
||||
|
color: #666; |
||||
|
padding-left: 20upx; |
||||
|
} |
||||
|
|
||||
|
.t-header-button:last-child { |
||||
|
justify-content: flex-end; |
||||
|
padding-right: 20upx; |
||||
|
} |
||||
|
|
||||
|
.t-mask { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: rgba(0, 0, 0, 0.6); |
||||
|
z-index: -1; |
||||
|
transition: all 0.3s; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
|
||||
|
.t-mask.active { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
.t-color__box { |
||||
|
position: relative; |
||||
|
height: 400upx; |
||||
|
background: rgb(255, 0, 0); |
||||
|
overflow: hidden; |
||||
|
box-sizing: border-box; |
||||
|
margin: 0 20upx; |
||||
|
margin-top: 20upx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.t-background { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0)); |
||||
|
} |
||||
|
|
||||
|
.t-color-mask { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
width: 100%; |
||||
|
height: 400upx; |
||||
|
background: linear-gradient(to top, #000, rgba(0, 0, 0, 0)); |
||||
|
} |
||||
|
|
||||
|
.t-pointer { |
||||
|
position: absolute; |
||||
|
bottom: -8px; |
||||
|
left: -8px; |
||||
|
z-index: 2; |
||||
|
width: 15px; |
||||
|
height: 15px; |
||||
|
border: 1px #fff solid; |
||||
|
border-radius: 50%; |
||||
|
} |
||||
|
|
||||
|
.t-show-color { |
||||
|
width: 100upx; |
||||
|
height: 50upx; |
||||
|
} |
||||
|
|
||||
|
.t-control__box { |
||||
|
margin-top: 50upx; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
padding-left: 20upx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.t-control__color { |
||||
|
flex-shrink: 0; |
||||
|
width: 100upx; |
||||
|
height: 100upx; |
||||
|
border-radius: 50%; |
||||
|
background-color: #fff; |
||||
|
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
||||
|
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
||||
|
background-size: 36upx 36upx; |
||||
|
background-position: 0 0, 18upx 18upx; |
||||
|
border: 1px #eee solid; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.t-control__color-content { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.t-control-box__item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: space-between; |
||||
|
width: 100%; |
||||
|
padding: 0 30upx; |
||||
|
} |
||||
|
|
||||
|
.t-controller { |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
height: 16px; |
||||
|
background-color: #fff; |
||||
|
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
||||
|
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
||||
|
background-size: 32upx 32upx; |
||||
|
background-position: 0 0, 16upx 16upx; |
||||
|
} |
||||
|
|
||||
|
.t-hue { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%); |
||||
|
} |
||||
|
|
||||
|
.t-transparency { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0)); |
||||
|
} |
||||
|
|
||||
|
.t-circle { |
||||
|
position: absolute; |
||||
|
/* right: -10px; */ |
||||
|
top: -2px; |
||||
|
width: 20px; |
||||
|
height: 20px; |
||||
|
box-sizing: border-box; |
||||
|
border-radius: 50%; |
||||
|
background: #fff; |
||||
|
box-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.t-result__box { |
||||
|
margin-top: 20upx; |
||||
|
padding: 10upx; |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.t-result__item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
padding: 10upx; |
||||
|
width: 100%; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.t-result__box-input { |
||||
|
padding: 10upx 0; |
||||
|
width: 100%; |
||||
|
font-size: 28upx; |
||||
|
box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.1); |
||||
|
color: #999; |
||||
|
text-align: center; |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.t-result__box-text { |
||||
|
margin-top: 10upx; |
||||
|
font-size: 28upx; |
||||
|
line-height: 2; |
||||
|
} |
||||
|
|
||||
|
.t-select { |
||||
|
flex-shrink: 0; |
||||
|
width: 150upx; |
||||
|
padding: 0 30upx; |
||||
|
} |
||||
|
|
||||
|
.t-select .t-result__box-input { |
||||
|
border-radius: 10upx; |
||||
|
border: none; |
||||
|
color: #999; |
||||
|
box-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1); |
||||
|
background: #fff; |
||||
|
} |
||||
|
|
||||
|
.t-select .t-result__box-input:active { |
||||
|
box-shadow: 0px 0px 1px 0px rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.t-alternative { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
/* justify-content: space-between; */ |
||||
|
width: 100%; |
||||
|
padding-right: 10upx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.t-alternative__item { |
||||
|
margin-left: 12upx; |
||||
|
margin-top: 10upx; |
||||
|
width: 50upx; |
||||
|
height: 50upx; |
||||
|
border-radius: 10upx; |
||||
|
background-color: #fff; |
||||
|
background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), |
||||
|
linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); |
||||
|
background-size: 36upx 36upx; |
||||
|
background-position: 0 0, 18upx 18upx; |
||||
|
border: 1px #eee solid; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.t-alternative__item-content { |
||||
|
width: 50upx; |
||||
|
height: 50upx; |
||||
|
background: rgba(255, 0, 0, 0.5); |
||||
|
} |
||||
|
|
||||
|
.t-alternative__item:active { |
||||
|
transition: all 0.3s; |
||||
|
transform: scale(1.1); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,140 @@ |
|||||
|
<template> |
||||
|
<view class="fab-tool"> |
||||
|
<view id="toolfab"> |
||||
|
<slot></slot> |
||||
|
</view> |
||||
|
<view class="fab-tool-content" :style="placementStyle" id="placementfab"> |
||||
|
<slot name="content" v-if="visible"></slot> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
props: { |
||||
|
visible: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
placement: { |
||||
|
type: String, |
||||
|
default: 'auto' // 'auto' | 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end' |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
placementHeight: '0', |
||||
|
placementType: '' |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
visible(newVal) { |
||||
|
if (newVal) { |
||||
|
const { screenWidth } = uni.getSystemInfoSync() |
||||
|
|
||||
|
this.$nextTick(() => { |
||||
|
let placementWidth = 0 |
||||
|
uni |
||||
|
.createSelectorQuery() |
||||
|
.in(this) |
||||
|
.select('#placementfab') |
||||
|
.boundingClientRect((res) => { |
||||
|
this.placementHeight = -res.height + 'px' |
||||
|
placementWidth = res.width |
||||
|
}) |
||||
|
.exec() |
||||
|
// 开启自动模式后 |
||||
|
if (this.placement == 'auto') { |
||||
|
uni |
||||
|
.createSelectorQuery() |
||||
|
.in(this) |
||||
|
.select('#toolfab') |
||||
|
.boundingClientRect((res) => { |
||||
|
let leftRemain = res.left |
||||
|
let rightRemain = screenWidth - leftRemain |
||||
|
if (rightRemain > placementWidth) { |
||||
|
this.placementType = 'bottom-start' |
||||
|
} else if (leftRemain > placementWidth) { |
||||
|
this.placementType = 'bottom-end' |
||||
|
} else { |
||||
|
this.placementType = 'bottom-center' |
||||
|
} |
||||
|
}) |
||||
|
.exec() |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
mounted() { |
||||
|
this.placementType = this.placement |
||||
|
}, |
||||
|
computed: { |
||||
|
placementStyle() { |
||||
|
let position = {} |
||||
|
switch (this.placementType) { |
||||
|
case 'top-start': |
||||
|
position = { |
||||
|
top: this.placementHeight, |
||||
|
left: 0 |
||||
|
} |
||||
|
break |
||||
|
case 'top-center': |
||||
|
position = { |
||||
|
top: this.placementHeight, |
||||
|
left: '50%', |
||||
|
transform: 'translateX(-50%)' |
||||
|
} |
||||
|
break |
||||
|
case 'top-end': |
||||
|
position = { |
||||
|
top: this.placementHeight, |
||||
|
right: 0 |
||||
|
} |
||||
|
break |
||||
|
case 'bottom-start': |
||||
|
position = { |
||||
|
bottom: this.placementHeight, |
||||
|
left: 0 |
||||
|
} |
||||
|
break |
||||
|
case 'bottom-center': |
||||
|
position = { |
||||
|
bottom: this.placementHeight, |
||||
|
left: '50%', |
||||
|
transform: 'translateX(-50%)' |
||||
|
} |
||||
|
break |
||||
|
case 'bottom-end': |
||||
|
position = { |
||||
|
bottom: this.placementHeight, |
||||
|
right: 0 |
||||
|
} |
||||
|
break |
||||
|
default: |
||||
|
break |
||||
|
} |
||||
|
return position |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
// |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.fab-tool { |
||||
|
position: relative; |
||||
|
|
||||
|
.fab-tool-content { |
||||
|
position: absolute; |
||||
|
z-index: 999; |
||||
|
|
||||
|
background-color: #ffffff; |
||||
|
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05); |
||||
|
border-radius: 12rpx; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,152 @@ |
|||||
|
<template> |
||||
|
<view class="link-edit-container" v-if="showPopup"> |
||||
|
<view class="link-edit"> |
||||
|
<view class="title">添加链接</view> |
||||
|
<view class="edit"> |
||||
|
<view class="description"> |
||||
|
链接描述: |
||||
|
<input v-model="descVal" type="text" class="input" placeholder="请输入链接描述" /> |
||||
|
</view> |
||||
|
<view class="address"> |
||||
|
链接地址: |
||||
|
<input v-model="addrVal" type="text" class="input" placeholder="请输入链接地址" /> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="control"> |
||||
|
<view class="cancel" @click="close">取消</view> |
||||
|
<view class="confirm" @click="onConfirm">确认</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
<view class="mask"></view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
showPopup: false, |
||||
|
descVal: '', |
||||
|
addrVal: '' |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
open() { |
||||
|
this.showPopup = true |
||||
|
this.$emit('open') |
||||
|
}, |
||||
|
close() { |
||||
|
this.showPopup = false |
||||
|
this.descVal = '' |
||||
|
this.addrVal = '' |
||||
|
this.$emit('close') |
||||
|
}, |
||||
|
onConfirm() { |
||||
|
if (!this.descVal) { |
||||
|
uni.showToast({ |
||||
|
title: '请输入链接描述', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
if (!this.addrVal) { |
||||
|
uni.showToast({ |
||||
|
title: '请输入链接地址', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
return |
||||
|
} |
||||
|
this.$emit('confirm', { |
||||
|
text: this.descVal, |
||||
|
href: this.addrVal |
||||
|
}) |
||||
|
this.close() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
.link-edit-container { |
||||
|
.link-edit { |
||||
|
width: 80%; |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
left: 50%; |
||||
|
transform: translate(-50%, -50%); |
||||
|
background-color: #ffffff; |
||||
|
box-shadow: -2px -2px 4px rgba(0, 0, 0, 0.05), 2px 2px 4px rgba(0, 0, 0, 0.05); |
||||
|
border-radius: 12rpx; |
||||
|
box-sizing: border-box; |
||||
|
z-index: 999; |
||||
|
font-size: 14px; |
||||
|
|
||||
|
.title { |
||||
|
height: 80rpx; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.edit { |
||||
|
padding: 24rpx; |
||||
|
border-top: 1px solid #eeeeee; |
||||
|
border-bottom: 1px solid #eeeeee; |
||||
|
box-sizing: border-box; |
||||
|
|
||||
|
.input { |
||||
|
flex: 1; |
||||
|
padding: 4px; |
||||
|
font-size: 14px; |
||||
|
border: 1px solid #eeeeee; |
||||
|
border-radius: 8rpx; |
||||
|
|
||||
|
.uni-input-placeholder { |
||||
|
color: #dddddd; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.description { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.address { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin-top: 24rpx; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.control { |
||||
|
height: 80rpx; |
||||
|
display: flex; |
||||
|
cursor: pointer; |
||||
|
|
||||
|
.cancel { |
||||
|
flex: 1; |
||||
|
color: #dd524d; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
.confirm { |
||||
|
border-left: 1px solid #eeeeee; |
||||
|
flex: 1; |
||||
|
color: #007aff; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
.mask { |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
bottom: 0; |
||||
|
background-color: rgba(0, 0, 0, 0.05); |
||||
|
z-index: 998; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,828 @@ |
|||||
|
<template> |
||||
|
<view class="sp-editor" :style="{ '--icon-size': iconSize, '--icon-columns': iconColumns }"> |
||||
|
<view class="sp-editor-toolbar" v-if="!readOnly" @tap="format"> |
||||
|
<!-- 标题栏 --> |
||||
|
<fab-tool v-if="toolbarList.includes('header')" :visible="curFab == 'header'"> |
||||
|
<view |
||||
|
:class="formats.header ? 'ql-active' : ''" |
||||
|
class="iconfont icon-header" |
||||
|
title="标题" |
||||
|
data-name="header" |
||||
|
@click.stop="fabTap('header')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'header')"> |
||||
|
<view v-for="item in fabTools.header" :key="item.value"> |
||||
|
<view |
||||
|
v-if="toolbarList.includes(item.name)" |
||||
|
class="fab-sub" |
||||
|
:class="[formats.header === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="header" |
||||
|
:data-value="item.value" |
||||
|
></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('bold')" |
||||
|
:class="formats.bold ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitijiacu" |
||||
|
title="加粗" |
||||
|
data-name="bold" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('italic')" |
||||
|
:class="formats.italic ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitixieti" |
||||
|
title="斜体" |
||||
|
data-name="italic" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('underline')" |
||||
|
:class="formats.underline ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitixiahuaxian" |
||||
|
title="下划线" |
||||
|
data-name="underline" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('strike')" |
||||
|
:class="formats.strike ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitishanchuxian" |
||||
|
title="删除线" |
||||
|
data-name="strike" |
||||
|
></view> |
||||
|
<!-- 对齐方式 --> |
||||
|
<fab-tool v-if="toolbarList.includes('align')" :visible="curFab == 'align'"> |
||||
|
<view |
||||
|
:class="formats.align ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zuoyouduiqi" |
||||
|
title="对齐方式" |
||||
|
data-name="align" |
||||
|
@click.stop="fabTap('align')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'align')"> |
||||
|
<view v-for="item in fabTools.align" :key="item.value"> |
||||
|
<view |
||||
|
v-if="toolbarList.includes(item.name)" |
||||
|
class="fab-sub" |
||||
|
:class="[formats.align === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="align" |
||||
|
:data-value="item.value" |
||||
|
></view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 行间距 --> |
||||
|
<fab-tool v-if="toolbarList.includes('lineHeight')" :visible="curFab == 'lineHeight'"> |
||||
|
<view |
||||
|
:class="formats.lineHeight ? 'ql-active' : ''" |
||||
|
class="iconfont icon-line-height" |
||||
|
title="行间距" |
||||
|
data-name="lineHeight" |
||||
|
@click.stop="fabTap('lineHeight')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'lineHeight')"> |
||||
|
<view v-for="item in fabTools.lineHeight" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[formats.lineHeight === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="lineHeight" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 字间距 --> |
||||
|
<fab-tool v-if="toolbarList.includes('letterSpacing')" :visible="curFab == 'letterSpacing'"> |
||||
|
<view |
||||
|
:class="formats.letterSpacing ? 'ql-active' : ''" |
||||
|
class="iconfont icon-Character-Spacing" |
||||
|
title="字间距" |
||||
|
data-name="letterSpacing" |
||||
|
@click.stop="fabTap('letterSpacing')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'letterSpacing')"> |
||||
|
<view v-for="item in fabTools.space" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[ |
||||
|
formats.letterSpacing === item.value ? 'ql-active' : '', |
||||
|
item.icon ? 'iconfont' : '', |
||||
|
item.icon |
||||
|
]" |
||||
|
:title="item.title" |
||||
|
data-name="letterSpacing" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 段前距 --> |
||||
|
<fab-tool v-if="toolbarList.includes('marginTop')" :visible="curFab == 'marginTop'"> |
||||
|
<view |
||||
|
:class="formats.marginTop ? 'ql-active' : ''" |
||||
|
class="iconfont icon-722bianjiqi_duanqianju" |
||||
|
title="段前距" |
||||
|
data-name="marginTop" |
||||
|
@click.stop="fabTap('marginTop')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'marginTop')"> |
||||
|
<view v-for="item in fabTools.space" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[formats.marginTop === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="marginTop" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 段后距 --> |
||||
|
<fab-tool v-if="toolbarList.includes('marginBottom')" :visible="curFab == 'marginBottom'"> |
||||
|
<view |
||||
|
:class="formats.marginBottom ? 'ql-active' : ''" |
||||
|
class="iconfont icon-723bianjiqi_duanhouju" |
||||
|
title="段后距" |
||||
|
data-name="marginBottom" |
||||
|
@click.stop="fabTap('marginBottom')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'marginBottom')"> |
||||
|
<view v-for="item in fabTools.space" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[ |
||||
|
formats.marginBottom === item.value ? 'ql-active' : '', |
||||
|
item.icon ? 'iconfont' : '', |
||||
|
item.icon |
||||
|
]" |
||||
|
:title="item.title" |
||||
|
data-name="marginBottom" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 字体栏 --> |
||||
|
<fab-tool v-if="toolbarList.includes('fontFamily')" :visible="curFab == 'fontFamily'"> |
||||
|
<view |
||||
|
:class="formats.fontFamily ? 'ql-active' : ''" |
||||
|
class="iconfont icon-font" |
||||
|
title="字体" |
||||
|
data-name="fontFamily" |
||||
|
@click.stop="fabTap('fontFamily')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'fontFamily')"> |
||||
|
<view v-for="item in fabTools.fontFamily" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[formats.fontFamily === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="fontFamily" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<!-- 字体大小栏 --> |
||||
|
<fab-tool v-if="toolbarList.includes('fontSize')" :visible="curFab == 'fontSize'"> |
||||
|
<view |
||||
|
:class="formats.fontSize ? 'ql-active' : ''" |
||||
|
class="iconfont icon-fontsize" |
||||
|
title="字号" |
||||
|
data-name="fontSize" |
||||
|
@click.stop="fabTap('fontSize')" |
||||
|
></view> |
||||
|
<template #content> |
||||
|
<view class="fab-tools" @click.stop="fabTapSub($event, 'fontSize')"> |
||||
|
<view v-for="item in fabTools.fontSize" :key="item.value"> |
||||
|
<view |
||||
|
class="fab-sub" |
||||
|
:class="[formats.fontSize === item.value ? 'ql-active' : '', item.icon ? 'iconfont' : '', item.icon]" |
||||
|
:title="item.title" |
||||
|
data-name="fontSize" |
||||
|
:data-value="item.value" |
||||
|
> |
||||
|
{{ item.name }} |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
</fab-tool> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('color')" |
||||
|
:style="{ color: formats.color ? textColor : 'initial' }" |
||||
|
class="iconfont icon-text_color" |
||||
|
title="文字颜色" |
||||
|
data-name="color" |
||||
|
:data-value="textColor" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('backgroundColor')" |
||||
|
:style="{ color: formats.backgroundColor ? backgroundColor : 'initial' }" |
||||
|
class="iconfont icon-fontbgcolor" |
||||
|
title="背景颜色" |
||||
|
data-name="backgroundColor" |
||||
|
:data-value="backgroundColor" |
||||
|
></view> |
||||
|
<view v-if="toolbarList.includes('date')" class="iconfont icon-date" title="日期" @tap="insertDate"></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('listCheck')" |
||||
|
class="iconfont icon--checklist" |
||||
|
title="待办" |
||||
|
data-name="list" |
||||
|
data-value="check" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('listOrdered')" |
||||
|
:class="formats.list === 'ordered' ? 'ql-active' : ''" |
||||
|
class="iconfont icon-youxupailie" |
||||
|
title="有序列表" |
||||
|
data-name="list" |
||||
|
data-value="ordered" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('listBullet')" |
||||
|
:class="formats.list === 'bullet' ? 'ql-active' : ''" |
||||
|
class="iconfont icon-wuxupailie" |
||||
|
title="无序列表" |
||||
|
data-name="list" |
||||
|
data-value="bullet" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('divider')" |
||||
|
class="iconfont icon-fengexian" |
||||
|
title="分割线" |
||||
|
@click="insertDivider" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('indentDec')" |
||||
|
class="iconfont icon-outdent" |
||||
|
title="减少缩进" |
||||
|
data-name="indent" |
||||
|
data-value="-1" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('indentInc')" |
||||
|
class="iconfont icon-indent" |
||||
|
title="增加缩进" |
||||
|
data-name="indent" |
||||
|
data-value="+1" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('scriptSub')" |
||||
|
:class="formats.script === 'sub' ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitixiabiao" |
||||
|
title="下标" |
||||
|
data-name="script" |
||||
|
data-value="sub" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('scriptSuper')" |
||||
|
:class="formats.script === 'super' ? 'ql-active' : ''" |
||||
|
class="iconfont icon-zitishangbiao" |
||||
|
title="上标" |
||||
|
data-name="script" |
||||
|
data-value="super" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('direction')" |
||||
|
:class="formats.direction === 'rtl' ? 'ql-active' : ''" |
||||
|
class="iconfont icon-direction-rtl" |
||||
|
title="文本方向" |
||||
|
data-name="direction" |
||||
|
data-value="rtl" |
||||
|
></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('image')" |
||||
|
class="iconfont icon-charutupian" |
||||
|
title="图片" |
||||
|
@tap="insertImage" |
||||
|
></view> |
||||
|
<view v-if="toolbarList.includes('video')" class="iconfont icon-video" title="视频" @tap="insertVideo"></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('link')" |
||||
|
class="iconfont icon-charulianjie" |
||||
|
title="超链接" |
||||
|
@tap="insertLink" |
||||
|
></view> |
||||
|
<view v-if="toolbarList.includes('undo')" class="iconfont icon-undo" title="撤销" @tap="undo"></view> |
||||
|
<view v-if="toolbarList.includes('redo')" class="iconfont icon-redo" title="重做" @tap="redo"></view> |
||||
|
<view |
||||
|
v-if="toolbarList.includes('removeFormat')" |
||||
|
class="iconfont icon-clearedformat" |
||||
|
title="清除格式" |
||||
|
@tap="removeFormat" |
||||
|
></view> |
||||
|
<view v-if="toolbarList.includes('clear')" class="iconfont icon-shanchu" title="清空" @tap="clear"></view> |
||||
|
<view v-if="toolbarList.includes('export')" class="iconfont icon-baocun" title="导出" @tap="exportHtml"></view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 自定义功能组件 --> |
||||
|
<!-- 调色板 --> |
||||
|
<color-picker |
||||
|
v-if="toolbarList.includes('color') || toolbarList.includes('backgroundColor')" |
||||
|
ref="colorPickerRef" |
||||
|
:color="defaultColor" |
||||
|
@confirm="confirmColor" |
||||
|
></color-picker> |
||||
|
<!-- 添加链接的操作弹窗 --> |
||||
|
<link-edit v-if="toolbarList.includes('link') && !readOnly" ref="linkEditRef" @confirm="confirmLink"></link-edit> |
||||
|
<view class="sp-editor-wrapper" @longpress="eLongpress"> |
||||
|
<editor |
||||
|
:id="editorId" |
||||
|
class="ql-editor editor-container" |
||||
|
:class="{ 'ql-image-overlay-none': readOnly }" |
||||
|
show-img-size |
||||
|
show-img-toolbar |
||||
|
show-img-resize |
||||
|
:placeholder="placeholder" |
||||
|
:read-only="readOnly" |
||||
|
@statuschange="onStatusChange" |
||||
|
@ready="onEditorReady" |
||||
|
@input="onEditorInput" |
||||
|
></editor> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
import ColorPicker from './color-picker.vue' |
||||
|
import LinkEdit from './link-edit.vue' |
||||
|
import FabTool from './fab-tool.vue' |
||||
|
import { addLink, linkFlag } from '../../utils' |
||||
|
|
||||
|
export default { |
||||
|
components: { |
||||
|
ColorPicker, |
||||
|
LinkEdit, |
||||
|
FabTool |
||||
|
}, |
||||
|
props: { |
||||
|
// 编辑器id可传入,以便循环组件使用,防止id重复 |
||||
|
editorId: { |
||||
|
type: String, |
||||
|
default: 'editor' |
||||
|
}, |
||||
|
placeholder: { |
||||
|
type: String, |
||||
|
default: '写点什么吧 ~' |
||||
|
}, |
||||
|
// 是否只读 |
||||
|
readOnly: { |
||||
|
type: Boolean, |
||||
|
default: false |
||||
|
}, |
||||
|
// 最大字数限制,-1不限 |
||||
|
maxlength: { |
||||
|
type: Number, |
||||
|
default: -1 |
||||
|
}, |
||||
|
// 工具栏配置 |
||||
|
toolbarConfig: { |
||||
|
type: Object, |
||||
|
default: () => { |
||||
|
return { |
||||
|
keys: [], // 要显示的工具,优先级最大 |
||||
|
excludeKeys: [], // 除这些指定的工具外,其他都显示 |
||||
|
iconSize: '18px', // 工具栏字体大小 |
||||
|
iconColumns: 10 // 工具栏列数 |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
watch: { |
||||
|
toolbarConfig: { |
||||
|
deep: true, |
||||
|
immediate: true, |
||||
|
handler(newToolbar) { |
||||
|
/** |
||||
|
* 若工具栏配置中keys存在,则以keys为准 |
||||
|
* 否则以excludeKeys向toolbarAllList中排查 |
||||
|
* 若keys与excludeKeys皆为空,则以toolbarAllList为准 |
||||
|
*/ |
||||
|
if (newToolbar.keys?.length > 0) { |
||||
|
this.toolbarList = newToolbar.keys |
||||
|
} else { |
||||
|
this.toolbarList = |
||||
|
newToolbar.excludeKeys?.length > 0 |
||||
|
? this.toolbarAllList.filter((item) => !newToolbar.excludeKeys.includes(item)) |
||||
|
: this.toolbarAllList |
||||
|
} |
||||
|
this.iconSize = newToolbar.iconSize || '18px' |
||||
|
this.iconColumns = newToolbar.iconColumns || 10 |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
data() { |
||||
|
return { |
||||
|
formats: {}, |
||||
|
curFab: '', // 当前悬浮工具栏 |
||||
|
fabXY: {}, |
||||
|
textColor: '', |
||||
|
backgroundColor: '', |
||||
|
curColor: '', |
||||
|
defaultColor: { r: 0, g: 0, b: 0, a: 1 }, // 调色板默认颜色 |
||||
|
iconSize: '20px', // 工具栏图标字体大小 |
||||
|
iconColumns: 10, // 工具栏列数 |
||||
|
toolbarList: [], |
||||
|
toolbarAllList: [ |
||||
|
'header', // 标题 |
||||
|
'H1', // 一级标题 |
||||
|
'H2', // 二级标题 |
||||
|
'H3', // 三级标题 |
||||
|
'H4', // 四级标题 |
||||
|
'H5', // 五级标题 |
||||
|
'H6', // 六级标题 |
||||
|
'bold', // 加粗 |
||||
|
'italic', // 斜体 |
||||
|
'underline', // 下划线 |
||||
|
'strike', // 删除线 |
||||
|
'align', // 对齐方式 |
||||
|
'alignLeft', // 左对齐 |
||||
|
'alignCenter', // 居中对齐 |
||||
|
'alignRight', // 右对齐 |
||||
|
'alignJustify', // 两端对齐 |
||||
|
'fontSize', // 字号 |
||||
|
'image', // 图片 |
||||
|
], |
||||
|
fabTools: { |
||||
|
header: [ |
||||
|
{ title: '一级标题', name: 'H1', value: 1, icon: 'icon-format-header-1' }, |
||||
|
{ title: '二级标题', name: 'H2', value: 2, icon: 'icon-format-header-2' }, |
||||
|
{ title: '三级标题', name: 'H3', value: 3, icon: 'icon-format-header-3' }, |
||||
|
{ title: '四级标题', name: 'H4', value: 4, icon: 'icon-format-header-4' }, |
||||
|
{ title: '五级标题', name: 'H5', value: 5, icon: 'icon-format-header-5' }, |
||||
|
{ title: '六级标题', name: 'H6', value: 6, icon: 'icon-format-header-6' } |
||||
|
], |
||||
|
fontFamily: [ |
||||
|
{ title: '宋体', name: '宋', value: '宋体', icon: '' }, |
||||
|
{ title: '黑体', name: '黑', value: '黑体', icon: '' }, |
||||
|
{ title: '楷体', name: '楷', value: '楷体', icon: '' }, |
||||
|
{ title: '仿宋', name: '仿', value: '仿宋', icon: '' }, |
||||
|
{ title: '华文隶书', name: '隶', value: 'STLiti', icon: '' }, |
||||
|
{ title: '华文行楷', name: '行', value: 'STXingkai', icon: '' }, |
||||
|
{ title: '幼圆', name: '圆', value: 'YouYuan', icon: '' } |
||||
|
], |
||||
|
fontSize: [ |
||||
|
{ title: '12', name: '12', value: '12px', icon: '' }, |
||||
|
{ title: '14', name: '14', value: '14px', icon: '' }, |
||||
|
{ title: '16', name: '16', value: '16px', icon: '' }, |
||||
|
{ title: '18', name: '18', value: '18px', icon: '' }, |
||||
|
{ title: '20', name: '20', value: '20px', icon: '' }, |
||||
|
{ title: '22', name: '22', value: '22px', icon: '' }, |
||||
|
{ title: '24', name: '24', value: '24px', icon: '' } |
||||
|
], |
||||
|
align: [ |
||||
|
{ title: '左对齐', name: 'alignLeft', value: 'left', icon: 'icon-zuoduiqi' }, |
||||
|
{ title: '居中对齐', name: 'alignCenter', value: 'center', icon: 'icon-juzhongduiqi' }, |
||||
|
{ title: '右对齐', name: 'alignRight', value: 'right', icon: 'icon-youduiqi' }, |
||||
|
{ title: '两端对齐', name: 'alignJustify', value: 'justify', icon: 'icon-zuoyouduiqi' } |
||||
|
], |
||||
|
lineHeight: [ |
||||
|
{ title: '1倍', name: '1', value: '1', icon: '' }, |
||||
|
{ title: '1.5倍', name: '1.5', value: '1.5', icon: '' }, |
||||
|
{ title: '2倍', name: '2', value: '2', icon: '' }, |
||||
|
{ title: '2.5倍', name: '2.5', value: '2.5', icon: '' }, |
||||
|
{ title: '3倍', name: '3', value: '3', icon: '' } |
||||
|
], |
||||
|
// 字间距/段前距/段后距 |
||||
|
space: [ |
||||
|
{ title: '0.5倍', name: '0.5', value: '0.5em', icon: '' }, |
||||
|
{ title: '1倍', name: '1', value: '1em', icon: '' }, |
||||
|
{ title: '1.5倍', name: '1.5', value: '1.5em', icon: '' }, |
||||
|
{ title: '2倍', name: '2', value: '2em', icon: '' }, |
||||
|
{ title: '2.5倍', name: '2.5', value: '2.5em', icon: '' }, |
||||
|
{ title: '3倍', name: '3', value: '3em', icon: '' } |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
methods: { |
||||
|
onEditorReady() { |
||||
|
uni |
||||
|
.createSelectorQuery() |
||||
|
.in(this) |
||||
|
.select('#' + this.editorId) |
||||
|
.context((res) => { |
||||
|
this.editorCtx = res.context |
||||
|
this.$emit('init', this.editorCtx, this.editorId) |
||||
|
}) |
||||
|
.exec() |
||||
|
}, |
||||
|
undo() { |
||||
|
this.editorCtx.undo() |
||||
|
}, |
||||
|
redo() { |
||||
|
this.editorCtx.redo() |
||||
|
}, |
||||
|
format(e) { |
||||
|
let { name, value } = e.target.dataset |
||||
|
if (!name) return |
||||
|
switch (name) { |
||||
|
case 'color': |
||||
|
case 'backgroundColor': |
||||
|
this.curColor = name |
||||
|
this.showPicker() |
||||
|
break |
||||
|
default: |
||||
|
this.editorCtx.format(name, value) |
||||
|
break |
||||
|
} |
||||
|
}, |
||||
|
// 悬浮工具点击 |
||||
|
fabTap(fabType) { |
||||
|
if (this.curFab != fabType) { |
||||
|
this.curFab = fabType |
||||
|
} else { |
||||
|
this.curFab = '' |
||||
|
} |
||||
|
}, |
||||
|
// 悬浮工具子集点击 |
||||
|
fabTapSub(e, fabType) { |
||||
|
this.format(e) |
||||
|
this.fabTap(fabType) |
||||
|
}, |
||||
|
showPicker() { |
||||
|
switch (this.curColor) { |
||||
|
case 'color': |
||||
|
this.defaultColor = this.textColor |
||||
|
? this.$refs.colorPickerRef.hex2Rgb(this.textColor) |
||||
|
: { r: 0, g: 0, b: 0, a: 1 } |
||||
|
break |
||||
|
case 'backgroundColor': |
||||
|
this.defaultColor = this.backgroundColor |
||||
|
? this.$refs.colorPickerRef.hex2Rgb(this.backgroundColor) |
||||
|
: { r: 0, g: 0, b: 0, a: 0 } |
||||
|
break |
||||
|
} |
||||
|
this.$refs.colorPickerRef.open() |
||||
|
}, |
||||
|
confirmColor(e) { |
||||
|
switch (this.curColor) { |
||||
|
case 'color': |
||||
|
this.textColor = e.hex |
||||
|
this.editorCtx.format('color', this.textColor) |
||||
|
break |
||||
|
case 'backgroundColor': |
||||
|
this.backgroundColor = e.hex |
||||
|
this.editorCtx.format('backgroundColor', this.backgroundColor) |
||||
|
break |
||||
|
} |
||||
|
}, |
||||
|
onStatusChange(e) { |
||||
|
if (e.detail.color) { |
||||
|
this.textColor = e.detail.color |
||||
|
} |
||||
|
if (e.detail.backgroundColor) { |
||||
|
this.backgroundColor = e.detail.backgroundColor |
||||
|
} |
||||
|
this.formats = e.detail |
||||
|
}, |
||||
|
insertDivider() { |
||||
|
this.editorCtx.insertDivider() |
||||
|
}, |
||||
|
clear() { |
||||
|
uni.showModal({ |
||||
|
title: '清空编辑器', |
||||
|
content: '确定清空编辑器吗?', |
||||
|
success: ({ confirm }) => { |
||||
|
if (confirm) { |
||||
|
this.editorCtx.clear() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
removeFormat() { |
||||
|
uni.showModal({ |
||||
|
title: '文本格式化', |
||||
|
content: '确定要清除所选择部分文本块格式吗?', |
||||
|
showCancel: true, |
||||
|
success: ({ confirm }) => { |
||||
|
if (confirm) { |
||||
|
this.editorCtx.removeFormat() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
insertDate() { |
||||
|
const date = new Date() |
||||
|
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}` |
||||
|
this.editorCtx.insertText({ text: formatDate }) |
||||
|
}, |
||||
|
insertLink() { |
||||
|
this.$refs.linkEditRef.open() |
||||
|
}, |
||||
|
/** |
||||
|
* 确认添加链接 |
||||
|
* @param {Object} e { text: '链接描述', href: '链接地址' } |
||||
|
*/ |
||||
|
confirmLink(e) { |
||||
|
this.$refs.linkEditRef.close() |
||||
|
addLink(this.editorCtx, e, () => { |
||||
|
// 修复添加超链接后,不触发input更新当前最新内容的bug,这里做一下手动更新 |
||||
|
this.editorCtx.getContents({ |
||||
|
success: (res) => { |
||||
|
this.$emit('input', { html: res.html, text: res.text }, this.editorId) |
||||
|
} |
||||
|
}) |
||||
|
}) |
||||
|
this.$emit('addLink', e, this.editorId) |
||||
|
}, |
||||
|
insertImage() { |
||||
|
// #ifdef APP-PLUS || H5 |
||||
|
uni.chooseImage({ |
||||
|
// count: 1, // 默认9 |
||||
|
success: (res) => { |
||||
|
const { tempFiles } = res |
||||
|
// 将文件和编辑器示例抛出,由开发者自行上传和插入图片 |
||||
|
this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId) |
||||
|
}, |
||||
|
fail() { |
||||
|
uni.showToast({ |
||||
|
title: '未授权访问相册权限,请授权后使用', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
// #endif |
||||
|
|
||||
|
// #ifdef MP-WEIXIN |
||||
|
// 微信小程序从基础库 2.21.0 开始, wx.chooseImage 停止维护,请使用 uni.chooseMedia 代替。 |
||||
|
uni.chooseMedia({ |
||||
|
// count: 1, // 默认9 |
||||
|
mediaType: ['image'], |
||||
|
success: (res) => { |
||||
|
// 同上chooseImage处理 |
||||
|
const { tempFiles } = res |
||||
|
this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId) |
||||
|
}, |
||||
|
fail() { |
||||
|
uni.showToast({ |
||||
|
title: '未授权访问相册权限,请授权后使用', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
// #endif |
||||
|
}, |
||||
|
insertVideo() { |
||||
|
uni.chooseVideo({ |
||||
|
sourceType: ['camera', 'album'], |
||||
|
success: (res) => { |
||||
|
const { tempFilePath } = res |
||||
|
// 将文件和编辑器示例抛出,由开发者自行上传和插入图片 |
||||
|
this.$emit('upinVideo', tempFilePath, this.editorCtx, this.editorId) |
||||
|
}, |
||||
|
fail() { |
||||
|
uni.showToast({ |
||||
|
title: '未授权访问媒体权限,请授权后使用', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
onEditorInput(e) { |
||||
|
// 注意不要使用getContents获取html和text,会导致重复触发onStatusChange从而失去toolbar工具的高亮状态 |
||||
|
// 复制粘贴的时候detail会为空,此时应当直接return |
||||
|
if (Object.keys(e.detail).length <= 0) return |
||||
|
const { html, text } = e.detail |
||||
|
// 识别到标识立即return |
||||
|
if (text.indexOf(linkFlag) !== -1) return |
||||
|
|
||||
|
const maxlength = parseInt(this.maxlength) |
||||
|
const textStr = text.replace(/[ \t\r\n]/g, '') |
||||
|
if (textStr.length > maxlength && maxlength != -1) { |
||||
|
uni.showModal({ |
||||
|
content: `超过${maxlength}字数啦~`, |
||||
|
confirmText: '确定', |
||||
|
showCancel: false, |
||||
|
success: () => { |
||||
|
this.$emit('overMax', { html, text }, this.editorId) |
||||
|
} |
||||
|
}) |
||||
|
} else { |
||||
|
this.$emit('input', { html, text }, this.editorId) |
||||
|
} |
||||
|
}, |
||||
|
// 导出 |
||||
|
exportHtml() { |
||||
|
this.editorCtx.getContents({ |
||||
|
success: (res) => { |
||||
|
this.$emit('exportHtml', res.html, this.editorId) |
||||
|
} |
||||
|
}) |
||||
|
}, |
||||
|
eLongpress() { |
||||
|
/** |
||||
|
* 微信小程序官方editor的长按事件有bug,需要重写覆盖,不需做任何逻辑,可见下面小程序社区问题链接 |
||||
|
* @tutorial https://developers.weixin.qq.com/community/develop/doc/000c04b3e1c1006f660065e4f61000 |
||||
|
*/ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss"> |
||||
|
@import '@/uni_modules/sp-editor/icons/editor-icon.css'; |
||||
|
@import '@/uni_modules/sp-editor/icons/custom-icon.css'; |
||||
|
|
||||
|
.sp-editor { |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.sp-editor-toolbar { |
||||
|
box-sizing: border-box; |
||||
|
padding: calc(var(--icon-size) / 4) 0; |
||||
|
border-bottom: 1px solid #e4e4e4; |
||||
|
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(var(--icon-columns), 1fr); |
||||
|
} |
||||
|
|
||||
|
.iconfont { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
justify-content: center; |
||||
|
width: 100%; |
||||
|
height: calc(var(--icon-size) * 1.8); |
||||
|
cursor: pointer; |
||||
|
font-size: var(--icon-size); |
||||
|
} |
||||
|
|
||||
|
.sp-editor-wrapper { |
||||
|
flex: 1; |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.editor-container { |
||||
|
padding: 8rpx 16rpx; |
||||
|
box-sizing: border-box; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
font-size: 16px; |
||||
|
line-height: 1.5; |
||||
|
} |
||||
|
|
||||
|
.ql-image-overlay-none { |
||||
|
::v-deep .ql-image-overlay { |
||||
|
pointer-events: none; |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
::v-deep .ql-editor.ql-blank::before { |
||||
|
font-style: normal; |
||||
|
color: #cccccc; |
||||
|
} |
||||
|
|
||||
|
::v-deep .ql-container { |
||||
|
min-height: unset; |
||||
|
} |
||||
|
|
||||
|
.ql-active { |
||||
|
color: #66ccff; |
||||
|
} |
||||
|
|
||||
|
.fab-tools { |
||||
|
display: flex; |
||||
|
padding: 0 10rpx; |
||||
|
box-sizing: border-box; |
||||
|
|
||||
|
.fab-sub { |
||||
|
width: auto; |
||||
|
height: auto; |
||||
|
margin: 10rpx; |
||||
|
} |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,24 @@ |
|||||
|
@font-face { |
||||
|
font-family: 'iconfont'; |
||||
|
src: url('data:font/ttf;charset=utf-8;base64,AAEAAAANAIAAAwBQRkZUTahWRN8AAAeEAAAAHEdERUYAKQALAAAHZAAAAB5PUy8yPElI7QAAAVgAAABgY21hcOY96ckAAAHMAAABSmdhc3D//wADAAAHXAAAAAhnbHlmPxK+mgAAAyQAAAF4aGVhZCjWXYcAAADcAAAANmhoZWEHngOFAAABFAAAACRobXR4DIgAQAAAAbgAAAASbG9jYQCkALwAAAMYAAAADG1heHABGQB9AAABOAAAACBuYW1lXoIBAgAABJwAAAKCcG9zdENa1TcAAAcgAAAAOQABAAAAAQAA20yLC18PPPUACwQAAAAAAOLbjMwAAAAA4tuMzABA/78DwAMzAAAACAACAAAAAAAAAAEAAAOA/4AAXAQAAAAAAAPAAAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAFAHEACgAAAAAAAgAAAAoACgAAAP8AAAAAAAAABAQAAZAABQAAAokCzAAAAI8CiQLMAAAB6wAyAQgAAAIABQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUGZFZADA5hTmHgOA/4AAAAPcAIAAAAABAAAAAAAAAAAAAAAgAAEEAAAAAAAAAAQAAAAEAABAAIgAAAAAAAMAAAADAAAAHAABAAAAAABEAAMAAQAAABwABAAoAAAABgAEAAEAAuYU5h7//wAA5hTmHv//Ge8Z5gABAAAAAAAAAAABBgAAAQAAAAAAAAABAgAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApAC8AAoAQABAA8ACwAARABQAGAAoADQAQABMAFgAZABwAAABHgEUBg8BBiIuAT0BND4BMh8BFTcBESERBSEiJjURNDYzITIWFREUBgE0NjIWFREUBiImNRMyFhQGKwEiJjQ2MxcyFhQGKwEiJjQ2MwE0NjIWFREUBiImNRMiJjQ2OwEyFhQGIwciJjQ2OwEyFhQGIwJwCAgICKAHEQ8JCQ8RBxBCAV79AAMA/QAbJSUbAwAbJSX9JRMaExMaEyANExMNgA0TEw2ADRMTDYANExMNAqATGhMTGhMgDRMTDYANExMNgA0TEw2ADRMTDQGcBQ8QDwVgBAgPCcAJDwgEVU4n/wACAP4AQCUbAgAbJSUb/gAbJQJgDRMTDf3gDRMTDQGAExoTExoTwBMaExMaEwFgDRMTDf3gDRMTDQFAExoTExoTwBMaExMaEwABAIj/vwNpAzMACwAAEzMRIREzESMRIREjiJABwZCQ/j+QAzP+jwFx/IwBiP54AAAAABIA3gABAAAAAAAAABMAKAABAAAAAAABAAgATgABAAAAAAACAAcAZwABAAAAAAADAAgAgQABAAAAAAAEAAgAnAABAAAAAAAFAAsAvQABAAAAAAAGAAgA2wABAAAAAAAKACsBPAABAAAAAAALABMBkAADAAEECQAAACYAAAADAAEECQABABAAPAADAAEECQACAA4AVwADAAEECQADABAAbwADAAEECQAEABAAigADAAEECQAFABYApQADAAEECQAGABAAyQADAAEECQAKAFYA5AADAAEECQALACYBaABDAHIAZQBhAHQAZQBkACAAYgB5ACAAaQBjAG8AbgBmAG8AbgB0AABDcmVhdGVkIGJ5IGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABSAGUAZwB1AGwAYQByAABSZWd1bGFyAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABWAGUAcgBzAGkAbwBuACAAMQAuADAAAFZlcnNpb24gMS4wAABpAGMAbwBuAGYAbwBuAHQAAGljb25mb250AABHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAABHZW5lcmF0ZWQgYnkgc3ZnMnR0ZiBmcm9tIEZvbnRlbGxvIHByb2plY3QuAABoAHQAdABwADoALwAvAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAABodHRwOi8vZm9udGVsbG8uY29tAAAAAAIAAAAAAAAACgAAAAAAAQAAAAAAAAAAAAAAAAAAAAAABQAAAAEAAgECAQMFdmlkZW8GaGVhZGVyAAAAAAAAAf//AAIAAQAAAAwAAAAWAAAAAgABAAMABAABAAQAAAACAAAAAAAAAAEAAAAA4p8rRgAAAADi24zMAAAAAOLbjMw=') format('truetype'); |
||||
|
font-weight: normal; |
||||
|
font-style: normal; |
||||
|
font-display: swap; |
||||
|
} |
||||
|
|
||||
|
.iconfont { |
||||
|
font-family: "iconfont" !important; |
||||
|
font-size: 16px; |
||||
|
font-style: normal; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
-moz-osx-font-smoothing: grayscale; |
||||
|
} |
||||
|
|
||||
|
.icon-header:before { |
||||
|
content: "\e61e"; |
||||
|
} |
||||
|
|
||||
|
.icon-video:before { |
||||
|
content: "\e614"; |
||||
|
} |
||||
|
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1,83 @@ |
|||||
|
{ |
||||
|
"id": "sp-editor", |
||||
|
"displayName": "官方富文本编辑器editor组件改良扩展优化版", |
||||
|
"version": "1.5.0", |
||||
|
"description": "基于官方的富文本编辑器editor组件,进行改良扩展优化版,添加了调色板,添加超链接等功能,可自定义扩展工具,快来试试吧~", |
||||
|
"keywords": [ |
||||
|
"富文本", |
||||
|
"editor", |
||||
|
"编辑器" |
||||
|
], |
||||
|
"repository": "", |
||||
|
"engines": { |
||||
|
}, |
||||
|
"dcloudext": { |
||||
|
"type": "component-vue", |
||||
|
"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", |
||||
|
"alipay": "n" |
||||
|
}, |
||||
|
"client": { |
||||
|
"Vue": { |
||||
|
"vue2": "y", |
||||
|
"vue3": "y" |
||||
|
}, |
||||
|
"App": { |
||||
|
"app-vue": "y", |
||||
|
"app-nvue": "y" |
||||
|
}, |
||||
|
"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", |
||||
|
"阿里": "u", |
||||
|
"百度": "u", |
||||
|
"字节跳动": "u", |
||||
|
"QQ": "u", |
||||
|
"钉钉": "u", |
||||
|
"快手": "u", |
||||
|
"飞书": "u", |
||||
|
"京东": "u" |
||||
|
}, |
||||
|
"快应用": { |
||||
|
"华为": "u", |
||||
|
"联盟": "u" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
# sp-editor |
||||
|
|
||||
|
### 视频插入功能 |
||||
|
|
||||
|
`于2024-7-20日v1.4.4版本更新视频插入功能(属实是鸽🕊了太久了)` |
||||
|
|
||||
|
- 实现方案:先以图片占位,在导出时,将携带视频链接的图片转换成视频标签。 |
||||
|
- 更多请参考示例一 |
||||
|
- 如果该插件有帮助到您,还望能点赞好评一下,谢谢!🌟 |
||||
|
|
||||
|
### 文档迁移 |
||||
|
|
||||
|
> 防止文档失效,提供下列五个地址,内容一致 |
||||
|
|
||||
|
- [地址一](https://sonvee.github.io/sv-app-docs/docs-github/src/plugins/sp-editor/sp-editor.html) |
||||
|
- [地址二](https://sv-app-docs.pages.dev/src/plugins/sp-editor/sp-editor.html) |
||||
|
- [地址三](https://sv-app-docs.4everland.app/src/plugins/sp-editor/sp-editor.html) |
||||
|
- [地址四](https://sv-app-docs.vercel.app/src/plugins/sp-editor/sp-editor.html) (需要梯子) |
||||
|
- [地址五](https://static-mp-74bfcbac-6ba6-4f39-8513-8831390ff75a.next.bspapp.com/docs-uni/src/plugins/sp-editor/sp-editor.html) (有IP限制) |
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,132 @@ |
|||||
|
// 标识必须独一无二 - 标识是为了使用insertText插入标识文本后,查找到标识所在delta位置的索引
|
||||
|
export const linkFlag = '#-*=*-*=*-*=*@-link超链接标识link-@*=*-*=*-*=*-#' |
||||
|
|
||||
|
export function addLink(editorCtx, attr, callback) { |
||||
|
// 先插入一段文本内容
|
||||
|
editorCtx.insertText({ |
||||
|
text: linkFlag |
||||
|
}) |
||||
|
// 获取全文delta内容
|
||||
|
editorCtx.getContents({ |
||||
|
success(res) { |
||||
|
let options = res.delta.ops |
||||
|
const findex = options.findIndex(item => { |
||||
|
return item.insert && typeof item.insert !== 'object' && item.insert?.indexOf(linkFlag) !== -1 |
||||
|
}) |
||||
|
// 根据标识查找到插入的位置
|
||||
|
if (findex > -1) { |
||||
|
const findOption = options[findex] |
||||
|
const findAttributes = findOption.attributes |
||||
|
// 将该findOption分成三部分:前内容 要插入的link 后内容
|
||||
|
const [prefix, suffix] = findOption.insert.split(linkFlag); |
||||
|
const handleOps = [] |
||||
|
// 前内容
|
||||
|
if (prefix) { |
||||
|
const prefixOps = findAttributes ? { |
||||
|
insert: prefix, |
||||
|
attributes: findAttributes |
||||
|
} : { |
||||
|
insert: prefix |
||||
|
} |
||||
|
handleOps.push(prefixOps) |
||||
|
} |
||||
|
// 插入的link
|
||||
|
const linkOps = { |
||||
|
insert: attr.text, |
||||
|
attributes: { |
||||
|
link: attr.href, |
||||
|
textDecoration: attr.textDecoration || 'none', // 下划线
|
||||
|
color: attr.color || '#007aff' |
||||
|
} |
||||
|
} |
||||
|
handleOps.push(linkOps) |
||||
|
// 后内容
|
||||
|
if (suffix) { |
||||
|
const suffixOps = findAttributes ? { |
||||
|
insert: suffix, |
||||
|
attributes: findAttributes |
||||
|
} : { |
||||
|
insert: suffix |
||||
|
} |
||||
|
handleOps.push(suffixOps) |
||||
|
} |
||||
|
// 删除原options[findex]并在findex位置插入上述三个ops
|
||||
|
options.splice(findex, 1); |
||||
|
options.splice(findex, 0, ...handleOps); |
||||
|
// 最后重新初始化内容,注意该方法会导致光标重置到最开始位置
|
||||
|
editorCtx.setContents({ |
||||
|
delta: { |
||||
|
ops: options |
||||
|
} |
||||
|
}) |
||||
|
// 所以最后建议使富文本光标失焦,让用户手动聚焦光标
|
||||
|
editorCtx.blur() |
||||
|
|
||||
|
// 后续回调操作
|
||||
|
if (callback) callback() |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将含有特殊图片形式视频的富文本转换成正常视频的富文本 |
||||
|
* @param {String} html 要进行处理的富文本字符串 |
||||
|
* @returns {String} 返回处理结果 |
||||
|
*/ |
||||
|
export function handleHtmlWithVideo(html) { |
||||
|
// 正则表达式用于匹配img标签中带有alt属性且alt属性值为视频链接的模式
|
||||
|
const regex = /<img\s+src="[^"]*"\s+alt="([^"]*)"[^>]*>/g |
||||
|
// 使用replace方法和一个函数回调来替换匹配到的内容
|
||||
|
return html.replace(regex, (match, videoUrl) => { |
||||
|
// 替换为video标签,并添加controls属性以便用户可以控制播放
|
||||
|
return `<video width="80%" controls><source src="${videoUrl}" type="video/mp4"></video>` |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将img标签中内联style属性中的宽高样式提取出标签width与height属性 |
||||
|
* @param {Object} html 要处理的富文本字符串 |
||||
|
* @returns {Object} 返回处理结果 |
||||
|
*/ |
||||
|
export function convertImgStylesToAttributes(html) { |
||||
|
return html.replace(/<img\s+([^>]+)\s*>/g, function(match, attributes) { |
||||
|
// 分割属性
|
||||
|
const attrs = attributes.split(/\s+/); |
||||
|
|
||||
|
// 找到style属性的位置
|
||||
|
const styleIndex = attrs.findIndex(attr => attr.startsWith('style=')); |
||||
|
if (styleIndex === -1) return match; // 如果没有找到style属性,则返回原样
|
||||
|
|
||||
|
// 提取style属性值
|
||||
|
const styleAttr = attrs.splice(styleIndex, 1)[0]; |
||||
|
const style = styleAttr.match(/"([^"]*)"/)[1]; |
||||
|
|
||||
|
// 解析 style 属性
|
||||
|
const styleObj = {}; |
||||
|
style.split(';').forEach(function(part) { |
||||
|
if (part) { |
||||
|
const [name, value] = part.split(':'); |
||||
|
styleObj[name.trim()] = value.trim(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// 创建新的 img 标签
|
||||
|
let newTag = '<img'; |
||||
|
if (styleObj.width) { |
||||
|
newTag += ` width="${styleObj.width}"`; |
||||
|
} |
||||
|
if (styleObj.height) { |
||||
|
newTag += ` height="${styleObj.height}"`; |
||||
|
} |
||||
|
|
||||
|
// 添加原有的属性,包括修改过的style属性
|
||||
|
newTag += ` ${styleAttr} ${attrs.join(' ')}`; |
||||
|
|
||||
|
// 关闭 img 标签
|
||||
|
newTag += '>'; |
||||
|
|
||||
|
return newTag; |
||||
|
}); |
||||
|
} |
||||
Loading…
Reference in new issue