This commit is contained in:
王创世 2024-06-21 16:15:47 +08:00
parent 75e13dff0e
commit 918331c0ad
13 changed files with 2147 additions and 0 deletions

134
pages/index/my_sign.vue Normal file
View File

@ -0,0 +1,134 @@
<template>
<view>
<tn-nav-bar :isBack="false" backTitle="" :bottomShadow="true" backgroundColor="#FFFFFF">
<view class="custom-nav tn-flex tn-flex-col-center tn-flex-row-left">
<view style="padding-left: 15rpx;" @click="goBack()">
<text class="tn-icon-left" style="font-size: 40rpx;"></text>
</view>
<view class="tn-margin-top"
style=";text-shadow: 1rpx 0 0 #FFF, 0 1rpx 0 #FFF, -1rpx 0 0 #FFF , 0 -1rpx 0 #FFF;">
<tn-tabs :list="[{name:'活动签到'}]" :current="topCurrent" activeColor="#000" :bold="false"
:fontSize="36"></tn-tabs>
</view>
</view>
</tn-nav-bar>
<view :style="{paddingTop: vuex_custom_bar_height + 20+'px'}" v-if="info!=''">
<view style="padding:0rpx 30rpx;text-align: center;">
<view style="">
<text class="tn-icon-trust-fill" style="font-size: 250rpx;color: #01BEFF;"></text>
</view>
<view style="font-size: 38rpx;font-weight: 600;margin-top: 40rpx;">{{info.activity_name}}</view>
<view style="margin-top: 100rpx;font-size: 32rpx;font-weight: 600;">
<view>开始时间{{info.activity_start_time}}</view>
<view style="margin: 20rpx 0rpx;">-</view>
<view>结束时间{{info.activity_end_time}}</view>
</view>
<view style="margin-top: 100rpx;">
<tn-button @click="sign()" backgroundColor="#01BEFF" :shadow="true" fontColor="#ffffff"
width="200rpx" height="200rpx" :fontSize="40" shape="icon" margin="10rpx 10rpx">签到</tn-button>
</view>
</view>
</view>
</view>
</template>
<script>
import {
activitySign,
eventInfo
} from '@/util/api.js';
import store from '@/store/index.js'
export default {
data() {
return {
topCurrent: 1,
activity_id: '',
member_id: '',
association_id: '',
info: ''
}
},
onLoad(query) {
const q = decodeURIComponent(query.q) //
var data = this.getUrlParams(q);
this.activity_id = data.id;
this.association_id = data.association_id;
var that = this;
console.log('---1---');
getApp().getUserLogin((r) => {
console.log('---2---');
that.member_id = r.data.id;
that.getEventInfo();
})
},
methods: {
getEventInfo() {
console.log('---3---');
eventInfo({
association_id: this.association_id,
id: this.activity_id
})
.then(res => {
console.log(res);
if (res.code == 1) {
this.info = res.data;
}
})
.catch(error => {
uni.showToast({
title: error,
icon: 'none',
duration: 2000
});
})
},
sign() {
activitySign({
activity_id: this.activity_id,
member_id: this.member_id,
association_id: this.association_id,
})
.then(res => {
console.log(res);
uni.showToast({
title: res.msg,
icon: 'none',
duration: 2000
});
})
.catch(error => {
uni.showToast({
title: error,
icon: 'none',
duration: 2000
});
})
},
goBack() {
if (getCurrentPages().length > 1) {
uni.navigateBack()
} else {
uni.redirectTo({
url: '/pages/index/index'
})
}
},
getUrlParams(url) {
// \w+ (线), [\u4e00-\u9fa5]+
let pattern = /(\w+|[\u4e00-\u9fa5]+)=(\w+|[\u4e00-\u9fa5]+)/ig;
let result = {};
url.replace(pattern, ($, $1, $2) => {
result[$1] = $2;
})
return result
}
}
}
</script>
<style>
page {
background-color: #ffffff;
}
</style>

