fix: 修复错误滚动到底部的问题。删除字数显示,添加toast提示超过字数。输入框扩大按钮移动至左侧。删除侧边栏边框。

This commit is contained in:
肖应宇 2026-03-12 10:24:22 +08:00
parent 965514b7b4
commit 8b8f77cfcc
3 changed files with 61 additions and 43 deletions

View File

@ -229,7 +229,10 @@ defineExpose({
}); });
onMounted(() => { onMounted(() => {
scrollToBottom(false); //
if (visibleMessages.value.length > 0) {
scrollToBottom(false);
}
}); });
</script> </script>

View File

@ -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% {

View File

@ -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>