This commit is contained in:
oneao committed 2026-10-12 17:32:50 +08:00
1 parent 78eb811ff7
commit 97fe2ff306
83 files changed
+10083 -3662

No files matched your search

@@ -54,6 +54,15 @@ const showHeader = computed(() => pref.value.header.enable && !isContentFullscre
/** 页签栏(局部全屏时保留) */
const showTabbar = computed(() => pref.value.tabbar.enable)
/**
* 页面缓存(KeepAlive)的 include 名单,受 tabbar.keepAlive 偏好控制。
*
* 关闭时传空数组而不是 undefined:KeepAlive 的 include 语义是「白名单」——
* 空数组没有任何 name 能匹配上 → 一律不缓存(已缓存的实例会被其内部的
* include watch 清掉);而 undefined 表示「不过滤」→ 反而会缓存所有页面。
*/
const keepAliveInclude = computed(() => (pref.value.tabbar.keepAlive ? appStore.cachedKeys : []))
/** 内容区样式:内边距走偏好 */
const contentStyle = computed(() => ({
padding: `${pref.value.app.contentPadding}px`,
@@ -180,7 +189,7 @@ watch(
:name="pref.transition.enable ? pref.transition.name : undefined"
mode="out-in"
>
<KeepAlive :include="appStore.cachedKeys" :max="8">
<KeepAlive :include="keepAliveInclude">
<component :is="formatComponentInstance(Component, r.fullPath)" :key="r.fullPath" />
</KeepAlive>
</Transition>
@@ -214,7 +214,7 @@ onMounted(async () => {
/* 首次挂载也要定位激活页签 —— 修复「刷新后页签不居中」。
*
* 为什么不能只靠下面的 watch:
* 页签是持久化的(g3erp:app:v2),刷新时 pinia 在组件挂载【之前】就把 tabs 还原好了,
* 页签是持久化的(g3erp:app),刷新时 pinia 在组件挂载【之前】就把 tabs 还原好了,
* 而 route.fullPath 在路由解析完成时也已确定。
* 于是 watch 的两个依赖在挂载这一刻【already 是初始值】,
* 没有 immediate 的 watch 根本不会触发 → revealActiveTab 一次都没跑
@@ -10,6 +10,7 @@ import PrefBlock from './PrefBlock.vue'
import SelectItem from './SelectItem.vue'
import SwitchItem from './SwitchItem.vue'
import ToggleItem from './ToggleItem.vue'
import { resolvePrimaryColor } from '../style-presets'
import type { Preferences } from '../types'
/**
@@ -43,6 +44,12 @@ const tabs = [
// 外观页签
// ---------------------------------------------------------------------------
/** 界面风格(换皮预设):default 保持现状,shadcn 现代极简 */
const styleOptions = [
{ label: '默认', value: 'default' },
{ label: '现代极简', value: 'shadcn' },
]
/** 主题模式卡片(对照 vben theme.vue 的 THEME_PRESET) */
const themePreset = [
{ icon: Sun, name: 'light' as const, label: '浅色' },
@@ -159,9 +166,9 @@ function setThemeMode(name: 'light' | 'dark' | 'auto'): void {
store.update({ theme: { mode: name } })
}
/** 主题色切换(直接写三段式色值) */
/** 主题色切换(直接写三段式色值);标记为用户显式选择,风格切换不再覆盖它 */
function setColorPrimary(value: string): void {
store.update({ theme: { colorPrimary: value } })
store.update({ theme: { colorPrimary: value, colorPrimaryCustom: true } })
}
/**
@@ -235,9 +242,13 @@ function hexToHslTriplet(hex: string): string {
return `${Math.round(h)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`
}
/** 当前主题色对应的 hex,喂给取色器。已是 hex/rgb/hsl() 的直接用。 */
/**
* 当前主题色对应的 hex,喂给取色器。
* 未显式选过色时(colorPrimaryCustom=false)解析为「当前风格的内置默认色」,
* 这样 shadcn 风格下取色器显示的是中性黑,而不是被忽略的 colorPrimary。
*/
const customColorHex = computed(() => {
const value = pref.value.theme.colorPrimary
const value = resolvePrimaryColor(pref.value.theme, pref.value.app.style, store.isDark)
if (value.startsWith('#') || value.startsWith('rgb') || value.startsWith('hsl')) return value
return hslTripletToHex(value) ?? '#165dff'
})
@@ -276,6 +287,19 @@ const hasDiff = computed(() => true)
<div class="pref-drawer__body">
<!-- ===================== 外观 ===================== -->
<template v-if="activeTab === 'appearance'">
<!-- 界面风格(换皮预设) -->
<PrefBlock title="风格">
<ToggleItem
:model-value="pref.app.style"
:items="styleOptions"
@update:model-value="
(v) => store.update({ app: { style: v as Preferences['app']['style'] } })
"
>
界面风格
</ToggleItem>
</PrefBlock>
<!-- 主题模式(图标卡片) -->
<PrefBlock title="主题模式">
<div class="pref-card-row">
@@ -320,7 +344,10 @@ const hasDiff = computed(() => true)
>
<div
class="pref-card__box"
:class="{ 'is-active': pref.theme.colorPrimary === theme.value }"
:class="{
'is-active':
pref.theme.colorPrimaryCustom && pref.theme.colorPrimary === theme.value,
}"
>
<div
class="pref-card__swatch"
@@ -336,7 +363,9 @@ const hasDiff = computed(() => true)
<div
class="pref-card__box"
:class="{
'is-active': !builtinThemes.some((t) => t.value === pref.theme.colorPrimary),
'is-active':
pref.theme.colorPrimaryCustom &&
!builtinThemes.some((t) => t.value === pref.theme.colorPrimary),
}"
>
<!-- 不传 presets:用组件默认值 true,面板自带常用色板便于快捷选色 -->
@@ -607,6 +636,14 @@ const hasDiff = computed(() => true)
>
刷新后保留
</SwitchItem>
<SwitchItem
:model-value="pref.tabbar.keepAlive"
:disabled="!pref.tabbar.enable"
tip="切换页签后再回来,保留页面状态(表单输入、滚动位置等)"
@update:model-value="(v) => store.update({ tabbar: { keepAlive: v } })"
>
缓存页签
</SwitchItem>
<SwitchItem
:model-value="pref.tabbar.wheelable"
:disabled="!pref.tabbar.enable"
@@ -14,6 +14,7 @@ export const defaultPreferences: Preferences = {
app: {
name: 'G3Soft ERP',
layout: 'sidebar-nav',
style: 'default',
contentPadding: 16,
dynamicTitle: true,
enablePreferences: true,
@@ -22,6 +23,7 @@ export const defaultPreferences: Preferences = {
theme: {
colorPrimary: '212 100% 45%',
colorPrimaryCustom: false,
colorSuccess: '144 57% 58%',
colorWarning: '42 84% 61%',
colorDestructive: '348 100% 61%',
@@ -94,8 +96,7 @@ export const defaultPreferences: Preferences = {
middleClickToClose: false,
wheelable: true,
persist: true,
maxCount: 0,
keepAlive: false,
keepAlive: true,
},
transition: {
@@ -15,6 +15,7 @@ export type {
PageTransitionType,
Preferences,
SidebarPreferences,
StyleType,
TabbarPreferences,
TabsStyleType,
ThemeModeType,
@@ -0,0 +1,53 @@
import type { StyleType } from './types'
/**
* 应用风格预设表(换皮)。
*
* 只承载「必须由 JS 产出」的那部分 —— 也就是被 `applyPreferencesToCss`
* 内联写进 `<html>` 的变量(内联样式压过一切样式表,皮肤样式块覆盖不了它们)。
* 其余配色/几何一律由样式层的 `html[data-g3-style=...]` 覆盖,见 styles/skin-shadcn.scss。
*/
export interface StylePreset {
/**
* 风格的内置默认主色(HSL 三段式,按明暗)。
* 仅在「用户没显式选过主色」时取用 —— 必须写成三段式:
* toColor() 对 #hex 会早退、丢掉 alpha,导致 hover/active 退化。
*/
defaultPrimary: { light: string; dark: string }
/**
* 主色淡底(选中/激活底)。给定则固定使用(shadcn 用 accent 灰);
* 缺省则按「主色 + 15%/20%」派生(default 沿用旧行为)。
*/
primarySoft?: { light: string; dark: string }
}
export const stylePresets: Record<StyleType, StylePreset> = {
default: {
// 与 config.ts 的默认主题色一致
defaultPrimary: { light: '212 100% 45%', dark: '212 100% 45%' },
},
shadcn: {
// #0f172b / #e2e8f0
defaultPrimary: { light: '223 48% 11%', dark: '214 32% 91%' },
// shadcn 的 accent:选中/激活底一律是中性的浅灰/深灰,而非主色淡底
primarySoft: { light: '#f1f5f9', dark: '#1d293d' },
},
}
/**
* 解析当前实际生效的主色。
*
* - 用户显式选过色(colorPrimaryCustom)→ colorPrimary;
* - 否则:shadcn 风格用该风格的内置中性主色(按明暗),default 风格仍用 colorPrimary
* (保持旧行为,避免"用户此前存下的自定义色因缺少 colorPrimaryCustom 标记被重置")。
*
* 规则集中在这里,供 update-css-variables 与偏好面板取色器共用,避免两处漂移。
*/
export function resolvePrimaryColor(
theme: { colorPrimary: string; colorPrimaryCustom: boolean },
style: StyleType,
dark: boolean,
): string {
if (theme.colorPrimaryCustom || style === 'default') return theme.colorPrimary
return stylePresets[style].defaultPrimary[dark ? 'dark' : 'light']
}
+17 -2
View File
@@ -40,6 +40,15 @@ export type BreadcrumbStyleType = 'background' | 'normal'
/** 页签风格。 */
export type TabsStyleType = 'card' | 'chrome' | 'plain'
/**
* 应用风格(换皮预设)。
* - default:保持现状(vben 风)
* - shadcn:现代极简(shadcn admin 风)
*
* 只影响配色/几何等「皮」,不影响布局形态(页签/导航/面包屑的 styleType 各自独立)。
*/
export type StyleType = 'default' | 'shadcn'
/** 页面切换动画。 */
export type PageTransitionType =
| 'fade'
@@ -53,6 +62,12 @@ export type PageTransitionType =
export interface ThemePreferences {
/** 主题色(HSL 三段式,如 '212 100% 45%' 或直接色值) */
colorPrimary: string
/**
* 主题色是否由用户显式选择过。
* false(默认)→ shadcn 风格下使用该风格的内置中性主色;default 风格沿用 colorPrimary。
* true → 两种风格都使用 colorPrimary 里存的具体色值。
*/
colorPrimaryCustom: boolean
/** 成功色 */
colorSuccess: string
/** 警告色 */
@@ -81,6 +96,8 @@ export interface AppPreferences {
name: string
/** 布局方式 */
layout: LayoutType
/** 应用风格(换皮预设) */
style: StyleType
/** 内容区左右内边距 */
contentPadding: number
/** 动态标题(切换路由时改 document.title) */
@@ -205,8 +222,6 @@ export interface TabbarPreferences {
wheelable: boolean
/** 持久化页签(刷新后保留) */
persist: boolean
/** 最大页签数,0 表示不限 */
maxCount: number
/** 开启 keepAlive 缓存 */
keepAlive: boolean
}
@@ -1,4 +1,5 @@
import type { Preferences } from './types'
import { resolvePrimaryColor, stylePresets } from './style-presets'
/**
* 偏好设置 → CSS 变量。
@@ -37,6 +38,7 @@ export function applyPreferencesToCss(preferences: Preferences): void {
if (!root) return
const { theme, app, sidebar, header } = preferences
const preset = stylePresets[app.style]
// ---- 深色模式:给 html 加 .dark 类,样式里用 .dark { ... } 覆盖变量 ----
const dark = isDarkTheme(theme.mode)
@@ -49,9 +51,16 @@ export function applyPreferencesToCss(preferences: Preferences): void {
// 输入框文字发黑看不见,下拉/弹窗底色依旧雪白。
root.dataset.g3Theme = dark ? 'dark' : 'light'
// ---- 风格钩子:皮肤样式层按 html[data-g3-style='shadcn'] 覆盖变量 ----
// (只能在这里写内联/属性;皮肤样式块负责「非内联」变量,见 styles/skin-shadcn.scss)
root.dataset.g3Style = app.style
// ---- 主题色 ----
// 主色来源见 resolvePrimaryColor:用户显式选过就用它,否则 shadcn 用内置中性主色。
const primary = resolvePrimaryColor(theme, app.style, dark)
const colors: Array<[string, string]> = [
['--g3-color-primary', theme.colorPrimary],
['--g3-color-primary', primary],
['--g3-color-success', theme.colorSuccess],
['--g3-color-warning', theme.colorWarning],
['--g3-color-danger', theme.colorDestructive],
@@ -61,18 +70,20 @@ export function applyPreferencesToCss(preferences: Preferences): void {
}
// ---- 主色半透明底(选中态背景 / 页签激活态)----
// 跟随主题色动态派生:选中背景 = 当前主色 + 15%/20% 透明度,
// 这样在偏好里换主题色时,选中背景会一起变,而不是停在写死的蓝色。
// 透明度对齐 vben:浅色 15%,深色 20%。
root.style.setProperty('--g3-color-primary-soft', toColor(theme.colorPrimary, dark ? 0.2 : 0.15))
// default:跟随主题色动态派生(当前主色 + 15%/20% 透明度),换主题色时选中背景一起变。
// shadcn:固定用中性 accent 灰 —— shadcn 的选中态是灰底而非主色淡底。
const soft = preset.primarySoft?.[dark ? 'dark' : 'light'] ?? toColor(primary, dark ? 0.2 : 0.15)
root.style.setProperty('--g3-color-primary-soft', toColor(soft))
// ---- 主色的悬停 / 按下态(@g3soft/ui 的按钮、链接 hover/active 读这两个变量)----
// 组件库在 @g3soft/tokens 里给的是「种子色 + 90% / 80% 透明度」,而种子色是 #18181b(近黑):
// 只覆盖 --g3-color-primary 的话,蓝色按钮 hover 会直接变成黑色。这里按同一套算法跟随主题色。
root.style.setProperty('--g3-color-primary-hover', toColor(theme.colorPrimary, 0.9))
root.style.setProperty('--g3-color-primary-active', toColor(theme.colorPrimary, 0.8))
root.style.setProperty('--g3-color-primary-hover', toColor(primary, 0.9))
root.style.setProperty('--g3-color-primary-active', toColor(primary, 0.8))
// ---- 圆角与字号 ----
// 注意:--g3-radius-base 目前无消费方(G3 组件读 --g3-radius/-sm/-lg,见
// @g3soft/tokens);shadcn 风格的圆角由 styles/skin-shadcn.scss 覆盖 --g3-radius* 实现。
root.style.setProperty('--g3-radius-base', `${theme.radius}rem`)
root.style.setProperty('--g3-font-size-base', `${theme.fontSize}px`)
+12 -23
View File
@@ -19,11 +19,8 @@ export interface TabItem {
affix?: boolean
}
/**
* 存储键带版本号:页签结构变更(如 v1 的 name/path → v2 的 key/fullPath)时递增,
* 旧键直接废弃 —— 仓库约定不兼容历史持久化数据,不做迁移。
*/
const STORAGE_KEY = 'g3erp:app:v2'
/** 页签持久化存储键 */
const STORAGE_KEY = 'g3erp:app'
export const useAppStore = defineStore(
'app',
@@ -35,9 +32,15 @@ export const useAppStore = defineStore(
* keep-alive 的 include 列表:已打开页签的 fullPath。
* 与 g3hd2026 的 tab.cachedKeys 同源 —— KeepAlive 按组件 name(= fullPath 字符串)
* 匹配缓存,绕开「异步组件 vnode.type 引用不稳定」导致缓存失效的问题。
* 开页签 push、关页签 splice,保证缓存槽与页签严格同步(关闭即销毁实例,不泄漏)。
*
* 【从 tabs 派生,而不是单独维护一份 ref】:
* 曾用「ref + 开页签 push / 关页签 prune」的写法,会与 tabs 漂移 ——
* hydrateTabs 恢复 tabs 时不会恢复 cachedKeys,且 addTab 对"已存在的页签"直接 return,
* 于是刷新后恢复的页签永远进不了 include,KeepAlive 匹配不到 → 缓存失效。
* 改为 computed 后二者天然一致:关闭页签会随之移出,KeepAlive 的 include watch
* 也会照常销毁对应缓存实例,不会泄漏。
*/
const cachedKeys = ref<string[]>([])
const cachedKeys = computed(() => tabs.value.map((item) => item.key))
/**
* 路由刷新计数表:key = fullPath,值变化即强制该页签重建组件。
@@ -62,13 +65,10 @@ export const useAppStore = defineStore(
contentFullscreen.value = !contentFullscreen.value
}
/** 打开(或激活)一个页签;已存在则不重复添加,新开则加入缓存列表 */
/** 打开(或激活)一个页签;已存在则不重复添加(include 的 cachedKeys 由 tabs 派生) */
function addTab(tab: TabItem): void {
if (!tabs.value.some((item) => item.key === tab.key)) {
tabs.value.push(tab)
if (!cachedKeys.value.includes(tab.key)) {
cachedKeys.value.push(tab.key)
}
}
}
@@ -77,9 +77,8 @@ export const useAppStore = defineStore(
const index = tabs.value.findIndex((item) => item.key === key)
if (index === -1) return undefined
// 关闭即销毁缓存实例:cachedKeys 由 tabs 派生,移出 tabs 后 include 自动收敛
tabs.value.splice(index, 1)
// 同步移出缓存列表(关闭即销毁缓存实例,避免泄漏)
pruneCachedKeys()
// 优先激活右侧页签,没有则激活左侧
return tabs.value[index] ?? tabs.value[index - 1]
}
@@ -87,13 +86,11 @@ export const useAppStore = defineStore(
/** 关闭除指定页签外的全部(保留 affix) */
function removeOtherTabs(key: string): void {
tabs.value = tabs.value.filter((item) => item.affix || item.key === key)
pruneCachedKeys()
}
/** 关闭全部(保留 affix) */
function removeAllTabs(): void {
tabs.value = tabs.value.filter((item) => item.affix)
pruneCachedKeys()
}
/** 关闭左侧:指定页签之前的、非 affix 的页签全部移除 */
@@ -101,7 +98,6 @@ export const useAppStore = defineStore(
const index = tabs.value.findIndex((item) => item.key === key)
if (index === -1) return
tabs.value = tabs.value.filter((item, i) => item.affix || i >= index)
pruneCachedKeys()
}
/** 关闭右侧:指定页签之后的、非 affix 的页签全部移除 */
@@ -109,13 +105,6 @@ export const useAppStore = defineStore(
const index = tabs.value.findIndex((item) => item.key === key)
if (index === -1) return
tabs.value = tabs.value.filter((item, i) => item.affix || i <= index)
pruneCachedKeys()
}
/** 把缓存列表收敛到当前打开的页签集合(关闭页签后调用,防缓存实例泄漏) */
function pruneCachedKeys(): void {
const keep = new Set(tabs.value.map((item) => item.key))
cachedKeys.value = cachedKeys.value.filter((key) => keep.has(key))
}
/** 取某页签的刷新计数(未刷新过为 0) */
@@ -3,8 +3,10 @@
// 1) tokens 的 CSS 产物(:root 变量),必须在最前
// 2) @g3soft/ui 的组件样式
// 3) 本应用全局样式与 reset
// 4) 风格层(换皮):必须最后,才能压过上面几层的同名变量
@use '@g3soft/tokens';
@use '@g3soft/ui/style.css';
@use './global';
@use './skin-shadcn';
@@ -0,0 +1,174 @@
// 风格层:现代极简(shadcn admin)。由偏好 app.style 决定是否命中。
//
// 触发钩子:applyPreferencesToCss 写 `html[data-g3-style='shadcn']`。
//
// 为什么是样式表 + JS 双管齐下:
// - applyPreferencesToCss 用 root.style.setProperty 内联写了 13 个变量(主色系列、
// primary-soft、radius-base、字号、布局尺寸),**内联样式压过一切样式表**,
// 所以那几个必须在 JS 里按风格产出(见 preferences/update-css-variables.ts)。
// - 其余变量(tokens 组件令牌族 + 业务外壳族)没有被内联,可以用本文件覆盖。
//
// 分层:
// 1) 变量层(浅色 / 深色)
// 2) 组件里写死颜色的局部块 —— 必须显式提权(非 scoped <style> 注入顺序不可靠)
// ---------------------------------------------------------------------------
// 1) 变量层(浅色)
// html[data-g3-style='shadcn'] 特异性 (0,2,0) > global.scss 的 .dark (0,1,0)
// ---------------------------------------------------------------------------
html[data-g3-style='shadcn'] {
// ---- tokens 组件令牌族(@g3soft/ui 组件读这组)----
--g3-text-1: #020618;
--g3-text-2: #45556c;
--g3-text-3: #62748e;
--g3-text-disabled: #90a1b9;
--g3-text-inverse: #ffffff;
--g3-bg-container: #ffffff;
--g3-bg-disabled: #f1f5f9;
--g3-border-color: #e2e8f0;
// 真正决定 G3 组件圆角的是这三个(--g3-radius-base 无消费方)
--g3-radius-sm: 6px;
--g3-radius: 8px;
--g3-radius-lg: 10px;
--g3-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%);
--g3-shadow-popup: 0 4px 12px -2px rgb(0 0 0 / 12%);
--g3-fill: #f1f5f9;
--g3-fill-hover: #e2e8f0;
--g3-mask-bg: rgb(2 6 24 / 45%);
// ---- 业务 / 外壳族 ----
--g3-color-text: #020618;
--g3-color-text-secondary: #45556c;
--g3-color-text-tertiary: #62748e;
--g3-color-bg: #ffffff;
--g3-color-bg-container: #ffffff;
--g3-color-border: #e2e8f0;
--g3-color-fill-hover: #f1f5f9;
--g3-color-primary-bg: #f1f5f9;
--g3-color-accent: #f1f5f9;
--g3-color-accent-hover: #e2e8f0;
--g3-color-foreground: #020618;
--g3-color-sidebar: #ffffff;
--g3-color-header: #ffffff;
}
// ---------------------------------------------------------------------------
// 2) 变量层(深色)
// 必须逐变量齐全:浅色块 (0,2,0) 高于 global.scss 的 .dark (0,1,0),
// 漏项会让深色下漏回浅色值(半 shadcn 半浅色)。
// ---------------------------------------------------------------------------
html[data-g3-style='shadcn'].dark {
--g3-text-1: #f8fafc;
--g3-text-2: #cad5e2;
--g3-text-3: #90a1b9;
--g3-text-disabled: #62748e;
--g3-text-inverse: #020618;
--g3-bg-container: #0f172b;
--g3-bg-disabled: #1d293d;
--g3-border-color: rgb(255 255 255 / 10%);
--g3-shadow: 0 1px 2px 0 rgb(0 0 0 / 40%);
--g3-shadow-popup: 0 4px 12px -2px rgb(0 0 0 / 50%);
--g3-fill: #1d293d;
--g3-fill-hover: #253047;
--g3-mask-bg: rgb(0 0 0 / 60%);
--g3-color-text: #f8fafc;
--g3-color-text-secondary: #cad5e2;
--g3-color-text-tertiary: #90a1b9;
--g3-color-bg: #020618;
--g3-color-bg-container: #0f172b;
--g3-color-border: rgb(255 255 255 / 10%);
--g3-color-fill-hover: #1d293d;
--g3-color-primary-bg: #1d293d;
--g3-color-accent: #1d293d;
--g3-color-accent-hover: #253047;
--g3-color-foreground: #f8fafc;
--g3-color-sidebar: #020618;
--g3-color-header: #020618;
}
// ---------------------------------------------------------------------------
// 3) 组件里写死的侧栏深色色板
// DefaultLayout / LayoutSidebar / LayoutMixedRail / LayoutMixedColumn 的
// `.dark &` 与 `.is-dark` 用字面量(#1d2129 / #2e3238 / #313438),
// 只改 --g3-color-* 覆盖不到,必须显式提权覆盖 --sidebar-*。
// ---------------------------------------------------------------------------
$sidebar-dark: (
'--sidebar-bg': #020618,
'--sidebar-border': rgb(255 255 255 / 10%),
'--sidebar-item-color': #cad5e2,
'--sidebar-item-hover-bg': #1d293d,
'--sidebar-item-hover-color': #f8fafc,
'--sidebar-item-active-bg': #1d293d,
'--sidebar-item-active-color': #f8fafc,
'--sidebar-muted-color': #90a1b9,
'--sidebar-title-color': #f8fafc,
);
// .layout 是变量宿主(本身无背景),只覆盖变量即可 —— 双列菜单两列从它继承。
html.dark[data-g3-style='shadcn'] .layout {
@each $name, $value in $sidebar-dark {
#{$name}: $value;
}
}
// 三个真实的侧栏列:除变量外还直写了 background / border-right-color。
html[data-g3-style='shadcn'] .layout-sidebar.is-dark,
html[data-g3-style='shadcn'] .layout-mixed-rail.is-dark,
html[data-g3-style='shadcn'] .layout-mixed-column.is-dark,
html.dark[data-g3-style='shadcn'] .layout-sidebar,
html.dark[data-g3-style='shadcn'] .layout-mixed-rail,
html.dark[data-g3-style='shadcn'] .layout-mixed-column {
@each $name, $value in $sidebar-dark {
#{$name}: $value;
}
background: var(--sidebar-bg);
border-right-color: var(--sidebar-border);
}
// ---------------------------------------------------------------------------
// 4) 半深色顶栏(global.scss 里写死 #1d2129/#2e3238/#f7f8fa)
// ---------------------------------------------------------------------------
html[data-g3-style='shadcn'] .semi-dark-header .layout-header {
color: #f8fafc;
background: #0f172b;
border-bottom-color: rgb(255 255 255 / 10%);
}
// ---------------------------------------------------------------------------
// 5) 「主色底 + 写死白字」的两处
// shadcn 深色下主色是近白(#e2e8f0),白字会压白底看不见。
// 改用 --g3-color-primary-on(浅色模式=#fafafa / 深色=#18181b),自动取对比色。
// ---------------------------------------------------------------------------
html[data-g3-style='shadcn'] .layout-mixed-rail__item.is-active {
color: var(--g3-color-primary-on, #fafafa);
}
html[data-g3-style='shadcn']
:is(
.login__logo-mark,
.layout-sidebar__logo-mark,
.layout-header__logo-mark,
.layout-mixed-rail__logo-mark
) {
color: var(--g3-color-primary-on, #fafafa);
}
// ---------------------------------------------------------------------------
// 6) 登录页装饰光晕:默认跟随主色(shadcn 主色近黑时本已中性,
// 但用户自定义主色后会变彩色)—— 这里固定成中性,保持极简观感。
// ---------------------------------------------------------------------------
html[data-g3-style='shadcn'] .login__glow {
background: linear-gradient(
154deg,
rgb(2 6 24 / 6%) 30%,
rgb(15 23 43 / 18%) 48%,
rgb(2 6 24 / 6%) 64%
);
}