mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-29 06:31:58 +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:
1 parent
1e1a585541
commit
5f1aba28b0
12 files changed
+38
-56
No files matched your search
@@ -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,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';
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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 +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();
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,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 +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,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';
|
||||
|
||||
Reference in new issue
Block a user