BIN
static/ico16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -0,0 +1,109 @@
## 1.4.02024-05-31
1. 更新示例工程
## 1.3.92024-05-31
1. 修复调色板无法正常选色的问题
## 1.3.82024-05-13
1. 更新示例三微信小程序上使用setContents造成聚焦滚动的处理
## 1.3.72024-05-10
1. 修复添加超链接后不触发input更新当前最新内容的bug
## 1.3.62024-05-09
1. 文档迁移
## 1.3.52024-05-09
1. 所有事件携带编辑器id参数以便循环时能区分处理
2. 更新示例工程
## 1.3.42024-05-08
1. 更新示例工程
2. 新增editorId参数
## 1.3.32024-03-22
1. 修复微信小程序长按无法粘贴的问题
## 1.3.22024-03-14
1. 更新了toolbar样式与配置见文档
2. 更新示例工程,媒体查询响应式写法
3. 优化了只读模式效果,开启只读模式后,文章内容的超链接可正常点击并跳转
## 1.3.12024-03-14
1. 优化了只读功能,开启只读后自动隐藏工具栏
2. 更新示例工程
## 1.3.02024-03-07
1. 新增addLink的emit事件
## 1.2.92024-02-23
1. 更新文档
## 1.2.82024-02-23
1. 新增了添加超链接的工具toolbar中link字段默认开启
2. 优化了部分逻辑
3. 更新文档、更新示例工程
## 1.2.72024-02-23
1. 更新文档,更新示例工程
2. 添加toolbar中图标字体大小可配置项
## 1.2.62024-02-22
1. 添加导出工具按钮可将当前已编辑的html导出至页面解析
2. 超链接工具按钮正在尝试开发中(貌似目前官方不支持)
## 1.2.52024-02-19
1. 更新示例工程(吸顶写法)
2. 完善调色板功能
## 1.2.42024-02-18
1. 修复工具栏颜色按钮底色动态切换问题
## 1.2.32024-02-18
1. 更新示例工程
## 1.2.22024-02-18
1. 删除log调试打印
## 1.2.12024-02-18
1. 修复了颜色图标不会动态切换的问题
## 1.2.02024-02-18
1. 修复选择颜色时会将所选文字删除的bug
## 1.1.92024-02-04
1. 更新示例工程
## 1.1.82024-02-04
1. 文档修改
## 1.1.72024-02-04
1. 新增toolbar配置项可自由配置工具栏工具列表
2. 移除组件内原templates属性默认初始化编辑器内容请看文档使用方式示例
3. 更新文档
## 1.1.62024-01-31
1. 更好的兼容vue2了修复在vue2下高度可能超出的问题
2. 示例工程兼容vue2
## 1.1.52024-01-30
1. 修复工具栏字体按钮无效的问题
## 1.1.42024-01-30
1. 解决默认初始化内容时前缀空格或缩进无效的问题
2. 解决点击工具栏高亮状态后输入内容时便失去高亮的bug
3. 更新示例工程
## 1.1.32024-01-23
1. 重写高度动态计算逻辑,现在对不同屏幕尺寸的适应性更强了
## 1.1.22024-01-17
1. 修复分割线会生成多条的问题
## 1.1.12024-01-15
1. 更新文档
## 1.1.02024-01-15
1. insertText方法在插入内容的时候会移动光标聚焦导致焦点回滚到视口处
2. 更新示例工程
## 1.0.92024-01-04
1. 更新文档
## 1.0.82024-01-04
1. 修复h5端官方cdn请求失败的问题详见问答贴https://ask.dcloud.net.cn/article/40900
## 1.0.72024-01-03
1. 移除v-bind="$attrs",该写法在微信小程序不支持
## 1.0.62023-12-29
1. 更新文档
## 1.0.52023-12-29
1. 更新了init方法可以使用返回的editor实例尽情自定义
2. 组件在<editor>上添加v-bind="$attrs"属性穿透可以使用原editor参数官方文档https://uniapp.dcloud.net.cn/component/editor.html
## 1.0.42023-12-29
1. 优化了切换文字和背景颜色是,可能会导致切换后不生效的问题
2. 修复在部分设备上的微信小程序中可能会存在颜色版无法正常滑动的问题
3. 更友好的交互体验:添加图标悬停字样描述、添加格式化文本弹窗确认
4. 有插入视频的需求暂时可能无法实现官方给予的回复是目前各端的eidtor组件都不能直接插入视频编辑时可以采用视频封面占位并在图片中保存视频信息在预览时再还原为视频。
## 1.0.32023-10-13
1. 更新readme文档
2. 更新调整组件示例项目,添加插件代码中部分注释
## 1.0.22023-10-13
1. 更新uni_modules规范可一键导入组件
2. 更新组件示例项目包括使用uniCloud.uploadFile多选上传图片示例方法
## 1.0.12023-10-12
1. 修复小程序中自动聚焦滚动到富文本组件区域的bug
2. 略微调整了富文本上方toolbar工具栏中按钮的大小尺寸
## 1.0.02023-9-19
1. 新增字体与背景颜色板
2. 可自定义预设内容模板
3. 解决官方样例在小程序和app部分报错不兼容的问题
4. 可配合云存储上传富文本中插入的图片 本质上是基于官方内置富文本editor组件改版封装所以官方有的功能都有官方能兼容的也都兼容

View File

@ -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]
// pageXclientX
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)表示饱和度Bbrightness表示亮度
*/
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>

View File

@ -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>

View File

