feat: 迁入 MiGu.Server、平台前端与车辆列表 reflection 回退
从 Simple-FR 拆出 Platform.Server 并重命名为 MiGu.Server;frontends 源码与构建脚本迁入本仓库。地图监控在 projection/cars 失败或为空时回退 reflection 车辆列表;Simple 仓库已移除旧 Platform.Server。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,120 @@
|
||||
import type { ThemePreset } from './themes'
|
||||
|
||||
/** 可在系统配置中取色盘调整的品牌色变量 */
|
||||
export const THEME_COLOR_FIELDS = [
|
||||
{ key: '--mg-primary', label: '主色(背景基色)', hint: '全页背景、左侧导航、卡片底色、按钮' },
|
||||
{ key: '--mg-primary-hover', label: '悬停/辉光', hint: '背景光晕、菜单高亮、悬停态' },
|
||||
{ key: '--mg-accent', label: '强调/高光', hint: '点缀、边框高光、Logo 光晕' }
|
||||
] as const
|
||||
|
||||
export type ThemeColorKey = (typeof THEME_COLOR_FIELDS)[number]['key']
|
||||
|
||||
export type ThemeColorOverrides = Partial<Record<ThemeColorKey, string>>
|
||||
|
||||
export type ThemeOverridesMap = Record<string, ThemeColorOverrides>
|
||||
|
||||
function parseHex(hex: string): [number, number, number] | null {
|
||||
const h = hex.trim().replace(/^#/, '')
|
||||
if (!/^[0-9a-f]{6}$/i.test(h)) return null
|
||||
return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)]
|
||||
}
|
||||
|
||||
export function hexToRgbString(hex: string): string {
|
||||
const c = parseHex(hex)
|
||||
return c ? `${c[0]}, ${c[1]}, ${c[2]}` : '0, 0, 0'
|
||||
}
|
||||
|
||||
function mixHex(a: string, b: string, t: number): string {
|
||||
const ca = parseHex(a)
|
||||
const cb = parseHex(b)
|
||||
if (!ca || !cb) return a
|
||||
const ch = (i: number) => Math.round(ca[i] + (cb[i] - ca[i]) * t)
|
||||
const to = (n: number) => n.toString(16).padStart(2, '0')
|
||||
return `#${to(ch(0))}${to(ch(1))}${to(ch(2))}`
|
||||
}
|
||||
|
||||
function lighten(hex: string, amount: number): string {
|
||||
return mixHex(hex, '#ffffff', amount)
|
||||
}
|
||||
|
||||
function darken(hex: string, amount: number): string {
|
||||
return mixHex(hex, '#000000', amount)
|
||||
}
|
||||
|
||||
/** 根据三项取色结果推导相关 CSS 变量与预览色 */
|
||||
export function deriveThemeVars(
|
||||
base: ThemePreset,
|
||||
overrides: ThemeColorOverrides
|
||||
): Record<string, string> {
|
||||
const vars = { ...base.vars }
|
||||
const primary = overrides['--mg-primary'] ?? vars['--mg-primary']
|
||||
const hover = overrides['--mg-primary-hover'] ?? vars['--mg-primary-hover']
|
||||
const accent = overrides['--mg-accent'] ?? vars['--mg-accent']
|
||||
|
||||
if (overrides['--mg-primary']) {
|
||||
const bgDeep = darken(primary, 0.72)
|
||||
const bgMid = darken(primary, 0.48)
|
||||
const bgShallow = darken(primary, 0.58)
|
||||
const asideMain = darken(primary, 0.54)
|
||||
const asideDeep = darken(primary, 0.7)
|
||||
const cardBase = darken(primary, 0.38)
|
||||
const cardHi = darken(primary, 0.28)
|
||||
const cardDark = darken(primary, 0.62)
|
||||
|
||||
vars['--mg-primary'] = primary
|
||||
vars['--mg-primary-rgb'] = hexToRgbString(primary)
|
||||
vars['--mg-primary-active'] = darken(primary, 0.35)
|
||||
vars['--mg-primary-dark-2'] = darken(primary, 0.35)
|
||||
vars['--mg-primary-light-3'] = lighten(primary, 0.22)
|
||||
vars['--mg-primary-light-5'] = lighten(primary, 0.35)
|
||||
vars['--mg-primary-light-7'] = mixHex(primary, '#888888', 0.45)
|
||||
vars['--mg-primary-light-8'] = mixHex(primary, '#666666', 0.55)
|
||||
vars['--mg-primary-light-9'] = mixHex(primary, '#444444', 0.65)
|
||||
|
||||
vars['--mg-bg-app-1'] = bgShallow
|
||||
vars['--mg-bg-app-2'] = bgMid
|
||||
vars['--mg-bg-app-3'] = bgDeep
|
||||
vars['--mg-bg-app-deep-rgb'] = hexToRgbString(bgDeep)
|
||||
vars['--mg-bg-aside-1'] = asideMain
|
||||
vars['--mg-bg-aside-2'] = asideDeep
|
||||
vars['--mg-bg-aside-rgb'] = hexToRgbString(asideMain)
|
||||
vars['--mg-bg-card-rgb'] = hexToRgbString(cardBase)
|
||||
vars['--mg-bg-card-hi-rgb'] = hexToRgbString(cardHi)
|
||||
vars['--mg-bg-card-darker-rgb'] = hexToRgbString(cardDark)
|
||||
vars['--mg-shadow-rgb'] = hexToRgbString(darken(primary, 0.78))
|
||||
}
|
||||
|
||||
if (overrides['--mg-primary-hover']) {
|
||||
vars['--mg-primary-hover'] = hover
|
||||
vars['--mg-primary-hover-rgb'] = hexToRgbString(hover)
|
||||
}
|
||||
|
||||
if (overrides['--mg-accent']) {
|
||||
vars['--mg-accent'] = accent
|
||||
vars['--mg-accent-rgb'] = hexToRgbString(accent)
|
||||
}
|
||||
|
||||
return vars
|
||||
}
|
||||
|
||||
export function mergeThemePreset(
|
||||
preset: ThemePreset,
|
||||
overrides: ThemeColorOverrides | undefined
|
||||
): ThemePreset {
|
||||
if (!overrides || Object.keys(overrides).length === 0) return preset
|
||||
const vars = deriveThemeVars(preset, overrides)
|
||||
return {
|
||||
...preset,
|
||||
preview: overrides['--mg-primary'] ?? preset.preview,
|
||||
previewAccent: overrides['--mg-accent'] ?? preset.previewAccent,
|
||||
vars
|
||||
}
|
||||
}
|
||||
|
||||
export function getOverrideOrDefault(
|
||||
preset: ThemePreset,
|
||||
overrides: ThemeColorOverrides | undefined,
|
||||
key: ThemeColorKey
|
||||
): string {
|
||||
return overrides?.[key] ?? preset.vars[key]
|
||||
}
|
||||
@@ -0,0 +1,336 @@
|
||||
/**
|
||||
* 主题色板预设
|
||||
*
|
||||
* 设计原则:
|
||||
* - 每个主题提供一组 CSS 变量,运行时通过 document.documentElement.style.setProperty 注入
|
||||
* - 命名遵循 theme.css 中既有的 --mg-* 体系
|
||||
* - 工业紫色 (industrial-purple) 为默认主题:紫蓝渐变,深蓝底 + 天蓝高光
|
||||
*/
|
||||
|
||||
export interface ThemePreset {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
/** 用于 UI 预览的代表色 */
|
||||
preview: string
|
||||
/** 强调高光,用于 UI 预览的次代表色 */
|
||||
previewAccent: string
|
||||
vars: Record<string, string>
|
||||
}
|
||||
|
||||
export const DEFAULT_THEME_ID = 'fame-lavender'
|
||||
|
||||
export const THEMES: ThemePreset[] = [
|
||||
{
|
||||
id: 'fame-lavender',
|
||||
name: '迷毂浅紫',
|
||||
description: '参考 FAME 系统:深紫侧栏 + 浅紫白主区,柔和耐看,长时间使用不刺眼',
|
||||
preview: '#7c3aed',
|
||||
previewAccent: '#a855f7',
|
||||
vars: {
|
||||
// 主色保留紫色(与 FAME 一致),但 hover/accent 使用更明亮的 violet/purple
|
||||
'--mg-primary': '#7c3aed',
|
||||
'--mg-primary-rgb': '124, 58, 237',
|
||||
'--mg-primary-hover': '#8b5cf6',
|
||||
'--mg-primary-hover-rgb': '139, 92, 246',
|
||||
'--mg-primary-active': '#5b21b6',
|
||||
'--mg-accent': '#a855f7',
|
||||
'--mg-accent-rgb': '168, 85, 247',
|
||||
|
||||
'--mg-primary-light-3': '#a78bfa',
|
||||
'--mg-primary-light-5': '#c4b5fd',
|
||||
'--mg-primary-light-7': '#ddd6fe',
|
||||
'--mg-primary-light-8': '#ede9fe',
|
||||
'--mg-primary-light-9': '#f5f3ff',
|
||||
'--mg-primary-dark-2': '#6d28d9',
|
||||
|
||||
// 主区背景:浅紫白色(FAME content #f0eaf6 风格)
|
||||
'--mg-bg-app-1': '#f5f0fb',
|
||||
'--mg-bg-app-2': '#f0eaf6',
|
||||
'--mg-bg-app-3': '#faf7fd',
|
||||
'--mg-bg-app-deep-rgb': '240, 234, 246',
|
||||
|
||||
// 侧栏:FAME 深紫渐变 #2d1b69 -> #1a1040
|
||||
'--mg-bg-aside-1': '#2d1b69',
|
||||
'--mg-bg-aside-2': '#1a1040',
|
||||
'--mg-bg-aside-rgb': '45, 27, 105',
|
||||
|
||||
// 卡片:白底(在 fame-lavender 专属规则中走纯 #fff)
|
||||
'--mg-bg-card-rgb': '255, 255, 255',
|
||||
'--mg-bg-card-hi-rgb': '249, 246, 255',
|
||||
'--mg-bg-card-darker-rgb':'245, 240, 251',
|
||||
'--mg-shadow-rgb': '124, 58, 237',
|
||||
|
||||
// 文字:深紫标题 #2d1b69, 中灰正文 #606266
|
||||
'--mg-text-tint-rgb': '45, 27, 105',
|
||||
'--mg-text-hi-rgb': '96, 98, 102',
|
||||
|
||||
// 关闭装饰性 orb
|
||||
'--mg-orb-a-opacity': '0',
|
||||
'--mg-orb-b-opacity': '0',
|
||||
'--mg-orb-c-opacity': '0'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'industrial-purple',
|
||||
name: '星云紫',
|
||||
description: '科幻紫主色,辅以蓝紫渐变,磨砂玻璃 · 霓虹辉光',
|
||||
preview: '#7c3aed',
|
||||
previewAccent: '#c4b5fd',
|
||||
vars: {
|
||||
// ── 主色:明亮 violet(紫),主导整个画面 ──
|
||||
'--mg-primary': '#7c3aed',
|
||||
'--mg-primary-rgb': '124, 58, 237',
|
||||
// hover/辉光:偏蓝紫 indigo,带来氛围流动感
|
||||
'--mg-primary-hover': '#8b5cf6',
|
||||
'--mg-primary-hover-rgb': '139, 92, 246',
|
||||
'--mg-primary-active': '#5b21b6',
|
||||
// accent:浅紫高光,边缘霓虹与点缀
|
||||
'--mg-accent': '#c4b5fd',
|
||||
'--mg-accent-rgb': '196, 181, 253',
|
||||
|
||||
'--mg-primary-light-3': '#9d6cf2',
|
||||
'--mg-primary-light-5': '#b594f7',
|
||||
'--mg-primary-light-7': '#6f4eab',
|
||||
'--mg-primary-light-8': '#4c357a',
|
||||
'--mg-primary-light-9': '#2d1f55',
|
||||
'--mg-primary-dark-2': '#4c1d95',
|
||||
|
||||
// ── 背景:深紫黑底,带 indigo 余晖 ──
|
||||
'--mg-bg-app-1': '#0e0a24',
|
||||
'--mg-bg-app-2': '#1f1147',
|
||||
'--mg-bg-app-3': '#06031a',
|
||||
'--mg-bg-app-deep-rgb': '6, 3, 26',
|
||||
|
||||
'--mg-bg-aside-1': '#0c0820',
|
||||
'--mg-bg-aside-2': '#03020e',
|
||||
'--mg-bg-aside-rgb': '10, 6, 30',
|
||||
|
||||
'--mg-bg-card-rgb': '38, 24, 78',
|
||||
'--mg-bg-card-hi-rgb': '58, 38, 110',
|
||||
'--mg-bg-card-darker-rgb': '20, 12, 48',
|
||||
'--mg-shadow-rgb': '8, 2, 24',
|
||||
|
||||
'--mg-text-tint-rgb': '242, 232, 255',
|
||||
'--mg-text-hi-rgb': '220, 200, 252',
|
||||
|
||||
'--mg-orb-a-opacity': '0.55',
|
||||
'--mg-orb-b-opacity': '0.50',
|
||||
'--mg-orb-c-opacity': '0.40'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'deep-azure',
|
||||
name: '深邃蓝',
|
||||
description: '科技感深蓝,沉静稳重',
|
||||
preview: '#1565c0',
|
||||
previewAccent: '#82b1ff',
|
||||
vars: {
|
||||
'--mg-primary': '#1565c0',
|
||||
'--mg-primary-rgb': '21, 101, 192',
|
||||
'--mg-primary-hover': '#1976d2',
|
||||
'--mg-primary-hover-rgb': '25, 118, 210',
|
||||
'--mg-primary-active': '#0d47a1',
|
||||
'--mg-accent': '#82b1ff',
|
||||
'--mg-accent-rgb': '130, 177, 255',
|
||||
|
||||
'--mg-primary-light-3': '#5a8fce',
|
||||
'--mg-primary-light-5': '#83aedb',
|
||||
'--mg-primary-light-7': '#adcde7',
|
||||
'--mg-primary-light-8': '#c4dbed',
|
||||
'--mg-primary-light-9': '#e3eef8',
|
||||
'--mg-primary-dark-2': '#0d47a1',
|
||||
|
||||
'--mg-bg-app-1': '#051a3a',
|
||||
'--mg-bg-app-2': '#0a2f6b',
|
||||
'--mg-bg-app-3': '#020a1f',
|
||||
'--mg-bg-app-deep-rgb': '2, 8, 26',
|
||||
|
||||
'--mg-bg-aside-1': '#06122a',
|
||||
'--mg-bg-aside-2': '#02060f',
|
||||
'--mg-bg-aside-rgb': '8, 18, 42',
|
||||
|
||||
'--mg-bg-card-rgb': '12, 36, 80',
|
||||
'--mg-bg-card-hi-rgb': '18, 50, 110',
|
||||
'--mg-bg-card-darker-rgb': '6, 18, 44',
|
||||
'--mg-shadow-rgb': '2, 4, 14',
|
||||
|
||||
'--mg-text-tint-rgb': '218, 234, 252',
|
||||
'--mg-text-hi-rgb': '170, 200, 240'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'emerald-forge',
|
||||
name: '翡翠绿',
|
||||
description: '机械翡翠绿,沉稳活力',
|
||||
preview: '#00796b',
|
||||
previewAccent: '#80cbc4',
|
||||
vars: {
|
||||
'--mg-primary': '#00796b',
|
||||
'--mg-primary-rgb': '0, 121, 107',
|
||||
'--mg-primary-hover': '#00897b',
|
||||
'--mg-primary-hover-rgb': '0, 137, 123',
|
||||
'--mg-primary-active': '#004d40',
|
||||
'--mg-accent': '#80cbc4',
|
||||
'--mg-accent-rgb': '128, 203, 196',
|
||||
|
||||
'--mg-primary-light-3': '#4ca094',
|
||||
'--mg-primary-light-5': '#79b9b0',
|
||||
'--mg-primary-light-7': '#a6d2cc',
|
||||
'--mg-primary-light-8': '#bfdfdb',
|
||||
'--mg-primary-light-9': '#e1f0ee',
|
||||
'--mg-primary-dark-2': '#004d40',
|
||||
|
||||
'--mg-bg-app-1': '#06241f',
|
||||
'--mg-bg-app-2': '#0a4538',
|
||||
'--mg-bg-app-3': '#020f0c',
|
||||
'--mg-bg-app-deep-rgb': '2, 12, 10',
|
||||
|
||||
'--mg-bg-aside-1': '#061a16',
|
||||
'--mg-bg-aside-2': '#020a08',
|
||||
'--mg-bg-aside-rgb': '8, 26, 22',
|
||||
|
||||
'--mg-bg-card-rgb': '10, 50, 42',
|
||||
'--mg-bg-card-hi-rgb': '14, 66, 56',
|
||||
'--mg-bg-card-darker-rgb': '6, 28, 24',
|
||||
'--mg-shadow-rgb': '2, 10, 8',
|
||||
|
||||
'--mg-text-tint-rgb': '220, 244, 240',
|
||||
'--mg-text-hi-rgb': '170, 220, 210'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'crimson-iron',
|
||||
name: '熔铁赤',
|
||||
description: '熔铁炉火般的暗红工业风',
|
||||
preview: '#b71c1c',
|
||||
previewAccent: '#ff8a80',
|
||||
vars: {
|
||||
'--mg-primary': '#b71c1c',
|
||||
'--mg-primary-rgb': '183, 28, 28',
|
||||
'--mg-primary-hover': '#d32f2f',
|
||||
'--mg-primary-hover-rgb': '211, 47, 47',
|
||||
'--mg-primary-active': '#8a0c0c',
|
||||
'--mg-accent': '#ff8a80',
|
||||
'--mg-accent-rgb': '255, 138, 128',
|
||||
|
||||
'--mg-primary-light-3': '#c95a5a',
|
||||
'--mg-primary-light-5': '#d68585',
|
||||
'--mg-primary-light-7': '#e4afaf',
|
||||
'--mg-primary-light-8': '#edc8c8',
|
||||
'--mg-primary-light-9': '#f7e6e6',
|
||||
'--mg-primary-dark-2': '#8a0c0c',
|
||||
|
||||
'--mg-bg-app-1': '#2a0606',
|
||||
'--mg-bg-app-2': '#4a0c0c',
|
||||
'--mg-bg-app-3': '#150303',
|
||||
'--mg-bg-app-deep-rgb': '20, 4, 4',
|
||||
|
||||
'--mg-bg-aside-1': '#200505',
|
||||
'--mg-bg-aside-2': '#0c0202',
|
||||
'--mg-bg-aside-rgb': '36, 6, 6',
|
||||
|
||||
'--mg-bg-card-rgb': '60, 10, 10',
|
||||
'--mg-bg-card-hi-rgb': '80, 14, 14',
|
||||
'--mg-bg-card-darker-rgb': '34, 5, 5',
|
||||
'--mg-shadow-rgb': '10, 1, 1',
|
||||
|
||||
'--mg-text-tint-rgb': '252, 220, 220',
|
||||
'--mg-text-hi-rgb': '240, 175, 175'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'graphite-steel',
|
||||
name: '石墨钢',
|
||||
description: '中性冷灰,纯粹工业',
|
||||
preview: '#455a64',
|
||||
previewAccent: '#b0bec5',
|
||||
vars: {
|
||||
'--mg-primary': '#455a64',
|
||||
'--mg-primary-rgb': '69, 90, 100',
|
||||
'--mg-primary-hover': '#607d8b',
|
||||
'--mg-primary-hover-rgb': '96, 125, 139',
|
||||
'--mg-primary-active': '#263238',
|
||||
'--mg-accent': '#b0bec5',
|
||||
'--mg-accent-rgb': '176, 190, 197',
|
||||
|
||||
'--mg-primary-light-3': '#7a8a92',
|
||||
'--mg-primary-light-5': '#9aa6ac',
|
||||
'--mg-primary-light-7': '#bac2c7',
|
||||
'--mg-primary-light-8': '#ccd2d6',
|
||||
'--mg-primary-light-9': '#e8ebed',
|
||||
'--mg-primary-dark-2': '#263238',
|
||||
|
||||
'--mg-bg-app-1': '#161e24',
|
||||
'--mg-bg-app-2': '#243038',
|
||||
'--mg-bg-app-3': '#0a0e12',
|
||||
'--mg-bg-app-deep-rgb': '10, 14, 18',
|
||||
|
||||
'--mg-bg-aside-1': '#11181d',
|
||||
'--mg-bg-aside-2': '#060a0d',
|
||||
'--mg-bg-aside-rgb': '18, 26, 32',
|
||||
|
||||
'--mg-bg-card-rgb': '30, 42, 50',
|
||||
'--mg-bg-card-hi-rgb': '42, 56, 66',
|
||||
'--mg-bg-card-darker-rgb': '16, 22, 28',
|
||||
'--mg-shadow-rgb': '4, 6, 10',
|
||||
|
||||
'--mg-text-tint-rgb': '226, 232, 238',
|
||||
'--mg-text-hi-rgb': '186, 198, 210'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'amber-forge',
|
||||
name: '琥珀金',
|
||||
description: '高对比的橙金调,警示与活力并存',
|
||||
preview: '#e65100',
|
||||
previewAccent: '#ffb74d',
|
||||
vars: {
|
||||
'--mg-primary': '#e65100',
|
||||
'--mg-primary-rgb': '230, 81, 0',
|
||||
'--mg-primary-hover': '#f57c00',
|
||||
'--mg-primary-hover-rgb': '245, 124, 0',
|
||||
'--mg-primary-active': '#bf360c',
|
||||
'--mg-accent': '#ffb74d',
|
||||
'--mg-accent-rgb': '255, 183, 77',
|
||||
|
||||
'--mg-primary-light-3': '#ec8344',
|
||||
'--mg-primary-light-5': '#f2a474',
|
||||
'--mg-primary-light-7': '#f7c4a3',
|
||||
'--mg-primary-light-8': '#fad6bc',
|
||||
'--mg-primary-light-9': '#fdeadd',
|
||||
'--mg-primary-dark-2': '#bf360c',
|
||||
|
||||
'--mg-bg-app-1': '#2a1404',
|
||||
'--mg-bg-app-2': '#4a2a0c',
|
||||
'--mg-bg-app-3': '#150a02',
|
||||
'--mg-bg-app-deep-rgb': '20, 10, 4',
|
||||
|
||||
'--mg-bg-aside-1': '#200f04',
|
||||
'--mg-bg-aside-2': '#0c0602',
|
||||
'--mg-bg-aside-rgb': '34, 16, 6',
|
||||
|
||||
'--mg-bg-card-rgb': '62, 32, 8',
|
||||
'--mg-bg-card-hi-rgb': '82, 44, 12',
|
||||
'--mg-bg-card-darker-rgb': '34, 16, 4',
|
||||
'--mg-shadow-rgb': '10, 4, 1',
|
||||
|
||||
'--mg-text-tint-rgb': '252, 232, 210',
|
||||
'--mg-text-hi-rgb': '240, 196, 140'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
export function findTheme(id: string | undefined | null): ThemePreset {
|
||||
if (!id) return THEMES[0]
|
||||
return THEMES.find((t) => t.id === id) ?? THEMES[0]
|
||||
}
|
||||
|
||||
/** 把主题变量写入 :root;多次调用幂等,会覆盖同名属性 */
|
||||
export function applyThemeVars(preset: ThemePreset): void {
|
||||
const root = document.documentElement
|
||||
for (const [key, value] of Object.entries(preset.vars)) {
|
||||
root.style.setProperty(key, value)
|
||||
}
|
||||
root.setAttribute('data-theme', preset.id)
|
||||
}
|
||||
Reference in New Issue
Block a user