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:
MHSanaeiandClaude Opus 4.7 committed 2026-05-08 17:39:36 +02:00
1 parent 1e1a585541
commit 5f1aba28b0
12 files changed
+38 -56

No files matched your search

+22
View File
@@ -1,4 +1,5 @@
import { reactive, computed, watchEffect } from 'vue';
import { theme as antdTheme } from 'ant-design-vue';
// Single shared theme state. `import { theme } from '@/composables/useTheme.js'`
// 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'));
// 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() {
theme.isDark = !theme.isDark;
}
-1
View File
@@ -1,7 +1,6 @@
import { createApp } from 'vue';
import Antd, { message } from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import '@/styles/legacy.css';
import { setupAxios } from '@/api/axios-init.js';
import '@/composables/useTheme.js';
-5
View File
@@ -1,11 +1,6 @@
import { createApp } from 'vue';
import Antd, { message } from 'ant-design-vue';
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';
// Importing useTheme triggers the boot side-effect that applies the
-1
View File
@@ -1,7 +1,6 @@
import { createApp } from 'vue';
import Antd, { message } from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import '@/styles/legacy.css';
import { setupAxios } from '@/api/axios-init.js';
// Importing this module triggers the boot side-effect that applies the
+3 -7
View File
@@ -1,7 +1,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { theme as antdTheme, Modal, message } from 'ant-design-vue';
import { Modal, message } from 'ant-design-vue';
import {
SwapOutlined,
PieChartOutlined,
@@ -12,7 +12,7 @@ import {
import { HttpUtil, SizeFormatter, RandomUtil } from '@/utils';
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 AppSidebar from '@/components/AppSidebar.vue';
import CustomStatistic from '@/components/CustomStatistic.vue';
@@ -28,10 +28,6 @@ import { useInbounds } from './useInbounds.js';
const { t } = useI18n();
const antdThemeConfig = computed(() => ({
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
}));
const {
fetched,
refreshing,
+1 -7
View File
@@ -1,7 +1,6 @@
<script setup>
import { computed, onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { theme as antdTheme } from 'ant-design-vue';
import {
BarsOutlined,
ControlOutlined,
@@ -19,7 +18,7 @@ import {
const { t } = useI18n();
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 { useMediaQuery } from '@/composables/useMediaQuery.js';
import AppSidebar from '@/components/AppSidebar.vue';
@@ -34,11 +33,6 @@ import CpuHistoryModal from './CpuHistoryModal.vue';
import XrayLogModal from './XrayLogModal.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 { isMobile } = useMediaQuery();
+6 -10
View File
@@ -1,22 +1,18 @@
<script setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { UserOutlined, LockOutlined, KeyOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { theme as antdTheme } from 'ant-design-vue';
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';
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
// panel's Vue 2 .is-visible / .is-hidden DOM-class swap, but driven
// reactively + with a Vue 3 <Transition> for the fade.
+3 -7
View File
@@ -1,7 +1,7 @@
<script setup>
import { computed, onMounted, ref } from 'vue';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { theme as antdTheme, Modal } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import {
SettingOutlined,
SafetyOutlined,
@@ -11,7 +11,7 @@ import {
} from '@ant-design/icons-vue';
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 AppSidebar from '@/components/AppSidebar.vue';
import { useAllSetting } from './useAllSetting.js';
@@ -23,10 +23,6 @@ import SubscriptionFormatsTab from './SubscriptionFormatsTab.vue';
const { t } = useI18n();
const antdThemeConfig = computed(() => ({
algorithm: themeState.isDark ? antdTheme.darkAlgorithm : antdTheme.defaultAlgorithm,
}));
const { fetched, spinning, saveDisabled, allSetting, fetchAll, saveAll } = useAllSetting();
const { isMobile } = useMediaQuery();
+3 -15
View File
@@ -1,7 +1,7 @@
<script setup>
import { computed, ref } from 'vue';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { theme as antdTheme, Modal } from 'ant-design-vue';
import { Modal, message } from 'ant-design-vue';
import {
SettingOutlined,
SwapOutlined,
@@ -12,9 +12,8 @@ import {
QuestionCircleOutlined,
} 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 { message } from 'ant-design-vue';
import AppSidebar from '@/components/AppSidebar.vue';
import BasicsTab from './BasicsTab.vue';
import RoutingTab from './RoutingTab.vue';
@@ -27,17 +26,6 @@ import { useXraySetting } from './useXraySetting.js';
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 {
fetched,
spinning,
-1
View File
@@ -1,7 +1,6 @@
import { createApp } from 'vue';
import Antd, { message } from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import '@/styles/legacy.css';
import { setupAxios } from '@/api/axios-init.js';
// Importing useTheme triggers the boot side-effect that applies the
-1
View File
@@ -1 +0,0 @@
:root{--color-primary-100:#008771;--dark-color-background:#0a1222;--dark-color-surface-100:#151f31;--dark-color-surface-200:#222d42;--dark-color-surface-300:#2c3950;--dark-color-surface-400:rgba(65,85,119,.5);--dark-color-surface-500:#2c3950;--dark-color-surface-600:#313f5a;--dark-color-surface-700:#111929;--dark-color-surface-700-rgb:17,25,41;--dark-color-table-hover:rgba(44,57,80,.2);--dark-color-text-primary:rgba(255,255,255,.75);--dark-color-stroke:#2c3950;--dark-color-btn-danger:#cd3838;--dark-color-btn-danger-border:transparent;--dark-color-btn-danger-hover:#e94b4b;--dark-color-tag-bg:rgba(255,255,255,.05);--dark-color-tag-border:rgba(255,255,255,.15);--dark-color-tag-color:rgba(255,255,255,.75);--dark-color-tag-green-bg:17,36,33;--dark-color-tag-green-border:25,81,65;--dark-color-tag-green-color:#3ad3ba;--dark-color-tag-purple-bg:#201425;--dark-color-tag-purple-border:#5a2969;--dark-color-tag-purple-color:#d988cd;--dark-color-tag-red-bg:#291515;--dark-color-tag-red-border:#5c2626;--dark-color-tag-red-color:#e04141;--dark-color-tag-orange-bg:#312313;--dark-color-tag-orange-border:#593914;--dark-color-tag-orange-color:#ffa031;--dark-color-tag-blue-bg:#111a2c;--dark-color-tag-blue-border:#1348ab;--dark-color-tag-blue-color:#529fff;--dark-color-codemirror-line-hover:rgba(0,135,113,.2);--dark-color-codemirror-line-selection:rgba(0,135,113,.3);--dark-color-login-background:var(--dark-color-background);--dark-color-login-wave:var(--dark-color-surface-200);--dark-color-tooltip:rgba(61,76,104,.9);--dark-color-back-top:rgba(61,76,104,.9);--dark-color-back-top-hover:rgba(61,76,104,1);--dark-color-scrollbar:#313f5a;--dark-color-scrollbar-webkit:#7484a0;--dark-color-scrollbar-webkit-hover:#90a4c7;--dark-color-table-ring:rgb(38 52 77);--dark-color-spin-container:#151f31;--dark-color-text-secondary:rgba(255,255,255,.45);--dark-color-table-header-bg:var(--dark-color-background);--dark-color-table-body-bg:var(--dark-color-surface-100);--dark-color-table-row-selected:rgba(0,135,113,.14);--dark-color-table-row-selected-hover:rgba(0,135,113,.22);--dark-color-table-column-sorted-bg:rgba(0,135,113,.08);--dark-color-table-sort-icon-muted:rgba(255,255,255,.35);--dark-color-table-pagination-surface:var(--dark-color-surface-200);--dark-color-table-filter-dropdown-bg:var(--dark-color-surface-200)}html[data-theme-animations='off']{.ant-menu,.ant-layout-sider,.ant-card,.ant-tag,.ant-progress-circle>*,.ant-input,.ant-table-row-expand-icon,.ant-switch,.ant-table-thead>tr>th,.ant-select-selection,.ant-btn,.ant-input-number,.ant-input-group-addon,.ant-checkbox-inner,.ant-progress-bg,.ant-progress-success-bg,.ant-radio-button-wrapper:not(:first-child):before,.ant-radio-button-wrapper,#login,.cm-s-xq.CodeMirror{transition:border 0s,background 0s!important}.ant-menu.ant-menu-inline .ant-menu-item:not(.ant-menu-sub .ant-menu-item),.ant-layout-sider-trigger,.ant-alert-close-icon .anticon-close,.ant-tabs-nav .ant-tabs-tab,.ant-input-number-input,.ant-collapse>.ant-collapse-item>.ant-collapse-header,.Line-Hover,.ant-menu-theme-switch,.ant-menu-submenu-title{transition:color 0s!important}.wave-btn-bg{transition:width 0s!important}}html[data-theme='ultra-dark']{--dark-color-background:#21242a;--dark-color-surface-100:#0c0e12;--dark-color-surface-200:#222327;--dark-color-surface-300:#32353b;--dark-color-surface-400:rgba(255,255,255,.1);--dark-color-surface-500:#3b404b;--dark-color-surface-600:#505663;--dark-color-surface-700:#101113;--dark-color-surface-700-rgb:16,17,19;--dark-color-table-hover:rgba(89,89,89,.15);--dark-color-text-primary:rgb(255 255 255 / 85%);--dark-color-stroke:#202025;--dark-color-tag-green-bg:17,36,33;--dark-color-tag-green-border:29,95,77;--dark-color-tag-green-color:#59cbac;--dark-color-tag-purple-bg:#241121;--dark-color-tag-purple-border:#5a2969;--dark-color-tag-purple-color:#d686ca;--dark-color-tag-red-bg:#2a1215;--dark-color-tag-red-border:#58181c;--dark-color-tag-red-color:#e84749;--dark-color-tag-orange-bg:#2b1d11;--dark-color-tag-orange-border:#593815;--dark-color-tag-orange-color:#e89a3c;--dark-color-tag-blue-bg:#111a2c;--dark-color-tag-blue-border:#0f367e;--dark-color-tag-blue-color:#3c89e8;--dark-color-codemirror-line-hover:rgba(82,84,94,.2);--dark-color-codemirror-line-selection:rgba(82,84,94,.3);--dark-color-login-background:#0a2227;--dark-color-login-wave:#0f2d32;--dark-color-tooltip:rgba(88,93,100,.9);--dark-color-back-top:rgba(88,93,100,.9);--dark-color-back-top-hover:rgba(88,93,100,1);--dark-color-scrollbar:rgb(107,107,107);--dark-color-scrollbar-webkit:#9f9f9f;--dark-color-scrollbar-webkit-hover:#d1d1d1;--dark-color-table-ring:rgb(37 39 42);--dark-color-spin-container:#1d1d1d;--dark-color-text-secondary:rgb(255 255 255 / 50%);--dark-color-table-row-selected:rgba(0,135,113,.2);--dark-color-table-row-selected-hover:rgba(0,135,113,.3);--dark-color-table-column-sorted-bg:rgba(0,135,113,.12);--dark-color-table-sort-icon-muted:rgb(255 255 255 / 42%);--dark-color-table-pagination-surface:var(--dark-color-surfLine truncated
-1
View File
@@ -1,7 +1,6 @@
import { createApp } from 'vue';
import Antd, { message } from 'ant-design-vue';
import 'ant-design-vue/dist/reset.css';
import '@/styles/legacy.css';
import { setupAxios } from '@/api/axios-init.js';
import '@/composables/useTheme.js';