fix: 修复错误滚动到底部的问题。删除字数显示,添加toast提示超过字数。输入框扩大按钮移动至左侧。删除侧边栏边框。
This commit is contained in:
parent
965514b7b4
commit
8b8f77cfcc
|
|
@ -229,7 +229,10 @@ defineExpose({
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
scrollToBottom(false);
|
// 只有当有消息时才滚动到底部,否则保持在顶部显示欢迎界面
|
||||||
|
if (visibleMessages.value.length > 0) {
|
||||||
|
scrollToBottom(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@
|
||||||
v-model="inputText"
|
v-model="inputText"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
:rows="1"
|
:rows="1"
|
||||||
|
@beforeinput="handleBeforeInput"
|
||||||
@input="autoResize"
|
@input="autoResize"
|
||||||
@focus="isFocused = true"
|
@focus="isFocused = true"
|
||||||
@blur="isFocused = false"
|
@blur="isFocused = false"
|
||||||
|
|
@ -90,6 +91,11 @@
|
||||||
<!-- 底部工具栏 -->
|
<!-- 底部工具栏 -->
|
||||||
<div class="input-toolbar">
|
<div class="input-toolbar">
|
||||||
<div class="toolbar-left">
|
<div class="toolbar-left">
|
||||||
|
<!-- 展开/收起 -->
|
||||||
|
<button class="toolbar-btn" title="展开输入框" @click="toggleExpand">
|
||||||
|
<Maximize2 v-if="!isExpanded" :size="16" />
|
||||||
|
<Minimize2 v-else :size="16" />
|
||||||
|
</button>
|
||||||
<!-- 深度思考开关 -->
|
<!-- 深度思考开关 -->
|
||||||
<button
|
<button
|
||||||
class="toolbar-btn"
|
class="toolbar-btn"
|
||||||
|
|
@ -126,23 +132,6 @@
|
||||||
<span>联网搜索</span>
|
<span>联网搜索</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- 展开/收起 -->
|
|
||||||
<button class="toolbar-btn" title="展开输入框" @click="toggleExpand">
|
|
||||||
<Maximize2 v-if="!isExpanded" :size="16" />
|
|
||||||
<Minimize2 v-else :size="16" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="toolbar-right">
|
|
||||||
<span
|
|
||||||
class="char-count"
|
|
||||||
:class="{ warning: charCount > maxChars * 0.9 }"
|
|
||||||
>
|
|
||||||
{{ charCount }} / {{ maxChars }}
|
|
||||||
</span>
|
|
||||||
<span class="send-hint">
|
|
||||||
{{ sendOnEnter ? "Enter 发送, Shift+Enter 换行" : "Ctrl+Enter 发送" }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -190,7 +179,7 @@ const props = withDefaults(
|
||||||
placeholder: "输入你的问题...",
|
placeholder: "输入你的问题...",
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
sendOnEnter: false,
|
sendOnEnter: false,
|
||||||
maxChars: 4000,
|
maxChars: 10,
|
||||||
disabled: false,
|
disabled: false,
|
||||||
// 默认全部支持
|
// 默认全部支持
|
||||||
supports_thinking: true,
|
supports_thinking: true,
|
||||||
|
|
@ -231,6 +220,18 @@ const textareaRef = ref<HTMLTextAreaElement | null>(null);
|
||||||
const fileInputRef = ref<HTMLInputElement | null>(null);
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
const imageInputRef = ref<HTMLInputElement | null>(null);
|
const imageInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
// toast 节流
|
||||||
|
let lastToastTime = 0;
|
||||||
|
const toastThrottleMs = 2000;
|
||||||
|
|
||||||
|
function showThrottledToast(message: string, type: 'error' = 'error') {
|
||||||
|
const now = Date.now();
|
||||||
|
if (now - lastToastTime >= toastThrottleMs) {
|
||||||
|
lastToastTime = now;
|
||||||
|
window.$toast?.(message, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const charCount = computed(() => inputText.value.length);
|
const charCount = computed(() => inputText.value.length);
|
||||||
const isUploading = computed(() => attachments.value.some((a) => a.uploading));
|
const isUploading = computed(() => attachments.value.some((a) => a.uploading));
|
||||||
|
|
@ -254,6 +255,27 @@ function autoResize() {
|
||||||
textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)+1}px`;
|
textarea.style.height = `${Math.min(textarea.scrollHeight, maxHeight)+1}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理输入前事件,限制字数
|
||||||
|
function handleBeforeInput(event: InputEvent) {
|
||||||
|
// 如果不是插入文本的操作(如删除、退格等),允许
|
||||||
|
if (!event.data) return;
|
||||||
|
|
||||||
|
// 检查输入后是否会超过限制
|
||||||
|
const currentLength = inputText.value.length;
|
||||||
|
const insertLength = event.data?.length || 0;
|
||||||
|
const selectionStart = (event.target as HTMLTextAreaElement).selectionStart || 0;
|
||||||
|
const selectionEnd = (event.target as HTMLTextAreaElement).selectionEnd || 0;
|
||||||
|
const selectedLength = selectionEnd - selectionStart;
|
||||||
|
|
||||||
|
// 计算输入后的长度
|
||||||
|
const newLength = currentLength - selectedLength + insertLength;
|
||||||
|
|
||||||
|
if (newLength > props.maxChars) {
|
||||||
|
event.preventDefault();
|
||||||
|
showThrottledToast(`已超${props.maxChars}字上限,请删除部分内容`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 处理键盘事件
|
// 处理键盘事件
|
||||||
function handleKeydown(event: KeyboardEvent) {
|
function handleKeydown(event: KeyboardEvent) {
|
||||||
// Ctrl+Enter 或 Cmd+Enter 发送
|
// Ctrl+Enter 或 Cmd+Enter 发送
|
||||||
|
|
@ -298,6 +320,22 @@ async function handlePaste(event: ClipboardEvent) {
|
||||||
const items = event.clipboardData?.items;
|
const items = event.clipboardData?.items;
|
||||||
if (!items) return;
|
if (!items) return;
|
||||||
|
|
||||||
|
// 检查粘贴文本是否会超过字数限制
|
||||||
|
const text = event.clipboardData?.getData('text');
|
||||||
|
if (text) {
|
||||||
|
const textarea = event.target as HTMLTextAreaElement;
|
||||||
|
const selectionStart = textarea.selectionStart || 0;
|
||||||
|
const selectionEnd = textarea.selectionEnd || 0;
|
||||||
|
const selectedLength = selectionEnd - selectionStart;
|
||||||
|
const newLength = inputText.value.length - selectedLength + text.length;
|
||||||
|
|
||||||
|
if (newLength > props.maxChars) {
|
||||||
|
event.preventDefault();
|
||||||
|
showThrottledToast(`已超${props.maxChars}字上限,请删除部分内容`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.type.startsWith("image/")) {
|
if (item.type.startsWith("image/")) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
@ -718,26 +756,6 @@ onMounted(() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-right {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.char-count {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
|
|
||||||
&.warning {
|
|
||||||
color: #f59e0b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-hint {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
|
|
|
||||||
|
|
@ -279,7 +279,6 @@ if (typeof window !== "undefined") {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
border-right: 1px solid #e2e8f0;
|
|
||||||
transition: width 0.3s ease;
|
transition: width 0.3s ease;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
@ -690,8 +689,6 @@ if (typeof window !== "undefined") {
|
||||||
cursor: col-resize;
|
cursor: col-resize;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: rgba(59, 130, 246, 0.3);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue