feat: 改造 theme store 支持暗黑模式双选架构

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-13 01:45:56 +08:00
parent 6fd9cfbff9
commit ce24937ba0
8 changed files with 79 additions and 30 deletions
+5 -6
View File
@@ -62,8 +62,7 @@ const roleLabel = computed(() => {
})
function cycleTheme() {
const next = themeStore.currentTheme === 'editorial' ? 'light' : 'editorial'
themeStore.setTheme(next)
themeStore.toggleMode()
}
</script>
@@ -108,11 +107,11 @@ function cycleTheme() {
</svg>
</button>
<div class="topbar-actions">
<button class="topbar-btn" @click="cycleTheme" :title="'切换至' + themeLabels[themeStore.currentTheme === 'editorial' ? 'light' : 'editorial']">
<button class="topbar-btn" @click="cycleTheme" :title="'切换至' + (themeStore.mode === 'light' ? '深色模式' : '亮色模式')">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle v-if="themeStore.currentTheme === 'editorial'" cx="12" cy="12" r="5"></circle>
<path v-if="themeStore.currentTheme === 'editorial'" d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"></path>
<path v-if="themeStore.currentTheme === 'light'" d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
<path v-if="themeStore.mode === 'light'" d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
<circle v-if="themeStore.mode === 'dark'" cx="12" cy="12" r="5"></circle>
<path v-if="themeStore.mode === 'dark'" d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"></path>
</svg>
</button>
<button class="topbar-btn" @click="router.push('/m')" title="移动端">
+44 -13
View File
@@ -1,30 +1,61 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { ref, computed } from 'vue'
export type Theme = 'editorial' | 'light'
export type Theme = 'editorial' | 'light' | 'minimal-dark' | 'tech-dark'
type Mode = 'light' | 'dark'
export const themeLabels: Record<Theme, string> = {
editorial: '编辑风',
light: '极简亮色',
'minimal-dark': '极简深色',
'tech-dark': '科技深色',
}
export const useThemeStore = defineStore('theme', () => {
const currentTheme = ref<Theme>(
(localStorage.getItem('theme') as Theme) || 'editorial'
)
export const lightThemes: Theme[] = ['editorial', 'light']
export const darkThemes: Theme[] = ['minimal-dark', 'tech-dark']
function applyTheme(theme: Theme) {
export const useThemeStore = defineStore('theme', () => {
const mode = ref<Mode>((localStorage.getItem('theme-mode') as Mode) || 'light')
const lightTheme = ref<Theme>((localStorage.getItem('theme-light') as Theme) || 'editorial')
const darkTheme = ref<Theme>((localStorage.getItem('theme-dark') as Theme) || 'tech-dark')
const effectiveTheme = computed<Theme>(() => {
return mode.value === 'light' ? lightTheme.value : darkTheme.value
})
function applyTheme(theme: Theme, isDark: boolean) {
document.documentElement.setAttribute('data-theme', theme)
if (isDark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
}
function setTheme(theme: Theme) {
currentTheme.value = theme
localStorage.setItem('theme', theme)
applyTheme(theme)
function apply() {
applyTheme(effectiveTheme.value, mode.value === 'dark')
}
function setLightTheme(theme: Theme) {
lightTheme.value = theme
localStorage.setItem('theme-light', theme)
if (mode.value === 'light') apply()
}
function setDarkTheme(theme: Theme) {
darkTheme.value = theme
localStorage.setItem('theme-dark', theme)
if (mode.value === 'dark') apply()
}
function toggleMode() {
mode.value = mode.value === 'light' ? 'dark' : 'light'
localStorage.setItem('theme-mode', mode.value)
apply()
}
// Apply on init
applyTheme(currentTheme.value)
apply()
return { currentTheme, setTheme }
return { mode, lightTheme, darkTheme, effectiveTheme, setLightTheme, setDarkTheme, toggleMode }
})
@@ -11,7 +11,7 @@ import axios from 'axios'
const auth = useAuthStore()
const themeStore = useThemeStore()
const isLight = computed(() => themeStore.currentTheme === 'light')
const isLight = computed(() => themeStore.mode === 'light')
const loading = ref(false)
const search = ref('')
const filterIndustry = ref('')
+1 -1
View File
@@ -12,7 +12,7 @@ import EditLogPanel from '@/components/EditLogPanel.vue'
const auth = useAuthStore()
const themeStore = useThemeStore()
const loading = ref(false)
const isLight = computed(() => themeStore.currentTheme === 'light')
const isLight = computed(() => themeStore.mode === 'light')
const keyVisits = ref<any[]>([])
const customers = ref<any[]>([])
const allManagers = ref<any[]>([])
+1 -1
View File
@@ -27,7 +27,7 @@ const leaveTypeColors: Record<string, string> = {
const filterManager = ref('')
const filterStatus = ref('')
const isLight = computed(() => themeStore.currentTheme === 'light')
const isLight = computed(() => themeStore.mode === 'light')
const today = new Date().toISOString().slice(0, 10)
+1 -1
View File
@@ -13,7 +13,7 @@ import EditLogPanel from '@/components/EditLogPanel.vue'
const auth = useAuthStore()
const themeStore = useThemeStore()
const loading = ref(false)
const isLight = computed(() => themeStore.currentTheme === 'light')
const isLight = computed(() => themeStore.mode === 'light')
const miniBusiness = ref<any[]>([])
const customers = ref<any[]>([])
const allManagers = ref<any[]>([])
+25 -6
View File
@@ -2,7 +2,7 @@
import { ref, onMounted } from 'vue'
import { ElMessage } from 'element-plus'
import api from '@/api/index'
import { useThemeStore, themeLabels, type Theme } from '@/stores/theme'
import { useThemeStore, themeLabels, type Theme, lightThemes, darkThemes } from '@/stores/theme'
const themeStore = useThemeStore()
@@ -165,10 +165,24 @@ async function handleImportPreview() {
</div>
</template>
<div class="theme-options">
<label v-for="t in (['editorial', 'light'] as Theme[])" :key="t" class="theme-opt" :class="{ 'theme-opt--active': themeStore.currentTheme === t }" @click="themeStore.setTheme(t)">
<span class="theme-opt-radio">{{ themeStore.currentTheme === t ? '●' : '○' }}</span>
<span class="theme-opt-label">{{ themeLabels[t] }}</span>
</label>
<div class="theme-mode-row">
<span class="theme-mode-label">模式</span>
<button class="theme-mode-btn" @click="themeStore.toggleMode()">
{{ themeStore.mode === 'light' ? ' 亮色模式' : '🌙 深色模式' }}
</button>
</div>
<div class="theme-sub-options">
<label
v-for="t in (themeStore.mode === 'light' ? lightThemes : darkThemes)"
:key="t"
class="theme-opt"
:class="{ 'theme-opt--active': (themeStore.mode === 'light' ? themeStore.lightTheme : themeStore.darkTheme) === t }"
@click="themeStore.mode === 'light' ? themeStore.setLightTheme(t) : themeStore.setDarkTheme(t)"
>
<span class="theme-opt-radio">{{ (themeStore.mode === 'light' ? themeStore.lightTheme : themeStore.darkTheme) === t ? '●' : '○' }}</span>
<span class="theme-opt-label">{{ themeLabels[t] }}</span>
</label>
</div>
</div>
</el-card>
@@ -395,7 +409,12 @@ async function handleImportPreview() {
.setting-label { font-family: var(--font-body); font-size: 13px; color: var(--ink); }
/* ── Theme Selector ── */
.theme-options { display: flex; gap: 16px; }
.theme-options { display: flex; flex-direction: column; gap: 12px; }
.theme-mode-row { display: flex; align-items: center; gap: 12px; padding-bottom: 8px; border-bottom: 1px solid var(--warm-border); margin-bottom: 4px; }
.theme-mode-label { font-family: var(--font-heading); font-size: 13px; color: var(--warm-gray); }
.theme-mode-btn { font-family: var(--font-body); font-size: 14px; cursor: pointer; padding: 6px 16px; border: 1px solid var(--warm-border); border-radius: 6px; background: var(--c-primary-bg); color: var(--c-text); transition: all var(--transition); }
.theme-mode-btn:hover { border-color: var(--gold); }
.theme-sub-options { display: flex; gap: 12px; padding-left: 4px; }
.theme-opt { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 8px 16px; border: 1px solid var(--warm-border); transition: all var(--transition); }
.theme-opt:hover { border-color: var(--ink); }
.theme-opt--active { border-color: var(--ink); background: var(--c-primary-bg); }
+1 -1
View File
@@ -31,7 +31,7 @@ const searchText = ref('')
function onSearch() { loadPlans() }
const isLight = computed(() => themeStore.currentTheme === 'light')
const isLight = computed(() => themeStore.mode === 'light')
const managerOptions = computed(() => {
const seen = new Set<string>()