emoji
This commit is contained in:
@@ -0,0 +1,233 @@
|
||||
<template>
|
||||
<view class="simple_editor_wrap">
|
||||
<textarea
|
||||
class="simple_editor_textarea"
|
||||
:value="textValue"
|
||||
:placeholder="placeholder"
|
||||
:auto-height="true"
|
||||
:maxlength="maxlength"
|
||||
:show-confirm-bar="false"
|
||||
:hold-keyboard="true"
|
||||
:adjust-position="false"
|
||||
@input="onInput"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
:selection-start="cursorPos"
|
||||
:selection-end="cursorPos"
|
||||
:id="textareaId"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GraphemeSplitter from 'grapheme-splitter';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
maxlength: {
|
||||
type: Number,
|
||||
default: -1,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
textValue: "",
|
||||
cursorPos: -1, // 光标位置
|
||||
textareaId: "simple_editor_" + Date.now(), // 唯一ID
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler(newVal) {
|
||||
if (newVal !== this.textValue) {
|
||||
this.textValue = newVal;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onInput(e) {
|
||||
this.textValue = e.detail.value;
|
||||
// 更新光标位置
|
||||
this.cursorPos = e.detail.cursor || this.textValue.length;
|
||||
this.$emit("input", {
|
||||
detail: {
|
||||
value: this.textValue,
|
||||
text: this.textValue,
|
||||
html: this.textValue // 简单编辑器,HTML 就是文本
|
||||
}
|
||||
});
|
||||
},
|
||||
onFocus(e) {
|
||||
this.$emit("focus", e);
|
||||
},
|
||||
onBlur(e) {
|
||||
// 保存光标位置
|
||||
this.cursorPos = e.detail.cursor || this.textValue.length;
|
||||
this.$emit("blur", e);
|
||||
},
|
||||
// 插入文本(表情或普通文本)
|
||||
insertText(text, successFn, errFn) {
|
||||
try {
|
||||
// 获取当前光标位置(从 textarea 获取)
|
||||
this.getCursorPosition((currentPos) => {
|
||||
// 在光标位置插入文本
|
||||
const beforeText = this.textValue.substring(0, currentPos);
|
||||
const afterText = this.textValue.substring(currentPos);
|
||||
const newText = beforeText + text + afterText;
|
||||
|
||||
// 更新文本值
|
||||
this.textValue = newText;
|
||||
|
||||
// 更新光标位置(插入文本后)
|
||||
const newCursorPos = currentPos + text.length;
|
||||
this.cursorPos = newCursorPos;
|
||||
|
||||
// 触发 input 事件
|
||||
this.$emit("input", {
|
||||
detail: {
|
||||
value: newText,
|
||||
text: newText,
|
||||
html: newText
|
||||
}
|
||||
});
|
||||
|
||||
// 使用 $nextTick 确保 DOM 更新后再设置光标
|
||||
this.$nextTick(() => {
|
||||
// 设置 textarea 的光标位置
|
||||
this.setCursorPosition(newCursorPos);
|
||||
|
||||
if (successFn) {
|
||||
successFn.call(this, [{ success: true }]);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("插入文本失败", err);
|
||||
if (errFn) {
|
||||
errFn.call(this, [err]);
|
||||
}
|
||||
}
|
||||
},
|
||||
// 获取光标位置
|
||||
getCursorPosition(callback) {
|
||||
// 尝试通过查询获取光标位置
|
||||
// 如果无法获取,使用保存的位置或文本长度
|
||||
const pos = this.cursorPos >= 0 ? this.cursorPos : this.textValue.length;
|
||||
if (callback) {
|
||||
callback(pos);
|
||||
}
|
||||
return pos;
|
||||
},
|
||||
// 设置光标位置
|
||||
setCursorPosition(pos) {
|
||||
this.cursorPos = pos;
|
||||
// 尝试通过选择范围来设置光标
|
||||
// 注意:uni-app 的 textarea 可能不支持动态设置 selection-start/end
|
||||
// 所以这里主要是更新内部状态
|
||||
},
|
||||
// 清空内容
|
||||
clear() {
|
||||
this.textValue = "";
|
||||
this.cursorPos = 0;
|
||||
this.$emit("input", {
|
||||
detail: {
|
||||
value: "",
|
||||
text: "",
|
||||
html: ""
|
||||
}
|
||||
});
|
||||
},
|
||||
// 删除(退格)
|
||||
delete() {
|
||||
if (this.textValue.length === 0) return;
|
||||
|
||||
const currentPos = this.cursorPos >= 0 ? this.cursorPos : this.textValue.length;
|
||||
if (currentPos <= 0) return;
|
||||
|
||||
const splitter = new GraphemeSplitter();
|
||||
const graphemes = splitter.splitGraphemes(this.textValue);
|
||||
|
||||
let currentIndex = 0;
|
||||
let graphemeIndex = 0;
|
||||
|
||||
// 找到光标位置对应的字素簇
|
||||
for (; graphemeIndex < graphemes.length; graphemeIndex++) {
|
||||
currentIndex += graphemes[graphemeIndex].length;
|
||||
if (currentIndex >= currentPos) break;
|
||||
}
|
||||
|
||||
if (graphemeIndex > 0) {
|
||||
// 计算被删除的字符长度
|
||||
const deletedLength = graphemes[graphemeIndex - 1].length;
|
||||
|
||||
// 删除字素簇
|
||||
graphemes.splice(graphemeIndex - 1, 1);
|
||||
|
||||
// 更新文本
|
||||
this.textValue = graphemes.join('');
|
||||
|
||||
// 更新光标位置
|
||||
this.cursorPos = currentPos - deletedLength;
|
||||
|
||||
// 触发 input 事件
|
||||
this.$emit("input", {
|
||||
detail: {
|
||||
value: this.textValue,
|
||||
text: this.textValue,
|
||||
html: this.textValue
|
||||
}
|
||||
});
|
||||
|
||||
// 使用 $nextTick 确保 DOM 更新后再设置光标
|
||||
this.$nextTick(() => {
|
||||
this.setCursorPosition(this.cursorPos);
|
||||
});
|
||||
}
|
||||
},
|
||||
// 获取纯文本内容
|
||||
getText() {
|
||||
return this.textValue;
|
||||
},
|
||||
// 获取内容(兼容 editor 组件的接口)
|
||||
getContents(successFn) {
|
||||
if (successFn) {
|
||||
successFn({
|
||||
text: this.textValue,
|
||||
html: this.textValue,
|
||||
delta: null
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.simple_editor_wrap {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.simple_editor_textarea {
|
||||
width: 100%;
|
||||
min-height: 30px;
|
||||
max-height: 120px;
|
||||
background-color: #fff;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
padding: 4px;
|
||||
word-break: break-all;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user