mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 09:36:05 +00:00
feat(frontend): unify theming on vanilla AD-Vue light/dark/ultra-dark
The legacy panel CSS (custom.min.css ported as legacy.css) tinted every non-primary button teal-green via .dark .ant-btn:not(.ant-btn-primary) overrides while AD-Vue 4's darkAlgorithm kept primary buttons blue — producing the mixed blue/green button look on dark mode. Drop legacy.css entirely and let AD-Vue 4's algorithms own the palette. Centralize antdThemeConfig in useTheme.js so every page resolves to the same source of truth (light = defaultAlgorithm, dark = darkAlgorithm, ultra-dark = darkAlgorithm + deeper colorBgBase/Layout/Container/ Elevated tokens). Each page's <a-config-provider> now imports the shared computed instead of defining its own copy. Drops the 67 KB legacy CSS chunk; per-page CSS bundles fall to ≤5.9 KB. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
1e1a585541
commit
5f1aba28b0
12 changed files with 38 additions and 56 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import { reactive, computed, watchEffect } from 'vue';
|
import { reactive, computed, watchEffect } from 'vue';
|
||||||
|
import { theme as antdTheme } from 'ant-design-vue';
|
||||||
|
|
||||||
// Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
|
// Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
|
||||||
// from any component to read/toggle. Boot side-effects (apply current
|
// from any component to read/toggle. Boot side-effects (apply current
|
||||||
|
|
@ -24,6 +25,27 @@ export const theme = reactive({
|
||||||
|
|
||||||
export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light'));
|
export const currentTheme = computed(() => (theme.isDark ? 'dark' : 'light'));
|
||||||
|
|
||||||
|
// AD-Vue 4 theme config consumed by every page's <a-config-provider>.
|
||||||
|
// Three modes — light / dark / ultra-dark — all share AD-Vue's vanilla
|
||||||
|
// blue primary. Ultra-dark layers deeper background tokens on top of
|
||||||
|
// darkAlgorithm so layouts/cards/popups all darken together.
|
||||||
|
const ULTRA_DARK_TOKENS = {
|
||||||
|
colorBgBase: '#000',
|
||||||
|
colorBgLayout: '#000',
|
||||||
|
colorBgContainer: '#0a0a0a',
|
||||||
|
colorBgElevated: '#141414',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const antdThemeConfig = computed(() => {
|
||||||
|
if (!theme.isDark) {
|
||||||
|
return { algorithm: antdTheme.defaultAlgorithm };
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
algorithm: antdTheme.darkAlgorithm,
|
||||||
|
token: theme.isUltra ? ULTRA_DARK_TOKENS : undefined,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
export function toggleTheme() {
|
export function toggleTheme() {
|
||||||
theme.isDark = !theme.isDark;
|
theme.isDark = !theme.isDark;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import Antd, { message } from 'ant-design-vue';
|
import Antd, { message } from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
import '@/styles/legacy.css';
|
|
||||||
|
|
||||||
import { setupAxios } from '@/api/axios-init.js';
|
import { setupAxios } from '@/api/axios-init.js';
|
||||||
import '@/composables/useTheme.js';
|
import '@/composables/useTheme.js';
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import Antd, { message } from 'ant-design-vue';
|
import Antd, { message } from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
// Legacy panel CSS — overrides AD-Vue defaults to match the
|
|
||||||
// pre-migration look (palette, dark mode contrast, tag colors,
|
|
||||||
// table/tooltip styling). Loaded after AD-Vue's reset so its
|
|
||||||
// rules win.
|
|
||||||
import '@/styles/legacy.css';
|
|
||||||
|
|
||||||
import { setupAxios } from '@/api/axios-init.js';
|
import { setupAxios } from '@/api/axios-init.js';
|
||||||
// Importing useTheme triggers the boot side-effect that applies the
|
// Importing useTheme triggers the boot side-effect that applies the
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import Antd, { message } from 'ant-design-vue';
|
import Antd, { message } from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
import '@/styles/legacy.css';
|
|
||||||
|
|
||||||
import { setupAxios } from '@/api/axios-init.js';
|
import { setupAxios } from '@/api/axios-init.js';
|
||||||
// Importing this module triggers the boot side-effect that applies the
|
// Importing this module triggers the boot side-effect that applies the
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { theme as antdTheme, Modal, message } from 'ant-design-vue';
|
import { Modal, message } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
SwapOutlined,
|
SwapOutlined,
|
||||||
PieChartOutlined,
|
PieChartOutlined,
|
||||||
|
|
@ -12,7 +12,7 @@ import {
|
||||||
|
|
||||||
import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils';
|
import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils';
|
||||||
import { Inbound } from '@/models/inbound.js';
|
import { Inbound } from '@/models/inbound.js';
|
||||||
import { theme as themeState } from '@/composables/useTheme.js';
|
import { theme as themeState, antdThemeConfig } from '@/composables/useTheme.js';
|
||||||
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
||||||
import AppSidebar from '@/components/AppSidebar.vue';
|
import AppSidebar from '@/components/AppSidebar.vue';
|
||||||
import CustomStatistic from '@/components/CustomStatistic.vue';
|
import CustomStatistic from '@/components/CustomStatistic.vue';
|
||||||
|
|
@ -28,10 +28,6 @@ import { useInbounds } from './useInbounds.js';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const antdThemeConfig = computed(() => ({
|
|
||||||
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fetched,
|
fetched,
|
||||||
refreshing,
|
refreshing,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { theme as antdTheme } from 'ant-design-vue';
|
|
||||||
import {
|
import {
|
||||||
BarsOutlined,
|
BarsOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
|
|
@ -19,7 +18,7 @@ import {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
import { HttpUtil, SizeFormatter, TimeFormatter } from '@/utils';
|
import { HttpUtil, SizeFormatter, TimeFormatter } from '@/utils';
|
||||||
import { theme as themeState } from '@/composables/useTheme.js';
|
import { theme as themeState, antdThemeConfig } from '@/composables/useTheme.js';
|
||||||
import { useStatus } from '@/composables/useStatus.js';
|
import { useStatus } from '@/composables/useStatus.js';
|
||||||
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
||||||
import AppSidebar from '@/components/AppSidebar.vue';
|
import AppSidebar from '@/components/AppSidebar.vue';
|
||||||
|
|
@ -34,11 +33,6 @@ import CpuHistoryModal from './CpuHistoryModal.vue';
|
||||||
import XrayLogModal from './XrayLogModal.vue';
|
import XrayLogModal from './XrayLogModal.vue';
|
||||||
import VersionModal from './VersionModal.vue';
|
import VersionModal from './VersionModal.vue';
|
||||||
|
|
||||||
// Drive AD-Vue 4's built-in dark algorithm from our reactive theme.
|
|
||||||
const antdThemeConfig = computed(() => ({
|
|
||||||
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { status, fetched, refresh } = useStatus();
|
const { status, fetched, refresh } = useStatus();
|
||||||
const { isMobile } = useMediaQuery();
|
const { isMobile } = useMediaQuery();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue';
|
||||||
import { theme as antdTheme } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { HttpUtil } from '@/utils';
|
import { HttpUtil } from '@/utils';
|
||||||
import { currentTheme, theme as themeState } from '@/composables/useTheme.js';
|
import {
|
||||||
|
antdThemeConfig,
|
||||||
|
currentTheme,
|
||||||
|
theme as themeState,
|
||||||
|
} from '@/composables/useTheme.js';
|
||||||
import ThemeSwitchLogin from '@/components/ThemeSwitchLogin.vue';
|
import ThemeSwitchLogin from '@/components/ThemeSwitchLogin.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
// Drive AD-Vue 4's built-in dark algorithm from our useTheme state.
|
|
||||||
// This re-themes every AD-Vue component without depending on the
|
|
||||||
// legacy panel's custom.min.css.
|
|
||||||
const antdThemeConfig = computed(() => ({
|
|
||||||
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Cycle the title between "Hello" and "Welcome" — matches the legacy
|
// Cycle the title between "Hello" and "Welcome" — matches the legacy
|
||||||
// panel's Vue 2 .is-visible / .is-hidden DOM-class swap, but driven
|
// panel's Vue 2 .is-visible / .is-hidden DOM-class swap, but driven
|
||||||
// reactively + with a Vue 3 <Transition> for the fade.
|
// reactively + with a Vue 3 <Transition> for the fade.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { onMounted, ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { theme as antdTheme, Modal } from 'ant-design-vue';
|
import { Modal } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SafetyOutlined,
|
SafetyOutlined,
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
import { HttpUtil, PromiseUtil } from '@/utils';
|
import { HttpUtil, PromiseUtil } from '@/utils';
|
||||||
import { theme as themeState } from '@/composables/useTheme.js';
|
import { theme as themeState, antdThemeConfig } from '@/composables/useTheme.js';
|
||||||
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
||||||
import AppSidebar from '@/components/AppSidebar.vue';
|
import AppSidebar from '@/components/AppSidebar.vue';
|
||||||
import { useAllSetting } from './useAllSetting.js';
|
import { useAllSetting } from './useAllSetting.js';
|
||||||
|
|
@ -23,10 +23,6 @@ import SubscriptionFormatsTab from './SubscriptionFormatsTab.vue';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const antdThemeConfig = computed(() => ({
|
|
||||||
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { fetched, spinning, saveDisabled, allSetting, fetchAll, saveAll } = useAllSetting();
|
const { fetched, spinning, saveDisabled, allSetting, fetchAll, saveAll } = useAllSetting();
|
||||||
const { isMobile } = useMediaQuery();
|
const { isMobile } = useMediaQuery();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
import { theme as antdTheme, Modal } from 'ant-design-vue';
|
import { Modal, message } from 'ant-design-vue';
|
||||||
import {
|
import {
|
||||||
SettingOutlined,
|
SettingOutlined,
|
||||||
SwapOutlined,
|
SwapOutlined,
|
||||||
|
|
@ -12,9 +12,8 @@ import {
|
||||||
QuestionCircleOutlined,
|
QuestionCircleOutlined,
|
||||||
} from '@ant-design/icons-vue';
|
} from '@ant-design/icons-vue';
|
||||||
|
|
||||||
import { theme as themeState } from '@/composables/useTheme.js';
|
import { theme as themeState, antdThemeConfig } from '@/composables/useTheme.js';
|
||||||
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
import { useMediaQuery } from '@/composables/useMediaQuery.js';
|
||||||
import { message } from 'ant-design-vue';
|
|
||||||
import AppSidebar from '@/components/AppSidebar.vue';
|
import AppSidebar from '@/components/AppSidebar.vue';
|
||||||
import BasicsTab from './BasicsTab.vue';
|
import BasicsTab from './BasicsTab.vue';
|
||||||
import RoutingTab from './RoutingTab.vue';
|
import RoutingTab from './RoutingTab.vue';
|
||||||
|
|
@ -27,17 +26,6 @@ import { useXraySetting } from './useXraySetting.js';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
// Phase 6-i: scaffold + advanced JSON tab. Other tabs (Basics, Routing,
|
|
||||||
// Outbounds, Balancers, DNS) land in subsequent 6-ii…vi commits — they
|
|
||||||
// each need their own tree of structured forms or a dedicated modal.
|
|
||||||
// For now they show an a-empty placeholder so the navigation is
|
|
||||||
// stable and users can still edit the full config via the Advanced
|
|
||||||
// (JSON) tab.
|
|
||||||
|
|
||||||
const antdThemeConfig = computed(() => ({
|
|
||||||
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fetched,
|
fetched,
|
||||||
spinning,
|
spinning,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import Antd, { message } from 'ant-design-vue';
|
import Antd, { message } from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
import '@/styles/legacy.css';
|
|
||||||
|
|
||||||
import { setupAxios } from '@/api/axios-init.js';
|
import { setupAxios } from '@/api/axios-init.js';
|
||||||
// Importing useTheme triggers the boot side-effect that applies the
|
// Importing useTheme triggers the boot side-effect that applies the
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -1,7 +1,6 @@
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import Antd, { message } from 'ant-design-vue';
|
import Antd, { message } from 'ant-design-vue';
|
||||||
import 'ant-design-vue/dist/reset.css';
|
import 'ant-design-vue/dist/reset.css';
|
||||||
import '@/styles/legacy.css';
|
|
||||||
|
|
||||||
import { setupAxios } from '@/api/axios-init.js';
|
import { setupAxios } from '@/api/axios-init.js';
|
||||||
import '@/composables/useTheme.js';
|
import '@/composables/useTheme.js';
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue