feat: 改造 theme store 支持暗黑模式双选架构
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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="移动端">
|
||||
|
||||
@@ -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('')
|
||||
|
||||
@@ -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[]>([])
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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[]>([])
|
||||
|
||||
@@ -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,11 +165,25 @@ 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>
|
||||
<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>
|
||||
|
||||
<!-- Manual Remind -->
|
||||
@@ -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); }
|
||||
|
||||
@@ -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>()
|
||||
|
||||
Reference in New Issue
Block a user