好的!下面是一个更清晰的项目结构和每个文件的代码内容,我会将每个文件的位置、作用以及内容详细列出,帮助你更好地组织和理解整个项目。
🎨 项目结构
csharp
src/
│
├── assets/
│ ├── global.css # 全局样式,包括主题相关的 CSS 变量
│
├── components/
│ ├── ThemeProvider.vue # 用于提供主题状态并处理主题切换
│
├── theme-context.ts # 主题上下文定义
├── App.vue # 根组件,包含 ThemeProvider
└── main.ts # 入口文件,初始化应用
1. src/assets/global.css
--- 全局样式(包括主题配色)
css
/* assets/global.css */
:root {
--bg-color: #ffffff; /* 背景色 */
--text-color: #333333; /* 文字颜色 */
--button-bg: #4CAF50; /* 按钮背景色 */
--button-text-color: #ffffff; /* 按钮文字颜色 */
--card-bg: #f4f4f4; /* 卡片背景色 */
--link-color: #1E90FF; /* 链接颜色 */
}
html.dark {
--bg-color: #121212; /* 背景色 */
--text-color: #e0e0e0; /* 文字颜色 */
--button-bg: #4CAF50; /* 按钮背景色 */
--button-text-color: #ffffff; /* 按钮文字颜色 */
--card-bg: #2c2c2c; /* 卡片背景色 */
--link-color: #1E90FF; /* 链接颜色 */
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Arial', sans-serif;
transition: background-color 0.3s, color 0.3s; /* 为过渡添加动画效果 */
}
a {
color: var(--link-color);
text-decoration: none;
}
button {
background-color: var(--button-bg);
color: var(--button-text-color);
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: darken(var(--button-bg), 10%);
}
.card {
background-color: var(--card-bg);
color: var(--text-color);
padding: 1rem;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
2. src/theme-context.ts
--- 主题上下文
typescript
// theme-context.ts
import { InjectionKey, Ref } from 'vue'
export type Theme = 'light' | 'dark'
export interface ThemeContext {
theme: Ref<Theme>
toggleTheme: () => void
setTheme: (t: Theme) => void
}
export const ThemeSymbol: InjectionKey<ThemeContext> = Symbol('ThemeContext')
3. src/components/ThemeProvider.vue
--- 主题提供器
xml
<template>
<div :class="theme">
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, provide, computed, watchEffect } from 'vue'
import { ThemeSymbol, Theme } from '../theme-context'
const theme = ref<Theme>('light')
// 切换主题函数
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light'
}
// 设置指定主题
const setTheme = (t: Theme) => {
theme.value = t
}
// 自动根据本地存储或系统偏好设置主题
if (localStorage.getItem('theme')) {
theme.value = localStorage.getItem('theme') as Theme
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
theme.value = 'dark'
}
watchEffect(() => {
// 将主题保存到 localStorage
localStorage.setItem('theme', theme.value)
// 根据主题切换类名
document.documentElement.className = theme.value
})
provide(ThemeSymbol, {
theme,
toggleTheme,
setTheme,
})
const themeClass = computed(() => theme.value)
</script>
4. src/App.vue
--- 根组件,使用 ThemeProvider
xml
<template>
<ThemeProvider>
<router-view />
</ThemeProvider>
</template>
<script setup lang="ts">
import ThemeProvider from './components/ThemeProvider.vue'
</script>
5. src/main.ts
--- 入口文件,初始化 Vue 应用
javascript
import { createApp } from 'vue'
import App from './App.vue'
import './assets/global.css' // 引入全局样式
createApp(App).mount('#app')
6. 使用 inject
获取主题并在组件中使用
xml
<template>
<div class="card">
<p>当前主题:{{ theme }}</p>
<button @click="toggleTheme">切换主题</button>
</div>
</template>
<script setup lang="ts">
import { inject } from 'vue'
import { ThemeSymbol } from '../theme-context'
const themeContext = inject(ThemeSymbol)
if (!themeContext) {
throw new Error('Theme context not provided')
}
const { theme, toggleTheme } = themeContext
</script>
<style scoped>
.card {
background-color: var(--card-bg);
color: var(--text-color);
padding: 1rem;
}
</style>
📝 小结
global.css
:定义了全局的主题配色,并通过 CSS 变量控制亮色与暗色主题。theme-context.ts
:定义了一个ThemeContext
类型,用来传递和管理当前的主题状态。ThemeProvider.vue
:提供了主题的状态、切换方法,并将它们通过provide
传递给整个应用。App.vue
:根组件,在此处包裹ThemeProvider
以便让整个应用都可以访问主题。main.ts
:入口文件,初始化 Vue 应用,并引入全局样式。
通过这个结构,你可以灵活地管理和切换主题,同时保持代码清晰和可维护。主题的切换效果是动态的,会实时反映在整个页面上。如果有其他需求或问题,随时告诉我!
多种主题设计
以下是一个完整可运行的 Vue 3 + TypeScript + Composition API 实现多主题切换的项目核心代码,支持 light
、dark
、solarized
、dracula
四种主题:
✅ 1. src/theme-context.ts
-- 定义主题上下文
typescript
// src/theme-context.ts
import { InjectionKey, Ref } from 'vue'
export type Theme = 'light' | 'dark' | 'solarized' | 'dracula'
export interface ThemeContext {
theme: Ref<Theme>
toggleTheme: () => void
setTheme: (t: Theme) => void
}
export const ThemeSymbol: InjectionKey<ThemeContext> = Symbol('ThemeContext')
✅ 2. src/assets/global.css
-- 全局样式 + 多主题变量
css
/* src/assets/global.css */
:root {
--bg-color: #ffffff;
--text-color: #333333;
--button-bg: #4CAF50;
--button-text-color: #ffffff;
--card-bg: #f4f4f4;
--link-color: #1E90FF;
}
html.dark {
--bg-color: #121212;
--text-color: #e0e0e0;
--button-bg: #4CAF50;
--button-text-color: #ffffff;
--card-bg: #2c2c2c;
--link-color: #1E90FF;
}
html.solarized {
--bg-color: #fdf6e3;
--text-color: #657b83;
--button-bg: #268bd2;
--button-text-color: #ffffff;
--card-bg: #eee8d5;
--link-color: #b58900;
}
html.dracula {
--bg-color: #282a36;
--text-color: #f8f8f2;
--button-bg: #6272a4;
--button-text-color: #ffffff;
--card-bg: #44475a;
--link-color: #8be9fd;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: 'Arial', sans-serif;
transition: background-color 0.3s, color 0.3s;
}
button {
background-color: var(--button-bg);
color: var(--button-text-color);
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
}
.card {
background-color: var(--card-bg);
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
✅ 3. src/components/ThemeProvider.vue
-- 主题提供器
xml
<!-- src/components/ThemeProvider.vue -->
<template>
<div :class="theme">
<slot />
</div>
</template>
<script setup lang="ts">
import { ref, provide, watchEffect } from 'vue'
import { ThemeSymbol, Theme } from '../theme-context'
const theme = ref<Theme>('light')
const toggleTheme = () => {
const themes: Theme[] = ['light', 'dark', 'solarized', 'dracula']
const index = themes.indexOf(theme.value)
theme.value = themes[(index + 1) % themes.length]
}
const setTheme = (t: Theme) => {
theme.value = t
}
const stored = localStorage.getItem('theme')
if (stored && ['light', 'dark', 'solarized', 'dracula'].includes(stored)) {
theme.value = stored as Theme
}
watchEffect(() => {
localStorage.setItem('theme', theme.value)
document.documentElement.className = theme.value
})
provide(ThemeSymbol, {
theme,
toggleTheme,
setTheme,
})
</script>
✅ 4. src/components/ThemeSelector.vue
-- 用户切换主题组件
xml
<!-- src/components/ThemeSelector.vue -->
<template>
<div class="card">
<p>当前主题:{{ theme }}</p>
<select v-model="selected" @change="setTheme(selected)">
<option value="light">浅色</option>
<option value="dark">深色</option>
<option value="solarized">Solarized</option>
<option value="dracula">Dracula</option>
</select>
</div>
</template>
<script setup lang="ts">
import { inject, ref, watch } from 'vue'
import { ThemeSymbol, Theme } from '../theme-context'
const themeContext = inject(ThemeSymbol)
if (!themeContext) throw new Error('Theme context not provided')
const { theme, setTheme } = themeContext
const selected = ref<Theme>(theme.value)
watch(theme, (val) => {
selected.value = val
})
</script>
✅ 5. src/App.vue
-- 应用根组件
xml
<!-- src/App.vue -->
<template>
<ThemeProvider>
<ThemeSelector />
</ThemeProvider>
</template>
<script setup lang="ts">
import ThemeProvider from './components/ThemeProvider.vue'
import ThemeSelector from './components/ThemeSelector.vue'
</script>
✅ 6. src/main.ts
-- 应用入口
javascript
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'
import './assets/global.css'
createApp(App).mount('#app')
✅ 使用方式总结
- 启动后自动应用 localStorage 中保存的主题或默认 light。
<ThemeSelector />
提供下拉菜单供用户选择主题。- CSS 变量基于
<html class="theme-name">
实现不同主题风格。