Files
im/pages/conversation/chating/components/ChatingFooter/SimpleEditor.vue
T

229 lines
5.8 KiB
Vue
Raw Normal View History

2025-12-09 09:27:29 +08:00
<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;
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent", {
"type":"input",
text: this.textValue,
html: this.textValue // 简单编辑器,HTML 就是文本
2025-12-09 09:27:29 +08:00
});
},
onFocus(e) {
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent",{
type:"focus",
e
});
2025-12-09 09:27:29 +08:00
},
onBlur(e) {
// 保存光标位置
this.cursorPos = e.detail.cursor || this.textValue.length;
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent",{type:"blur", e});
2025-12-09 09:27:29 +08:00
},
// 插入文本(表情或普通文本)
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 事件
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent", {
type:"input",
html: newText,
text: newText,
2025-12-09 09:27:29 +08:00
});
// 使用 $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;
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent", {
type:"input",
text: "",
html: ""
2025-12-09 09:27:29 +08:00
});
},
// 删除(退格)
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 事件
2025-12-23 00:18:46 +08:00
this.$emit("onUserEvent", {
type:"input",
text: this.textValue,
html: this.textValue
2025-12-09 09:27:29 +08:00
});
// 使用 $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%;
2025-12-23 00:18:46 +08:00
min-height: 60rpx;
2025-12-09 09:27:29 +08:00
max-height: 120px;
2025-12-23 00:18:46 +08:00
line-height: 60rpx;
2025-12-09 09:27:29 +08:00
background-color: #fff;
2025-12-23 00:18:46 +08:00
font-size: 30rpx;
padding: 0 6rpx;
2025-12-09 09:27:29 +08:00
word-break: break-all;
box-sizing: border-box;
}
</style>