vue+vite 全局主题

好的!下面是一个更清晰的项目结构和每个文件的代码内容,我会将每个文件的位置、作用以及内容详细列出,帮助你更好地组织和理解整个项目。


🎨 项目结构

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 实现多主题切换的项目核心代码,支持 lightdarksolarizeddracula 四种主题:


✅ 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"> 实现不同主题风格。

相关推荐
Hilaku12 分钟前
用好了 watchEffect 才算会用 Vue3 —— 那些让人误解的响应式陷阱
前端·javascript·vue.js
GISer_Jing16 分钟前
Zustand 状态管理库:极简而强大的解决方案
前端·javascript·react.js
zacksleo16 分钟前
鸿蒙Flutter实战:25-混合开发详解-5-跳转Flutter页面
前端·flutter·harmonyos
zacksleo23 分钟前
鸿蒙Flutter实战:23-混合开发详解-3-源码模式引入
前端·flutter·harmonyos
三年三月24 分钟前
018-场景遍历和世界坐标系
前端·three.js
zacksleo25 分钟前
鸿蒙Flutter实战:22-混合开发详解-2-Har包模式引入
前端·flutter·harmonyos
doubleZ28 分钟前
使用Trae从零开发一个跳转顶部的Chrome插件
前端·javascript
RR133528 分钟前
图标统计页面的设计与控件 Apache echarts
前端·apache·echarts
怀予32 分钟前
JavaScript 对象拯救计划:从"对象恐惧症"到"对象操纵大师"!
前端
zacksleo33 分钟前
鸿蒙Flutter实战:21-混合开发详解-1-概述
前端·flutter·harmonyos