U:将所有的颜色样式抽离为单独的变量文件
This commit is contained in:
1 parent
ca73e07323
commit
c374165dcb
7 files changed
+304
-375
No files matched your search
@@ -0,0 +1,180 @@
|
||||
/* theme-variables.css - 统一的主题变量管理 */
|
||||
|
||||
/* ==================== 主题切换的根选择器 ==================== */
|
||||
:root {
|
||||
/* ========== 颜色定义 - 亮色主题(默认) ========== */
|
||||
|
||||
/* 背景色层级 */
|
||||
--bg-primary: #ffffff; /* 主背景色 */
|
||||
--bg-secondary: #f8fafc; /* 次级背景 */
|
||||
--bg-tertiary: #f1f5f9; /* 三级背景 */
|
||||
--bg-quaternary: #e2e8f0; /* 四级背景 */
|
||||
--bg-overlay: rgba(255, 255, 255, 0.95); /* 遮罩层 */
|
||||
--bg-glass: rgba(248, 250, 252, 0.8); /* 玻璃效果 */
|
||||
--bg-card: rgba(241, 245, 249, 0.8); /* 卡片背景 */
|
||||
--bg-hover: rgba(59, 130, 246, 0.08); /* 悬停背景 */
|
||||
--bg-selected: rgba(59, 130, 246, 0.15); /* 选中背景 */
|
||||
|
||||
/* 文字色层级 */
|
||||
--text-primary: #0f172a; /* 主文字色 */
|
||||
--text-secondary: #1e293b; /* 次级文字 */
|
||||
--text-tertiary: #334155; /* 三级文字 */
|
||||
--text-quaternary: #475569; /* 四级文字 */
|
||||
--text-muted: #64748b; /* 柔和文字 */
|
||||
--text-disabled: #94a3b8; /* 禁用文字 */
|
||||
|
||||
/* 边框色层级 */
|
||||
--border-primary: #e2e8f0;
|
||||
--border-secondary: rgba(226, 232, 240, 0.8);
|
||||
--border-tertiary: rgba(203, 213, 225, 0.6);
|
||||
--border-hover: rgba(59, 130, 246, 0.3);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
/* 主题色 - 这些在明暗主题中保持一致 */
|
||||
--accent-primary: #3b82f6; /* 主要强调色 - 蓝色 */
|
||||
--accent-secondary: #06b6d4; /* 次要强调色 - 青色 */
|
||||
--accent-success: #22c55e; /* 成功 - 绿色 */
|
||||
--accent-warning: #f59e0b; /* 警告 - 橙色 */
|
||||
--accent-error: #ef4444; /* 错误 - 红色 */
|
||||
--accent-purple: #8b5cf6; /* 紫色 */
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* 其他效果 */
|
||||
--backdrop-blur: blur(20px);
|
||||
--transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* ==================== 暗色主题 - .dark 类名方式 ==================== */
|
||||
.dark {
|
||||
/* 背景色层级 - 暗色 */
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--bg-quaternary: #475569;
|
||||
--bg-overlay: rgba(15, 23, 42, 0.95);
|
||||
--bg-glass: rgba(30, 41, 59, 0.8);
|
||||
--bg-card: rgba(51, 65, 85, 0.5);
|
||||
--bg-hover: rgba(59, 130, 246, 0.1);
|
||||
--bg-selected: rgba(59, 130, 246, 0.2);
|
||||
|
||||
/* 文字色层级 - 暗色 */
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #f1f5f9;
|
||||
--text-tertiary: #e2e8f0;
|
||||
--text-quaternary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-disabled: #64748b;
|
||||
|
||||
/* 边框色层级 - 暗色 */
|
||||
--border-primary: #334155;
|
||||
--border-secondary: rgba(51, 65, 85, 0.3);
|
||||
--border-tertiary: rgba(71, 85, 105, 0.3);
|
||||
--border-hover: rgba(59, 130, 246, 0.5);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
/* 阴影 - 暗色模式 */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* ==================== 暗色主题 - data-theme 属性方式(向后兼容) ==================== */
|
||||
[data-theme="dark"] {
|
||||
/* 背景色层级 - 暗色 */
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--bg-quaternary: #475569;
|
||||
--bg-overlay: rgba(15, 23, 42, 0.95);
|
||||
--bg-glass: rgba(30, 41, 59, 0.8);
|
||||
--bg-card: rgba(51, 65, 85, 0.5);
|
||||
--bg-hover: rgba(59, 130, 246, 0.1);
|
||||
--bg-selected: rgba(59, 130, 246, 0.2);
|
||||
|
||||
/* 文字色层级 - 暗色 */
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #f1f5f9;
|
||||
--text-tertiary: #e2e8f0;
|
||||
--text-quaternary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-disabled: #64748b;
|
||||
|
||||
/* 边框色层级 - 暗色 */
|
||||
--border-primary: #334155;
|
||||
--border-secondary: rgba(51, 65, 85, 0.3);
|
||||
--border-tertiary: rgba(71, 85, 105, 0.3);
|
||||
--border-hover: rgba(59, 130, 246, 0.5);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
/* 阴影 - 暗色模式下的阴影更深 */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* ==================== 主题切换过渡效果 ==================== */
|
||||
* {
|
||||
/* 确保所有元素都使用主题过渡 */
|
||||
transition: background-color 0.2s ease,
|
||||
border-color 0.2s ease,
|
||||
color 0.2s ease;
|
||||
}
|
||||
|
||||
/* 防止页面初始加载时的闪烁 */
|
||||
body:not(.theme-ready) * {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
/* ==================== 应用主题到根元素 ==================== */
|
||||
body {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* ==================== 工具类 ==================== */
|
||||
/* 快速应用背景色 */
|
||||
.bg-primary { background-color: var(--bg-primary); }
|
||||
.bg-secondary { background-color: var(--bg-secondary); }
|
||||
.bg-tertiary { background-color: var(--bg-tertiary); }
|
||||
.bg-quaternary { background-color: var(--bg-quaternary); }
|
||||
.bg-card { background-color: var(--bg-card); }
|
||||
.bg-glass { background-color: var(--bg-glass); }
|
||||
.bg-overlay { background-color: var(--bg-overlay); }
|
||||
|
||||
/* 快速应用文字色 */
|
||||
.text-primary { color: var(--text-primary); }
|
||||
.text-secondary { color: var(--text-secondary); }
|
||||
.text-tertiary { color: var(--text-tertiary); }
|
||||
.text-quaternary { color: var(--text-quaternary); }
|
||||
.text-muted { color: var(--text-muted); }
|
||||
.text-disabled { color: var(--text-disabled); }
|
||||
|
||||
/* 快速应用边框 */
|
||||
.border-primary { border-color: var(--border-primary); }
|
||||
.border-secondary { border-color: var(--border-secondary); }
|
||||
.border-tertiary { border-color: var(--border-tertiary); }
|
||||
.border-hover { border-color: var(--border-hover); }
|
||||
.border-focus { border-color: var(--border-focus); }
|
||||
|
||||
/* 快速应用主题色 */
|
||||
.accent-primary { color: var(--accent-primary); }
|
||||
.accent-secondary { color: var(--accent-secondary); }
|
||||
.accent-success { color: var(--accent-success); }
|
||||
.accent-warning { color: var(--accent-warning); }
|
||||
.accent-error { color: var(--accent-error); }
|
||||
.accent-purple { color: var(--accent-purple); }
|
||||
|
||||
/* 快速应用阴影 */
|
||||
.shadow-sm { box-shadow: var(--shadow-sm); }
|
||||
.shadow-md { box-shadow: var(--shadow-md); }
|
||||
.shadow-lg { box-shadow: var(--shadow-lg); }
|
||||
.shadow-xl { box-shadow: var(--shadow-xl); }
|
||||
@@ -1,41 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vite.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,94 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
|
||||
const openReadmeInEditor = () => fetch('/__open-in-editor?file=README.md')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vite.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/vuejs/language-tools" target="_blank" rel="noopener">Vue - Official</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://vitest.dev/" target="_blank" rel="noopener">Vitest</a>
|
||||
and
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a>
|
||||
/
|
||||
<a href="https://playwright.dev/" target="_blank" rel="noopener">Playwright</a>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in
|
||||
<a href="javascript:void(0)" @click="openReadmeInEditor"><code>README.md</code></a
|
||||
>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>
|
||||
(our official Discord server), or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also follow the official
|
||||
<a href="https://bsky.app/profile/vuejs.org" target="_blank" rel="noopener">@vuejs.org</a>
|
||||
Bluesky account or the
|
||||
<a href="https://x.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
X account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
||||
@@ -1,87 +0,0 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="appContainer" class="app-container">
|
||||
<div class="app-container">
|
||||
<!-- 顶部标题栏组件 -->
|
||||
<TitleBar />
|
||||
|
||||
@@ -11,37 +11,15 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, watch, nextTick, computed } from 'vue'
|
||||
import { onMounted } from 'vue'
|
||||
import { useThemeStore } from '@/stores/theme'
|
||||
import TitleBar from '@/components/TitleBar.vue'
|
||||
|
||||
const themeStore = useThemeStore()
|
||||
const isDark = computed(() => themeStore.isDark)
|
||||
|
||||
// 引用app容器元素
|
||||
const appContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
// 监听主题变化并更新CSS类
|
||||
watch(isDark, () => {
|
||||
nextTick(() => {
|
||||
updateThemeClass()
|
||||
})
|
||||
}, { immediate: true })
|
||||
|
||||
// 更新主题CSS类
|
||||
const updateThemeClass = () => {
|
||||
if (appContainer.value) {
|
||||
if (isDark.value) {
|
||||
appContainer.value.classList.add('dark-theme')
|
||||
} else {
|
||||
appContainer.value.classList.remove('dark-theme')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 初始化主题类
|
||||
updateThemeClass()
|
||||
// 初始化主题
|
||||
themeStore.initTheme()
|
||||
|
||||
// 页面加载完成后启用过渡动画
|
||||
setTimeout(() => {
|
||||
@@ -51,90 +29,21 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 主题色彩变量 */
|
||||
.app-container {
|
||||
/* 暗色主题 */
|
||||
--bg-primary: #0f172a;
|
||||
--bg-secondary: #1e293b;
|
||||
--bg-tertiary: #334155;
|
||||
--bg-quaternary: #475569;
|
||||
--bg-overlay: rgba(15, 23, 42, 0.95);
|
||||
--bg-glass: rgba(30, 41, 59, 0.8);
|
||||
--bg-card: rgba(51, 65, 85, 0.5);
|
||||
--bg-hover: rgba(59, 130, 246, 0.1);
|
||||
--bg-selected: rgba(59, 130, 246, 0.2);
|
||||
|
||||
--text-primary: #f8fafc;
|
||||
--text-secondary: #f1f5f9;
|
||||
--text-tertiary: #e2e8f0;
|
||||
--text-quaternary: #cbd5e1;
|
||||
--text-muted: #94a3b8;
|
||||
--text-disabled: #64748b;
|
||||
|
||||
--border-primary: #334155;
|
||||
--border-secondary: rgba(51, 65, 85, 0.3);
|
||||
--border-tertiary: rgba(71, 85, 105, 0.3);
|
||||
--border-hover: rgba(59, 130, 246, 0.5);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--accent-primary: #3b82f6;
|
||||
--accent-secondary: #06b6d4;
|
||||
--accent-success: #22c55e;
|
||||
--accent-warning: #f59e0b;
|
||||
--accent-error: #ef4444;
|
||||
--accent-purple: #8b5cf6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
|
||||
--backdrop-blur: blur(20px);
|
||||
--transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 亮色主题变量 */
|
||||
.app-container:not(.dark-theme) {
|
||||
--bg-primary: #ffffff;
|
||||
--bg-secondary: #f8fafc;
|
||||
--bg-tertiary: #f1f5f9;
|
||||
--bg-quaternary: #e2e8f0;
|
||||
--bg-overlay: rgba(255, 255, 255, 0.95);
|
||||
--bg-glass: rgba(248, 250, 252, 0.8);
|
||||
--bg-card: rgba(241, 245, 249, 0.8);
|
||||
--bg-hover: rgba(59, 130, 246, 0.08);
|
||||
--bg-selected: rgba(59, 130, 246, 0.15);
|
||||
|
||||
--text-primary: #0f172a;
|
||||
--text-secondary: #1e293b;
|
||||
--text-tertiary: #334155;
|
||||
--text-quaternary: #475569;
|
||||
--text-muted: #64748b;
|
||||
--text-disabled: #94a3b8;
|
||||
|
||||
--border-primary: #e2e8f0;
|
||||
--border-secondary: rgba(226, 232, 240, 0.8);
|
||||
--border-tertiary: rgba(203, 213, 225, 0.6);
|
||||
--border-hover: rgba(59, 130, 246, 0.3);
|
||||
--border-focus: #3b82f6;
|
||||
|
||||
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 根容器样式 */
|
||||
/* 主容器样式 */
|
||||
.app-container {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
||||
color: var(--text-primary);
|
||||
user-select: none;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
/* 滚动条样式 */
|
||||
/* 主内容区域 */
|
||||
main {
|
||||
min-height: calc(100vh - 60px); /* 减去标题栏高度 */
|
||||
}
|
||||
|
||||
/* 滚动条样式 - 使用 CSS 变量 */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
@@ -153,7 +62,7 @@ onMounted(() => {
|
||||
background: var(--border-hover);
|
||||
}
|
||||
|
||||
/* 自定义Naive UI组件样式 */
|
||||
/* 全局 Naive UI 组件样式覆盖 */
|
||||
:deep(.n-input .n-input__input-el) {
|
||||
background: var(--bg-card);
|
||||
border-color: var(--border-tertiary);
|
||||
@@ -189,39 +98,28 @@ onMounted(() => {
|
||||
border-color: #1d4ed8;
|
||||
}
|
||||
|
||||
/* 其他按钮类型样式 */
|
||||
:deep(.n-button--info-type) {
|
||||
background: var(--accent-secondary);
|
||||
border-color: var(--accent-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--info-type:hover) {
|
||||
background: #0891b2;
|
||||
border-color: #0891b2;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type) {
|
||||
background: var(--accent-warning);
|
||||
border-color: var(--accent-warning);
|
||||
color: white;
|
||||
}
|
||||
|
||||
:deep(.n-button--warning-type:hover) {
|
||||
background: #d97706;
|
||||
border-color: #d97706;
|
||||
:deep(.n-button--error-type) {
|
||||
background: var(--accent-error);
|
||||
border-color: var(--accent-error);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 主题切换过渡效果 - 只在页面加载完成后启用 */
|
||||
body.theme-ready * {
|
||||
transition:
|
||||
background-color 0.3s ease,
|
||||
border-color 0.3s ease,
|
||||
color 0.3s ease,
|
||||
box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
/* 初始加载时禁用所有过渡 */
|
||||
body:not(.theme-ready) * {
|
||||
transition: none !important;
|
||||
:deep(.n-button--success-type) {
|
||||
background: var(--accent-success);
|
||||
border-color: var(--accent-success);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,5 @@
|
||||
// 引入主题变量(放在其他样式之前)
|
||||
import './assets/theme-variables.css'
|
||||
import './assets/main.css'
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
@@ -13,13 +15,8 @@ import { useThemeStore } from '@/stores/theme'
|
||||
|
||||
const app = createApp(App)
|
||||
app.use(createPinia())
|
||||
|
||||
|
||||
app.use(router)
|
||||
|
||||
app.use(websocketPlugin)
|
||||
|
||||
|
||||
// 使用 主题 支持
|
||||
app.use(createNaiveUI())
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// stores/theme.ts - 完善的主题存储
|
||||
// stores/theme.ts - 增强版主题存储
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { darkTheme, lightTheme } from 'naive-ui'
|
||||
import type { GlobalTheme, GlobalThemeOverrides } from 'naive-ui'
|
||||
|
||||
@@ -8,61 +8,129 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
// 主题状态
|
||||
const isDark = ref(false)
|
||||
|
||||
// 初始化主题(从本地存储读取)
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
isDark.value = savedTheme === 'dark'
|
||||
// 是否跟随系统主题
|
||||
const followSystem = ref(false)
|
||||
|
||||
// 同步到 HTML 元素
|
||||
if (isDark.value) {
|
||||
// 系统主题媒体查询
|
||||
const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)')
|
||||
|
||||
// 应用主题到 DOM
|
||||
const applyTheme = (dark: boolean) => {
|
||||
if (dark) {
|
||||
document.documentElement.classList.add('dark')
|
||||
// 同时设置 data-theme 属性,方便 CSS 选择器使用
|
||||
document.documentElement.setAttribute('data-theme', 'dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
document.documentElement.removeAttribute('data-theme')
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化主题
|
||||
const initTheme = () => {
|
||||
const savedTheme = localStorage.getItem('theme')
|
||||
const savedFollowSystem = localStorage.getItem('theme-follow-system')
|
||||
|
||||
// 恢复跟随系统设置
|
||||
followSystem.value = savedFollowSystem === 'true'
|
||||
|
||||
if (followSystem.value) {
|
||||
// 跟随系统主题
|
||||
isDark.value = systemPrefersDark.matches
|
||||
} else if (savedTheme) {
|
||||
// 使用保存的主题
|
||||
isDark.value = savedTheme === 'dark'
|
||||
} else {
|
||||
// 默认跟随系统
|
||||
isDark.value = systemPrefersDark.matches
|
||||
followSystem.value = true
|
||||
}
|
||||
|
||||
applyTheme(isDark.value)
|
||||
|
||||
// 监听系统主题变化
|
||||
systemPrefersDark.addEventListener('change', handleSystemThemeChange)
|
||||
}
|
||||
|
||||
// 处理系统主题变化
|
||||
const handleSystemThemeChange = (e: MediaQueryListEvent) => {
|
||||
if (followSystem.value) {
|
||||
isDark.value = e.matches
|
||||
applyTheme(isDark.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 切换主题
|
||||
const toggleTheme = () => {
|
||||
isDark.value = !isDark.value
|
||||
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
|
||||
followSystem.value = false // 手动切换后不再跟随系统
|
||||
|
||||
// 同步到 HTML 元素
|
||||
if (isDark.value) {
|
||||
document.documentElement.classList.add('dark')
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark')
|
||||
localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
|
||||
localStorage.setItem('theme-follow-system', 'false')
|
||||
|
||||
applyTheme(isDark.value)
|
||||
}
|
||||
|
||||
// 设置是否跟随系统
|
||||
const setFollowSystem = (follow: boolean) => {
|
||||
followSystem.value = follow
|
||||
localStorage.setItem('theme-follow-system', follow.toString())
|
||||
|
||||
if (follow) {
|
||||
isDark.value = systemPrefersDark.matches
|
||||
applyTheme(isDark.value)
|
||||
}
|
||||
}
|
||||
|
||||
// 监听主题变化
|
||||
watch(isDark, (newValue) => {
|
||||
if (!followSystem.value) {
|
||||
localStorage.setItem('theme', newValue ? 'dark' : 'light')
|
||||
}
|
||||
})
|
||||
|
||||
// Naive UI 主题
|
||||
const theme = computed<GlobalTheme | null>(() => {
|
||||
return isDark.value ? darkTheme : null
|
||||
})
|
||||
|
||||
// 主题覆盖配置 - 抽取共同配置
|
||||
const commonColors = {
|
||||
primaryColor: '#3b82f6',
|
||||
infoColor: '#06b6d4',
|
||||
successColor: '#22c55e',
|
||||
warningColor: '#f59e0b',
|
||||
errorColor: '#ef4444',
|
||||
}
|
||||
|
||||
// 主题覆盖配置
|
||||
const themeOverrides = computed<GlobalThemeOverrides>(() => {
|
||||
const baseOverrides = {
|
||||
common: {
|
||||
...commonColors,
|
||||
borderRadius: '8px',
|
||||
borderRadiusSmall: '6px',
|
||||
}
|
||||
}
|
||||
|
||||
if (isDark.value) {
|
||||
// 暗色主题配置
|
||||
return {
|
||||
common: {
|
||||
primaryColor: '#3b82f6',
|
||||
...baseOverrides.common,
|
||||
primaryColorHover: '#60a5fa',
|
||||
primaryColorPressed: '#2563eb',
|
||||
primaryColorSuppl: '#1d4ed8',
|
||||
|
||||
infoColor: '#06b6d4',
|
||||
infoColorHover: '#22d3ee',
|
||||
infoColorPressed: '#0891b2',
|
||||
|
||||
successColor: '#22c55e',
|
||||
successColorHover: '#4ade80',
|
||||
successColorPressed: '#16a34a',
|
||||
|
||||
warningColor: '#f59e0b',
|
||||
warningColorHover: '#fbbf24',
|
||||
warningColorPressed: '#d97706',
|
||||
|
||||
errorColor: '#ef4444',
|
||||
errorColorHover: '#f87171',
|
||||
errorColorPressed: '#dc2626',
|
||||
|
||||
@@ -98,6 +166,8 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
textColorGhost: '#cbd5e1',
|
||||
textColorGhostHover: '#f1f5f9',
|
||||
textColorGhostPressed: '#e2e8f0',
|
||||
borderColor: '#334155',
|
||||
borderColorHover: '#475569',
|
||||
},
|
||||
|
||||
Input: {
|
||||
@@ -115,24 +185,20 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
// 亮色主题配置
|
||||
return {
|
||||
common: {
|
||||
primaryColor: '#3b82f6',
|
||||
...baseOverrides.common,
|
||||
primaryColorHover: '#2563eb',
|
||||
primaryColorPressed: '#1d4ed8',
|
||||
primaryColorSuppl: '#60a5fa',
|
||||
|
||||
infoColor: '#06b6d4',
|
||||
infoColorHover: '#0891b2',
|
||||
infoColorPressed: '#0e7490',
|
||||
|
||||
successColor: '#22c55e',
|
||||
successColorHover: '#16a34a',
|
||||
successColorPressed: '#15803d',
|
||||
|
||||
warningColor: '#f59e0b',
|
||||
warningColorHover: '#d97706',
|
||||
warningColorPressed: '#b45309',
|
||||
|
||||
errorColor: '#ef4444',
|
||||
errorColorHover: '#dc2626',
|
||||
errorColorPressed: '#b91c1c',
|
||||
|
||||
@@ -168,6 +234,8 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
textColorGhost: '#475569',
|
||||
textColorGhostHover: '#1e293b',
|
||||
textColorGhostPressed: '#334155',
|
||||
borderColor: '#e2e8f0',
|
||||
borderColorHover: '#cbd5e1',
|
||||
},
|
||||
|
||||
Input: {
|
||||
@@ -184,11 +252,19 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 清理函数
|
||||
const cleanup = () => {
|
||||
systemPrefersDark.removeEventListener('change', handleSystemThemeChange)
|
||||
}
|
||||
|
||||
return {
|
||||
isDark,
|
||||
followSystem,
|
||||
theme,
|
||||
themeOverrides,
|
||||
initTheme,
|
||||
toggleTheme
|
||||
toggleTheme,
|
||||
setFollowSystem,
|
||||
cleanup
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user