mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-14 01:56:03 +00:00
Bumps Vite to 8.0.11 (npm install picked up 6.4.2 from the stale
lockfile; clean install resolves the new constraint). Bumps vue-i18n
to 11.1.4 since v10 was just EOL'd.
Migrates aThemeSwitch.html — the two-flavor theme picker + global
themeSwitcher object — into:
- composables/useTheme.js: single reactive `theme` state with
toggleTheme / toggleUltra. Boot side-effect applies the stored theme
to <body>/<html> before Vue renders; watchEffect persists changes
back to localStorage.
- components/ThemeSwitch.vue: full menu version for the main panel.
- components/ThemeSwitchLogin.vue: login-popover version.
AD-Vue 1 → 4 changes hit on this component:
- <a-icon type="bulb" :theme="filled|outlined"> dropped — replaced by
explicit BulbFilled / BulbOutlined imports from
@ant-design/icons-vue, swapped via <component :is="BulbIcon">
- Vue.component('a-theme-switch', { ... }) global registration → SFC
+ per-page import
- this.$message.config(...) (Vue 2 instance method) → message.config(...)
imported from ant-design-vue, called once in login.js at boot
Login page now surfaces a settings button → popover → theme picker.
Known gap: web/assets/css/custom.min.css isn't yet imported into the
new bundle, so toggling dark mode currently only re-themes AD-Vue's
own components, not the panel chrome. The body class is still toggled
so behavior is correct; visual fidelity returns when custom.css is
ported or directly imported.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
67 lines
2.2 KiB
JavaScript
67 lines
2.2 KiB
JavaScript
import { reactive, computed, watchEffect } from 'vue';
|
|
|
|
// Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
|
|
// from any component to read/toggle. Boot side-effects (apply current
|
|
// theme to <body>/<html>) run once at module load so the page is in the
|
|
// right theme before Vue mounts.
|
|
|
|
const STORAGE_DARK = 'dark-mode';
|
|
const STORAGE_ULTRA = 'isUltraDarkThemeEnabled';
|
|
|
|
function readBool(key, fallback) {
|
|
const raw = localStorage.getItem(key);
|
|
if (raw === null) return fallback;
|
|
return raw === 'true';
|
|
}
|
|
|
|
const isDark = readBool(STORAGE_DARK, true);
|
|
const isUltra = readBool(STORAGE_ULTRA, false);
|
|
|
|
export const theme = reactive({
|
|
isDark,
|
|
isUltra,
|
|
});
|
|
|
|
export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light'));
|
|
|
|
export function toggleTheme() {
|
|
theme.isDark = !theme.isDark;
|
|
}
|
|
|
|
export function toggleUltra() {
|
|
theme.isUltra = !theme.isUltra;
|
|
}
|
|
|
|
// Briefly disable theme transition animations while a toggle is in
|
|
// flight, then re-enable on mouseleave. Mirrors the legacy panel's
|
|
// behavior of preventing flicker when hovering the theme menu.
|
|
export function pauseAnimationsUntilLeave(elementId) {
|
|
document.documentElement.setAttribute('data-theme-animations', 'off');
|
|
const el = document.getElementById(elementId);
|
|
if (!el) return;
|
|
const restore = () => {
|
|
document.documentElement.removeAttribute('data-theme-animations');
|
|
el.removeEventListener('mouseleave', restore);
|
|
el.removeEventListener('touchend', restore);
|
|
};
|
|
el.addEventListener('mouseleave', restore);
|
|
el.addEventListener('touchend', restore);
|
|
}
|
|
|
|
// Apply theme to DOM and persist whenever it changes.
|
|
watchEffect(() => {
|
|
document.body.setAttribute('class', theme.isDark ? 'dark' : 'light');
|
|
localStorage.setItem(STORAGE_DARK, String(theme.isDark));
|
|
|
|
if (theme.isUltra) {
|
|
document.documentElement.setAttribute('data-theme', 'ultra-dark');
|
|
} else {
|
|
document.documentElement.removeAttribute('data-theme');
|
|
}
|
|
localStorage.setItem(STORAGE_ULTRA, String(theme.isUltra));
|
|
|
|
// Keep the global #message container's class in sync so AD-Vue toasts
|
|
// pick up the right styling.
|
|
const msg = document.getElementById('message');
|
|
if (msg) msg.className = theme.isDark ? 'dark' : 'light';
|
|
});
|