@ -0,0 +1,518 @@
<template>
<view class="sp-editor" :style="{ '--icon-size': iconSize, '--icon-columns': iconColumns }">
<view class="sp-editor-toolbar" v-if="!readOnly" @tap="format">
<view v-if="toolbarList.includes('header')" :class="formats.header === 1 ? 'ql-active' : ''"
class="iconfont icon-format-header-1" title="标题" data-name="header" :data-value="1"></view>
<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>
<!-- #ifndef MP-BAIDU -->
<view v-if="toolbarList.includes('alignLeft')" :class="formats.align === 'left' ? 'ql-active' : ''"
class="iconfont icon-zuoduiqi" title="左对齐" data-name="align" data-value="left"></view>
<!-- #endif -->
<view v-if="toolbarList.includes('alignCenter')" :class="formats.align === 'center' ? 'ql-active' : ''"
class="iconfont icon-juzhongduiqi" title="居中对齐" data-name="align" data-value="center"></view>
<view v-if="toolbarList.includes('alignRight')" :class="formats.align === 'right' ? 'ql-active' : ''"
class="iconfont icon-youduiqi" title="右对齐" data-name="align" data-value="right"></view>
<view v-if="toolbarList.includes('alignJustify')" :class="formats.align === 'justify' ? 'ql-active' : ''"
class="iconfont icon-zuoyouduiqi" title="两端对齐" data-name="align" data-value="justify"></view>
<!-- #ifndef MP-BAIDU -->
<view v-if="toolbarList.includes('lineHeight')" :class="formats.lineHeight ? 'ql-active' : ''"
class="iconfont icon-line-height" title="行间距" data-name="lineHeight" data-value="2"></view>
<view v-if="toolbarList.includes('letterSpacing')" :class="formats.letterSpacing ? 'ql-active' : ''"
class="iconfont icon-Character-Spacing" title="字间距" data-name="letterSpacing" data-value="2em"></view>
<view v-if="toolbarList.includes('marginTop')" :class="formats.marginTop ? 'ql-active' : ''"
class="iconfont icon-722bianjiqi_duanqianju" title="段前距" data-name="marginTop" data-value="20px"></view>
<view v-if="toolbarList.includes('marginBottom')" :class="formats.marginBottom ? 'ql-active' : ''"
class="iconfont icon-723bianjiqi_duanhouju" title="段后距" data-name="marginBottom" data-value="20px">
</view>
<!-- #endif -->
<!-- #ifndef MP-BAIDU -->
<view v-if="toolbarList.includes('fontSize')" :class="formats.fontFamily ? 'ql-active' : ''"
class="iconfont icon-font" title="字体" data-name="fontFamily" data-value="宋体"></view>
<view v-if="toolbarList.includes('fontSize')" :class="formats.fontSize === '24px' ? 'ql-active' : ''"
class="iconfont icon-fontsize" title="字号" data-name="fontSize" data-value="24px"></view>
<!-- #endif -->
<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('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 {
addLink,
linkFlag
} from '../../utils'
export default {
components: {
ColorPicker,
LinkEdit
},
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: {},
textColor: '',
backgroundColor: '',
curColor: '',
defaultColor: {
r: 0,
g: 0,
b: 0,
a: 1
}, //
iconSize: '20px', //
iconColumns: 10, //
toolbarList: [],
toolbarAllList: [
'bold', //
'italic', //
'underline', // 线
'strike', // 线
'alignLeft', //
'alignCenter', //
'alignRight', //
'alignJustify', //
'lineHeight', //
'letterSpacing', //
'marginTop', //
'marginBottom', //
'fontFamily', //
'fontSize', //
'color', //
'backgroundColor', //
'date', //
'listCheck', //
'listOrdered', //
'listBullet', //
'indentInc', //
'indentDec', //
'divider', // 线
'header', //
'scriptSub', //
'scriptSuper', //
'direction', //
'image', //
'link', //
'undo', //
'redo', //
'removeFormat', //
'clear', //
'export' //
]
}
},
methods: {
onEditorReady() {
// #ifdef MP-BAIDU
this.editorCtx = requireDynamicLib('editorLib').createEditorContext(this.editorId)
// #endif
// #ifdef APP || MP-WEIXIN || H5
uni
.createSelectorQuery()
.in(this)
.select('#' + this.editorId)
.context((res) => {
this.editorCtx = res.context
this.$emit('init', this.editorCtx, this.editorId)
})
.exec()
// #endif
},
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
}
},
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)
this.$emit('addLink', e, this.editorId)
// inputbug
this.editorCtx.getContents({
success: (res) => {
this.$emit('input', {
html: res.html,
text: res.text
}, 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
success: (res) => {
// chooseImage
const {
tempFiles
} = res
this.$emit('upinImage', tempFiles, this.editorCtx, this.editorId)
},
fail() {
uni.showToast({
title: '未授权访问相册权限,请授权后使用',
icon: 'none'
})
}
})
// #endif
},
onEditorInput(e) {
// 使getContentshtmltextonStatusChangetoolbar
// detailreturn
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';
.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);
position: fixed;
width: 95%;
z-index: 100;
background: #fff;
}
.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;
margin-top: 245rpx;
}
.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;
}
</style>

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -0,0 +1,83 @@
{
"id": "sp-editor",
"displayName": "官方富文本编辑器editor组件改良扩展优化版",
"version": "1.4.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",
"百度": "y",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,11 @@
# sp-editor
### 文档迁移
> 防止文档失效,提供下列五个地址,内容一致
- [地址一](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

View File

@ -0,0 +1,68 @@
// 标识必须独一无二 - 标识是为了使用insertText插入标识文本后查找到标识所在delta位置的索引
export const linkFlag = '#-*=*-*=*-*=*@-link超链接标识link-@*=*-*=*-*=*-#'
export function addLink(editorCtx, attr) {
// 先插入一段文本内容
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()
}
}
})
}