Merge remote-tracking branch 'origin/master' into refactor/provider-source

This commit is contained in:
Soulter
2025-12-19 15:48:42 +08:00
8 changed files with 290 additions and 198 deletions
+46 -49
View File
@@ -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;
+3 -3
View File
@@ -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>
+39 -3
View File
@@ -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": "按",