Merge remote-tracking branch 'origin/master' into refactor/provider-source
This commit is contained in:
@@ -18,61 +18,27 @@
|
||||
@editTitle="showEditTitleDialog"
|
||||
@deleteConversation="handleDeleteConversation"
|
||||
@closeMobileSidebar="closeMobileSidebar"
|
||||
@toggleTheme="toggleTheme"
|
||||
@toggleFullscreen="toggleFullscreen"
|
||||
/>
|
||||
|
||||
<!-- 右侧聊天内容区域 -->
|
||||
<div class="chat-content-panel">
|
||||
|
||||
<div class="conversation-header fade-in">
|
||||
<div class="conversation-header fade-in" v-if="isMobile">
|
||||
<!-- 手机端菜单按钮 -->
|
||||
<v-btn icon class="mobile-menu-btn" @click="toggleMobileSidebar" v-if="isMobile" variant="text">
|
||||
<v-btn icon class="mobile-menu-btn" @click="toggleMobileSidebar" variant="text">
|
||||
<v-icon>mdi-menu</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<!-- <div v-if="currCid && getCurrentConversation">
|
||||
<h3
|
||||
style="max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
|
||||
{{ getCurrentConversation.title || tm('conversation.newConversation') }}</h3>
|
||||
<span style="font-size: 12px;">{{ formatDate(getCurrentConversation.updated_at) }}</span>
|
||||
</div> -->
|
||||
<div class="conversation-header-actions">
|
||||
<!-- router 推送到 /chatbox -->
|
||||
<v-tooltip :text="tm('actions.fullscreen')" v-if="!chatboxMode">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props"
|
||||
@click="router.push(currSessionId ? `/chatbox/${currSessionId}` : '/chatbox')"
|
||||
class="fullscreen-icon">mdi-fullscreen</v-icon>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<!-- 语言切换按钮 -->
|
||||
<v-tooltip :text="t('core.common.language')" v-if="chatboxMode">
|
||||
<template v-slot:activator="{ props }">
|
||||
<LanguageSwitcher variant="chatbox" />
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<!-- 主题切换按钮 -->
|
||||
<v-tooltip :text="isDark ? tm('modes.lightMode') : tm('modes.darkMode')" v-if="chatboxMode">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn v-bind="props" icon @click="toggleTheme" class="theme-toggle-icon"
|
||||
size="small" rounded="sm" style="margin-right: 8px;" variant="text">
|
||||
<v-icon>{{ isDark ? 'mdi-weather-night' : 'mdi-white-balance-sunny' }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<!-- router 推送到 /chat -->
|
||||
<v-tooltip :text="tm('actions.exitFullscreen')" v-if="chatboxMode">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon v-bind="props" @click="router.push(currSessionId ? `/chat/${currSessionId}` : '/chat')"
|
||||
class="fullscreen-icon">mdi-fullscreen-exit</v-icon>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MessageList v-if="messages && messages.length > 0" :messages="messages" :isDark="isDark"
|
||||
:isStreaming="isStreaming || isConvRunning" @openImagePreview="openImagePreview"
|
||||
@replyMessage="handleReplyMessage"
|
||||
ref="messageList" />
|
||||
<div class="message-list-wrapper" v-if="messages && messages.length > 0">
|
||||
<MessageList :messages="messages" :isDark="isDark"
|
||||
:isStreaming="isStreaming || isConvRunning" @openImagePreview="openImagePreview"
|
||||
@replyMessage="handleReplyMessage"
|
||||
ref="messageList" />
|
||||
<div class="message-list-fade" :class="{ 'fade-dark': isDark }"></div>
|
||||
</div>
|
||||
<div class="welcome-container fade-in" v-else>
|
||||
<div class="welcome-title">
|
||||
<span>Hello, I'm</span>
|
||||
@@ -260,6 +226,14 @@ function toggleTheme() {
|
||||
theme.global.name.value = newTheme;
|
||||
}
|
||||
|
||||
function toggleFullscreen() {
|
||||
if (props.chatboxMode) {
|
||||
router.push(currSessionId.value ? `/chat/${currSessionId.value}` : '/chat');
|
||||
} else {
|
||||
router.push(currSessionId.value ? `/chatbox/${currSessionId.value}` : '/chatbox');
|
||||
}
|
||||
}
|
||||
|
||||
function openImagePreview(imageUrl: string) {
|
||||
previewImageUrl.value = imageUrl;
|
||||
imagePreviewDialog.value = true;
|
||||
@@ -303,11 +277,14 @@ function clearReply() {
|
||||
async function handleSelectConversation(sessionIds: string[]) {
|
||||
if (!sessionIds[0]) return;
|
||||
|
||||
// 立即更新选中状态,避免需要点击两次
|
||||
currSessionId.value = sessionIds[0];
|
||||
selectedSessions.value = [sessionIds[0]];
|
||||
|
||||
// 更新 URL
|
||||
const basePath = props.chatboxMode ? '/chatbox' : '/chat';
|
||||
if (route.path !== `${basePath}/${sessionIds[0]}`) {
|
||||
router.push(`${basePath}/${sessionIds[0]}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 手机端关闭侧边栏
|
||||
@@ -317,9 +294,6 @@ async function handleSelectConversation(sessionIds: string[]) {
|
||||
|
||||
// 清除引用状态
|
||||
clearReply();
|
||||
|
||||
currSessionId.value = sessionIds[0];
|
||||
selectedSessions.value = [sessionIds[0]];
|
||||
|
||||
await getSessionMsg(sessionIds[0], router);
|
||||
|
||||
@@ -510,6 +484,29 @@ onBeforeUnmount(() => {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.message-list-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.message-list-fade {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40px;
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.message-list-fade.fade-dark {
|
||||
background: linear-gradient(to top, rgba(30, 30, 30, 1) 0%, rgba(30, 30, 30, 0) 100%);
|
||||
}
|
||||
|
||||
.conversation-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="input-area fade-in">
|
||||
<div class="input-container"
|
||||
style="width: 85%; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 24px;">
|
||||
style="width: 85%; max-width: 900px; margin: 0 auto; border: 1px solid #e0e0e0; border-radius: 24px; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.1);">
|
||||
<!-- 引用预览区 -->
|
||||
<div class="reply-preview" v-if="props.replyTo">
|
||||
<div class="reply-content">
|
||||
@@ -16,8 +16,8 @@
|
||||
@keydown="handleKeyDown"
|
||||
:disabled="disabled"
|
||||
placeholder="Ask AstrBot..."
|
||||
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 8px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 0px 12px;">
|
||||
style="width: 100%; resize: none; outline: none; border: 1px solid var(--v-theme-border); border-radius: 12px; padding: 12px 16px; min-height: 40px; font-family: inherit; font-size: 16px; background-color: var(--v-theme-surface);"></textarea>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 14px;">
|
||||
<div style="display: flex; justify-content: flex-start; margin-top: 4px; align-items: center; gap: 8px;">
|
||||
<ConfigSelector
|
||||
:session-id="sessionId || null"
|
||||
|
||||
@@ -5,21 +5,11 @@
|
||||
'mobile-sidebar-open': isMobile && mobileMenuOpen,
|
||||
'mobile-sidebar': isMobile
|
||||
}"
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f1f4f9' }"
|
||||
@mouseenter="handleSidebarMouseEnter"
|
||||
@mouseleave="handleSidebarMouseLeave">
|
||||
|
||||
<div style="display: flex; align-items: center; justify-content: center; padding: 16px; padding-bottom: 0px;"
|
||||
v-if="chatboxMode">
|
||||
<img width="50" src="@/assets/images/icon-no-shadow.svg" alt="AstrBot Logo">
|
||||
<span v-if="!sidebarCollapsed"
|
||||
style="font-weight: 1000; font-size: 26px; margin-left: 8px;">AstrBot</span>
|
||||
</div>
|
||||
:style="{ 'background-color': isDark ? sidebarCollapsed ? '#1e1e1e' : '#2d2d2d' : sidebarCollapsed ? '#ffffff' : '#f1f4f9' }">
|
||||
|
||||
<div class="sidebar-collapse-btn-container" v-if="!isMobile">
|
||||
<v-btn icon class="sidebar-collapse-btn" @click="toggleSidebar" variant="text" color="deep-purple">
|
||||
<v-icon>{{ (sidebarCollapsed || (!sidebarCollapsed && sidebarHoverExpanded)) ?
|
||||
'mdi-chevron-right' : 'mdi-chevron-left' }}</v-icon>
|
||||
<v-icon>{{ sidebarCollapsed ? 'mdi-chevron-right' : 'mdi-chevron-left' }}</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
@@ -30,19 +20,14 @@
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<div style="padding: 16px; padding-top: 8px;">
|
||||
<div style="padding: 8px; opacity: 0.6;">
|
||||
<v-btn block variant="text" class="new-chat-btn" @click="$emit('newChat')" :disabled="!currSessionId"
|
||||
v-if="!sidebarCollapsed || isMobile" prepend-icon="mdi-plus"
|
||||
style="background-color: transparent !important; border-radius: 4px;">{{ tm('actions.newChat') }}</v-btn>
|
||||
<v-btn icon="mdi-plus" rounded="lg" @click="$emit('newChat')" :disabled="!currSessionId"
|
||||
v-if="!sidebarCollapsed || isMobile" prepend-icon="mdi-square-edit-outline">{{ tm('actions.newChat') }}</v-btn>
|
||||
<v-btn icon="mdi-square-edit-outline" rounded="xl" @click="$emit('newChat')" :disabled="!currSessionId"
|
||||
v-if="sidebarCollapsed && !isMobile" elevation="0"></v-btn>
|
||||
</div>
|
||||
|
||||
<div v-if="!sidebarCollapsed || isMobile">
|
||||
<v-divider class="mx-4"></v-divider>
|
||||
</div>
|
||||
|
||||
<div style="overflow-y: auto; flex-grow: 1;" :class="{ 'fade-in': sidebarHoverExpanded }"
|
||||
<div style="overflow-y: auto; flex-grow: 1;"
|
||||
v-if="!sidebarCollapsed || isMobile">
|
||||
<v-card v-if="sessions.length > 0" flat style="background-color: transparent;">
|
||||
<v-list density="compact" nav class="conversation-list"
|
||||
@@ -53,15 +38,15 @@
|
||||
<v-list-item-title v-if="!sidebarCollapsed || isMobile" class="conversation-title">
|
||||
{{ item.display_name || tm('conversation.newConversation') }}
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle v-if="!sidebarCollapsed || isMobile" class="timestamp">
|
||||
<!-- <v-list-item-subtitle v-if="!sidebarCollapsed || isMobile" class="timestamp">
|
||||
{{ new Date(item.updated_at).toLocaleString() }}
|
||||
</v-list-item-subtitle>
|
||||
</v-list-item-subtitle> -->
|
||||
|
||||
<template v-if="!sidebarCollapsed || isMobile" v-slot:append>
|
||||
<div class="conversation-actions">
|
||||
<v-btn icon="mdi-pencil" size="x-small" variant="text"
|
||||
class="edit-title-btn"
|
||||
@click.stop="$emit('editTitle', item.session_id, item.display_name)" />
|
||||
@click.stop="$emit('editTitle', item.session_id, item.display_name ?? '')" />
|
||||
<v-btn icon="mdi-delete" size="x-small" variant="text"
|
||||
class="delete-conversation-btn" color="error"
|
||||
@click.stop="handleDeleteConversation(item)" />
|
||||
@@ -74,19 +59,71 @@
|
||||
<v-fade-transition>
|
||||
<div class="no-conversations" v-if="sessions.length === 0">
|
||||
<v-icon icon="mdi-message-text-outline" size="large" color="grey-lighten-1"></v-icon>
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || sidebarHoverExpanded || isMobile">
|
||||
<div class="no-conversations-text" v-if="!sidebarCollapsed || isMobile">
|
||||
{{ tm('conversation.noHistory') }}
|
||||
</div>
|
||||
</div>
|
||||
</v-fade-transition>
|
||||
</div>
|
||||
|
||||
<!-- 收起时的占位元素 -->
|
||||
<div class="sidebar-spacer" v-if="sidebarCollapsed && !isMobile"></div>
|
||||
|
||||
<!-- 底部设置按钮 -->
|
||||
<div class="sidebar-footer">
|
||||
<StyledMenu location="top" :close-on-content-click="false">
|
||||
<template v-slot:activator="{ props: menuProps }">
|
||||
<v-btn
|
||||
v-bind="menuProps"
|
||||
:icon="sidebarCollapsed && !isMobile"
|
||||
:block="!sidebarCollapsed || isMobile"
|
||||
variant="text"
|
||||
class="settings-btn"
|
||||
:class="{ 'settings-btn-collapsed': sidebarCollapsed && !isMobile }"
|
||||
:prepend-icon="(!sidebarCollapsed || isMobile) ? 'mdi-cog-outline' : undefined"
|
||||
>
|
||||
<v-icon v-if="sidebarCollapsed && !isMobile">mdi-cog-outline</v-icon>
|
||||
<template v-if="!sidebarCollapsed || isMobile">{{ t('core.common.settings') }}</template>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<!-- 语言切换 -->
|
||||
<v-list-item class="styled-menu-item">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>mdi-translate</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ t('core.common.language') }}</v-list-item-title>
|
||||
<template v-slot:append>
|
||||
<LanguageSwitcher variant="chatbox" />
|
||||
</template>
|
||||
</v-list-item>
|
||||
|
||||
<!-- 主题切换 -->
|
||||
<v-list-item class="styled-menu-item" @click="$emit('toggleTheme')">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>{{ isDark ? 'mdi-weather-night' : 'mdi-white-balance-sunny' }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ isDark ? tm('modes.lightMode') : tm('modes.darkMode') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<!-- 全屏/退出全屏 -->
|
||||
<v-list-item class="styled-menu-item" @click="$emit('toggleFullscreen')">
|
||||
<template v-slot:prepend>
|
||||
<v-icon>{{ chatboxMode ? 'mdi-fullscreen-exit' : 'mdi-fullscreen' }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ chatboxMode ? tm('actions.exitFullscreen') : tm('actions.fullscreen') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</StyledMenu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { useModuleI18n } from '@/i18n/composables';
|
||||
import { useI18n, useModuleI18n } from '@/i18n/composables';
|
||||
import type { Session } from '@/composables/useSessions';
|
||||
import LanguageSwitcher from '@/components/shared/LanguageSwitcher.vue';
|
||||
import StyledMenu from '@/components/shared/StyledMenu.vue';
|
||||
|
||||
interface Props {
|
||||
sessions: Session[];
|
||||
@@ -106,15 +143,14 @@ const emit = defineEmits<{
|
||||
editTitle: [sessionId: string, title: string];
|
||||
deleteConversation: [sessionId: string];
|
||||
closeMobileSidebar: [];
|
||||
toggleTheme: [];
|
||||
toggleFullscreen: [];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const { tm } = useModuleI18n('features/chat');
|
||||
|
||||
const sidebarCollapsed = ref(true);
|
||||
const sidebarHovered = ref(false);
|
||||
const sidebarHoverTimer = ref<number | null>(null);
|
||||
const sidebarHoverExpanded = ref(false);
|
||||
const sidebarHoverDelay = 100;
|
||||
|
||||
// 从 localStorage 读取侧边栏折叠状态
|
||||
const savedCollapsedState = localStorage.getItem('sidebarCollapsed');
|
||||
@@ -125,40 +161,10 @@ if (savedCollapsedState !== null) {
|
||||
}
|
||||
|
||||
function toggleSidebar() {
|
||||
if (sidebarHoverExpanded.value) {
|
||||
sidebarHoverExpanded.value = false;
|
||||
return;
|
||||
}
|
||||
sidebarCollapsed.value = !sidebarCollapsed.value;
|
||||
localStorage.setItem('sidebarCollapsed', JSON.stringify(sidebarCollapsed.value));
|
||||
}
|
||||
|
||||
function handleSidebarMouseEnter() {
|
||||
if (!sidebarCollapsed.value || props.isMobile) return;
|
||||
|
||||
sidebarHovered.value = true;
|
||||
sidebarHoverTimer.value = window.setTimeout(() => {
|
||||
if (sidebarHovered.value) {
|
||||
sidebarHoverExpanded.value = true;
|
||||
sidebarCollapsed.value = false;
|
||||
}
|
||||
}, sidebarHoverDelay);
|
||||
}
|
||||
|
||||
function handleSidebarMouseLeave() {
|
||||
sidebarHovered.value = false;
|
||||
|
||||
if (sidebarHoverTimer.value) {
|
||||
clearTimeout(sidebarHoverTimer.value);
|
||||
sidebarHoverTimer.value = null;
|
||||
}
|
||||
|
||||
if (sidebarHoverExpanded.value) {
|
||||
sidebarCollapsed.value = true;
|
||||
}
|
||||
sidebarHoverExpanded.value = false;
|
||||
}
|
||||
|
||||
function handleDeleteConversation(session: Session) {
|
||||
const sessionTitle = session.display_name || tm('conversation.newConversation');
|
||||
const message = tm('conversation.confirmDelete', { name: sessionTitle });
|
||||
@@ -184,8 +190,8 @@ function handleDeleteConversation(session: Session) {
|
||||
}
|
||||
|
||||
.sidebar-collapsed {
|
||||
max-width: 75px;
|
||||
min-width: 75px;
|
||||
max-width: 60px;
|
||||
min-width: 60px;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
@@ -206,7 +212,7 @@ function handleDeleteConversation(session: Session) {
|
||||
}
|
||||
|
||||
.sidebar-collapse-btn-container {
|
||||
margin: 16px;
|
||||
margin: 8px;
|
||||
margin-bottom: 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
@@ -218,13 +224,19 @@ function handleDeleteConversation(session: Session) {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
margin-bottom: 4px;
|
||||
border-radius: 8px !important;
|
||||
transition: all 0.2s ease;
|
||||
height: auto !important;
|
||||
min-height: 56px;
|
||||
.new-chat-btn {
|
||||
justify-content: flex-start;
|
||||
background-color: transparent !important;
|
||||
border-radius: 20px;
|
||||
padding: 8px 16px !important;
|
||||
}
|
||||
|
||||
.conversation-item {
|
||||
/* margin-bottom: 4px; */
|
||||
border-radius: 20px !important;
|
||||
height: auto !important;
|
||||
/* min-height: 56px; */
|
||||
padding: 0px 16px !important;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -287,17 +299,31 @@ function handleDeleteConversation(session: Session) {
|
||||
transition: opacity 0.25s ease;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeInContent 0.3s ease;
|
||||
.sidebar-spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@keyframes fadeInContent {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
.sidebar-footer {
|
||||
padding: 8px 8px;
|
||||
padding-bottom: 16px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.settings-btn {
|
||||
opacity: 0.6;
|
||||
justify-content: flex-start;
|
||||
padding: 8px 16px !important;
|
||||
border-radius: 20px !important;
|
||||
}
|
||||
|
||||
.settings-btn:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-btn-collapsed {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -773,11 +773,40 @@ export default {
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.messages-container {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.message-list {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
padding: 2px 8px;
|
||||
padding: 2px 12px;
|
||||
}
|
||||
|
||||
.bot-message {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bot-message-content {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.bot-bubble {
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.bot-avatar {
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -945,6 +974,7 @@ export default {
|
||||
color: var(--v-theme-primaryText);
|
||||
font-size: 15px;
|
||||
max-width: 100%;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
.user-avatar,
|
||||
@@ -1097,7 +1127,7 @@ export default {
|
||||
margin-bottom: 12px;
|
||||
margin-top: 6px;
|
||||
border: 1px solid var(--v-theme-border);
|
||||
border-radius: 8px;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
width: fit-content;
|
||||
}
|
||||
@@ -1113,7 +1143,7 @@ export default {
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: background-color 0.2s ease;
|
||||
border-radius: 8px;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.reasoning-header:hover {
|
||||
@@ -1366,6 +1396,7 @@ export default {
|
||||
.markdown-content p {
|
||||
margin-top: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 15.5px;
|
||||
}
|
||||
|
||||
.markdown-content pre {
|
||||
@@ -1377,6 +1408,11 @@ export default {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.markdown-content hr {
|
||||
margin: 12px 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.markdown-content code {
|
||||
background-color: rgb(var(--v-theme-codeBg));
|
||||
padding: 2px 4px;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<v-menu offset="12" location="bottom center">
|
||||
<StyledMenu offset="12" location="bottom center">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-btn
|
||||
v-bind="activatorProps"
|
||||
@@ -22,25 +22,21 @@
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card class="language-dropdown" elevation="8" rounded="lg">
|
||||
<v-list density="compact" class="pa-1">
|
||||
<v-list-item
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
@click="changeLanguage(lang.code)"
|
||||
:class="{ 'v-list-item--active': currentLocale === lang.code, 'language-item-selected': currentLocale === lang.code }"
|
||||
class="language-item"
|
||||
rounded="md"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<span class="language-flag">{{ lang.flag }}</span>
|
||||
</template>
|
||||
<v-list-item-title>{{ lang.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<v-list-item
|
||||
v-for="lang in languages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
@click="changeLanguage(lang.code)"
|
||||
:class="{ 'styled-menu-item-active': currentLocale === lang.code }"
|
||||
class="styled-menu-item"
|
||||
rounded="md"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<span class="language-flag">{{ lang.flag }}</span>
|
||||
</template>
|
||||
<v-list-item-title>{{ lang.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</StyledMenu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -48,6 +44,7 @@ import { computed } from 'vue'
|
||||
import { useI18n, useLanguageSwitcher } from '@/i18n/composables'
|
||||
import { useCustomizerStore } from '@/stores/customizer'
|
||||
import type { Locale } from '@/i18n/types'
|
||||
import StyledMenu from '@/components/shared/StyledMenu.vue'
|
||||
|
||||
// 定义props来控制样式变体
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -110,49 +107,4 @@ const changeLanguage = async (langCode: string) => {
|
||||
:deep(.v-theme--PurpleThemeDark) .language-switcher--default:hover {
|
||||
background: rgba(114, 46, 209, 0.12) !important;
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
min-width: 100px;
|
||||
width: fit-content;
|
||||
border: 1px solid rgba(94, 53, 177, 0.15) !important;
|
||||
background: #f8f6fc !important;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 深色模式下的下拉框样式 */
|
||||
:deep(.v-theme--PurpleThemeDark) .language-dropdown {
|
||||
background: #2a2733 !important;
|
||||
border: 1px solid rgba(110, 60, 180, 0.692) !important;
|
||||
}
|
||||
|
||||
.language-item {
|
||||
margin: 2px 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.language-item:hover {
|
||||
background: rgba(94, 53, 177, 0.08) !important;
|
||||
}
|
||||
|
||||
.language-item-selected {
|
||||
background: rgba(94, 53, 177, 0.15) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.language-item-selected:hover {
|
||||
background: rgba(94, 53, 177, 0.2) !important;
|
||||
}
|
||||
|
||||
/* 深色模式下的列表项悬停效果 */
|
||||
:deep(.v-theme--PurpleThemeDark) .language-item:hover {
|
||||
background: rgba(114, 46, 209, 0.12) !important;
|
||||
}
|
||||
|
||||
:deep(.v-theme--PurpleThemeDark) .language-item-selected {
|
||||
background: rgba(114, 46, 209, 0.2) !important;
|
||||
}
|
||||
|
||||
:deep(.v-theme--PurpleThemeDark) .language-item-selected:hover {
|
||||
background: rgba(114, 46, 209, 0.25) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<v-menu v-bind="$attrs" :close-on-content-click="closeOnContentClick">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<slot name="activator" :props="activatorProps"></slot>
|
||||
</template>
|
||||
|
||||
<v-card class="styled-menu-card" elevation="8" rounded="lg">
|
||||
<v-list density="compact" class="styled-menu-list pa-1">
|
||||
<slot></slot>
|
||||
</v-list>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
})
|
||||
|
||||
withDefaults(defineProps<{
|
||||
closeOnContentClick?: boolean
|
||||
}>(), {
|
||||
closeOnContentClick: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.styled-menu-card {
|
||||
min-width: 100px;
|
||||
width: fit-content;
|
||||
border: 1px solid rgba(94, 53, 177, 0.15) !important;
|
||||
background: #f8f6fc !important;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.styled-menu-list {
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
:deep(.styled-menu-item) {
|
||||
margin: 2px 0;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
:deep(.styled-menu-item:hover) {
|
||||
background: rgba(94, 53, 177, 0.08) !important;
|
||||
}
|
||||
|
||||
:deep(.styled-menu-item-active) {
|
||||
background: rgba(94, 53, 177, 0.15) !important;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
:deep(.styled-menu-item-active:hover) {
|
||||
background: rgba(94, 53, 177, 0.2) !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* 深色模式下的下拉框样式 - 需要全局样式才能检测主题 */
|
||||
.v-theme--PurpleThemeDark .styled-menu-card {
|
||||
background: #2a2733 !important;
|
||||
border: 1px solid rgba(110, 60, 180, 0.692) !important;
|
||||
}
|
||||
|
||||
/* 深色模式下的列表项悬停效果 */
|
||||
.v-theme--PurpleThemeDark .styled-menu-item:hover {
|
||||
background: rgba(114, 46, 209, 0.12) !important;
|
||||
}
|
||||
|
||||
.v-theme--PurpleThemeDark .styled-menu-item-active {
|
||||
background: rgba(114, 46, 209, 0.2) !important;
|
||||
}
|
||||
|
||||
.v-theme--PurpleThemeDark .styled-menu-item-active:hover {
|
||||
background: rgba(114, 46, 209, 0.25) !important;
|
||||
}
|
||||
</style>
|
||||
@@ -27,6 +27,7 @@
|
||||
"uninstall": "Uninstall",
|
||||
"update": "Update",
|
||||
"language": "Language",
|
||||
"settings": "Settings",
|
||||
"locale": "en-US",
|
||||
"type": "Type",
|
||||
"press": "Press",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"uninstall": "卸载",
|
||||
"update": "更新",
|
||||
"language": "语言",
|
||||
"settings": "设置",
|
||||
"locale": "zh-CN",
|
||||
"type": "输入",
|
||||
"press": "按",
|
||||
|
||||
Reference in New Issue
Block a user