ForcePilot/web/src/layouts/AppLayout.vue

947 lines
22 KiB
Vue
Raw Normal View History

2024-07-14 16:42:38 +08:00
<script setup>
import { ref, reactive, onMounted, computed, provide, watch } from 'vue'
import { RouterLink, RouterView, useRoute, useRouter } from 'vue-router'
import { GithubOutlined } from '@ant-design/icons-vue'
import {
LibraryBig,
BarChart3,
ClipboardList,
Blocks,
Box,
FolderKanban,
PanelLeftClose,
PanelLeftOpen,
MessageCirclePlus
} from 'lucide-vue-next'
2024-07-27 14:32:57 +08:00
import { useConfigStore } from '@/stores/config'
import { useAgentStore } from '@/stores/agent'
import { useChatThreadsStore } from '@/stores/chatThreads'
2026-05-06 13:22:20 +08:00
import { useChatUIStore } from '@/stores/chatUI'
import { useDatabaseStore } from '@/stores/database'
import { useInfoStore } from '@/stores/info'
import { useTaskerStore } from '@/stores/tasker'
import { useUserStore } from '@/stores/user'
import { storeToRefs } from 'pinia'
2025-05-02 23:56:59 +08:00
import UserInfoComponent from '@/components/UserInfoComponent.vue'
import DebugComponent from '@/components/DebugComponent.vue'
import TaskCenterDrawer from '@/components/TaskCenterDrawer.vue'
import SettingsModal from '@/components/SettingsModal.vue'
import ConversationNavSection from '@/components/ConversationNavSection.vue'
const configStore = useConfigStore()
const agentStore = useAgentStore()
const chatThreadsStore = useChatThreadsStore()
2026-05-06 13:22:20 +08:00
const chatUIStore = useChatUIStore()
const databaseStore = useDatabaseStore()
const infoStore = useInfoStore()
const taskerStore = useTaskerStore()
const userStore = useUserStore()
const { activeCount: activeCountRef, isDrawerOpen } = storeToRefs(taskerStore)
const { threads, currentThreadId, hasMoreThreads, isLoadingMoreThreads } =
storeToRefs(chatThreadsStore)
2024-09-24 11:58:28 +08:00
const layoutSettings = reactive({
showDebug: false,
useTopBar: false // 是否使用顶栏
2024-09-24 11:58:28 +08:00
})
2024-08-25 20:29:24 +08:00
2025-02-26 12:37:06 +08:00
// Add state for GitHub stars
const githubStars = ref(0)
const isLoadingStars = ref(false)
// Add state for debug modal
const showDebugModal = ref(false)
// Add state for settings modal
const showSettingsModal = ref(false)
2026-05-06 13:22:20 +08:00
const { sidebarCollapsed } = storeToRefs(chatUIStore)
// Provide settings modal methods to child components
const openSettingsModal = () => {
showSettingsModal.value = true
}
// Handle debug modal close
const handleDebugModalClose = () => {
showDebugModal.value = false
}
const getRemoteConfig = async () => {
if (!userStore.isAdmin) return
try {
await configStore.refreshConfig()
} catch (error) {
console.warn('加载系统配置失败:', error)
}
}
const getRemoteDatabase = async () => {
try {
await databaseStore.loadDatabases()
} catch (error) {
console.warn('加载知识库列表失败:', error)
}
}
2025-02-26 12:37:06 +08:00
// Fetch GitHub stars count
const fetchGithubStars = async () => {
try {
isLoadingStars.value = true
2025-05-02 23:56:59 +08:00
// 公共API可以直接使用fetch
const response = await fetch('https://api.github.com/repos/xerrors/Yuxi')
2025-02-26 12:37:06 +08:00
const data = await response.json()
githubStars.value = data.stargazers_count
} catch (error) {
2025-05-02 23:56:59 +08:00
console.error('获取GitHub stars失败:', error)
2025-02-26 12:37:06 +08:00
} finally {
isLoadingStars.value = false
}
}
onMounted(async () => {
// 加载信息配置与知识库数据无依赖,可并行
await Promise.all([infoStore.loadInfoConfig(), getRemoteDatabase()])
await initAgentNavigation()
// 仅管理员加载系统配置和任务中心数据
if (userStore.isAdmin) {
await getRemoteConfig()
taskerStore.loadTasks()
fetchGithubStars() // Fetch GitHub stars on mount
}
})
2024-07-14 16:42:38 +08:00
const route = useRoute()
const router = useRouter()
2025-03-25 05:40:07 +08:00
const activeTaskCount = computed(() => activeCountRef.value || 0)
const organizationName = computed(() => {
return infoStore.organization.name || infoStore.branding.name || 'Yuxi'
})
2025-03-25 05:40:07 +08:00
// 下面是导航菜单部分,添加智能体项
const isLiteMode = import.meta.env.VITE_LITE_MODE === 'true'
const mainList = computed(() => {
const items = [
{
name: '创建新对话',
path: '/agent',
icon: MessageCirclePlus,
activeIcon: MessageCirclePlus,
action: true
}
]
items.push({
name: '工作区',
path: '/workspace',
icon: FolderKanban,
activeIcon: FolderKanban
})
if (userStore.isAdmin) {
if (!isLiteMode) {
items.push({
name: '知识库',
path: '/database',
activePaths: ['/database', '/graph'],
icon: LibraryBig,
activeIcon: LibraryBig
})
}
items.push({
name: '扩展管理',
path: '/extensions',
icon: Blocks,
activeIcon: Blocks
})
items.push({
name: '模型配置',
path: '/model-config',
icon: Box,
activeIcon: Box
})
items.push({
name: 'Dashboard',
path: '/dashboard',
icon: BarChart3,
activeIcon: BarChart3
})
2025-03-25 05:40:07 +08:00
}
return items
})
const isNavItemActive = (item) => {
const activePaths = item.activePaths || [item.path]
return activePaths.some((path) => route.path === path || route.path.startsWith(`${path}/`))
}
const setSidebarCollapsed = (collapsed) => {
sidebarCollapsed.value = collapsed
}
const toggleSidebar = () => {
setSidebarCollapsed(!sidebarCollapsed.value)
}
const initAgentNavigation = async () => {
try {
if (!agentStore.isInitialized) {
await agentStore.initialize()
}
await chatThreadsStore.loadThreads()
} catch (error) {
console.warn('加载对话导航失败:', error)
}
}
const handleSelectChat = (threadId) => {
if (!threadId) return
chatThreadsStore.setCurrentThreadId(threadId)
router.push({ name: 'AgentCompWithThreadId', params: { thread_id: threadId } })
}
const handleDeleteChat = async (threadId) => {
if (!threadId) return
try {
await chatThreadsStore.deleteThread(threadId)
if (route.params.thread_id === threadId) {
await router.replace({ name: 'AgentComp' })
}
} catch (error) {
console.warn('删除对话失败:', error)
}
}
const handleRenameChat = async ({ chatId, title }) => {
try {
await chatThreadsStore.updateThread(chatId, title)
} catch (error) {
console.warn('重命名对话失败:', error)
}
}
const handleTogglePinChat = async (threadId) => {
const thread = threads.value.find((item) => item.id === threadId)
if (!thread) return
try {
await chatThreadsStore.updateThread(threadId, null, !thread.is_pinned)
await chatThreadsStore.loadThreads()
if (currentThreadId.value) {
chatThreadsStore.setCurrentThreadId(currentThreadId.value)
}
} catch (error) {
console.warn('更新置顶状态失败:', error)
}
}
watch(
() => [route.path, route.params.thread_id],
() => {
if (!route.path.startsWith('/agent')) return
const threadId = typeof route.params.thread_id === 'string' ? route.params.thread_id : null
chatThreadsStore.setCurrentThreadId(threadId)
},
{ immediate: true }
)
// Provide settings modal methods to child components
provide('settingsModal', {
openSettingsModal
})
2024-07-14 16:42:38 +08:00
</script>
<template>
<div
class="app-layout"
:class="{ 'use-top-bar': layoutSettings.useTopBar, 'sidebar-collapsed': sidebarCollapsed }"
>
<div class="header" :class="{ 'top-bar': layoutSettings.useTopBar }">
<div class="sidebar-brand" @click.stop>
<router-link v-if="!sidebarCollapsed" to="/" class="brand-link">
<img :src="infoStore.organization.avatar" class="brand-avatar" />
<span class="brand-name">{{ organizationName }}</span>
2024-09-24 11:58:28 +08:00
</router-link>
<button
v-else
type="button"
class="brand-link brand-expand-button"
aria-label="展开侧边栏"
@click="setSidebarCollapsed(false)"
>
<img :src="infoStore.organization.avatar" class="brand-avatar brand-avatar-image" />
<PanelLeftOpen class="brand-expand-icon" size="20" />
</button>
<button
v-if="!sidebarCollapsed"
type="button"
class="sidebar-toggle"
aria-label="折叠侧边栏"
@click="toggleSidebar"
>
<PanelLeftClose size="18" />
</button>
2024-07-14 16:42:38 +08:00
</div>
<div class="nav">
2025-03-25 05:40:07 +08:00
<!-- 使用mainList渲染导航项 -->
<RouterLink
2025-03-25 05:40:07 +08:00
v-for="(item, index) in mainList"
:key="index"
:to="item.path"
v-show="!item.hidden"
class="nav-item"
:class="{ active: !item.action && isNavItemActive(item), 'primary-action': item.action }"
:active-class="item.action ? '' : 'active'"
@click.stop
>
<a-tooltip placement="right" :open="sidebarCollapsed ? undefined : false">
<template #title>{{ item.name }}</template>
<component
class="icon"
:is="isNavItemActive(item) ? item.activeIcon : item.icon"
size="18"
/>
</a-tooltip>
<span class="nav-text">{{ item.name }}</span>
</RouterLink>
2024-07-14 16:42:38 +08:00
</div>
<div class="fill">
<ConversationNavSection
v-if="!sidebarCollapsed"
class="sidebar-conversations"
:current-chat-id="currentThreadId"
:chats-list="threads"
:has-more-chats="hasMoreThreads"
:is-loading-more="isLoadingMoreThreads"
@select-chat="handleSelectChat"
@delete-chat="handleDeleteChat"
@rename-chat="handleRenameChat"
@toggle-pin="handleTogglePinChat"
@load-more-chats="() => chatThreadsStore.loadMoreThreads()"
/>
2025-04-01 00:39:54 +08:00
</div>
<div class="foo">
<div class="github nav-item" @click.stop>
<a-tooltip placement="right" :open="sidebarCollapsed ? undefined : false">
<template #title>欢迎 Star</template>
<a href="https://github.com/xerrors/Yuxi" target="_blank" class="github-link">
<GithubOutlined class="icon" />
<span class="nav-text">GitHub</span>
<span v-if="githubStars > 0" class="github-stars">
<span class="star-count">{{ (githubStars / 1000).toFixed(1) }}k</span>
</span>
</a>
</a-tooltip>
</div>
<!-- 用户信息组件 -->
<div class="nav-item user-info" @click.stop>
<UserInfoComponent :show-role="!sidebarCollapsed">
<template v-if="userStore.isAdmin" #actions>
<a-tooltip placement="top" title="任务中心">
<button
class="user-task-center"
:class="{ active: isDrawerOpen }"
type="button"
aria-label="任务中心"
@click.stop="taskerStore.openDrawer()"
>
<a-badge
:count="activeTaskCount"
:overflow-count="99"
class="task-center-badge"
size="small"
>
<ClipboardList class="icon" size="16" />
</a-badge>
</button>
</a-tooltip>
</template>
</UserInfoComponent>
</div>
</div>
</div>
2024-09-14 02:46:21 +08:00
<router-view v-slot="{ Component, route }" id="app-router-view">
<keep-alive v-if="route.meta.keepAlive !== false">
2024-07-14 16:42:38 +08:00
<component :is="Component" />
</keep-alive>
2024-09-14 02:46:21 +08:00
<component :is="Component" v-else />
2024-07-14 16:42:38 +08:00
</router-view>
<!-- Debug Modal -->
<a-modal
v-model:open="showDebugModal"
title="调试面板"
width="90%"
:footer="null"
@cancel="handleDebugModalClose"
:maskClosable="true"
:destroyOnClose="true"
class="debug-modal"
>
<DebugComponent />
</a-modal>
<TaskCenterDrawer v-if="userStore.isAdmin" />
<SettingsModal v-model:visible="showSettingsModal" @close="() => (showSettingsModal = false)" />
2024-07-14 16:42:38 +08:00
</div>
</template>
<style lang="less" scoped>
// Less 变量定义
@sidebar-width: 252px;
@sidebar-collapsed-width: 56px;
@sidebar-padding: 6px 8px;
@sidebar-item-height: 40px;
@sidebar-item-padding-x: 10px;
@sidebar-icon-size: 18px;
2025-04-15 12:39:46 +08:00
2024-07-14 16:42:38 +08:00
.app-layout {
display: flex;
flex-direction: row;
width: 100%;
height: 100vh;
min-width: var(--min-width);
}
div.header,
#app-router-view {
2024-07-14 16:42:38 +08:00
height: 100%;
max-width: 100%;
2024-09-14 02:46:21 +08:00
user-select: none;
2024-07-14 16:42:38 +08:00
}
#app-router-view {
flex: 1 1 auto;
overflow-y: auto;
}
.header {
display: flex;
flex-direction: column;
flex: 0 0 @sidebar-width;
2024-07-14 16:42:38 +08:00
justify-content: flex-start;
align-items: stretch;
gap: 16px;
background-color: var(--main-5);
2024-07-14 16:42:38 +08:00
height: 100%;
width: @sidebar-width;
border-right: 1px solid var(--gray-100);
padding: @sidebar-padding;
overflow: hidden;
transition:
width 0.18s ease,
flex-basis 0.18s ease;
2024-07-14 16:42:38 +08:00
.nav {
display: flex;
flex: 0 0 auto;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
position: relative;
gap: 4px;
}
.sidebar-conversations {
height: 100%;
min-height: 0;
overflow: hidden;
}
.sidebar-brand,
:deep(.conversation-nav-section:not(.sidebar-conversations)),
.github,
.user-info {
flex-shrink: 0;
}
.fill {
flex: 1 1 0;
min-height: 0;
}
.sidebar-brand {
display: flex;
align-items: center;
justify-content: space-between;
height: @sidebar-item-height;
gap: 8px;
}
2024-07-14 16:42:38 +08:00
.brand-link {
display: flex;
flex: 1 1 auto;
align-items: center;
min-width: 0;
height: @sidebar-item-height;
color: var(--gray-900);
text-decoration: none;
border: 0;
background: transparent;
padding: 0 6px;
cursor: pointer;
}
2024-07-14 16:42:38 +08:00
.brand-avatar {
flex: 0 0 28px;
width: 28px;
height: 28px;
border-radius: 6px;
object-fit: cover;
}
.brand-name {
min-width: 0;
margin-left: 10px;
overflow: hidden;
color: var(--gray-1000);
font-size: 15px;
font-weight: 650;
line-height: 20px;
text-overflow: ellipsis;
white-space: nowrap;
}
.sidebar-toggle {
display: inline-flex;
flex: 0 0 32px;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid transparent;
border-radius: 8px;
background: transparent;
color: var(--gray-600);
cursor: pointer;
transition:
background-color 0.2s ease,
border-color 0.2s ease,
color 0.2s ease;
&:hover,
&:focus-visible {
border-color: var(--main-50);
background: var(--main-20);
color: var(--main-color);
outline: none;
2024-07-14 16:42:38 +08:00
}
}
2024-07-16 18:14:27 +08:00
.nav-item {
2024-09-14 02:46:21 +08:00
display: flex;
align-items: center;
justify-content: flex-start;
width: 100%;
height: @sidebar-item-height;
padding: 0 @sidebar-item-padding-x;
2024-09-14 02:46:21 +08:00
border: 1px solid transparent;
border-radius: 8px;
background-color: transparent;
color: var(--gray-700);
font-size: 14px;
font-weight: 600;
transition:
background-color 0.2s ease-in-out,
border-color 0.2s ease-in-out,
color 0.2s ease-in-out;
2025-04-15 12:39:46 +08:00
margin: 0;
2024-09-14 02:46:21 +08:00
text-decoration: none;
cursor: pointer;
outline: none;
.icon {
flex: 0 0 @sidebar-icon-size;
width: @sidebar-icon-size;
height: @sidebar-icon-size;
}
.nav-text {
min-width: 0;
max-width: 140px;
margin-left: 8px;
overflow: hidden;
line-height: 20px;
font-weight: 500;
text-overflow: ellipsis;
white-space: nowrap;
transition:
opacity 0.12s ease,
margin-left 0.18s ease,
max-width 0.18s ease;
}
& > svg:focus {
outline: none;
}
& > svg:focus-visible {
outline: none;
}
&.active {
border-color: transparent;
background-color: color-mix(in srgb, var(--main-color) 6%, var(--gray-0));
font-weight: 600;
color: var(--main-color);
}
&.primary-action {
margin-bottom: 8px;
border-color: var(--gray-150);
background-color: var(--gray-0);
color: var(--main-color);
box-shadow: 0 3px 4px rgba(0, 10, 20, 0.02);
&:hover {
border-color: var(--gray-200);
background-color: var(--gray-0);
color: var(--main-color);
box-shadow: 0 3px 4px rgba(0, 10, 20, 0.07);
}
}
&.warning {
color: var(--color-error-500);
}
&:hover {
border-color: transparent;
background-color: var(--main-20);
color: var(--main-color);
}
2024-09-14 02:46:21 +08:00
&.github {
margin-bottom: 8px;
2024-09-24 11:58:28 +08:00
&:hover {
border-color: transparent;
2024-09-24 11:58:28 +08:00
}
2025-02-26 12:37:06 +08:00
.github-link {
display: flex;
align-items: center;
width: 100%;
min-width: 0;
2025-02-26 12:37:06 +08:00
color: inherit;
text-decoration: none;
}
.icon {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: @sidebar-icon-size;
line-height: 1;
2025-02-26 12:37:06 +08:00
}
.github-stars {
display: flex;
align-items: center;
max-width: 48px;
margin-left: auto;
overflow: hidden;
2025-02-26 12:37:06 +08:00
font-size: 12px;
color: var(--gray-500);
white-space: nowrap;
transition:
opacity 0.12s ease,
max-width 0.18s ease;
2025-02-26 12:37:06 +08:00
.star-icon {
color: var(--color-warning-500);
2025-02-26 12:37:06 +08:00
font-size: 12px;
margin-right: 2px;
}
.star-count {
font-weight: 600;
}
}
2024-09-14 02:46:21 +08:00
}
2025-04-01 00:39:54 +08:00
&.api-docs {
padding: 10px 12px;
}
&.docs {
display: none;
}
Add dark/light theme toggle feature (#343) * Add dark/light theme toggle feature * update: refine dark/light theme and related components * 调整语义化主题变量,优化暗/亮模式切换效果 * Revert "调整语义化主题变量,优化暗/亮模式切换效果" This reverts commit 85d9373297686fdbacb252a880b2847d20a39641. * 深色模式适配:减少 !important,迁移 CSS 变量,优化布局 * style: 替换硬编码颜色值为CSS变量以支持主题切换 将多处硬编码的颜色值(如#fff、#f0f0f0等)替换为CSS变量(如--gray-0、--gray-150等),统一管理颜色样式,便于主题切换和样式维护。主要修改包括背景色、边框色、文字色等视觉元素,同时移除不再需要的深色模式适配代码。 * style: 使用CSS变量替换硬编码颜色值 * refactor(theme): 重构主题系统,统一使用CSS变量并优化暗色模式实现 - 移除冗余的theme.js配置文件,将主题配置集中到theme store - 使用ant-design-vue的darkAlgorithm实现暗色模式 - 在多个组件中替换硬编码颜色值为CSS变量 - 优化用户信息组件,整合文档中心和主题切换功能 - 更新基础CSS样式,完善颜色系统和滚动条样式 * reset: 恢复被覆盖的修改(96d257a7ace38c94e2b113d56472d211d1141087) * feat(theme): 实现深色主题支持并重构样式系统 重构颜色变量系统,添加深色主题支持 移除硬编码颜色值,统一使用CSS变量 优化图表组件以响应主题切换 清理无用样式代码,提升可维护性 * docs: 更新开发规范文档 * style: 调整边框和背景颜色样式 --------- Co-authored-by: Wenjie Zhang <xerrors@163.com>
2025-11-23 01:39:44 +08:00
&.theme-toggle-nav {
.theme-toggle-icon {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
cursor: pointer;
color: var(--gray-1000);
transition: color 0.2s ease-in-out;
&:hover {
color: var(--main-color);
}
}
}
&.user-info {
margin-bottom: 8px;
padding: 0 3px;
overflow: hidden;
:deep(.user-info-component) {
width: 100%;
}
:deep(.user-info-dropdown) {
width: 100%;
height: @sidebar-item-height;
border-radius: 8px;
transition:
background-color 0.2s ease,
color 0.2s ease;
}
:deep(.user-info-dropdown:hover) {
background: var(--main-20);
color: var(--main-color);
}
:deep(.user-name) {
flex: 1 1 auto;
}
:deep(.user-task-center) {
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
padding: 0;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: var(--gray-600);
cursor: pointer;
transition:
background-color 0.2s ease,
color 0.2s ease;
&:hover,
&.active {
background: var(--main-30);
color: var(--main-color);
}
.task-center-badge {
display: flex;
justify-content: center;
}
.icon {
display: block;
width: 16px;
height: 16px;
}
}
}
}
}
.app-layout.sidebar-collapsed {
.header {
flex-basis: @sidebar-collapsed-width;
width: @sidebar-collapsed-width;
align-items: stretch;
padding: @sidebar-padding;
.sidebar-brand {
justify-content: flex-start;
width: 100%;
}
.brand-expand-button {
flex: 0 0 @sidebar-item-height;
justify-content: center;
width: @sidebar-item-height;
padding: 0 6px;
border-radius: 8px;
.brand-expand-icon {
display: none;
width: @sidebar-icon-size;
height: @sidebar-icon-size;
color: var(--main-color);
}
&:hover,
&:focus-visible {
background: var(--main-20);
outline: none;
.brand-avatar-image {
display: none;
}
.brand-expand-icon {
display: block;
}
}
}
.nav {
align-items: stretch;
width: 100%;
}
.nav-item {
justify-content: flex-start;
width: @sidebar-item-height;
padding: 0 10px;
.nav-text,
.github-stars {
max-width: 0;
margin-left: 0;
opacity: 0;
pointer-events: none;
}
&.github {
.github-link {
justify-content: flex-start;
}
}
&.user-info {
padding: 0;
:deep(.user-info-actions) {
display: none;
}
}
2024-09-14 02:46:21 +08:00
}
2024-07-14 16:42:38 +08:00
}
2024-07-27 14:32:57 +08:00
}
2024-09-24 11:58:28 +08:00
.app-layout.use-top-bar {
flex-direction: column;
}
.header.top-bar {
flex-direction: row;
flex: 0 0 50px;
width: 100%;
height: 50px;
border-right: none;
border-bottom: 1px solid var(--main-40);
background-color: var(--main-20);
2024-09-24 11:58:28 +08:00
padding: 0 20px;
gap: 24px;
.logo {
width: fit-content;
height: 28px;
margin-right: 16px;
display: flex;
align-items: center;
a {
display: flex;
align-items: center;
text-decoration: none;
color: inherit;
}
img {
width: 28px;
height: 28px;
margin-right: 8px;
}
}
.nav {
flex-direction: row;
height: auto;
gap: 20px;
}
.nav-item {
flex-direction: row;
width: auto;
padding: 4px 16px;
margin: 0;
.icon {
margin-right: 8px;
font-size: 15px; // 减小图标大小
border: none;
outline: none;
&:focus,
&:active {
border: none;
outline: none;
}
2024-09-24 11:58:28 +08:00
}
.text {
margin-top: 0;
font-size: 15px;
}
&.github {
2024-09-24 11:58:28 +08:00
padding: 8px 12px;
.icon {
margin-right: 0;
font-size: 18px;
}
&.active {
color: var(--main-color);
2024-09-24 11:58:28 +08:00
}
a {
display: flex;
align-items: center;
}
2025-02-26 12:37:06 +08:00
.github-stars {
display: flex;
align-items: center;
margin-left: 6px;
.star-icon {
color: var(--color-warning-500);
2025-02-26 12:37:06 +08:00
font-size: 14px;
margin-right: 2px;
}
}
2024-09-24 11:58:28 +08:00
}
Add dark/light theme toggle feature (#343) * Add dark/light theme toggle feature * update: refine dark/light theme and related components * 调整语义化主题变量,优化暗/亮模式切换效果 * Revert "调整语义化主题变量,优化暗/亮模式切换效果" This reverts commit 85d9373297686fdbacb252a880b2847d20a39641. * 深色模式适配:减少 !important,迁移 CSS 变量,优化布局 * style: 替换硬编码颜色值为CSS变量以支持主题切换 将多处硬编码的颜色值(如#fff、#f0f0f0等)替换为CSS变量(如--gray-0、--gray-150等),统一管理颜色样式,便于主题切换和样式维护。主要修改包括背景色、边框色、文字色等视觉元素,同时移除不再需要的深色模式适配代码。 * style: 使用CSS变量替换硬编码颜色值 * refactor(theme): 重构主题系统,统一使用CSS变量并优化暗色模式实现 - 移除冗余的theme.js配置文件,将主题配置集中到theme store - 使用ant-design-vue的darkAlgorithm实现暗色模式 - 在多个组件中替换硬编码颜色值为CSS变量 - 优化用户信息组件,整合文档中心和主题切换功能 - 更新基础CSS样式,完善颜色系统和滚动条样式 * reset: 恢复被覆盖的修改(96d257a7ace38c94e2b113d56472d211d1141087) * feat(theme): 实现深色主题支持并重构样式系统 重构颜色变量系统,添加深色主题支持 移除硬编码颜色值,统一使用CSS变量 优化图表组件以响应主题切换 清理无用样式代码,提升可维护性 * docs: 更新开发规范文档 * style: 调整边框和背景颜色样式 --------- Co-authored-by: Wenjie Zhang <xerrors@163.com>
2025-11-23 01:39:44 +08:00
&.theme-toggle-nav {
padding: 8px 12px;
.theme-toggle-icon {
display: flex;
align-items: center;
justify-content: center;
color: var(--gray-1000);
transition: color 0.2s ease-in-out;
cursor: pointer;
&:hover {
color: var(--main-color);
}
}
&.active {
.theme-toggle-icon {
color: var(--main-color);
}
}
}
2024-09-24 11:58:28 +08:00
}
}
</style>