mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-11 02:12:53 +00:00
perf: 优化输入框工具栏
This commit is contained in:
parent
63a792d169
commit
8f3e250073
@ -24,6 +24,29 @@
|
||||
</EPopover>
|
||||
</div>
|
||||
|
||||
<!-- 工具栏 -->
|
||||
<div class="chat-input-toolbar">
|
||||
<EPopover
|
||||
ref="toolbarRef"
|
||||
v-model="selectedText"
|
||||
:visibleArrow="false"
|
||||
transition=""
|
||||
placement="top-start"
|
||||
popperClass="chat-input-toolbar-popover">
|
||||
<div slot="reference"></div>
|
||||
<ul class="chat-input-toolbar-menu">
|
||||
<li
|
||||
v-for="(item, index) in tools"
|
||||
:key="index"
|
||||
:data-label="item.label"
|
||||
:data-type="item.type"
|
||||
v-touchmouse="onMenu">
|
||||
<i class="taskfont" v-html="item.icon"></i>
|
||||
</li>
|
||||
</ul>
|
||||
</EPopover>
|
||||
</div>
|
||||
|
||||
<div ref="inputWrapper" class="chat-input-wrapper">
|
||||
<!-- 回复、修改 -->
|
||||
<div v-if="quoteData" class="chat-quote">
|
||||
@ -275,16 +298,18 @@
|
||||
footer-hide
|
||||
fullscreen>
|
||||
<div class="chat-input-box" :style="chatInputBoxStyle">
|
||||
<!-- 输入区域 -->
|
||||
<div class="chat-input-wrapper">
|
||||
<div ref="editorFull" class="no-dark-content"></div>
|
||||
</div>
|
||||
<ul class="chat-input-menu" :class="{activation: fullSelection.length > 0}">
|
||||
<!-- 工具栏 -->
|
||||
<ul class="chat-input-menu" :class="{activation: fullSelected}">
|
||||
<li
|
||||
v-for="(item, index) in fullTools"
|
||||
v-for="(item, index) in tools"
|
||||
:key="index"
|
||||
@touchstart.prevent=""
|
||||
@touchend.prevent="onFullMenu(item.label, item.type)"
|
||||
@click="onFullMenu(item.label, item.type)">
|
||||
:data-label="item.label"
|
||||
:data-type="item.type"
|
||||
v-touchmouse="onMenu">
|
||||
<i class="taskfont" v-html="item.icon"></i>
|
||||
</li>
|
||||
</ul>
|
||||
@ -299,6 +324,7 @@ import {mapGetters, mapState} from "vuex";
|
||||
import Quill from 'quill-hi';
|
||||
import {Delta} from "quill-hi/core";
|
||||
import "quill-mention-hi";
|
||||
import "./selection-plugin";
|
||||
import ChatEmoji from "./emoji";
|
||||
import touchmouse from "../../../../directives/touchmouse";
|
||||
import touchclick from "../../../../directives/touchclick";
|
||||
@ -442,11 +468,14 @@ export default {
|
||||
moreTimer: null,
|
||||
selectTimer: null,
|
||||
selectRange: null,
|
||||
selectedText: false,
|
||||
|
||||
fullInput: false,
|
||||
fullQuill: null,
|
||||
fullSelection: {index: 0, length: 0},
|
||||
fullTools: [
|
||||
fullSelected: false,
|
||||
fullSelection: null,
|
||||
|
||||
tools: [
|
||||
{
|
||||
label: 'bold',
|
||||
type: '',
|
||||
@ -937,7 +966,7 @@ export default {
|
||||
readOnly: false,
|
||||
placeholder: this.placeholder,
|
||||
modules: {
|
||||
toolbar: this.$isEEUIApp || this.windowTouch ? false : this.toolbar,
|
||||
toolbar: false,
|
||||
keyboard: this.simpleMode ? {} : {
|
||||
bindings: {
|
||||
'short enter': {
|
||||
@ -975,6 +1004,17 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
selectionPlugin: {
|
||||
onTextSelected: (selectedText) => {
|
||||
if (this.$isEEUIApp || this.windowTouch) {
|
||||
return
|
||||
}
|
||||
this.selectedText = !!selectedText.trim()
|
||||
},
|
||||
onSelectionCleared: () => {
|
||||
this.selectedText = false
|
||||
}
|
||||
},
|
||||
mention: this.quillMention()
|
||||
}
|
||||
}, this.options)
|
||||
@ -1786,6 +1826,14 @@ export default {
|
||||
placeholder: this.placeholder,
|
||||
modules: {
|
||||
toolbar: false,
|
||||
selectionPlugin: {
|
||||
onTextSelected: (selectedText) => {
|
||||
this.fullSelected = !!selectedText.trim()
|
||||
},
|
||||
onSelectionCleared: () => {
|
||||
this.fullSelected = false
|
||||
}
|
||||
},
|
||||
mention: this.quillMention()
|
||||
}
|
||||
}, this.options))
|
||||
@ -1800,9 +1848,6 @@ export default {
|
||||
}, 100)
|
||||
}
|
||||
})
|
||||
this.fullQuill.on('text-change', _ => {
|
||||
this.fullSelection = this.fullQuill.getSelection()
|
||||
})
|
||||
this.fullQuill.enable(true)
|
||||
this.$refs.editorFull.firstChild.innerHTML = this.$refs.editor.firstChild.innerHTML
|
||||
this.$nextTick(_ => {
|
||||
@ -1822,31 +1867,36 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
onFullMenu(action, type) {
|
||||
const {length} = this.fullQuill.getSelection(true);
|
||||
onMenu(action, _, el) {
|
||||
if (action !== 'up') {
|
||||
return;
|
||||
}
|
||||
const quill = this.getEditor();
|
||||
const {length} = quill.getSelection(true);
|
||||
if (length === 0) {
|
||||
$A.messageWarning("请选择文字后再操作")
|
||||
return
|
||||
}
|
||||
switch (action) {
|
||||
const label = el.getAttribute('data-label');
|
||||
switch (label) {
|
||||
case 'bold':
|
||||
this.fullQuill.format('bold', !this.fullQuill.getFormat().bold);
|
||||
quill.format('bold', !quill.getFormat().bold);
|
||||
break;
|
||||
case 'strike':
|
||||
this.fullQuill.format('strike', !this.fullQuill.getFormat().strike);
|
||||
quill.format('strike', !quill.getFormat().strike);
|
||||
break;
|
||||
case 'italic':
|
||||
this.fullQuill.format('italic', !this.fullQuill.getFormat().italic);
|
||||
quill.format('italic', !quill.getFormat().italic);
|
||||
break;
|
||||
case 'underline':
|
||||
this.fullQuill.format('underline', !this.fullQuill.getFormat().underline);
|
||||
quill.format('underline', !quill.getFormat().underline);
|
||||
break;
|
||||
case 'blockquote':
|
||||
this.fullQuill.format('blockquote', !this.fullQuill.getFormat().blockquote);
|
||||
quill.format('blockquote', !quill.getFormat().blockquote);
|
||||
break;
|
||||
case 'link':
|
||||
if (this.fullQuill.getFormat().link) {
|
||||
this.fullQuill.format('link', false);
|
||||
if (quill.getFormat().link) {
|
||||
quill.format('link', false);
|
||||
return
|
||||
}
|
||||
$A.modalInput({
|
||||
@ -1856,12 +1906,13 @@ export default {
|
||||
if (!link) {
|
||||
return false;
|
||||
}
|
||||
this.fullQuill.format('link', link);
|
||||
quill.format('link', link);
|
||||
}
|
||||
})
|
||||
break;
|
||||
case 'list':
|
||||
this.fullQuill.format('list', this.fullQuill.getFormat().list === type ? false : type);
|
||||
const type = el.getAttribute('data-type') || '';
|
||||
quill.format('list', quill.getFormat().list === type ? false : type);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
128
resources/assets/js/pages/manage/components/ChatInput/selection-plugin.js
vendored
Normal file
128
resources/assets/js/pages/manage/components/ChatInput/selection-plugin.js
vendored
Normal file
@ -0,0 +1,128 @@
|
||||
// selection-plugin.js - 独立的 Quill 插件文件
|
||||
import Quill from 'quill-hi';
|
||||
import Emitter from "quill-hi/core/emitter";
|
||||
|
||||
// 通过 Quill.import 导入需要的核心模块
|
||||
const Module = Quill.import('core/module');
|
||||
|
||||
class SelectionPlugin extends Module {
|
||||
static DEFAULTS = {
|
||||
immediate: true,
|
||||
minLength: 1,
|
||||
onTextSelected: null,
|
||||
onSelectionCleared: null,
|
||||
onSelectionChange: null
|
||||
};
|
||||
|
||||
constructor(quill, options = {}) {
|
||||
super(quill, { ...SelectionPlugin.DEFAULTS, ...options });
|
||||
|
||||
this.lastRange = null;
|
||||
this.debounceTimer = null;
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// 监听选择变化事件
|
||||
this.quill.on(Emitter.events.SELECTION_CHANGE, (range, oldRange, source) => {
|
||||
this.handleSelectionChange(range, oldRange, source);
|
||||
});
|
||||
|
||||
// 监听文本变化事件,处理输入替换选中文本的情况
|
||||
this.quill.on(Emitter.events.TEXT_CHANGE, (delta, oldDelta, source) => {
|
||||
// 延迟检查,确保选择状态已更新
|
||||
setTimeout(() => {
|
||||
const currentRange = this.quill.getSelection();
|
||||
this.handleSelectionChange(currentRange, this.lastRange, source);
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
|
||||
handleSelectionChange(range, oldRange, source) {
|
||||
// 防抖处理
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
}
|
||||
|
||||
this.debounceTimer = setTimeout(() => {
|
||||
this.processSelectionChange(range, oldRange, source);
|
||||
this.lastRange = range;
|
||||
this.debounceTimer = null;
|
||||
}, this.options.immediate ? 0 : 100);
|
||||
}
|
||||
|
||||
processSelectionChange(range, oldRange, source) {
|
||||
// 通用选择变化回调
|
||||
if (this.options.onSelectionChange) {
|
||||
this.options.onSelectionChange(range, oldRange, source);
|
||||
}
|
||||
|
||||
// 处理文本被选中的情况
|
||||
if (range && range.length >= this.options.minLength) {
|
||||
const selectedText = this.quill.getText(range.index, range.length);
|
||||
|
||||
if (this.options.onTextSelected) {
|
||||
this.options.onTextSelected(selectedText, range, source);
|
||||
}
|
||||
|
||||
// 发射自定义事件
|
||||
this.quill.emitter.emit('text-selected', {
|
||||
text: selectedText,
|
||||
range: range,
|
||||
source: source
|
||||
});
|
||||
}
|
||||
// 处理选择被清除的情况
|
||||
else if ((!range || range.length === 0) && oldRange && oldRange.length > 0) {
|
||||
if (this.options.onSelectionCleared) {
|
||||
this.options.onSelectionCleared(oldRange, source);
|
||||
}
|
||||
|
||||
// 发射自定义事件
|
||||
this.quill.emitter.emit('selection-cleared', {
|
||||
previousRange: oldRange,
|
||||
source: source
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 公共 API 方法
|
||||
getSelectedText() {
|
||||
const range = this.quill.getSelection();
|
||||
if (range && range.length > 0) {
|
||||
return this.quill.getText(range.index, range.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
hasSelection() {
|
||||
const range = this.quill.getSelection();
|
||||
return !!(range && range.length > 0);
|
||||
}
|
||||
|
||||
selectText(index, length) {
|
||||
this.quill.setSelection(index, length, Emitter.sources.API);
|
||||
}
|
||||
|
||||
clearSelection() {
|
||||
const range = this.quill.getSelection();
|
||||
if (range) {
|
||||
this.quill.setSelection(range.index, 0, Emitter.sources.API);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理资源
|
||||
destroy() {
|
||||
if (this.debounceTimer) {
|
||||
clearTimeout(this.debounceTimer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 注册插件
|
||||
Quill.register('modules/selectionPlugin', SelectionPlugin);
|
||||
|
||||
// 导出给模块系统使用(可选)
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = SelectionPlugin;
|
||||
}
|
||||
@ -81,6 +81,16 @@
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.chat-input-toolbar {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 24px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.chat-input-wrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
@ -253,23 +263,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ql-bubble {
|
||||
.ql-tooltip {
|
||||
z-index: 1;
|
||||
button {
|
||||
&.ql-active {
|
||||
position: relative;
|
||||
background: #3d3d3d;
|
||||
border-radius: 6px;
|
||||
}
|
||||
}
|
||||
.ql-formats {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-space {
|
||||
float: right;
|
||||
width: 170px;
|
||||
@ -726,7 +719,37 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-input-toolbar-popover {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
box-shadow: none;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
.chat-input-toolbar-menu {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> li {
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
> i {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
> i {
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
}
|
||||
}
|
||||
.chat-input-more-popover {
|
||||
min-width: 100px;
|
||||
padding: 8px;
|
||||
@ -1045,6 +1068,11 @@
|
||||
> li {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
> i {
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
&:active {
|
||||
background-color: #eee;
|
||||
}
|
||||
@ -1065,6 +1093,7 @@
|
||||
> i {
|
||||
color: #555;
|
||||
font-size: 16px;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
@media screen and (max-width: 320px) {
|
||||
height: 52px;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user