17
This commit is contained in:
@@ -1,193 +1,284 @@
|
||||
<template>
|
||||
<view class="editor_wrap">
|
||||
<editor
|
||||
:placeholder="placeholder"
|
||||
id="editor2"
|
||||
@ready="editorReady"
|
||||
@focus="editorFocus"
|
||||
@blur="editorBlur"
|
||||
@input="editorInput" />
|
||||
</view>
|
||||
<div id="editor-container" :call="option" :change:call="editorModule.call"><!-- 编辑器 --></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { forIn } from "lodash";
|
||||
import {html2Text} from "@/util/common";
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorCtx: null,
|
||||
lastStr: "",
|
||||
isInsertingEmoji: false, // 标记是否正在插入表情
|
||||
hasFocus: false, // 记录编辑器是否有焦点
|
||||
};
|
||||
timer:null,
|
||||
option:null,
|
||||
events:[],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
editorReady() {
|
||||
uni
|
||||
.createSelectorQuery()
|
||||
.select("#editor2")
|
||||
.context((res) => {
|
||||
//this.$emit("ready", res);
|
||||
this.editorCtx = res.context;
|
||||
})
|
||||
.exec();
|
||||
insertText(text,successFn,errorFn){
|
||||
this.addEvent('insertText',text);
|
||||
},
|
||||
editorFocus() {
|
||||
this.hasFocus = true;
|
||||
// 如果正在插入表情,不触发 focus 事件,并立即隐藏键盘
|
||||
if (this.isInsertingEmoji) {
|
||||
// #ifdef APP-PLUS || H5
|
||||
uni.hideKeyboard();
|
||||
// #endif
|
||||
return;
|
||||
}
|
||||
this.$emit("focus");
|
||||
insertImgEmoji(src,successFn,errorFn){
|
||||
this.addEvent('insertImgEmoji',src);
|
||||
},
|
||||
editorBlur() {
|
||||
this.hasFocus = false;
|
||||
this.$emit("blur");
|
||||
insertMention(username,userid){
|
||||
this.addEvent('insertMention',{
|
||||
username,
|
||||
userid
|
||||
});
|
||||
},
|
||||
clear(){
|
||||
this.editorCtx.clear()
|
||||
this.addEvent('clear');
|
||||
},
|
||||
insertText(text,successFn,errFn){
|
||||
// 标记正在插入表情,阻止 focus 事件触发
|
||||
this.isInsertingEmoji = true;
|
||||
|
||||
// 先隐藏键盘,避免插入时键盘弹出
|
||||
// #ifdef APP-PLUS || H5
|
||||
uni.hideKeyboard();
|
||||
// #endif
|
||||
|
||||
// 如果编辑器当前有焦点,先让它失焦(通过点击外部区域)
|
||||
// 但这种方式可能不太可靠,所以我们主要依赖 isInsertingEmoji 标志
|
||||
|
||||
// 使用 insertText 插入文本(这是最可靠的方法)
|
||||
// 虽然会触发焦点,但我们已经通过 isInsertingEmoji 标志阻止了 focus 事件
|
||||
this.editorCtx.insertText({
|
||||
text: text,
|
||||
success: (res) => {
|
||||
successFn && successFn.call(this, [res]);
|
||||
console.log("插入文字成功");
|
||||
|
||||
// 插入后立即隐藏键盘,防止键盘弹出
|
||||
// #ifdef APP-PLUS || H5
|
||||
// 使用多个延迟确保键盘被隐藏
|
||||
setTimeout(() => {
|
||||
uni.hideKeyboard();
|
||||
}, 10);
|
||||
setTimeout(() => {
|
||||
uni.hideKeyboard();
|
||||
}, 50);
|
||||
setTimeout(() => {
|
||||
uni.hideKeyboard();
|
||||
}, 100);
|
||||
// #endif
|
||||
|
||||
// 延迟重置标志,确保 focus 事件被完全忽略
|
||||
setTimeout(() => {
|
||||
this.isInsertingEmoji = false;
|
||||
}, 300);
|
||||
},
|
||||
fail: (err) => {
|
||||
errFn && errFn.call(this, [err]);
|
||||
console.log("插入文字失败", err);
|
||||
this.isInsertingEmoji = false;
|
||||
}
|
||||
});
|
||||
blur(){
|
||||
this.addEvent('blur');
|
||||
},
|
||||
delete(){
|
||||
this.editorCtx.getContents({
|
||||
success({html,text,delta}){
|
||||
console.log(html,text,delta);
|
||||
}
|
||||
})
|
||||
return ;
|
||||
//setContents(OBJECT)
|
||||
let emojiStr = this.editorCtx.getContents();
|
||||
let emojiArr = [];
|
||||
emojiStr = emojiStr.replace(/\[([^(\]|\[)]*)\]/g, function(item, index) {
|
||||
emojiArr.unshift(item);
|
||||
});
|
||||
let sendStr ="";
|
||||
if (emojiArr.length > 0) {
|
||||
if (this.sendStr.endsWith(emojiArr[0])) {
|
||||
this.sendStr = this.sendStr.replace(emojiArr[0], "");
|
||||
focus(){
|
||||
this.addEvent('focus');
|
||||
},
|
||||
setHtml(html){
|
||||
this.addEvent('setHtml',html);
|
||||
},
|
||||
getText(){
|
||||
this.addEvent('getText');
|
||||
},
|
||||
getHtml(){
|
||||
console.log(this);
|
||||
return 1;
|
||||
this.addEvent('getHtml');
|
||||
},
|
||||
getSelectionPosition(){
|
||||
this.addEvent('getSelectionPosition');
|
||||
},
|
||||
getParentNode(){
|
||||
this.addEvent('getParentNode');
|
||||
},
|
||||
// 调用
|
||||
call() {
|
||||
if (this.timer) return;
|
||||
// 消费事件队列(生产者/消费者机制)
|
||||
this.timer = setInterval(() => {
|
||||
if (this.events.length) {
|
||||
this.option = this.events.shift();
|
||||
console.log(this.option);
|
||||
} else {
|
||||
this.sendStr = this.sendStr.slice(0, this.sendStr.length - 1);
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
} else {
|
||||
this.sendStr = this.sendStr.slice(0, this.sendStr.length - 1);
|
||||
}, 10);
|
||||
},
|
||||
// 添加事件队列
|
||||
addEvent(name, data) {
|
||||
// #ifdef APP-PLUS
|
||||
// tips:由于采用监听option改变来调用方法,
|
||||
// 如果连续变化两次option,渲染层只会则监听到最后一次
|
||||
// 导致调用丢失,所以采用事件队列形式解决,稍微延时10ms
|
||||
// 等待渲染进程监听到option变化,在进行更改option
|
||||
// 从性能上,几乎无感可以放心使用
|
||||
const option = {
|
||||
id: this.genId(),
|
||||
name: `_${name}`,
|
||||
data
|
||||
};
|
||||
this.events.push(option);
|
||||
this.call();
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
this[`_${name}`] && this[`_${name}`](data);
|
||||
// #endif
|
||||
},
|
||||
genId() {
|
||||
let result = '';
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
const charactersLength = characters.length;
|
||||
for (let i = 0; i < 30; i++) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
}
|
||||
this.editorCtx.setContents({
|
||||
html:sendStr
|
||||
})
|
||||
console.log('delete')
|
||||
},
|
||||
editorInput(e) {
|
||||
let str = e.detail.html;
|
||||
const oldArr = (this.lastStr ?? '').split("");
|
||||
let contentStr = str;
|
||||
oldArr.forEach((str) => {
|
||||
contentStr = contentStr.replace(str, "");
|
||||
});
|
||||
contentStr = html2Text(contentStr);
|
||||
this.$emit("input", e);
|
||||
this.lastStr = e.detail.html;
|
||||
return Date.now() + result;
|
||||
},
|
||||
// 开始拖拽地图
|
||||
UserEvent(data) {
|
||||
//console.log(data);
|
||||
this.$emit('onUserEvent',data);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script module="editorModule" lang="renderjs">
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
_editorIns:null
|
||||
}
|
||||
},
|
||||
};
|
||||
mounted() {
|
||||
this._initEditor();
|
||||
},
|
||||
methods: {
|
||||
_initEditor() {
|
||||
if (typeof window.wangEditor === 'function') {
|
||||
this._initialize();
|
||||
} else {
|
||||
const script = document.createElement('script');
|
||||
script.onload = this._initialize;
|
||||
script.src = "static/wangeditor/index.js";
|
||||
document.head.appendChild(script);
|
||||
|
||||
const link = document.createElement('link');
|
||||
link.href = "static/wangeditor/style.css";
|
||||
link.rel = "stylesheet";
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
},
|
||||
// 创建地图
|
||||
_initialize() {
|
||||
const _this = this;
|
||||
// 创建地图实例
|
||||
const {createEditor} = window.wangEditor
|
||||
|
||||
const editor = createEditor({
|
||||
selector: '#editor-container',
|
||||
html: '',
|
||||
config: {
|
||||
placeholder: '',
|
||||
maxLength:100,
|
||||
hoverbarKeys:{
|
||||
divider: {menuKeys: [],},
|
||||
link: {menuKeys: [],},
|
||||
image: {menuKeys: [],},
|
||||
pre: {menuKeys: [],},
|
||||
table: {menuKeys: [],},
|
||||
text: {menuKeys: [],},
|
||||
video: {menuKeys: [],},
|
||||
},
|
||||
onCreated(){
|
||||
_this.$ownerInstance.callMethod('UserEvent',{
|
||||
type:'ready'
|
||||
});
|
||||
},
|
||||
//onDestroyed(){},
|
||||
onFocus(){
|
||||
_this.$ownerInstance.callMethod('UserEvent',{
|
||||
type:'focus'
|
||||
});
|
||||
},
|
||||
onBlur(){
|
||||
_this.$ownerInstance.callMethod('UserEvent',{
|
||||
type:'blur'
|
||||
});
|
||||
},
|
||||
//onDestroyed(){},
|
||||
onChange(editor) {
|
||||
const html = editor.getHtml()
|
||||
const text = editor.getText()
|
||||
//console.log('editor content', html)
|
||||
// 也可以同步到 <textarea>
|
||||
_this.$ownerInstance.callMethod('UserEvent',{
|
||||
type:'onChange',
|
||||
html,
|
||||
text,
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
editor.on('atevent',()=>{
|
||||
_this.$ownerInstance.callMethod('UserEvent',{
|
||||
type:'atevent'
|
||||
});
|
||||
})
|
||||
this._editorIns = editor;
|
||||
console.log(editor.insertText);
|
||||
this._editorIns.insertText("text");
|
||||
},
|
||||
|
||||
_insertText(text){
|
||||
console.log('_insertText',text);
|
||||
this._editorIns.insertText(text);
|
||||
},
|
||||
_insertNode(node){
|
||||
this._editorIns.insertNode(node);
|
||||
},
|
||||
_insertImgEmoji(src){
|
||||
this._insertNode({
|
||||
type: 'image',
|
||||
style:{width:'30px',height:'30px'},
|
||||
src: src,
|
||||
children: [{text: ''}],
|
||||
});
|
||||
},
|
||||
_insertMention(data){
|
||||
this._insertNode({
|
||||
type: 'mention',
|
||||
username: data.username,
|
||||
userid: data.userid,
|
||||
children: [{
|
||||
text: ''
|
||||
}],
|
||||
});
|
||||
},
|
||||
_clear(){
|
||||
this._editorIns.clear();
|
||||
},
|
||||
_delete(){
|
||||
this._editorIns.deleteBackward();
|
||||
},
|
||||
_blur(){
|
||||
this._editorIns.blur();
|
||||
},
|
||||
_focus(){
|
||||
this._editorIns.focus();
|
||||
},
|
||||
_setHtml(html){
|
||||
this._editorIns.setHtml(html);
|
||||
},
|
||||
_getText(){
|
||||
return this._editorIns.getText();
|
||||
},
|
||||
_getHtml(){
|
||||
return this._editorIns.getHtml();
|
||||
},
|
||||
_getSelectionPosition(){
|
||||
return this._editorIns.getSelectionPosition();
|
||||
},
|
||||
_getParentNode(){
|
||||
return this._editorIns.getParentNode();
|
||||
},
|
||||
// 通过监听call来调用渲染层方法
|
||||
call(newValue, oldValue, ownerInstance, instance) {
|
||||
if(!newValue){
|
||||
return false;
|
||||
}
|
||||
if (this[newValue.name] && typeof this[newValue.name] === "function") {
|
||||
this[newValue.name](newValue.data);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.editor_wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#editor2 {
|
||||
background-color: #fff;
|
||||
min-height: 30px;
|
||||
max-height: 120px;
|
||||
height: auto;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
::v-deep.ql-editor {
|
||||
img {
|
||||
vertical-align: sub !important;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.canvas_container {
|
||||
position: fixed;
|
||||
bottom: -99px;
|
||||
z-index: -100;
|
||||
|
||||
&_name {
|
||||
max-width: 480rpx;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#atCanvas {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.convas_container_name {
|
||||
font-size: 16px !important;
|
||||
.custom_editor {
|
||||
::v-deep.w-e-text-container{
|
||||
background: transparent;
|
||||
|
||||
[data-slate-editor]{
|
||||
padding: 0;
|
||||
}
|
||||
p,
|
||||
span{
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
}
|
||||
p{
|
||||
white-space: pre-wrap; /* 保留空格 */
|
||||
margin: 0;
|
||||
}
|
||||
img{
|
||||
}
|
||||
span{
|
||||
}
|
||||
span[data-w-e-type="mention"]{
|
||||
background-color: #ccc;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user