Compare commits

..
2 Commits
8 changed files with 43 additions and 65 deletions
+7 -11
View File
@@ -27,8 +27,7 @@
- **Seed 与 Map 可追踪**:主色和功能色均按 Ant Design 从 Seed 派生 P1-P10;浅色 P6 等于 Seed,深色 P6
经过暗色色板适配。全局 `colorPrimary` 保留原始 Seed;功能色 Base 保留 P6。文字和按钮组件可在派生层做对比度校正,Seed 不随之改写
- **主色与表面分离**:品牌色负责交互和强调,页面、容器、浮层与文字由独立的背景和文本 Seed 派生
- **语义独立**:`success`、`warning`、`error`、`info` 不随品牌色隐式改变;未显式配置 `link`
时跟随 `primary`,显式配置后独立派生
- **语义独立**:成功、警告、错误、信息和链接色各自派生,不随品牌色改变
- **无边设计**:默认依靠表面色差、间距和轻阴影表达层级;边框只用于焦点、错误和必要分隔
- **运行时可定制**:用户输入在浏览器中实时派生,因此使用 CSS Variables,不使用构建时 SCSS 变量
- **单一事实源**:Store 只保存最小配置,所有 Map、Alias 与未覆盖的 Component Token 均由纯函数生成
@@ -158,7 +157,7 @@ interface ThemeCustomization {
表格行高与卡片圆角通过 `customization.component` 的 `tableRowHeight`、`cardBorderRadius` 配置,属于组件尺寸覆盖
完整主题预设组合品牌配色、推荐材质、全局 Seed、组件尺寸和可编辑 Token。选择时替换草稿配置,保留当前明暗模式;保存后应用到管理端
切换内置主题预设会更新草稿中的品牌色、材质、尺寸与风格,保留明暗模式以及「功能色与中性色」的设置;保存后应用到管理端
表面材质可单独选择:`default` 使用简洁表面,`soft` 使用柔和光影。材质只影响表面高光、渐变与阴影,不改变配色、圆角、字号或组件尺寸
@@ -206,10 +205,8 @@ Preset 的实心色使用 P6,普通 Container、Strong Container 与边界按
- `colorText / Heading / Secondary / Tertiary / Quaternary / Disabled`
- `colorBorder / BorderSecondary / Split / Shadow`
星津夜航使用稳定的浅色 / 深色中性基线;水墨江汀、粉墙回廊与朱阙金瓦预置额外提供各自的 `colorBgBase`、`colorTextBase`
画像,自定义主色则只向通用中性基线注入少量色温。默认浅色采用中性锚点,默认深色使用 HSL 色调派生;带色温
主题按外观距离平滑过渡到背景和文字 Seed 的 RGB 混色结果。稳定锚点用于 Surface 与 Shadow,Component 从角色派生,
所有预置、自定义 Seed 和用户覆盖仍进入同一条算法,不在页面或组件中追加 HEX 特判。
所有品牌色共用当前明暗模式的背景和文字基色。手动设置 `colorBgBase` 或 `colorTextBase` 后,两个模式都使用该字段的自定义值;单项恢复后重新跟随明暗模式。
默认浅色采用中性锚点,默认深色使用 HSL 色调派生;自定义中性色按外观距离平滑过渡到背景和文字 Seed 的 RGB 混色结果。
Input、阴影与其他 Component Token 从 Fill、Surface、Primary 和 Semantic 派生,不在常量文件维护整套颜色表
正常文字同时检查 Layout、Container、Elevated、文字交互背景、三级 Fill,以及控件透明填充在各宿主上的合成色
@@ -291,7 +288,7 @@ Purple Strong Container 或 Purple Solid;同一组颜色仍由运行时色板
| ID | 名称 | 推荐品牌色 | 表面 |
| --- | --- | --- | --- |
| `default` | 默认 | 星津夜航 | 简洁表面与轻量阴影 |
| `soft` | 柔光 | 星津夜航 | 表面高光与分层阴影,配色跟随品牌 |
| `soft` | 柔光 | 星津夜航 | 表面高光、渐变与分层阴影 |
### 主题预设
@@ -302,8 +299,7 @@ Purple Strong Container 或 Purple Solid;同一组颜色仍由运行时色板
| `signal-violet` | 粉墙回廊 | `#D395A8` | 默认材质,14px 正文与 68px 表格行高 |
| `graphite` | 朱阙金瓦 | `#A34537` | 柔光,12px 卡片圆角与 54px 表格行高 |
品牌配色改变品牌主色,并可提供与该品牌匹配的浅色 / 深色背景和文字 Seed;不改变字号、密度、圆角或布局。完整预设提供的 Seed 优先于品牌基线,用户覆盖优先于预设
所有预置与自定义主题走同一条 Surface、Map、Alias 与 Component 派生链路,不存在页面级特判
单独编辑品牌主色不改变尺寸和风格。预置与自定义主题使用相同的 Surface、Map、Alias 与 Component 派生链路
## 运行时架构
@@ -448,7 +444,7 @@ utility / arbitrary variant;Vue Transition、跨浏览器 Range、动态富文
### 增加预置主题
在 UI 库的 `themeProfiles` 中组合配色 ID 对应的材质与 `ThemeCustomization`。设置页将其复制为可编辑草稿,保存实际配置,无需持久化额外的选中标记。材质仍由组件 Token 派生层实现
在 UI 库的 `themeProfiles` 中为每个配色 ID 配置材质与 `ThemeCustomization`。内置预设不指定功能色与中性色。设置页通过 `resolveThemeProfile` 应用预设,保留预设未指定的颜色;选中状态由同一结果推导。材质由组件 Token 派生层实现
单独增加品牌配色:
+1 -1
View File
@@ -17,7 +17,7 @@
},
"dependencies": {
"@boxicons/vue": "1.0.1",
"@codex-proxy/ui": "github:zyycn/codex-proxy-ui#v0.9.1",
"@codex-proxy/ui": "github:zyycn/codex-proxy-ui#v0.10.0",
"@fontsource-variable/inter": "^5.3.0",
"@fontsource-variable/jetbrains-mono": "^5.3.0",
"@lucide/vue": "^1.70.0",
+6 -6
View File
@@ -195,8 +195,8 @@ importers:
specifier: 1.0.1
version: 1.0.1(vue@3.5.45(typescript@6.0.3))
'@codex-proxy/ui':
specifier: github:zyycn/codex-proxy-ui#v0.9.1
version: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/46ade64479009ab9bc7cb1c8ec6d143fbd0a3b6c(axios@1.21.0(debug@4.4.3))(change-case@5.4.4)(jiti@2.7.0)(postcss@8.5.30)(vite@8.3.6(@types/node@26.6.6)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.45(typescript@6.0.3))(yaml@2.9.0)
specifier: github:zyycn/codex-proxy-ui#v0.10.0
version: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/45b076735d67f973588d93144078c7605cf1ade7(axios@1.21.0(debug@4.4.3))(change-case@5.4.4)(jiti@2.7.0)(postcss@8.5.30)(vite@8.3.6(@types/node@26.6.6)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.45(typescript@6.0.3))(yaml@2.9.0)
'@fontsource-variable/inter':
specifier: ^5.3.0
version: 5.3.0
@@ -456,9 +456,9 @@ packages:
resolution: {integrity: sha512-dlT1m5e/0yUL0kRNcQn7yGLVThkgbB0Ga/1AmfDDC/8ik6AIiSf2QLQO2zPYvefsHP0aFgxO93cVLCCfDp7kzQ==}
engines: {node: '>= 20.12.0'}
'@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/46ade64479009ab9bc7cb1c8ec6d143fbd0a3b6c':
resolution: {gitHosted: true, integrity: sha512-4gsO8MKvqlYmi1+TbgVt0r2EabvYzPJ2YpvhrLlyQz0EPX+jhA6mK0ZXOlDRSSs7/AIZ8Ihfi+zvVFfSIVLdxg==, tarball: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/46ade64479009ab9bc7cb1c8ec6d143fbd0a3b6c}
version: 0.9.1
'@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/45b076735d67f973588d93144078c7605cf1ade7':
resolution: {gitHosted: true, integrity: sha512-TyY1/D8rzd1MwU5HUbKOEzca5q7nPqydWaRpb5iMUrSy6G4dXleZNSZd6VXAum9WXZrZGb9ZzBm9cOMIOg+/xg==, tarball: https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/45b076735d67f973588d93144078c7605cf1ade7}
version: 0.10.0
engines: {node: '>=24'}
peerDependencies:
vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -3530,7 +3530,7 @@ snapshots:
fast-wrap-ansi: 0.2.2
sisteransi: 1.0.5
'@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/46ade64479009ab9bc7cb1c8ec6d143fbd0a3b6c(axios@1.21.0(debug@4.4.3))(change-case@5.4.4)(jiti@2.7.0)(postcss@8.5.30)(vite@8.3.6(@types/node@26.6.6)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.45(typescript@6.0.3))(yaml@2.9.0)':
'@codex-proxy/ui@https://codeload.github.com/zyycn/codex-proxy-ui/tar.gz/45b076735d67f973588d93144078c7605cf1ade7(axios@1.21.0(debug@4.4.3))(change-case@5.4.4)(jiti@2.7.0)(postcss@8.5.30)(vite@8.3.6(@types/node@26.6.6)(jiti@2.7.0)(yaml@2.9.0))(vue@3.5.45(typescript@6.0.3))(yaml@2.9.0)':
dependencies:
'@ant-design/colors': 8.0.1
'@ant-design/fast-color': 3.0.1
@@ -1,10 +1,10 @@
<script setup lang="ts">
import type { Theme, ThemeColorPresetId, ThemeMode, ThemePresetId, ThemeSeedOverrides } from '@codex-proxy/ui/theme'
import type { Theme, ThemeColorPresetId, ThemeMode, ThemePresetId, ThemeSeedColors, ThemeSeedDimensions, ThemeSeedOverrides } from '@codex-proxy/ui/theme'
import type { ThemeEditorDraft, ThemeEditorGlobalCategory } from '../composables/useThemeEditor'
import { ZSegmented } from '@codex-proxy/ui'
import { createThemeSeedTokens, themeColorPresets, themePresets, themeProfiles } from '@codex-proxy/ui/theme'
import { resolveThemeProfile, themeColorPresets, themePresets } from '@codex-proxy/ui/theme'
import { Check, ChevronDown, Laptop, Moon, PanelsTopLeft, Sparkles, Sun } from '@lucide/vue'
import { isEqual } from 'es-toolkit'
@@ -28,17 +28,8 @@ const emit = defineEmits<{
resetPrimary: []
profile: [value: ThemeColorPresetId]
primary: [value: string]
seedColor: [
key: Exclude<
keyof ThemeSeedOverrides,
'fontSize' | 'sizeUnit' | 'sizeStep' | 'controlHeight' | 'borderRadius' | 'shadowStrength'
>,
value: string,
]
seedNumber: [
key: 'fontSize' | 'sizeUnit' | 'sizeStep' | 'controlHeight' | 'borderRadius' | 'shadowStrength',
value: number,
]
seedColor: [key: keyof ThemeSeedColors, value: string]
seedNumber: [key: keyof ThemeSeedDimensions, value: number]
resetSeed: [key: keyof ThemeSeedOverrides]
}>()
@@ -47,25 +38,18 @@ const { t } = useI18n({ useScope: 'global' })
const category = defineModel<ThemeEditorGlobalCategory>('category', { required: true })
const derivedOpen = shallowRef(false)
const profiles = themeColorPresets.map((preset) => {
const options = { ...themeProfiles[preset.id], colorId: preset.id }
const light = createThemeSeedTokens({ ...options, scheme: 'light' })
return {
...preset,
paper: light.colorBgBase,
accent: light.colorWarning,
ink: createThemeSeedTokens({ ...options, scheme: 'dark' }).colorBgBase,
}
})
const localizedPresets = computed(() => profiles.map(p => ({
const localizedPresets = computed(() => themeColorPresets.map(p => ({
...p,
...uiLocale.value.theme[p.id],
})))
const activeProfile = computed(() => {
const { color, preset, customization } = props.draft
if (color !== 'custom' && themeProfiles[color].preset === preset && isEqual(themeProfiles[color].customization, customization))
return color
return undefined
if (color === 'custom')
return undefined
const profile = resolveThemeProfile(color, customization.seed)
return profile.preset === preset && isEqual(profile.customization, customization)
? color
: undefined
})
const colorPresets = themeColorPresets.map(preset => preset.seed)
const themePresetDetails = computed(() => ({
@@ -215,7 +199,7 @@ function seedOverridden(key: keyof ThemeSeedOverrides) {
>
<svg
class="block h-11 w-full overflow-hidden rounded-z-sm"
:style="{ 'color': preset.seed, '--scene-paper': preset.paper, '--scene-ink': preset.ink, '--scene-accent': preset.accent }"
:style="{ color: preset.seed }"
viewBox="0 0 160 44"
preserveAspectRatio="xMidYMid slice"
aria-hidden="true"
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg">
<symbol id="relay-blue" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice">
<symbol id="relay-blue" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice" style="--scene-paper: #FFFFFF; --scene-ink: #111A29">
<path fill="var(--scene-ink)" d="M0 0h160v44H0z"/>
<path fill="currentColor" opacity=".3" d="M0 34q25-4 56-1t58-1q25-2 46 0v12H0z"/>
<path fill="none" stroke="currentColor" opacity=".7" d="m14 16 12-8 13 6 15-7"/>
@@ -12,7 +12,7 @@
<path fill="currentColor" d="m109 20 5 9h-5zm-13 11h21l-4 3h-12z"/>
<path fill="none" stroke="currentColor" opacity=".65" stroke-linecap="round" d="m85 36 14-.5m5 3 6-.5"/>
</symbol>
<symbol id="deep-teal" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice">
<symbol id="deep-teal" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice" style="--scene-paper: #F8F8F3; --scene-ink: #202727">
<path fill="var(--scene-paper)" d="M0 0h160v44H0z"/>
<g fill="currentColor">
<path opacity=".18" d="m0 18 12-5 9 1 15-9 7 2 6-3 14 12 8-3 13 6 16-9 8 2 12-6 17 12 9-3 14 4v5H0z"/>
@@ -27,21 +27,21 @@
<path fill="var(--scene-ink)" d="M100 30q5 2 16 0l-3 2h-9z"/>
<path fill="none" stroke="var(--scene-ink)" stroke-linecap="round" d="m110 30-1-2-2 2m2-1 6 4"/>
</symbol>
<symbol id="signal-violet" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice">
<symbol id="signal-violet" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice" style="--scene-paper: #FCF6F7; --scene-ink: #29242A">
<path fill="var(--scene-paper)" d="M0 0h160v44H0z"/>
<path fill="currentColor" d="M0 5l57 10v29H0zM74 18l26 5v21H74zM119 24l13 2v18h-13z"/>
<path fill="var(--scene-ink)" opacity=".57" d="M57 15l10-4v33H57zM100 23l5-2v23h-5zM132 26l4-1v19h-4z"/>
<path fill="currentColor" opacity=".28" d="M0 44l160-13v13z"/>
<circle cx="143" cy="10" r="4" fill="currentColor"/>
</symbol>
<symbol id="graphite" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice">
<symbol id="graphite" viewBox="0 0 160 44" preserveAspectRatio="xMidYMid slice" fill="#BD8C3F" style="--scene-paper: #FBF5E8; --scene-ink: #302421">
<path fill="var(--scene-paper)" d="M0 0h160v44H0z"/>
<path fill="currentColor" d="M0 33h160v11H0zM26 21h5v16h-5zm21 0h5v16h-5zm22 0h5v16h-5zm23 0h5v16h-5zm22 0h5v16h-5zm20 0h5v16h-5z"/>
<path fill="var(--scene-ink)" d="M22 18h120l-5 5H28z"/>
<path fill="var(--scene-accent)" d="M12 14q12 6 28-1 8-3 15-11h55q7 8 15 11 14 6 27 1-4 7-19 7H32q-15 0-20-7z"/>
<path d="M12 14q12 6 28-1 8-3 15-11h55q7 8 15 11 14 6 27 1-4 7-19 7H32q-15 0-20-7z"/>
<path fill="none" stroke="var(--scene-ink)" stroke-width="1.4" d="M12 15q8 7 24 6h94q15 1 22-6M54 2h57"/>
<path fill="none" stroke="var(--scene-paper)" opacity=".5" d="m56 5-10 10m20-10-5 10m15-10-2 10m13-10 1 10m10-10 4 10m6-10 8 10"/>
<path fill="var(--scene-accent)" d="M0 33h160v2H0z"/>
<path d="M0 33h160v2H0z"/>
<path fill="var(--scene-ink)" opacity=".25" d="M0 40h160v4H0z"/>
</symbol>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -6,6 +6,8 @@ import type {
ThemeMode,
ThemePresetId,
ThemeScheme,
ThemeSeedColors,
ThemeSeedDimensions,
ThemeSeedOverrides,
ThemeTokenName,
} from '@codex-proxy/ui/theme'
@@ -22,7 +24,7 @@ import {
isThemeMode,
isThemePresetId,
normalizeThemeCustomization,
themeProfiles,
resolveThemeProfile,
} from '@codex-proxy/ui/theme'
import { storeToRefs } from 'pinia'
@@ -48,9 +50,7 @@ export interface ThemeEditorDraft {
customization: ThemeCustomization
}
type NumericSeedKey = 'fontSize' | 'sizeUnit' | 'sizeStep' | 'controlHeight' | 'borderRadius' | 'shadowStrength'
type ComponentNumberKey = keyof ThemeComponentOverrides
type ColorSeedKey = Exclude<keyof ThemeSeedOverrides, NumericSeedKey>
export function useThemeEditor() {
const themeStore = useThemeStore()
@@ -125,13 +125,11 @@ export function useThemeEditor() {
}
function applyProfile(color: ThemeColorPresetId) {
const { preset, customization } = themeProfiles[color]
draft.value = {
...draft.value,
color,
preset,
customColor: defaultThemePrimaryColor,
customization: normalizeThemeCustomization(customization),
...resolveThemeProfile(color, draft.value.customization.seed),
}
}
@@ -157,14 +155,14 @@ export function useThemeEditor() {
: mode
}
function setSeedColor(key: ColorSeedKey, color: string) {
function setSeedColor(key: keyof ThemeSeedColors, color: string) {
const normalized = normalizeHexColor(color)
if (!normalized)
return
updateSeed({ [key]: normalized })
}
function setSeedNumber(key: NumericSeedKey, value: number) {
function setSeedNumber(key: keyof ThemeSeedDimensions, value: number) {
if (!Number.isFinite(value))
return
updateSeed({ [key]: value })
+4 -4
View File
@@ -1,7 +1,7 @@
version: 3.45.2
published:
'3':
stable: 3.45.1
rc: 3.45.1
beta: 3.45.1
alpha: 3.45.1
stable: 3.45.2
rc: 3.45.2
beta: 3.45.2
alpha: 3.45.2