锁定 Outpost 主路径主题,并优化地图编辑属性面板浅色对比。

统一 outpost-light/purple/blue 壳层 token,修复扩展字段与操作按钮在浅色下的可读性。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zhaowei.huang
2026-07-26 11:31:53 +08:00
co-authored by Cursor
parent 6986370e9d
commit 95c1a74122
18 changed files with 3790 additions and 1651 deletions
@@ -0,0 +1,23 @@
# 迷毂 Outpost UI 实现计划(P0P1
> **面向 AI 代理的工作者:** 按任务顺序实现;规格见 `docs/superpowers/specs/2026-07-25-migu-outpost-ui-lock-design.md`。
## 文件
| 文件 | 职责 |
|---|---|
| `src/styles/themes.ts` | PRIMARY 三色 + LEGACY;默认 `outpost-light``data-shell` |
| `src/styles/theme.css` | 浅色壳选择器改 `data-shell="outpost"`;默认 token 贴近浅色 |
| `src/stores/ui.ts` | `availableThemes` / `legacyThemes`;未知 id 迁移 |
| `src/components/ThemeSwitcher.vue` | 仅精选 3 |
| `src/layouts/AppShell.vue` | 高级 → 兼容主题 |
| `src/views/LoginView.vue` | 弱化霓虹(P1 |
| 若干 Vue 内 `fame-lavender` 选择器 | 改为 `data-shell="outpost"` |
## 任务
1. 重写 `themes.ts`(精选 + legacy + applyThemeVars 写 `data-shell`
2. `theme.css` / 组件选择器:`fame-lavender``data-shell="outpost"`
3. `ui` store + ThemeSwitcher + AppShell 高级入口
4. Login 弱化星空主导(能快则做)
5. `pnpm`/`npm` typecheck 或 build 验证
@@ -0,0 +1,273 @@
# 迷毂平台 UI · 锁定 Outpost 浅色设计规格
> 状态:待用户审查(主题策略已按反馈修订)
> 日期:2026-07-25
> 参考站:[Outpost · Fairyland Technology](https://fairylandtech.amerc.ai)
> 决策:方案 1 壳层气质(Outpost)+ **主路径精选 3 套配色(浅色 / 紫色 / 蓝色)**;旧深色霓虹主题**保留并藏入高级**
> 范围前端:`Migu2.0/frontends/apps/simple-platform-vue`
> 不改:SimpleLite ImGui / CycleGUI 桌面壳、后端 API 契约
---
## 1. 目标与非目标
### 1.1 目标
- 迷毂 Web(登录 → AppShell → 业务页 → 地图编辑器 chrome)在视觉与壳层气质上对齐 Outpost。
- **壳层统一为 Outpost 结构**:浅主区 + 深色纹理侧栏 + 纸白卡片 + 柔阴影;无默认霓虹 orb。
- **主路径提供 3 套精选配色**(可一键切换):浅色(默认)、紫色、蓝色——同结构、不同主色/侧栏色相。
- 旧深色霓虹主题(星云紫等)**代码保留**,放入「高级 / 兼容主题」,不进主切换列表。
- 保留调度业务信息架构;**不**把 Outpost 会话列表硬套进调度导航。
- 状态色(绿/琥珀/红/蓝/灰)与品牌色解耦,语义不变。
### 1.2 非目标
- 不重写业务逻辑、路由权限、Projection/SSE 协议。
- 不第一阶段替换 Element Plus(以 CSS 变量 + 皮肤覆盖为主)。
- 不强制 3D 画布(webVRender)改浅色底。
- 不把 Outpost 的 Agent 对话 IA 迁移为迷毂主导航。
- 主路径不堆砌过多主题(精选 3 + 高级兼容即可)。
---
## 2. 产品决策(已确认)
| 项 | 决策 |
|---|---|
| 壳层气质 | Outpost:浅主区 + 深侧栏 + 纸白卡 + 柔阴影 |
| 主路径配色 | **3 套精选**:浅色(默认)/ 紫色 / 蓝色 |
| 旧深色主题 | **保留**,藏入用户菜单「高级 → 兼容主题」 |
| ThemeSwitcher | **保留在顶栏**,只列出精选 3 套;兼容主题在高级入口 |
| 登录页 | 跟当前精选主题(默认浅色 Outpost);去掉星空霓虹主导 |
| 地图编辑器 | Chrome 跟精选浅色壳;**3D 画布保持深色** |
| AI 助手抽屉 | 视觉跟 Outpost 对话区,不改后端权限 |
---
## 3. 参考站提炼(Outpost
### 3.1 信息架构(借壳不借业务)
```
┌────────────┬──────────────────────────────────────┐
│ Sidebar │ Top context (optional, light) │
│ ~256px ├──────────────────────────────────────┤
│ dark plum │ Main workspace │
│ textured │ paper cards / lists / forms │
│ brand+nav │ │
└────────────┴──────────────────────────────────────┘
```
迷毂映射:
| Outpost | 迷毂 |
|---|---|
| Sidebar brand + list | `AppShell` 侧栏 logo + `el-menu` |
| Workspace | `el-main` + 各业务 View |
| Artifact / card | KPI 卡、列表卡、属性面板、抽屉 |
| Conversation chrome | AI 助手抽屉(仅视觉) |
### 3.2 设计 Token(锁定值)
来源:Outpost 线上 CSS`color-scheme: light`)。
| Token | 值 | 用途 |
|---|---|---|
| `--op-page` | `#f6f3fb` | 应用主区底 |
| `--op-paper` | `#fffefd` | 卡片/面板底 |
| `--op-paper-soft` | `#fbf9fd` | 次级表面 |
| `--op-ink` | `#28213a` | 主文字 |
| `--op-muted` | `#756d85` | 次级文字 |
| `--op-faint` | `#9a93a7` | 弱化/占位 |
| `--op-line` | `#e8e2ef` | 分割线 |
| `--op-line-strong` | `#ddd4e8` | 强调边框 |
| `--op-violet` | `#7543e8` | 品牌主色 |
| `--op-violet-strong` | `#5d2ecb` | 主色按下/深 |
| `--op-violet-soft` | `#eee8ff` | 主色浅底 |
| `--op-mint` / `--op-amber` / `--op-danger` / `--op-blue` | 见参考站 | 仅作辅助;**业务状态仍用既有 `--mg-status-*`** |
| `--op-shadow` | `0 12px 35px rgba(54,35,78,.08)` | 默认浮起 |
| `--op-shadow-strong` | `0 22px 65px rgba(46,24,72,.18)` | 弹层/强调卡 |
| `--op-radius` | `16px` | 默认圆角 |
| Sidebar | `linear-gradient(160deg,#3f2454,#2c193c 45%,#241530)` + 细纹理 | 侧栏底 |
| Font | Inter + 系统无衬线;数据位保留 JetBrains Mono | 与现 `--mg-font-*` 对齐 |
### 3.3 明确禁止(相对当前「星云紫」)
- 主路径禁用:大面积霓虹 glow、多层 orb 背景、深色玻璃卡片作默认表面。
- 主路径禁用:把品牌紫当 success/warning。
- 避免再引入第三套互不兼容的色板命名;Outpost token **映射进**既有 `--mg-*`,业务组件继续只读 `--mg-*`
---
## 4. 精选三套主题 + Token 映射
三套均遵守同一 Outpost 壳层规则(浅主区 / 纸白卡 / 柔阴影 / orb=0 / radius≈16),仅 **主色 + 侧栏色相 + 主区底轻微染色** 不同。
### 4.1 主路径精选(`PRIMARY_THEMES`
| id | 显示名 | 定位 | 主色 | 侧栏 | 主区底 |
|---|---|---|---|---|---|
| `outpost-light` | **浅色**(默认) | 最贴近参考站 | `#7543e8` | `#3f2454 → #241530`plum | `#f6f3fb` |
| `outpost-purple` | **紫色** | 同结构、更饱和紫品牌 | `#7c3aed` | `#2d1b69 → #1a1040` | `#f3eefc`(略偏紫) |
| `outpost-blue` | **蓝色** | 同结构、蓝科技感 | `#3c78d2` | `#0f2744 → #0a1a30` | `#f3f6fb`(略偏蓝) |
共用表面:
| `--mg-*` | 值 |
|---|---|
| `--mg-bg-card-rgb` | paper `255,254,253` |
| `--mg-text` 系 | ink `#28213a` / muted `#756d85`(浅底深字) |
| `--mg-glass-*` | 白卡片 + 细边 + 柔阴影(无强 blur glow |
| `--mg-radius*` | 基准 16px |
| `--mg-orb-*-opacity` | **0** |
| 状态色 | 继续 `--mg-status-*`,不随主题主色改语义 |
### 4.2 高级兼容主题(`LEGACY_THEMES`,保留)
现有深色霓虹预设移入此列表(至少含):
- `industrial-purple` 星云紫
- `deep-azure` 深邃蓝
- `emerald-forge` / `crimson-iron` / `graphite-steel` / `amber-forge`(若仍在代码中)
-`fame-lavender`:合并进 `outpost-purple` 或标 deprecated 后从主列表移除
入口:用户头像菜单 → **高级 → 兼容主题**(折叠)。主顶栏 `ThemeSwitcher` **只显示 3 个精选**
### 4.3 默认与迁移
- `DEFAULT_THEME_ID = 'outpost-light'`
- `:root` 默认 CSS 与 `outpost-light` 一致,避免闪屏
- localStorage 若为未知 / 已 deprecated id → 迁移到 `outpost-light`
- 若为 `LEGACY_THEMES` 中的 id → **尊重用户选择**(不强制踢回浅色)
- 登录页品牌区跟当前主题侧栏色;`--lg-*` 随精选主题切换或登录页固定跟 `outpost-light`(实现时二选一,推荐:**登录固定 outpost-light,进壳后再跟用户主题**)
---
## 5. 壳层与页面改造要点
### 5.1 AppShell
- 侧栏:纹理渐变(随主题色相)、品牌区分隔线、菜单选中用 soft 底或左侧高光条。
- 主区装饰 orb`opacity=0`
- 顶栏:浅底、细边、弱阴影;breadcrumb + **精选 ThemeSwitcher3 项)** + 用户区。
- 用户菜单增加「高级 → 兼容主题」子菜单(列出 `LEGACY_THEMES`)。
- 底栏:弱化字色或并入顶栏次级信息(可选后续)。
- `aside-footer`:版本号;可选极简当前主题名。
### 5.2 登录 / 配置向导
- 布局可保留左右分栏,但:
- 背景:浅底 + 可选极轻纹理,不用星空粒子主导。
- 左:深色品牌板(默认跟 `outpost-light` 侧栏)。
- 右:paper 表单卡 + 主色按钮。
- 配置向导与登录同肤。
### 5.3 Element Plus 皮肤
统一覆盖(全局):
- Button primary / default
- Card / Dialog / Drawer / MessageBox
- Table / Pagination
- Menu(侧栏已透明底,补选中态)
- Input / Select / Tabs / Tag
原则:纸白表面、ink 文字、violet 主按钮、radius≈1416、阴影用 `--op-shadow`
### 5.4 业务页(渐进,同一皮肤)
优先级:
1. Dashboard / 地图监控 / 调度工作台
2. 任务 / 告警 / 车辆中心
3. OTA / 配置中心 / 服务状态
4. AI 助手抽屉
统一模式:`page-header` + paper 卡片栅格 + 紫 CTA;清理页面内 `industrial-purple` 专用覆盖或改为仅在「兼容主题」下生效。
### 5.5 地图编辑器
| 层 | 皮肤 |
|---|---|
| EditTopBar / ToolRail / PropertyPanel / StatusBar / AI 侧栏 | Outpost 浅色 |
| Workspace3D / webVRender 画布 | **保持深色视口** |
| 与壳层交界 | 1px `--op-line`,避免深浅硬切刺眼 |
### 5.6 主题列表处理
| 分组 | 主题 | 处理 |
|---|---|---|
| 精选 | `outpost-light` / `outpost-purple` / `outpost-blue` | 新建或由现有浅色主题改版;顶栏可切换 |
| 合并 | `fame-lavender` | 并入 `outpost-purple` 或启动时 remap |
| 高级兼容 | `industrial-purple``deep-azure`、绿/赤/钢/琥珀等 | 保留完整 vars,仅高级菜单可选 |
| 持久化 | 精选或 legacy id | 精选正常读写;未知 id → `outpost-light`legacy id **不强制迁移** |
---
## 6. 文件影响面(实现时)
| 文件/目录 | 职责 |
|---|---|
| `src/styles/theme.css` | 默认 token、Element 覆盖、禁 orb |
| `src/styles/themes.ts` | `PRIMARY_THEMES`3+ `LEGACY_THEMES`;默认 `outpost-light` |
| `src/stores/ui.ts`(或等价) | 默认主题、未知 id 迁移、legacy 可选 |
| `src/components/ThemeSwitcher.vue` | 只渲染精选 3;样式 Outpost 化 |
| `src/layouts/AppShell.vue` | 侧栏/顶栏;用户菜单挂「兼容主题」 |
| `src/views/LoginView.vue` | 登录换肤 |
| `design-system/MASTER.md` | 产品风格改为 Outpost 浅色工作台 |
| `design-system/outpost.md` | Token 与组件规范(新建) |
| 各 View 内 `:root[data-theme="industrial-purple"]` 块 | 降级为 legacy 或删除 |
| 地图编辑器 chrome 组件 | 浅色适配 |
---
## 7. 分阶段交付
| Phase | 内容 | 验收 |
|---|---|---|
| **P0** | 三套精选 token + 默认 `outpost-light` + legacy 分组 + 文档 | 冷启动浅色 Outpost;可切紫/蓝 |
| **P1** | AppShell + 登录 + Element 皮肤 + ThemeSwitcher(3) + 高级兼容入口 | 壳层/登录 Outpost 化;高级仍能开星云紫等 |
| **P2** | Dashboard / 监控 / 调度 / 列表页 | 主业务无霓虹玻璃默认态 |
| **P3** | 地图编辑器 chrome + AI 抽屉 | 外围浅、画布深;助手区纸白气泡 |
| **P4** | 清理 legacy 覆盖、MASTER 更新、回归清单 | 无残留 `industrial-purple` 主路径样式 |
每阶段可独立合并;**不在本规格内自动改 SimpleLite 原生菜单皮肤**。
---
## 8. 风险与缓解
| 风险 | 缓解 |
|---|---|
| 深色主题下写死的白字/半透明在浅底不可读 | P0 强制迁移 + P2 逐页扫对比度 |
| 地图页信息密度高,浅色易显脏 | 卡片分区 + 表格紧凑密度,状态色保留 |
| Element 覆盖不全 | 先覆盖高频控件,缺口记入回归清单 |
| 用户 localStorage 旧主题 | 启动迁移 + 一次性提示(可选) |
---
## 9. 验收清单(整站)
- [ ] 未登录:登录页为 Outpost 浅色气质
- [ ] 登录后:侧栏深紫纹理 + 主区 `#f6f3fb` + 纸白卡片
- [ ] 顶栏可切换 **浅色 / 紫色 / 蓝色** 三套精选
- [ ] 用户菜单「高级 → 兼容主题」仍能选星云紫等旧深色
- [ ] 监控/调度/OTA/配置:纸白卡、ink 字、无默认霓虹 orb
- [ ] 地图编辑器:工具/属性浅色,3D 画布仍深色可读
- [ ] 告警/成功/故障色不被品牌色替换
- [ ] 刷新后主题保持;未知 id 回落 `outpost-light`
---
## 10. 开放项(审查时确认)
1. ~~旧深色主题~~**已确认:保留并藏入高级**
2. **底栏 footer**:保留弱化版,还是删除并入顶栏?(建议:P1 先弱化保留)
3. 实现计划:`Migu2.0/docs/superpowers/plans/2026-07-25-migu-outpost-ui-lock.md`(本规格通过后编写)
---
## 11. 审查结论(请用户勾选)
- [ ] 同意本规格(含精选三色 + 高级保留旧主题),可编写实现计划并开工
- [ ] 需要修改:________
@@ -0,0 +1,22 @@
# Outpost 视觉规范(迷毂壳层)
> 权威决策见:`Migu2.0/docs/superpowers/specs/2026-07-25-migu-outpost-ui-lock-design.md`
> 参考:https://fairylandtech.amerc.ai
## 一句话
Outpost 壳层:浅主区 + 深色纹理侧栏 + 纸白卡片 + 柔阴影。
主路径精选三色:**浅色(默认)/ 紫色 / 蓝色**;旧深色霓虹主题保留在「高级 → 兼容主题」。
## 精选主题 id
| id | 名称 |
|---|---|
| `outpost-light` | 浅色(默认) |
| `outpost-purple` | 紫色 |
| `outpost-blue` | 蓝色 |
## 例外
- webVRender / 地图 3D 画布:保持深色视口。
- 状态色:继续用 `--mg-status-*`
@@ -1,11 +1,14 @@
<!doctype html> <!doctype html>
<html lang="zh-CN"> <html lang="zh-CN" data-shell="outpost" data-theme="outpost-light">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>迷毂 · 智能调度平台</title> <title>迷毂 · 智能调度平台</title>
<meta name="theme-color" content="#2e3f8a" /> <meta name="theme-color" content="#7543e8" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
@@ -291,14 +291,23 @@ defineExpose({ reload, enterFullscreen })
.workspace-3d-mask { .workspace-3d-mask {
position: absolute; inset: 0; display: flex; flex-direction: column; position: absolute; inset: 0; display: flex; flex-direction: column;
align-items: center; justify-content: center; gap: 10px; align-items: center; justify-content: center; gap: 10px;
/* 画布加载光晕固定深紫(不跟浅色壳白底 token,避免被冲成灰白) */
background: background:
radial-gradient(ellipse at center, rgba(var(--mg-primary-rgb), 0.35) 0%, rgba(var(--mg-bg-app-deep-rgb), 0.92) 80%), radial-gradient(
rgba(var(--mg-bg-app-deep-rgb), 0.85); ellipse at 50% 42%,
rgba(155, 124, 255, 0.42) 0%,
rgba(88, 48, 168, 0.72) 38%,
rgba(45, 27, 105, 0.92) 68%,
rgba(15, 4, 32, 0.98) 100%
);
color: rgba(232, 220, 255, 0.92); color: rgba(232, 220, 255, 0.92);
font-size: 13px; font-size: 13px;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
pointer-events: none; pointer-events: none;
} }
.workspace-3d-mask .muted { color: rgba(var(--mg-accent-rgb), 0.7); font-size: 12px; } .workspace-3d-mask .muted { color: rgba(196, 181, 253, 0.78); font-size: 12px; }
.workspace-3d-mask .el-icon { color: var(--mg-accent); filter: drop-shadow(0 0 10px rgba(var(--mg-accent-rgb), 0.6)); } .workspace-3d-mask .el-icon {
color: #c4b5fd;
filter: drop-shadow(0 0 12px rgba(155, 124, 255, 0.65));
}
</style> </style>
@@ -0,0 +1,168 @@
<template>
<el-dialog
:model-value="open"
title="选择字段增加"
width="520px"
append-to-body
destroy-on-close
class="better-add-field-dialog"
@update:model-value="onOpenChange"
>
<p class="baf-hint">
目标{{ targetSummary }}选择场景已有字段再输入要批量写入的值对齐 Ctrl+I
</p>
<el-input
v-model="search"
clearable
placeholder="搜索字段名"
class="baf-search"
/>
<div v-loading="loadingKeys" class="baf-list">
<button
v-for="key in filteredKeys"
:key="key"
type="button"
class="baf-key"
:class="{ 'is-active': key === pickedKey }"
@click="pickedKey = key"
>
{{ key }}
</button>
<div v-if="!loadingKeys && filteredKeys.length === 0" class="baf-empty">
{{ keys.length === 0 ? '当前场景还没有任何自定义字段可选' : '无匹配字段' }}
</div>
</div>
<el-input
v-model="value"
:disabled="!pickedKey"
:placeholder="pickedKey ? `输入字段 ${pickedKey} 的值` : '先选择字段'"
class="baf-value"
@keydown.enter.prevent="confirm"
/>
<template #footer>
<el-button @click="close">取消</el-button>
<el-button type="primary" :loading="applying" :disabled="!pickedKey" @click="confirm">
写入选中对象
</el-button>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { mapEditApi } from '@/api/mapEdit'
const props = defineProps<{
open: boolean
targets: Array<{ kind: string; id: number }>
targetSummary: string
}>()
const emit = defineEmits<{
'update:open': [v: boolean]
applied: [payload: { key: string; value: string; affected: number }]
}>()
const keys = ref<string[]>([])
const loadingKeys = ref(false)
const search = ref('')
const pickedKey = ref('')
const value = ref('')
const applying = ref(false)
const filteredKeys = computed(() => {
const q = search.value.trim().toLowerCase()
if (!q) return keys.value
return keys.value.filter((k) => k.toLowerCase().includes(q))
})
watch(
() => props.open,
async (v) => {
if (!v) return
search.value = ''
pickedKey.value = ''
value.value = ''
loadingKeys.value = true
try {
const r = await mapEditApi.sceneFieldKeys()
keys.value = r.keys ?? []
} catch (err) {
keys.value = []
ElMessage.error(`加载字段列表失败:${(err as Error).message}`)
} finally {
loadingKeys.value = false
}
}
)
function onOpenChange(v: boolean) {
emit('update:open', v)
}
function close() {
emit('update:open', false)
}
async function confirm() {
if (!pickedKey.value || props.targets.length === 0) return
applying.value = true
try {
const r = await mapEditApi.batchSetField(pickedKey.value, value.value, props.targets)
ElMessage.success(`已对 ${r.affected}/${props.targets.length} 个对象设置 ${pickedKey.value}`)
emit('applied', { key: pickedKey.value, value: value.value, affected: r.affected })
close()
} catch (err) {
ElMessage.error(`写入失败:${(err as Error).message}`)
} finally {
applying.value = false
}
}
</script>
<style scoped>
.baf-hint {
margin: 0 0 12px;
font-size: 13px;
line-height: 1.45;
color: var(--el-text-color-secondary);
}
.baf-search { margin-bottom: 10px; width: 100%; }
.baf-list {
min-height: 180px;
max-height: 280px;
overflow: auto;
border: 1px solid var(--el-border-color);
border-radius: 8px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 2px;
margin-bottom: 12px;
}
.baf-key {
appearance: none;
border: 0;
background: transparent;
text-align: left;
padding: 8px 10px;
border-radius: 6px;
font-size: 13px;
color: var(--el-text-color-primary);
cursor: pointer;
}
.baf-key:hover { background: var(--el-fill-color-light); }
.baf-key.is-active {
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-weight: 600;
}
.baf-empty {
padding: 28px 12px;
text-align: center;
font-size: 13px;
color: var(--el-text-color-secondary);
}
.baf-value { width: 100%; }
</style>
@@ -16,11 +16,8 @@
<div v-else class="object-pane"> <div v-else class="object-pane">
<!-- Header cardID / 类型 / 名称 / 图层 / 删除按钮 --> <!-- Header cardID / 类型 / 名称 / 图层 / 删除按钮 -->
<section class="prop-card prop-card--header"> <section class="prop-card prop-card--header">
<div class="prop-card-row"> <header class="prop-card-header">
<div class="prop-id-block"> <span class="prop-card-title">基础属性</span>
<span class="prop-id-tag">{{ selectionCount > 1 ? `多选 ${selectionCount}` : `#${primary?.id ?? '-'}` }}</span>
<span class="prop-type-tag">{{ primary?.typeName ?? '-' }}</span>
</div>
<div class="prop-header-actions"> <div class="prop-header-actions">
<el-tag <el-tag
v-if="primary?.status" v-if="primary?.status"
@@ -42,11 +39,18 @@
:disabled="deleting" :disabled="deleting"
@click="onDelete" @click="onDelete"
> >
<span class="prop-delete-icon">{{ deleting ? '⟳' : '🗑' }}</span> <span class="prop-delete-icon">{{ deleting ? '⟳' : '×' }}</span>
<span>删除</span> <span>删除</span>
</button> </button>
</el-tooltip> </el-tooltip>
</div> </div>
</header>
<div class="prop-card-row prop-id-row">
<div class="prop-id-block">
<span class="prop-id-tag">{{ selectionCount > 1 ? `多选 ${selectionCount}` : `#${primary?.id ?? '-'}` }}</span>
<span class="prop-type-tag">{{ primary?.typeName ?? '-' }}</span>
</div>
</div> </div>
<div class="prop-card-row prop-name-row"> <div class="prop-card-row prop-name-row">
@@ -863,17 +867,17 @@ function onDelete() {
</script> </script>
<style scoped> <style scoped>
/* Style 2 分组卡片 + Style 5 主题色相(--me-* / --mg-primary */
.edit-property-panel { .edit-property-panel {
height: 100%; height: 100%;
width: 360px; width: 360px;
background: linear-gradient(180deg, rgba(28, 12, 56, 0.85) 0%, rgba(18, 6, 36, 0.95) 100%); background: linear-gradient(180deg, var(--me-chrome-1) 0%, var(--me-chrome-2) 100%);
border-left: 1px solid rgba(255,255,255,0.10); border-left: 1px solid var(--me-border);
padding: 0; padding: 0;
backdrop-filter: blur(10px);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: rgba(232,215,245,0.92); color: var(--me-text);
} }
.prop-tabs { .prop-tabs {
height: 100%; height: 100%;
@@ -882,146 +886,205 @@ function onDelete() {
} }
.prop-tabs :deep(.el-tabs__header) { .prop-tabs :deep(.el-tabs__header) {
margin: 0; margin: 0;
padding: 0 8px; padding: 6px 10px 0;
border-bottom: 1px solid rgba(255,255,255,0.08); border-bottom: 1px solid var(--me-border);
} background: var(--me-surface);
.prop-tabs :deep(.el-tabs__nav-wrap)::after {
background: transparent;
} }
.prop-tabs :deep(.el-tabs__nav-wrap)::after { background: transparent; }
.prop-tabs :deep(.el-tabs__item) { .prop-tabs :deep(.el-tabs__item) {
color: rgba(220, 200, 240, 0.7); color: var(--me-text-muted);
font-weight: 500; font-weight: 500;
font-size: 13px; font-size: 13px;
height: 36px; height: 34px;
line-height: 36px; line-height: 34px;
padding: 0 14px !important;
} }
.prop-tabs :deep(.el-tabs__item.is-active) { .prop-tabs :deep(.el-tabs__item.is-active) {
color: #fff; color: var(--mg-primary);
font-weight: 700;
} }
.prop-tabs :deep(.el-tabs__active-bar) { .prop-tabs :deep(.el-tabs__active-bar) {
background: linear-gradient(90deg, rgba(170, 110, 250, 1) 0%, rgba(255, 110, 220, 1) 100%); background: var(--mg-primary);
height: 2px; height: 2px;
border-radius: 2px;
} }
.prop-tabs :deep(.el-tabs__content) { .prop-tabs :deep(.el-tabs__content) {
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: 10px 12px 14px; padding: 12px;
} }
.empty-hint { .empty-hint {
display: flex; align-items: center; gap: 6px; display: flex;
padding: 56px 16px; text-align: center; align-items: center;
color: rgba(220, 200, 240, 0.55); gap: 8px;
padding: 48px 16px;
text-align: center;
color: var(--me-text-muted);
flex-direction: column; flex-direction: column;
} }
.empty-icon { font-size: 28px; color: rgba(180, 130, 220, 0.45); } .empty-icon { font-size: 28px; color: rgba(var(--mg-primary-rgb), 0.45); }
.empty-title { font-size: 13px; color: rgba(232, 215, 245, 0.75); font-weight: 500; } .empty-title { font-size: 13px; color: var(--me-text); font-weight: 600; }
.empty-desc { font-size: 11.5px; color: rgba(220, 200, 240, 0.5); } .empty-desc { font-size: 11.5px; color: var(--me-text-muted); max-width: 220px; line-height: 1.45; }
.loading-hint { .loading-hint {
display: flex; align-items: center; gap: 8px; display: flex;
padding: 32px 16px; text-align: center; align-items: center;
color: rgba(232,215,245,0.65); font-size: 12.5px; gap: 8px;
padding: 32px 16px;
justify-content: center; justify-content: center;
color: var(--me-text-muted);
font-size: 12.5px;
} }
.object-pane { .object-pane,
.defaults-pane {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 12px;
} }
/* 分组卡片:跟 --me-card-*Harbor 浅壳 / 紫蓝深壳共用) */
.prop-card { .prop-card {
background: rgba(255,255,255,0.04); position: relative;
border: 1px solid rgba(255,255,255,0.08); background: var(--me-card-bg);
border-radius: 8px; border: 1px solid var(--me-card-border);
padding: 10px 12px; border-radius: 12px;
box-shadow: 0 1px 4px rgba(0,0,0,0.20); padding: 12px 12px 10px 14px;
box-shadow: 0 4px 14px rgba(var(--mg-shadow-rgb, 22, 58, 74), 0.08);
}
.prop-card::before {
content: '';
position: absolute;
left: 0;
top: 10px;
bottom: 10px;
width: 3px;
border-radius: 0 3px 3px 0;
background: rgba(var(--mg-primary-rgb), 0.55);
} }
.prop-card--header { .prop-card--header {
background: linear-gradient(140deg, rgba(170, 110, 250, 0.18) 0%, rgba(70, 30, 110, 0.10) 100%); background: linear-gradient(
border-color: rgba(170, 110, 250, 0.28); 135deg,
rgba(var(--mg-primary-rgb), 0.12) 0%,
var(--me-card-bg) 55%
);
border-color: rgba(var(--mg-primary-rgb), 0.28);
box-shadow: 0 4px 16px rgba(var(--mg-primary-rgb), 0.08);
} }
.prop-card--geom { .prop-card--header::before {
background: linear-gradient(140deg, rgba(80, 140, 220, 0.10) 0%, rgba(40, 70, 130, 0.06) 100%); background: var(--mg-primary);
border-color: rgba(120, 170, 230, 0.22); top: 8px;
bottom: 8px;
width: 3px;
} }
.prop-card--style { .prop-card--geom,
background: linear-gradient(140deg, rgba(220, 110, 200, 0.10) 0%, rgba(150, 70, 130, 0.06) 100%); .prop-card--style,
border-color: rgba(230, 130, 200, 0.22); .prop-card--custom,
.prop-card--status,
.prop-card--actions {
background: var(--me-card-bg);
border-color: var(--me-card-border);
} }
.prop-card--custom {
background: rgba(255, 255, 255, 0.03);
border-color: rgba(170, 110, 250, 0.18);
}
.prop-card--status { background: rgba(80, 140, 220, 0.06); border-color: rgba(120, 170, 230, 0.18); }
.prop-card--actions { background: rgba(140, 80, 200, 0.06); border-color: rgba(170, 110, 250, 0.18); }
.prop-card-header { .prop-card-header {
display: flex; align-items: center; justify-content: space-between; display: flex;
margin-bottom: 8px; align-items: center;
gap: 8px;
margin-bottom: 12px;
min-height: 22px;
padding-bottom: 8px;
border-bottom: 1px solid var(--me-card-border);
} }
.prop-card-title { .prop-card-title {
font-size: 11.5px; font-size: 11px;
font-weight: 600; font-weight: 700;
letter-spacing: 1px; letter-spacing: 0.08em;
color: rgba(220, 180, 250, 0.85);
text-transform: uppercase; text-transform: uppercase;
color: var(--me-text);
} }
.prop-card-meta { .prop-card-meta {
font-size: 10.5px; margin-left: auto;
color: rgba(200, 180, 220, 0.5); font-size: 11px;
font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
font-variant-numeric: tabular-nums;
color: var(--me-text-muted);
background: var(--me-inset);
border: 1px solid var(--me-card-border);
border-radius: 999px;
padding: 1px 8px;
} }
.prop-card-flag { .prop-card-flag {
display: inline-flex; align-items: center; gap: 4px; display: inline-flex;
align-items: center;
gap: 5px;
font-size: 10.5px; font-size: 10.5px;
color: rgba(180, 200, 240, 0.6); color: var(--me-text-muted);
padding: 2px 7px; padding: 2px 8px;
border-radius: 10px; border-radius: 999px;
background: rgba(120, 160, 220, 0.10); background: var(--me-surface);
transition: background .25s ease, color .25s ease, box-shadow .25s ease; border: 1px solid var(--me-card-border);
margin-left: auto; margin-left: auto;
} }
.prop-card-flag.is-synced { .prop-card-flag.is-synced {
background: linear-gradient(120deg, rgba(80, 200, 130, 0.32), rgba(120, 220, 200, 0.32)); background: rgba(34, 197, 94, 0.12);
color: rgba(220, 255, 230, 0.95); border-color: rgba(34, 197, 94, 0.35);
box-shadow: 0 0 8px rgba(80, 200, 130, 0.45); color: #15803d;
box-shadow: none;
} }
.prop-card-flag-dot { .prop-card-flag-dot {
width: 6px; height: 6px; border-radius: 50%; width: 6px;
background: rgba(180, 200, 240, 0.6); height: 6px;
transition: background .25s ease, box-shadow .25s ease; border-radius: 50%;
background: var(--me-text-muted);
} }
.prop-card-flag.is-synced .prop-card-flag-dot { .prop-card-flag.is-synced .prop-card-flag-dot {
background: #6ce7a8; background: #4ade80;
box-shadow: 0 0 6px rgba(108, 231, 168, 0.85); box-shadow: none;
} }
.prop-card-row { .prop-card-row {
display: flex; align-items: center; justify-content: space-between; display: flex;
margin-bottom: 6px; align-items: center;
justify-content: space-between;
margin-bottom: 8px;
gap: 8px;
} }
.prop-card-row:last-child { margin-bottom: 0; } .prop-card-row:last-child { margin-bottom: 0; }
.prop-id-row { margin-bottom: 10px; }
.prop-id-block { display: flex; align-items: center; gap: 6px; } .prop-id-block {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
flex-wrap: wrap;
}
.prop-id-tag { .prop-id-tag {
background: rgba(170, 110, 250, 0.30); background: var(--mg-primary);
color: #fff; color: #fff;
padding: 2px 8px; padding: 3px 9px;
font-size: 11.5px; font-size: 12px;
font-weight: 600; font-weight: 700;
border-radius: 4px; border-radius: 6px;
letter-spacing: 0.5px; font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
font-variant-numeric: tabular-nums;
} }
.prop-type-tag { .prop-type-tag {
color: rgba(232, 215, 245, 0.75); color: var(--me-text-muted);
font-size: 11.5px; font-size: 12px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
background: var(--me-inset);
border: 1px solid var(--me-card-border);
border-radius: 6px;
padding: 2px 8px;
} }
.prop-status-tag { font-weight: 500; } .prop-status-tag { font-weight: 600; }
.prop-header-actions { .prop-header-actions {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
margin-left: auto;
} }
.prop-delete-btn { .prop-delete-btn {
@@ -1030,31 +1093,31 @@ function onDelete() {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 4px; gap: 4px;
padding: 3px 9px; padding: 4px 10px;
border-radius: 5px; border-radius: 6px;
font-size: 11.5px; font-size: 12px;
font-weight: 500; font-weight: 550;
letter-spacing: 0.4px; color: #fecaca;
color: rgba(255, 200, 210, 0.95); background: rgba(239, 68, 68, 0.12);
background: linear-gradient(135deg, rgba(255, 90, 120, 0.18) 0%, rgba(220, 60, 110, 0.22) 100%); border: 1px solid rgba(239, 68, 68, 0.35);
border: 1px solid rgba(255, 110, 140, 0.45); transition: background .12s ease, border-color .12s ease;
transition: background .14s ease, border-color .14s ease, color .14s ease, box-shadow .14s ease, transform .12s ease;
} }
.prop-delete-btn:hover:not(:disabled) { .prop-delete-btn:hover:not(:disabled) {
background: linear-gradient(135deg, rgba(255, 90, 120, 0.55) 0%, rgba(220, 60, 110, 0.65) 100%); background: rgba(239, 68, 68, 0.22);
border-color: rgba(255, 140, 170, 0.85); border-color: rgba(239, 68, 68, 0.55);
color: #fff; color: #fff;
box-shadow: 0 3px 10px rgba(220, 70, 110, 0.45); box-shadow: none;
} }
.prop-delete-btn:active:not(:disabled) { transform: scale(0.96); }
.prop-delete-btn:disabled { opacity: 0.55; cursor: progress; } .prop-delete-btn:disabled { opacity: 0.55; cursor: progress; }
.prop-delete-icon { font-size: 12px; line-height: 1; } .prop-delete-icon { font-size: 12px; line-height: 1; }
.prop-name-row, .prop-layer-row, .prop-direction-row { gap: 8px; } .prop-name-row,
.prop-layer-row,
.prop-direction-row { gap: 10px; }
.prop-name-label { .prop-name-label {
width: 36px; width: 36px;
font-size: 11.5px; font-size: 12px;
color: rgba(200, 180, 220, 0.7); color: var(--me-text-muted);
flex: none; flex: none;
} }
.prop-layer-select { flex: 1; } .prop-layer-select { flex: 1; }
@@ -1062,15 +1125,15 @@ function onDelete() {
.prop-grid { .prop-grid {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
column-gap: 8px; column-gap: 10px;
row-gap: 8px; row-gap: 10px;
} }
.prop-grid-item { display: flex; flex-direction: column; gap: 3px; min-width: 0; } .prop-grid-item { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.prop-grid-item--wide { grid-column: 1 / -1; } .prop-grid-item--wide { grid-column: 1 / -1; }
.prop-field-label { .prop-field-label {
font-size: 11px; font-size: 11px;
color: rgba(200, 180, 230, 0.78); color: var(--me-text-muted);
letter-spacing: 0.3px; letter-spacing: 0.2px;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: baseline; align-items: baseline;
@@ -1081,155 +1144,230 @@ function onDelete() {
} }
.prop-field-unit { .prop-field-unit {
font-size: 10px; font-size: 10px;
color: rgba(170, 150, 200, 0.55); color: var(--me-text-muted);
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
flex: none; flex: none;
} }
.prop-field-num :deep(.el-input-number) { width: 100%; } .prop-field-num :deep(.el-input-number) { width: 100%; }
.prop-field-num :deep(.el-input__wrapper) { .prop-field-num :deep(.el-input__wrapper),
background: rgba(255,255,255,0.06); .prop-card :deep(.el-input__wrapper),
box-shadow: inset 0 0 0 1px rgba(170, 110, 250, 0.18); .prop-card :deep(.el-select__wrapper),
.prop-card :deep(.el-textarea__inner) {
background: var(--me-inset) !important;
box-shadow: inset 0 0 0 1px var(--me-card-border) !important;
} }
.prop-field-row { display: flex; align-items: center; gap: 4px; } .prop-field-num :deep(.el-input__inner),
.prop-card :deep(.el-input__inner),
.prop-card :deep(.el-textarea__inner),
.prop-card :deep(.el-select__selected-item),
.prop-card :deep(.el-select__placeholder) {
color: var(--me-text) !important;
}
.prop-field-num :deep(.el-input__wrapper:hover),
.prop-card :deep(.el-input__wrapper:hover),
.prop-card :deep(.el-select__wrapper:hover) {
box-shadow: inset 0 0 0 1px rgba(var(--mg-primary-rgb), 0.55) !important;
}
.prop-field-num :deep(.el-input__wrapper.is-focus),
.prop-card :deep(.el-input__wrapper.is-focus),
.prop-card :deep(.el-select__wrapper.is-focused) {
box-shadow:
inset 0 0 0 1px var(--mg-primary),
0 0 0 3px rgba(var(--mg-primary-rgb), 0.16) !important;
}
.prop-field-row { display: flex; align-items: center; gap: 6px; }
.prop-field-icon-btn { .prop-field-icon-btn {
appearance: none; border: 0; cursor: pointer; appearance: none;
width: 22px; height: 22px; border: 1px solid rgba(239, 68, 68, 0.28);
border-radius: 4px; cursor: pointer;
background: rgba(255, 100, 130, 0.12); width: 28px;
color: rgba(255, 180, 200, 0.85); height: 28px;
font-size: 14px; line-height: 1; border-radius: 7px;
display: inline-flex; align-items: center; justify-content: center; background: rgba(239, 68, 68, 0.08);
color: #dc2626;
font-size: 15px;
line-height: 1;
display: inline-flex;
align-items: center;
justify-content: center;
flex: none; flex: none;
transition: background .14s, color .14s; transition: background .12s ease, border-color .12s ease, color .12s ease;
} }
.prop-field-icon-btn:hover { .prop-field-icon-btn:hover {
background: rgba(255, 100, 130, 0.32); background: rgba(239, 68, 68, 0.16);
color: #fff; border-color: rgba(239, 68, 68, 0.45);
color: #b91c1c;
} }
.prop-empty-row { .prop-empty-row {
font-size: 11.5px; font-size: 12px;
color: rgba(200, 180, 220, 0.4); color: var(--me-text-muted);
text-align: center; text-align: center;
padding: 8px 0; padding: 14px 8px;
border-radius: 8px;
background: var(--me-inset);
border: 1px dashed var(--me-card-border);
} }
.prop-add-field-btn { .prop-add-field-btn {
appearance: none; border: 0; cursor: pointer; appearance: none;
cursor: pointer;
width: 100%; width: 100%;
margin-top: 8px; margin-top: 10px;
padding: 6px 10px; padding: 8px 10px;
border-radius: 6px; border-radius: 8px;
background: rgba(170, 110, 250, 0.14); background: var(--me-surface);
border: 1px dashed rgba(170, 110, 250, 0.40); border: 1px dashed rgba(var(--mg-primary-rgb), 0.42);
color: rgba(220, 180, 250, 0.92); color: var(--mg-primary);
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 600;
transition: background .14s ease, border-color .14s ease, color .14s ease; transition: background .12s ease, border-color .12s ease, color .12s ease;
} }
.prop-add-field-btn:hover { .prop-add-field-btn:hover:not(:disabled) {
background: rgba(170, 110, 250, 0.26); background: var(--me-hover);
border-color: rgba(170, 110, 250, 0.65); border-color: rgba(var(--mg-primary-rgb), 0.7);
color: #fff; border-style: solid;
color: var(--mg-primary);
} }
.prop-add-field-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.custom-field-list { .custom-field-list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 10px; gap: 8px;
margin-bottom: 10px;
} }
.custom-field-item { .custom-field-item {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 3px; gap: 5px;
min-width: 0; min-width: 0;
padding: 9px 10px;
border-radius: 9px;
background: var(--me-inset);
border: 1px solid var(--me-card-border);
} }
.custom-field-key { .custom-field-key {
margin: 0; margin: 0;
font-family: var(--mg-font-mono, ui-monospace, monospace);
font-size: 11px; font-size: 11px;
font-weight: 500; font-weight: 650;
color: rgba(200, 180, 230, 0.88); letter-spacing: 0.02em;
color: var(--me-text);
white-space: normal; white-space: normal;
word-break: break-all; word-break: break-all;
line-height: 1.35; line-height: 1.35;
cursor: default; cursor: default;
} }
.custom-field-input { flex: 1; min-width: 0; width: 100%; }
.custom-field-input {
flex: 1;
min-width: 0;
width: 100%;
}
.custom-field-input :deep(.el-input__wrapper) { .custom-field-input :deep(.el-input__wrapper) {
background: rgba(255, 255, 255, 0.06); background: var(--me-card-bg) !important;
box-shadow: inset 0 0 0 1px rgba(170, 110, 250, 0.18); box-shadow: inset 0 0 0 1px var(--me-card-border) !important;
min-height: 30px;
}
.custom-field-input :deep(.el-input__inner) {
color: var(--me-text) !important;
font-weight: 550;
font-variant-numeric: tabular-nums;
} }
.custom-field-input :deep(.el-input__wrapper:hover) { .custom-field-input :deep(.el-input__wrapper:hover) {
box-shadow: inset 0 0 0 1px rgba(170, 110, 250, 0.32); box-shadow: inset 0 0 0 1px rgba(var(--mg-primary-rgb), 0.45) !important;
} }
.custom-field-input :deep(.el-input__wrapper.is-focus) { .custom-field-input :deep(.el-input__wrapper.is-focus) {
box-shadow: inset 0 0 0 1px rgba(190, 130, 255, 0.55); box-shadow:
inset 0 0 0 1px var(--mg-primary),
0 0 0 3px rgba(var(--mg-primary-rgb), 0.14) !important;
} }
.add-field-select { width: 100%; }
.add-field-select { /* 运行状态:胶囊行 */
width: 100%; .prop-status-list {
display: flex;
flex-direction: column;
gap: 5px;
max-height: 280px;
overflow: auto;
padding-right: 2px;
} }
.prop-status-list { display: flex; flex-direction: column; gap: 4px; }
.prop-status-item { .prop-status-item {
display: flex; justify-content: space-between; align-items: center; display: flex;
padding: 5px 8px; justify-content: space-between;
border-radius: 4px; align-items: center;
background: rgba(255,255,255,0.03); gap: 10px;
padding: 7px 10px;
border-radius: 8px;
background: var(--me-inset);
border: 1px solid var(--me-card-border);
font-size: 11.5px; font-size: 11.5px;
} }
.prop-status-key { color: rgba(170, 200, 240, 0.85); font-weight: 500; } .prop-status-item:nth-child(even) {
.prop-status-value { background: rgba(var(--mg-primary-rgb), 0.08);
color: rgba(232, 245, 255, 0.95); }
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; .prop-status-key {
max-width: 60%; color: var(--me-text-muted);
font-weight: 600;
flex: none;
max-width: 46%;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.prop-status-value {
color: var(--me-text);
font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
font-variant-numeric: tabular-nums;
max-width: 54%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
}
.prop-actions { .prop-actions {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 6px; gap: 8px;
} }
.prop-action-btn { .prop-action-btn {
appearance: none; border: 0; cursor: pointer; appearance: none;
padding: 7px 10px; cursor: pointer;
border-radius: 7px; padding: 9px 11px;
background: linear-gradient(135deg, rgba(170, 110, 250, 0.20) 0%, rgba(120, 70, 220, 0.20) 100%); border-radius: 8px;
border: 1px solid rgba(170, 110, 250, 0.35); background: var(--mg-primary);
color: rgba(232, 215, 245, 0.95); border: 1px solid var(--mg-primary);
color: #fff;
font-size: 12px; font-size: 12px;
font-weight: 500; font-weight: 600;
display: flex; align-items: center; justify-content: flex-start; gap: 6px; display: flex;
transition: background .14s ease, border-color .14s ease, transform .12s ease, box-shadow .14s ease; align-items: center;
justify-content: flex-start;
gap: 6px;
text-align: left; text-align: left;
transition: background .12s ease, border-color .12s ease, box-shadow .12s ease, transform .12s ease;
}
.prop-action-btn:nth-child(2n) {
background: var(--me-card-bg);
border-color: rgba(var(--mg-primary-rgb), 0.38);
color: var(--mg-primary);
}
.prop-action-btn:nth-child(2n) .prop-action-icon {
color: var(--mg-primary);
} }
.prop-action-btn:hover:not(:disabled) { .prop-action-btn:hover:not(:disabled) {
background: linear-gradient(135deg, rgba(170, 110, 250, 0.62) 0%, rgba(255, 110, 220, 0.55) 100%); background: var(--mg-primary-hover, var(--mg-primary));
border-color: rgba(255, 130, 220, 0.7); border-color: var(--mg-primary-hover, var(--mg-primary));
color: #fff; color: #fff;
box-shadow: 0 4px 12px rgba(180, 90, 220, 0.40); box-shadow: 0 4px 12px var(--me-active-glow);
transform: translateY(-1px);
} }
.prop-action-btn:active:not(:disabled) { transform: scale(0.985); } .prop-action-btn:nth-child(2n):hover:not(:disabled) {
.prop-action-btn:disabled { opacity: 0.6; cursor: progress; } background: rgba(var(--mg-primary-rgb), 0.1);
.prop-action-icon { border-color: var(--mg-primary);
font-size: 9px; color: var(--mg-primary);
color: rgba(255, 200, 250, 0.85); box-shadow: 0 2px 8px var(--me-active-glow);
flex: none;
} }
.prop-action-btn:nth-child(2n):hover:not(:disabled) .prop-action-icon {
color: var(--mg-primary);
}
.prop-action-btn:disabled { opacity: 0.55; cursor: progress; transform: none; }
.prop-action-icon { font-size: 9px; color: rgba(255, 255, 255, 0.92); flex: none; }
.prop-action-spinner { .prop-action-spinner {
display: inline-block; display: inline-block;
animation: prop-spin .8s linear infinite; animation: prop-spin .8s linear infinite;
@@ -1237,56 +1375,63 @@ function onDelete() {
} }
.prop-action-label { .prop-action-label {
flex: 1; flex: 1;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
@keyframes prop-spin { @keyframes prop-spin {
from { transform: rotate(0deg); } from { transform: rotate(0deg); }
to { transform: rotate(360deg); } to { transform: rotate(360deg); }
} }
.defaults-pane { display: flex; flex-direction: column; gap: 10px; } .defaults-alert { margin-bottom: 0; }
.defaults-alert { margin-bottom: 4px; } .defaults-empty { padding: 32px 12px; }
.defaults-empty { padding: 40px 12px; }
.defaults-form .el-button { margin-top: 8px; } .defaults-form .el-button { margin-top: 8px; }
.defaults-form :deep(.el-form-item) { margin-bottom: 10px; } .defaults-form :deep(.el-form-item) { margin-bottom: 10px; }
/* 图层 Tab:白底数据块(深侧栏上的可读「岛」),各主题下表头/单元格均用深字 */ .defaults-form :deep(.el-form-item__label) { color: var(--me-text-muted); }
.layer-table { .layer-table {
margin: 6px 0; margin: 0;
border-radius: 8px; border-radius: 12px;
overflow: hidden; overflow: hidden;
background: #fff; background: rgba(255, 255, 255, 0.06);
box-shadow: 0 0 0 1px rgba(124, 58, 237, 0.14); border: 1px solid rgba(255, 255, 255, 0.10);
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.18);
} }
.layer-table :deep(.el-table__inner-wrapper) { .layer-table :deep(.el-table__inner-wrapper),
background: #fff; .layer-table :deep(.el-table),
.layer-table :deep(.el-table tr) {
background: transparent !important;
} }
.layer-table :deep(th.el-table__cell) { .layer-table :deep(th.el-table__cell) {
background: #f5f3ff !important; background: rgba(var(--mg-primary-rgb), 0.16) !important;
color: #2d1b69 !important; color: var(--me-text) !important;
font-weight: 600; font-weight: 650;
border-color: rgba(255, 255, 255, 0.08) !important;
} }
.layer-table :deep(td.el-table__cell) { .layer-table :deep(td.el-table__cell) {
background: #fff !important; background: transparent !important;
color: #1a0f3d !important; color: var(--me-text) !important;
font-weight: 500; border-color: rgba(255, 255, 255, 0.06) !important;
}
.layer-table :deep(.cell) {
color: #1a0f3d !important;
} }
.layer-table :deep(.cell) { color: inherit !important; }
.layer-table :deep(tr:hover > td.el-table__cell) { .layer-table :deep(tr:hover > td.el-table__cell) {
background: #ede9fe !important; background: rgba(var(--mg-primary-rgb), 0.12) !important;
}
.new-layer { margin-top: 10px; }
.new-layer :deep(.el-input__wrapper) {
background: rgba(0, 0, 0, 0.22) !important;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.10) !important;
} }
.new-layer { margin-top: 8px; }
.new-layer :deep(.el-input__inner) { .new-layer :deep(.el-input__inner) {
color: #1a0f3d; color: var(--me-text);
font-weight: 500; font-weight: 500;
} }
.new-layer :deep(.el-input__inner::placeholder) { .new-layer :deep(.el-input__inner::placeholder) {
color: #7a6b9a; color: var(--me-text-muted);
} }
.new-layer :deep(.el-input-group__append .el-button) { .new-layer :deep(.el-input-group__append .el-button) {
color: #5b21b6; color: var(--mg-accent);
font-weight: 600; font-weight: 600;
} }
</style> </style>
@@ -43,15 +43,15 @@ function formatXY(v: number) {
align-items: center; align-items: center;
gap: 16px; gap: 16px;
padding: 4px 14px; padding: 4px 14px;
background: rgba(20, 8, 40, 0.85); background: var(--me-chrome-2);
border-top: 1px solid rgba(255,255,255,0.08); border-top: 1px solid var(--me-border);
color: rgba(232,215,245,0.85); color: var(--me-text-muted);
font-size: 12px; font-size: 12px;
font-family: ui-monospace, Menlo, Consolas, monospace; font-family: var(--mg-font-mono, ui-monospace, Menlo, Consolas, monospace);
height: 28px; height: 28px;
} }
.spacer { flex: 1; } .spacer { flex: 1; }
.seg b { color: var(--mg-accent, #c4a4ff); } .seg b { color: var(--mg-accent); }
.seg .on { color: #67c23a; } .seg .on { color: #67c23a; }
.seg .off { color: #a0a0a0; } .seg .off { color: #a0a0a0; }
.seg.ok { color: #67c23a; } .seg.ok { color: #67c23a; }
@@ -99,7 +99,6 @@ const groups: ToolGroup[] = [
{ id: 'transform.move', label: '移动', glyph: '✥' }, { id: 'transform.move', label: '移动', glyph: '✥' },
{ id: 'transform.rotate', label: '旋转', glyph: '⟳' }, { id: 'transform.rotate', label: '旋转', glyph: '⟳' },
{ id: 'transform.scale', label: '缩放', glyph: '⤡' }, { id: 'transform.scale', label: '缩放', glyph: '⤡' },
{ id: 'transform.duplicate', label: '复制副本', glyph: '⎘', tip: 'Ctrl+D' },
{ id: 'transform.copy', label: '复制', glyph: '📋', tip: 'Ctrl+C' }, { id: 'transform.copy', label: '复制', glyph: '📋', tip: 'Ctrl+C' },
{ id: 'transform.paste', label: '粘贴', glyph: '📥', tip: 'Ctrl+V' }, { id: 'transform.paste', label: '粘贴', glyph: '📥', tip: 'Ctrl+V' },
{ id: 'transform.delete', label: '删除', glyph: '🗑', tip: 'Delete' }, { id: 'transform.delete', label: '删除', glyph: '🗑', tip: 'Delete' },
@@ -152,12 +151,12 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
.edit-tool-rail { .edit-tool-rail {
height: 100%; height: 100%;
width: 170px; width: 170px;
background: linear-gradient(180deg, rgba(28, 12, 56, 0.85) 0%, rgba(18, 6, 36, 0.95) 100%); background: linear-gradient(180deg, var(--me-chrome-1) 0%, var(--me-chrome-2) 100%);
border-right: 1px solid rgba(255,255,255,0.10); border-right: 1px solid var(--me-border);
padding: 4px 0 12px; padding: 4px 0 12px;
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
display: flex; flex-direction: column; display: flex; flex-direction: column;
box-shadow: inset -1px 0 0 rgba(255,255,255,0.04); box-shadow: inset -1px 0 0 var(--me-card-border, transparent);
} }
.rail-scroll { height: 100%; } .rail-scroll { height: 100%; }
.rail-group { .rail-group {
@@ -168,12 +167,12 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
display: flex; align-items: center; gap: 6px; display: flex; align-items: center; gap: 6px;
font-size: 10px; font-size: 10px;
letter-spacing: 2px; letter-spacing: 2px;
color: rgba(190, 160, 230, 0.7); color: var(--me-text-muted);
text-transform: uppercase; text-transform: uppercase;
margin: 4px 0 6px; margin: 4px 0 6px;
font-weight: 600; font-weight: 600;
} }
.rail-group-bar { flex: 1; height: 1px; background: rgba(190, 160, 230, 0.2); } .rail-group-bar { flex: 1; height: 1px; background: rgba(var(--mg-primary-rgb), 0.18); }
.rail-group-list { .rail-group-list {
display: flex; flex-direction: column; display: flex; flex-direction: column;
gap: 2px; gap: 2px;
@@ -181,7 +180,7 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
.rail-divider { .rail-divider {
width: 100%; width: 100%;
height: 1px; height: 1px;
background: linear-gradient(90deg, rgba(255,255,255,0.0) 0%, rgba(255,255,255,0.08) 50%, rgba(255,255,255,0.0) 100%); background: linear-gradient(90deg, transparent 0%, var(--me-border) 50%, transparent 100%);
margin: 6px 0 2px; margin: 6px 0 2px;
} }
.rail-btn { .rail-btn {
@@ -192,8 +191,8 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
min-height: 32px; min-height: 32px;
padding: 0 8px; padding: 0 8px;
border-radius: 6px; border-radius: 6px;
background: rgba(255,255,255,0.04); background: var(--me-surface);
color: rgba(232,215,245,0.92); color: var(--me-text);
display: flex; align-items: center; display: flex; align-items: center;
gap: 8px; gap: 8px;
font-size: 12.5px; font-size: 12.5px;
@@ -202,16 +201,16 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
transition: background .14s ease, color .14s ease, box-shadow .14s ease, transform .12s ease; transition: background .14s ease, color .14s ease, box-shadow .14s ease, transform .12s ease;
} }
.rail-btn:hover { .rail-btn:hover {
background: rgba(150, 90, 230, 0.20); background: var(--me-hover);
color: #fff; color: var(--mg-primary);
} }
.rail-btn:active { .rail-btn:active {
transform: scale(0.985); transform: scale(0.985);
} }
.rail-btn.rail-btn--active { .rail-btn.rail-btn--active {
background: linear-gradient(120deg, rgba(170, 110, 250, 0.92) 0%, rgba(120, 70, 220, 0.92) 100%); background: var(--mg-primary);
color: #fff; color: #fff;
box-shadow: 0 3px 10px rgba(140, 80, 230, 0.45), inset 0 0 0 1px rgba(255,255,255,0.18); box-shadow: 0 3px 10px var(--me-active-glow), inset 0 0 0 1px rgba(255,255,255,0.18);
} }
.rail-btn-glyph { .rail-btn-glyph {
width: 20px; width: 20px;
@@ -236,21 +235,21 @@ function onClick(id: EditToolId) { emit('select-tool', id) }
position: absolute; position: absolute;
left: 8px; right: 8px; top: 0; left: 8px; right: 8px; top: 0;
height: 1px; height: 1px;
background: linear-gradient(90deg, rgba(255,255,255,0.0) 0%, rgba(220, 140, 230, 0.35) 50%, rgba(255,255,255,0.0) 100%); background: linear-gradient(90deg, transparent 0%, rgba(var(--mg-accent-rgb), 0.4) 50%, transparent 100%);
} }
.rail-btn--ai { .rail-btn--ai {
background: linear-gradient(135deg, rgba(120, 70, 220, 0.55) 0%, rgba(255, 90, 200, 0.55) 100%); background: var(--mg-primary);
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
box-shadow: 0 2px 10px rgba(220, 90, 220, 0.30), inset 0 0 0 1px rgba(255,255,255,0.20); box-shadow: 0 2px 10px var(--me-active-glow), inset 0 0 0 1px rgba(255,255,255,0.20);
} }
.rail-btn--ai:hover { .rail-btn--ai:hover {
background: linear-gradient(135deg, rgba(150, 90, 240, 0.85) 0%, rgba(255, 110, 220, 0.85) 100%); background: var(--mg-primary-hover);
color: #fff; color: #fff;
box-shadow: 0 4px 14px rgba(230, 110, 230, 0.55), inset 0 0 0 1px rgba(255,255,255,0.30); box-shadow: 0 4px 14px var(--me-active-glow), inset 0 0 0 1px rgba(255,255,255,0.30);
} }
.rail-btn--ai .rail-btn-glyph { .rail-btn--ai .rail-btn-glyph {
font-size: 16px; font-size: 16px;
text-shadow: 0 0 8px rgba(255, 200, 250, 0.6); text-shadow: 0 0 8px rgba(var(--mg-accent-rgb), 0.55);
} }
</style> </style>
@@ -148,16 +148,16 @@ function mark(on: boolean): string {
align-items: center; align-items: center;
gap: 4px; gap: 4px;
padding: 8px 14px; padding: 8px 14px;
background: linear-gradient(180deg, rgba(50, 22, 88, 0.95) 0%, rgba(34, 14, 64, 0.95) 100%); background: linear-gradient(180deg, var(--me-chrome-1) 0%, var(--me-chrome-2) 100%);
border-bottom: 1px solid rgba(255,255,255,0.12); border-bottom: 1px solid var(--me-border);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); box-shadow: 0 2px 8px rgba(var(--mg-shadow-rgb, 22, 58, 74), 0.08);
} }
.spacer { flex: 1; } .spacer { flex: 1; }
.el-divider--vertical { margin: 0 6px; background: rgba(255,255,255,0.15); } .el-divider--vertical { margin: 0 6px; background: var(--me-border); }
.topbar-btn { .topbar-btn {
color: #fdf6ff !important; color: var(--me-text) !important;
font-weight: 600; font-weight: 600;
font-size: 13.5px; font-size: 13.5px;
padding: 6px 12px; padding: 6px 12px;
@@ -166,22 +166,23 @@ function mark(on: boolean): string {
transition: background .15s ease, color .15s ease; transition: background .15s ease, color .15s ease;
} }
.topbar-btn:hover { .topbar-btn:hover {
color: #fff !important; color: var(--mg-primary) !important;
background: rgba(170, 110, 250, 0.28) !important; background: var(--me-hover) !important;
} }
.topbar-btn:focus { .topbar-btn:focus {
background: rgba(170, 110, 250, 0.30) !important; background: var(--me-hover) !important;
} }
.topbar-icon-btn { .topbar-icon-btn {
color: rgba(253, 246, 255, 0.92) !important; color: var(--me-text) !important;
background: rgba(255,255,255,0.05) !important; background: var(--me-surface) !important;
border: 1px solid rgba(255,255,255,0.15) !important; border: 1px solid var(--me-border) !important;
font-size: 15px; font-size: 15px;
} }
.topbar-icon-btn:hover:not(.is-disabled) { .topbar-icon-btn:hover:not(.is-disabled) {
background: rgba(170, 110, 250, 0.30) !important; background: var(--me-hover) !important;
border-color: rgba(170, 110, 250, 0.55) !important; border-color: rgba(var(--mg-primary-rgb), 0.55) !important;
color: var(--mg-primary) !important;
} }
.topbar-icon-btn.is-disabled { .topbar-icon-btn.is-disabled {
opacity: 0.4; opacity: 0.4;
@@ -194,11 +195,19 @@ function mark(on: boolean): string {
height: auto; height: auto;
border-radius: 6px; border-radius: 6px;
margin-right: 4px; margin-right: 4px;
--el-button-bg-color: var(--mg-primary) !important;
--el-button-border-color: var(--mg-primary) !important;
--el-button-hover-bg-color: var(--mg-primary-hover) !important;
--el-button-hover-border-color: var(--mg-primary-hover) !important;
--el-button-active-bg-color: var(--mg-primary-active) !important;
--el-button-active-border-color: var(--mg-primary-active) !important;
--el-button-text-color: #fff !important;
--el-button-hover-text-color: #fff !important;
} }
.topbar-filter-menu :deep(.topbar-filter-header) { .topbar-filter-menu :deep(.topbar-filter-header) {
font-size: 11px; font-size: 11px;
color: rgba(232, 215, 245, 0.55); color: var(--me-text-muted);
cursor: default; cursor: default;
pointer-events: none; pointer-events: none;
padding-top: 6px; padding-top: 6px;
@@ -53,7 +53,7 @@
<div class="aside-footer"> <div class="aside-footer">
<span class="aside-footer-badge">v1.6</span> <span class="aside-footer-badge">v1.6</span>
<span v-if="!ui.sidebarCollapsed">{{ ui.activeTheme.name }} · {{ ui.activeTheme.preview }}</span> <span v-if="!ui.sidebarCollapsed">{{ ui.activeTheme.name }}</span>
</div> </div>
</el-aside> </el-aside>
@@ -82,6 +82,15 @@
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item v-if="auth.scope === 'Platform'" command="wizard">配置向导</el-dropdown-item> <el-dropdown-item v-if="auth.scope === 'Platform'" command="wizard">配置向导</el-dropdown-item>
<el-dropdown-item command="status">服务状态</el-dropdown-item> <el-dropdown-item command="status">服务状态</el-dropdown-item>
<el-dropdown-item divided disabled class="legacy-theme-label">高级 · 兼容主题</el-dropdown-item>
<el-dropdown-item
v-for="t in ui.legacyThemes"
:key="t.id"
:command="`theme:${t.id}`"
:class="{ 'is-legacy-active': t.id === ui.themeId }">
<span class="legacy-swatch" :style="{ background: t.preview }" />
{{ t.name }}
</el-dropdown-item>
<el-dropdown-item command="logout" divided>退出登录</el-dropdown-item> <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
@@ -97,10 +106,13 @@
</el-main> </el-main>
<el-footer class="footer"> <el-footer class="footer">
<span>{{ scopeLabel }} · webVRender :{{ vrPort }} · API {{ apiBase }}</span> <span>{{ scopeLabel }} · Projection :{{ slPort }} · API {{ apiBase }}</span>
</el-footer> </el-footer>
</el-container> </el-container>
</el-container> </el-container>
<!-- 全局 AI 助手右侧抽屉 + 右下角悬浮唤起所有界面可呼出后端要求 Platform 权限故按 scope 显示 -->
<AiAssistantDrawer v-if="auth.scope === 'Platform'" />
</div> </div>
</template> </template>
@@ -113,13 +125,14 @@ import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui' import { useUiStore } from '@/stores/ui'
import ScopeSwitcher from '@/components/ScopeSwitcher.vue' import ScopeSwitcher from '@/components/ScopeSwitcher.vue'
import ThemeSwitcher from '@/components/ThemeSwitcher.vue' import ThemeSwitcher from '@/components/ThemeSwitcher.vue'
import AiAssistantDrawer from '@/components/assistant/AiAssistantDrawer.vue'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const auth = useAuthStore() const auth = useAuthStore()
const ui = useUiStore() const ui = useUiStore()
const vrPort = computed(() => (import.meta.env.VITE_VRENDER_HOST as string | undefined)?.split(':')[1] ?? '8223') const slPort = computed(() => (import.meta.env.VITE_SL_PORT as string | undefined) ?? '8222')
const apiBase = computed(() => (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api') const apiBase = computed(() => (import.meta.env.VITE_API_BASE as string | undefined) ?? '/api')
const initial = computed(() => (auth.user?.displayName ?? auth.user?.username ?? '?').slice(0, 1).toUpperCase()) const initial = computed(() => (auth.user?.displayName ?? auth.user?.username ?? '?').slice(0, 1).toUpperCase())
@@ -171,6 +184,10 @@ const activePath = computed(() => {
const currentTitle = computed(() => (route.meta.title as string | undefined) ?? '') const currentTitle = computed(() => (route.meta.title as string | undefined) ?? '')
function onUserCommand(cmd: string) { function onUserCommand(cmd: string) {
if (cmd.startsWith('theme:')) {
ui.setThemeId(cmd.slice('theme:'.length))
return
}
if (cmd === 'logout') { if (cmd === 'logout') {
auth.logout() auth.logout()
router.push('/login') router.push('/login')
@@ -187,6 +204,7 @@ function onUserCommand(cmd: string) {
.app-shell { .app-shell {
position: relative; position: relative;
height: 100vh; height: 100vh;
height: 100dvh;
overflow: hidden; overflow: hidden;
} }
@@ -236,6 +254,7 @@ function onUserCommand(cmd: string) {
position: relative; position: relative;
z-index: 1; z-index: 1;
height: 100vh; height: 100vh;
height: 100dvh;
} }
/* ─────────────── Sidebar ─────────────── */ /* ─────────────── Sidebar ─────────────── */
@@ -549,3 +568,25 @@ function onUserCommand(cmd: string) {
flex-direction: column; flex-direction: column;
} }
</style> </style>
<style>
/* 用户菜单「兼容主题」项(teleport 到 body */
.legacy-theme-label {
font-size: 11px !important;
opacity: 0.65;
cursor: default !important;
}
.legacy-swatch {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
vertical-align: middle;
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.el-dropdown-menu__item.is-legacy-active {
color: var(--mg-primary) !important;
font-weight: 600;
}
</style>
@@ -2,8 +2,11 @@ import { defineStore } from 'pinia'
import { import {
DEFAULT_THEME_ID, DEFAULT_THEME_ID,
LEGACY_THEMES,
PRIMARY_THEMES,
applyThemeVars, applyThemeVars,
findTheme, findTheme,
resolveThemeId,
THEMES, THEMES,
type ThemePreset type ThemePreset
} from '@/styles/themes' } from '@/styles/themes'
@@ -28,6 +31,9 @@ interface UiState {
} }
const STORAGE_KEY = 'simple.ui.state' const STORAGE_KEY = 'simple.ui.state'
/** Outpost 壳层上线:旧本地主题只换了 id、观感几乎不变 → 升 schema 强制一次默认浅色 */
const THEME_SCHEMA_KEY = 'simple.ui.themeSchema'
const THEME_SCHEMA_VERSION = 2
const DEFAULT_STATE: UiState = { const DEFAULT_STATE: UiState = {
sidebarCollapsed: false, sidebarCollapsed: false,
@@ -39,14 +45,33 @@ const DEFAULT_STATE: UiState = {
function loadInitial(): UiState { function loadInitial(): UiState {
try { try {
const raw = localStorage.getItem(STORAGE_KEY) const raw = localStorage.getItem(STORAGE_KEY)
if (!raw) return { ...DEFAULT_STATE } if (!raw) {
localStorage.setItem(THEME_SCHEMA_KEY, String(THEME_SCHEMA_VERSION))
return { ...DEFAULT_STATE }
}
const parsed = JSON.parse(raw) as Partial<UiState> const parsed = JSON.parse(raw) as Partial<UiState>
return { let themeId = resolveThemeId(parsed.themeId ?? DEFAULT_STATE.themeId)
const schema = Number(localStorage.getItem(THEME_SCHEMA_KEY) || '0')
const migrated = schema < THEME_SCHEMA_VERSION
if (migrated) {
// ponytail: 一次强制 outpost-light,兼容主题仍可从高级菜单切回
themeId = DEFAULT_THEME_ID
localStorage.setItem(THEME_SCHEMA_KEY, String(THEME_SCHEMA_VERSION))
}
const state: UiState = {
sidebarCollapsed: parsed.sidebarCollapsed ?? DEFAULT_STATE.sidebarCollapsed, sidebarCollapsed: parsed.sidebarCollapsed ?? DEFAULT_STATE.sidebarCollapsed,
theme: parsed.theme === 'dark' ? 'dark' : 'light', theme: parsed.theme === 'dark' ? 'dark' : 'light',
themeId: parsed.themeId ?? DEFAULT_STATE.themeId, themeId,
themeOverrides: parsed.themeOverrides ?? {} themeOverrides: parsed.themeOverrides ?? {}
} }
if (migrated) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(state))
} catch {
/* ignore */
}
}
return state
} catch (err) { } catch (err) {
console.warn('[ui.store] localStorage 中的 UI 状态解析失败,已回退默认:', err) console.warn('[ui.store] localStorage 中的 UI 状态解析失败,已回退默认:', err)
return { ...DEFAULT_STATE } return { ...DEFAULT_STATE }
@@ -60,8 +85,16 @@ export const useUiStore = defineStore('ui', {
activeTheme(state): ThemePreset { activeTheme(state): ThemePreset {
return mergeThemePreset(findTheme(state.themeId), state.themeOverrides[state.themeId]) return mergeThemePreset(findTheme(state.themeId), state.themeOverrides[state.themeId])
}, },
/** 所有可选主题列表 */ /** 顶栏精选主题(浅色 / 紫色 / 蓝色) */
availableThemes(): ThemePreset[] { availableThemes(): ThemePreset[] {
return PRIMARY_THEMES
},
/** 高级 → 兼容主题(旧深色霓虹) */
legacyThemes(): ThemePreset[] {
return LEGACY_THEMES
},
/** 全量(自定义配色等内部用) */
allThemes(): ThemePreset[] {
return THEMES return THEMES
} }
}, },
@@ -107,7 +140,7 @@ export const useUiStore = defineStore('ui', {
this.persist() this.persist()
}, },
/** 切换品牌主题色板 */ /** 切换品牌主题色板(精选或兼容均可) */
setThemeId(id: string) { setThemeId(id: string) {
const preset = findTheme(id) const preset = findTheme(id)
this.themeId = preset.id this.themeId = preset.id
File diff suppressed because it is too large Load Diff
@@ -1,10 +1,9 @@
/** /**
* 主题色板预设 * 主题色板预设
* *
* 设计原则: * 主路径:Outpost 壳层三套精选(浅色 / 紫色 / 蓝色)
* - 每个主题提供一组 CSS 变量,运行时通过 document.documentElement.style.setProperty 注入 * 高级:旧深色霓虹主题(LEGACY_THEMES
* - 命名遵循 theme.css 中既有的 --mg-* 体系 * 规范:docs/superpowers/specs/2026-07-25-migu-outpost-ui-lock-design.md
* - 工业紫色 (industrial-purple) 为默认主题:紫蓝渐变,深蓝底 + 天蓝高光
*/ */
export interface ThemePreset { export interface ThemePreset {
@@ -15,20 +14,77 @@ export interface ThemePreset {
preview: string preview: string
/** 强调高光,用于 UI 预览的次代表色 */ /** 强调高光,用于 UI 预览的次代表色 */
previewAccent: string previewAccent: string
/** Outpost 浅色壳(纸白主区);legacy 深色为 false */
outpostShell?: boolean
vars: Record<string, string> vars: Record<string, string>
} }
export const DEFAULT_THEME_ID = 'fame-lavender' /** Outpost 浅色壳共用表面(纸白卡、深字、关 orb);各主题可覆盖 card/tint */
const OUTPOST_SHELL_VARS: Record<string, string> = {
'--mg-bg-card-rgb': '255, 254, 253',
'--mg-bg-card-hi-rgb': '251, 249, 253',
'--mg-bg-card-darker-rgb': '245, 240, 251',
'--mg-text-tint-rgb': '40, 33, 58',
'--mg-text-hi-rgb': '117, 109, 133',
'--mg-orb-a-opacity': '0',
'--mg-orb-b-opacity': '0',
'--mg-orb-c-opacity': '0',
'--mg-radius-sm': '10px',
'--mg-radius': '16px',
'--mg-radius-lg': '20px',
'--mg-radius-xl': '24px'
}
export const THEMES: ThemePreset[] = [ /**
{ * 地图编辑 chrome(顶栏/工具轨/属性栏/底栏)跟主题色相走。
id: 'fame-lavender', * 默认真色壳(跟侧栏);浅色 Harbor 等可显式覆盖 --me-* 为浅壳。
name: '迷毂浅紫', * 画布始终保持深色。
description: '参考 FAME 系统:深紫侧栏 + 浅紫白主区,柔和耐看,长时间使用不刺眼', */
preview: '#7c3aed', function withMapEditorChrome(vars: Record<string, string>): Record<string, string> {
previewAccent: '#a855f7', const primaryRgb = vars['--mg-primary-rgb'] ?? '30, 58, 95'
const chrome1 = vars['--mg-bg-aside-1'] ?? '#1e3a5f'
const chrome2 = vars['--mg-bg-aside-2'] ?? '#0b1220'
const chromeRgb = vars['--mg-bg-aside-rgb'] ?? primaryRgb
const lightChrome = vars['--me-chrome-mode'] === 'light'
return {
...vars,
'--me-chrome-1': vars['--me-chrome-1'] ?? chrome1,
'--me-chrome-2': vars['--me-chrome-2'] ?? chrome2,
'--me-chrome-rgb': vars['--me-chrome-rgb'] ?? chromeRgb,
'--me-border': vars['--me-border'] ?? (lightChrome ? 'rgba(30, 58, 95, 0.12)' : 'rgba(255, 255, 255, 0.12)'),
'--me-text': vars['--me-text'] ?? (lightChrome ? 'rgba(11, 18, 32, 0.92)' : 'rgba(245, 242, 255, 0.94)'),
'--me-text-muted': vars['--me-text-muted'] ?? (lightChrome ? 'rgba(11, 18, 32, 0.55)' : 'rgba(210, 200, 235, 0.68)'),
'--me-surface': vars['--me-surface'] ?? (lightChrome ? 'rgba(30, 58, 95, 0.04)' : 'rgba(255, 255, 255, 0.05)'),
'--me-hover': vars['--me-hover'] ?? (lightChrome ? `rgba(${primaryRgb}, 0.08)` : `rgba(${primaryRgb}, 0.28)`),
'--me-active-glow': vars['--me-active-glow'] ?? `rgba(${primaryRgb}, 0.28)`,
'--me-card-bg': vars['--me-card-bg'] ?? (lightChrome ? 'rgba(255, 255, 255, 0.96)' : 'rgba(0, 0, 0, 0.22)'),
'--me-card-border': vars['--me-card-border'] ?? (lightChrome ? '#e2e6ec' : 'rgba(255, 255, 255, 0.08)'),
'--me-inset': vars['--me-inset'] ?? (lightChrome ? '#f4f6f8' : 'rgba(0, 0, 0, 0.28)')
}
}
function outpostPreset(
partial: Omit<ThemePreset, 'outpostShell' | 'vars'> & { vars: Record<string, string> }
): ThemePreset {
return {
...partial,
outpostShell: true,
vars: withMapEditorChrome({ ...OUTPOST_SHELL_VARS, ...partial.vars })
}
}
/** 默认:Outpost 浅色(对齐 fairylandtech.amerc.ai */
export const DEFAULT_THEME_ID = 'outpost-light'
/** 主路径精选三色(顶栏 ThemeSwitcher */
export const PRIMARY_THEMES: ThemePreset[] = [
outpostPreset({
id: 'outpost-light',
name: '浅色',
description: '纯白壳 + 紫色品牌点缀;地图壳白,画布深色',
preview: '#ffffff',
previewAccent: '#7c3aed',
vars: { vars: {
// 主色保留紫色(与 FAME 一致),但 hover/accent 使用更明亮的 violet/purple
'--mg-primary': '#7c3aed', '--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237', '--mg-primary-rgb': '124, 58, 237',
'--mg-primary-hover': '#8b5cf6', '--mg-primary-hover': '#8b5cf6',
@@ -36,84 +92,139 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#5b21b6', '--mg-primary-active': '#5b21b6',
'--mg-accent': '#a855f7', '--mg-accent': '#a855f7',
'--mg-accent-rgb': '168, 85, 247', '--mg-accent-rgb': '168, 85, 247',
'--mg-primary-light-3': '#a78bfa', '--mg-primary-light-3': '#a78bfa',
'--mg-primary-light-5': '#c4b5fd', '--mg-primary-light-5': '#c4b5fd',
'--mg-primary-light-7': '#ddd6fe', '--mg-primary-light-7': '#ddd6fe',
'--mg-primary-light-8': '#ede9fe', '--mg-primary-light-8': '#ede9fe',
'--mg-primary-light-9': '#f5f3ff', '--mg-primary-light-9': '#f5f3ff',
'--mg-primary-dark-2': '#6d28d9', '--mg-primary-dark-2': '#6d28d9',
'--mg-bg-app-1': '#ffffff',
// 主区背景:浅薰衣草灰(v3:从近白下沉到 ~92% 明度,让白卡片靠阴影清晰浮起) '--mg-bg-app-2': '#ffffff',
'--mg-bg-app-1': '#edeaf5', '--mg-bg-app-3': '#ffffff',
'--mg-bg-app-2': '#e7e4f1', '--mg-bg-app-deep-rgb': '255, 255, 255',
'--mg-bg-app-3': '#f1eef8', '--mg-bg-aside-1': '#ffffff',
'--mg-bg-app-deep-rgb': '231, 228, 241', '--mg-bg-aside-2': '#fafafa',
'--mg-bg-aside-rgb': '255, 255, 255',
// 侧栏:FAME 深紫渐变 #2d1b69 -> #1a1040 '--mg-shadow-rgb': '76, 29, 149',
'--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-rgb': '255, 255, 255',
'--mg-bg-card-hi-rgb': '249, 246, 255', '--mg-bg-card-hi-rgb': '255, 255, 255',
'--mg-bg-card-darker-rgb':'245, 240, 251', '--mg-bg-card-darker-rgb': '248, 250, 252',
'--mg-shadow-rgb': '124, 58, 237',
// 文字:深紫标题 #2d1b69, 中灰正文 #606266
'--mg-text-tint-rgb': '45, 27, 105', '--mg-text-tint-rgb': '45, 27, 105',
'--mg-text-hi-rgb': '96, 98, 102', '--mg-text-hi-rgb': '109, 40, 217',
/* 地图编辑:纯白壳 */
// 关闭装饰性 orb '--me-chrome-mode': 'light',
'--mg-orb-a-opacity': '0', '--me-chrome-1': '#ffffff',
'--mg-orb-b-opacity': '0', '--me-chrome-2': '#ffffff',
'--mg-orb-c-opacity': '0' '--me-chrome-rgb': '255, 255, 255',
'--me-border': 'rgba(124, 58, 237, 0.12)',
'--me-text': 'rgba(45, 27, 105, 0.92)',
'--me-text-muted': 'rgba(76, 29, 149, 0.55)',
'--me-surface': 'rgba(124, 58, 237, 0.04)',
'--me-hover': 'rgba(124, 58, 237, 0.08)',
'--me-active-glow': 'rgba(124, 58, 237, 0.18)',
'--me-card-bg': '#ffffff',
'--me-card-border': '#e5e7eb',
'--me-inset': '#f8fafc'
} }
}, }),
{ outpostPreset({
id: 'industrial-purple', id: 'outpost-purple',
name: '星云紫', name: '紫',
description: '科幻紫主色,辅以蓝紫渐变,磨砂玻璃 · 霓虹辉光', description: '同壳层、更饱和紫品牌;侧栏深蓝紫',
preview: '#7c3aed', preview: '#7c3aed',
previewAccent: '#c4b5fd', previewAccent: '#a855f7',
vars: {
'--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',
'--mg-bg-app-1': '#f3eefc',
'--mg-bg-app-2': '#edeaf5',
'--mg-bg-app-3': '#f8f5ff',
'--mg-bg-app-deep-rgb': '243, 238, 252',
'--mg-bg-aside-1': '#2d1b69',
'--mg-bg-aside-2': '#1a1040',
'--mg-bg-aside-rgb': '45, 27, 105',
'--mg-shadow-rgb': '124, 58, 237'
}
}),
outpostPreset({
id: 'outpost-blue',
name: '蓝色',
description: '同壳层、蓝科技感;侧栏深蓝',
preview: '#3c78d2',
previewAccent: '#6ea8f0',
vars: {
'--mg-primary': '#3c78d2',
'--mg-primary-rgb': '60, 120, 210',
'--mg-primary-hover': '#4d8ae0',
'--mg-primary-hover-rgb': '77, 138, 224',
'--mg-primary-active': '#2a5fad',
'--mg-accent': '#6ea8f0',
'--mg-accent-rgb': '110, 168, 240',
'--mg-primary-light-3': '#6ea8f0',
'--mg-primary-light-5': '#9bc4f5',
'--mg-primary-light-7': '#c5ddf9',
'--mg-primary-light-8': '#e3effc',
'--mg-primary-light-9': '#f3f7fc',
'--mg-primary-dark-2': '#2a5fad',
'--mg-bg-app-1': '#f3f6fb',
'--mg-bg-app-2': '#eef2f8',
'--mg-bg-app-3': '#f8fafc',
'--mg-bg-app-deep-rgb': '243, 246, 251',
'--mg-bg-aside-1': '#0f2744',
'--mg-bg-aside-2': '#0a1a30',
'--mg-bg-aside-rgb': '15, 39, 68',
'--mg-shadow-rgb': '30, 58, 95'
}
})
]
/** 高级兼容:旧深色霓虹主题(用户菜单「兼容主题」) */
export const LEGACY_THEMES: ThemePreset[] = [
{
id: 'industrial-purple',
name: '星云紫',
description: '科幻紫主色 · 磨砂玻璃 · 霓虹辉光(兼容)',
preview: '#7c3aed',
previewAccent: '#c4b5fd',
outpostShell: false,
vars: { vars: {
// ── 主色:明亮 violet(紫),主导整个画面 ──
'--mg-primary': '#7c3aed', '--mg-primary': '#7c3aed',
'--mg-primary-rgb': '124, 58, 237', '--mg-primary-rgb': '124, 58, 237',
// hover/辉光:偏蓝紫 indigo,带来氛围流动感
'--mg-primary-hover': '#8b5cf6', '--mg-primary-hover': '#8b5cf6',
'--mg-primary-hover-rgb': '139, 92, 246', '--mg-primary-hover-rgb': '139, 92, 246',
'--mg-primary-active': '#5b21b6', '--mg-primary-active': '#5b21b6',
// accent:浅紫高光,边缘霓虹与点缀
'--mg-accent': '#c4b5fd', '--mg-accent': '#c4b5fd',
'--mg-accent-rgb': '196, 181, 253', '--mg-accent-rgb': '196, 181, 253',
'--mg-primary-light-3': '#9d6cf2', '--mg-primary-light-3': '#9d6cf2',
'--mg-primary-light-5': '#b594f7', '--mg-primary-light-5': '#b594f7',
'--mg-primary-light-7': '#6f4eab', '--mg-primary-light-7': '#6f4eab',
'--mg-primary-light-8': '#4c357a', '--mg-primary-light-8': '#4c357a',
'--mg-primary-light-9': '#2d1f55', '--mg-primary-light-9': '#2d1f55',
'--mg-primary-dark-2': '#4c1d95', '--mg-primary-dark-2': '#4c1d95',
// ── 背景:深紫黑底,带 indigo 余晖 ──
'--mg-bg-app-1': '#0e0a24', '--mg-bg-app-1': '#0e0a24',
'--mg-bg-app-2': '#1f1147', '--mg-bg-app-2': '#1f1147',
'--mg-bg-app-3': '#06031a', '--mg-bg-app-3': '#06031a',
'--mg-bg-app-deep-rgb': '6, 3, 26', '--mg-bg-app-deep-rgb': '6, 3, 26',
'--mg-bg-aside-1': '#0c0820', '--mg-bg-aside-1': '#0c0820',
'--mg-bg-aside-2': '#03020e', '--mg-bg-aside-2': '#03020e',
'--mg-bg-aside-rgb': '10, 6, 30', '--mg-bg-aside-rgb': '10, 6, 30',
'--mg-bg-card-rgb': '38, 24, 78', '--mg-bg-card-rgb': '38, 24, 78',
'--mg-bg-card-hi-rgb': '58, 38, 110', '--mg-bg-card-hi-rgb': '58, 38, 110',
'--mg-bg-card-darker-rgb': '20, 12, 48', '--mg-bg-card-darker-rgb': '20, 12, 48',
'--mg-shadow-rgb': '8, 2, 24', '--mg-shadow-rgb': '8, 2, 24',
'--mg-text-tint-rgb': '242, 232, 255', '--mg-text-tint-rgb': '242, 232, 255',
'--mg-text-hi-rgb': '220, 200, 252', '--mg-text-hi-rgb': '220, 200, 252',
'--mg-orb-a-opacity': '0.55', '--mg-orb-a-opacity': '0.55',
'--mg-orb-b-opacity': '0.50', '--mg-orb-b-opacity': '0.50',
'--mg-orb-c-opacity': '0.40' '--mg-orb-c-opacity': '0.40'
@@ -122,9 +233,10 @@ export const THEMES: ThemePreset[] = [
{ {
id: 'deep-azure', id: 'deep-azure',
name: '深邃蓝', name: '深邃蓝',
description: '科技感深蓝,沉静稳重', description: '科技感深蓝,沉静稳重(兼容)',
preview: '#1565c0', preview: '#1565c0',
previewAccent: '#82b1ff', previewAccent: '#82b1ff',
outpostShell: false,
vars: { vars: {
'--mg-primary': '#1565c0', '--mg-primary': '#1565c0',
'--mg-primary-rgb': '21, 101, 192', '--mg-primary-rgb': '21, 101, 192',
@@ -133,28 +245,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#0d47a1', '--mg-primary-active': '#0d47a1',
'--mg-accent': '#82b1ff', '--mg-accent': '#82b1ff',
'--mg-accent-rgb': '130, 177, 255', '--mg-accent-rgb': '130, 177, 255',
'--mg-primary-light-3': '#5a8fce', '--mg-primary-light-3': '#5a8fce',
'--mg-primary-light-5': '#83aedb', '--mg-primary-light-5': '#83aedb',
'--mg-primary-light-7': '#adcde7', '--mg-primary-light-7': '#adcde7',
'--mg-primary-light-8': '#c4dbed', '--mg-primary-light-8': '#c4dbed',
'--mg-primary-light-9': '#e3eef8', '--mg-primary-light-9': '#e3eef8',
'--mg-primary-dark-2': '#0d47a1', '--mg-primary-dark-2': '#0d47a1',
'--mg-bg-app-1': '#051a3a', '--mg-bg-app-1': '#051a3a',
'--mg-bg-app-2': '#0a2f6b', '--mg-bg-app-2': '#0a2f6b',
'--mg-bg-app-3': '#020a1f', '--mg-bg-app-3': '#020a1f',
'--mg-bg-app-deep-rgb': '2, 8, 26', '--mg-bg-app-deep-rgb': '2, 8, 26',
'--mg-bg-aside-1': '#06122a', '--mg-bg-aside-1': '#06122a',
'--mg-bg-aside-2': '#02060f', '--mg-bg-aside-2': '#02060f',
'--mg-bg-aside-rgb': '8, 18, 42', '--mg-bg-aside-rgb': '8, 18, 42',
'--mg-bg-card-rgb': '12, 36, 80', '--mg-bg-card-rgb': '12, 36, 80',
'--mg-bg-card-hi-rgb': '18, 50, 110', '--mg-bg-card-hi-rgb': '18, 50, 110',
'--mg-bg-card-darker-rgb': '6, 18, 44', '--mg-bg-card-darker-rgb': '6, 18, 44',
'--mg-shadow-rgb': '2, 4, 14', '--mg-shadow-rgb': '2, 4, 14',
'--mg-text-tint-rgb': '218, 234, 252', '--mg-text-tint-rgb': '218, 234, 252',
'--mg-text-hi-rgb': '170, 200, 240' '--mg-text-hi-rgb': '170, 200, 240'
} }
@@ -162,9 +269,10 @@ export const THEMES: ThemePreset[] = [
{ {
id: 'emerald-forge', id: 'emerald-forge',
name: '翡翠绿', name: '翡翠绿',
description: '机械翡翠绿,沉稳活力', description: '机械翡翠绿(兼容)',
preview: '#00796b', preview: '#00796b',
previewAccent: '#80cbc4', previewAccent: '#80cbc4',
outpostShell: false,
vars: { vars: {
'--mg-primary': '#00796b', '--mg-primary': '#00796b',
'--mg-primary-rgb': '0, 121, 107', '--mg-primary-rgb': '0, 121, 107',
@@ -173,28 +281,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#004d40', '--mg-primary-active': '#004d40',
'--mg-accent': '#80cbc4', '--mg-accent': '#80cbc4',
'--mg-accent-rgb': '128, 203, 196', '--mg-accent-rgb': '128, 203, 196',
'--mg-primary-light-3': '#4ca094', '--mg-primary-light-3': '#4ca094',
'--mg-primary-light-5': '#79b9b0', '--mg-primary-light-5': '#79b9b0',
'--mg-primary-light-7': '#a6d2cc', '--mg-primary-light-7': '#a6d2cc',
'--mg-primary-light-8': '#bfdfdb', '--mg-primary-light-8': '#bfdfdb',
'--mg-primary-light-9': '#e1f0ee', '--mg-primary-light-9': '#e1f0ee',
'--mg-primary-dark-2': '#004d40', '--mg-primary-dark-2': '#004d40',
'--mg-bg-app-1': '#06241f', '--mg-bg-app-1': '#06241f',
'--mg-bg-app-2': '#0a4538', '--mg-bg-app-2': '#0a4538',
'--mg-bg-app-3': '#020f0c', '--mg-bg-app-3': '#020f0c',
'--mg-bg-app-deep-rgb': '2, 12, 10', '--mg-bg-app-deep-rgb': '2, 12, 10',
'--mg-bg-aside-1': '#061a16', '--mg-bg-aside-1': '#061a16',
'--mg-bg-aside-2': '#020a08', '--mg-bg-aside-2': '#020a08',
'--mg-bg-aside-rgb': '8, 26, 22', '--mg-bg-aside-rgb': '8, 26, 22',
'--mg-bg-card-rgb': '10, 50, 42', '--mg-bg-card-rgb': '10, 50, 42',
'--mg-bg-card-hi-rgb': '14, 66, 56', '--mg-bg-card-hi-rgb': '14, 66, 56',
'--mg-bg-card-darker-rgb': '6, 28, 24', '--mg-bg-card-darker-rgb': '6, 28, 24',
'--mg-shadow-rgb': '2, 10, 8', '--mg-shadow-rgb': '2, 10, 8',
'--mg-text-tint-rgb': '220, 244, 240', '--mg-text-tint-rgb': '220, 244, 240',
'--mg-text-hi-rgb': '170, 220, 210' '--mg-text-hi-rgb': '170, 220, 210'
} }
@@ -202,9 +305,10 @@ export const THEMES: ThemePreset[] = [
{ {
id: 'crimson-iron', id: 'crimson-iron',
name: '熔铁赤', name: '熔铁赤',
description: '熔铁炉火般的暗红工业风', description: '暗红工业风(兼容)',
preview: '#b71c1c', preview: '#b71c1c',
previewAccent: '#ff8a80', previewAccent: '#ff8a80',
outpostShell: false,
vars: { vars: {
'--mg-primary': '#b71c1c', '--mg-primary': '#b71c1c',
'--mg-primary-rgb': '183, 28, 28', '--mg-primary-rgb': '183, 28, 28',
@@ -213,28 +317,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#8a0c0c', '--mg-primary-active': '#8a0c0c',
'--mg-accent': '#ff8a80', '--mg-accent': '#ff8a80',
'--mg-accent-rgb': '255, 138, 128', '--mg-accent-rgb': '255, 138, 128',
'--mg-primary-light-3': '#c95a5a', '--mg-primary-light-3': '#c95a5a',
'--mg-primary-light-5': '#d68585', '--mg-primary-light-5': '#d68585',
'--mg-primary-light-7': '#e4afaf', '--mg-primary-light-7': '#e4afaf',
'--mg-primary-light-8': '#edc8c8', '--mg-primary-light-8': '#edc8c8',
'--mg-primary-light-9': '#f7e6e6', '--mg-primary-light-9': '#f7e6e6',
'--mg-primary-dark-2': '#8a0c0c', '--mg-primary-dark-2': '#8a0c0c',
'--mg-bg-app-1': '#2a0606', '--mg-bg-app-1': '#2a0606',
'--mg-bg-app-2': '#4a0c0c', '--mg-bg-app-2': '#4a0c0c',
'--mg-bg-app-3': '#150303', '--mg-bg-app-3': '#150303',
'--mg-bg-app-deep-rgb': '20, 4, 4', '--mg-bg-app-deep-rgb': '20, 4, 4',
'--mg-bg-aside-1': '#200505', '--mg-bg-aside-1': '#200505',
'--mg-bg-aside-2': '#0c0202', '--mg-bg-aside-2': '#0c0202',
'--mg-bg-aside-rgb': '36, 6, 6', '--mg-bg-aside-rgb': '36, 6, 6',
'--mg-bg-card-rgb': '60, 10, 10', '--mg-bg-card-rgb': '60, 10, 10',
'--mg-bg-card-hi-rgb': '80, 14, 14', '--mg-bg-card-hi-rgb': '80, 14, 14',
'--mg-bg-card-darker-rgb': '34, 5, 5', '--mg-bg-card-darker-rgb': '34, 5, 5',
'--mg-shadow-rgb': '10, 1, 1', '--mg-shadow-rgb': '10, 1, 1',
'--mg-text-tint-rgb': '252, 220, 220', '--mg-text-tint-rgb': '252, 220, 220',
'--mg-text-hi-rgb': '240, 175, 175' '--mg-text-hi-rgb': '240, 175, 175'
} }
@@ -242,9 +341,10 @@ export const THEMES: ThemePreset[] = [
{ {
id: 'graphite-steel', id: 'graphite-steel',
name: '石墨钢', name: '石墨钢',
description: '中性冷灰,纯粹工业', description: '中性冷灰(兼容)',
preview: '#455a64', preview: '#455a64',
previewAccent: '#b0bec5', previewAccent: '#b0bec5',
outpostShell: false,
vars: { vars: {
'--mg-primary': '#455a64', '--mg-primary': '#455a64',
'--mg-primary-rgb': '69, 90, 100', '--mg-primary-rgb': '69, 90, 100',
@@ -253,28 +353,23 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#263238', '--mg-primary-active': '#263238',
'--mg-accent': '#b0bec5', '--mg-accent': '#b0bec5',
'--mg-accent-rgb': '176, 190, 197', '--mg-accent-rgb': '176, 190, 197',
'--mg-primary-light-3': '#7a8a92', '--mg-primary-light-3': '#7a8a92',
'--mg-primary-light-5': '#9aa6ac', '--mg-primary-light-5': '#9aa6ac',
'--mg-primary-light-7': '#bac2c7', '--mg-primary-light-7': '#bac2c7',
'--mg-primary-light-8': '#ccd2d6', '--mg-primary-light-8': '#ccd2d6',
'--mg-primary-light-9': '#e8ebed', '--mg-primary-light-9': '#e8ebed',
'--mg-primary-dark-2': '#263238', '--mg-primary-dark-2': '#263238',
'--mg-bg-app-1': '#161e24', '--mg-bg-app-1': '#161e24',
'--mg-bg-app-2': '#243038', '--mg-bg-app-2': '#243038',
'--mg-bg-app-3': '#0a0e12', '--mg-bg-app-3': '#0a0e12',
'--mg-bg-app-deep-rgb': '10, 14, 18', '--mg-bg-app-deep-rgb': '10, 14, 18',
'--mg-bg-aside-1': '#11181d', '--mg-bg-aside-1': '#11181d',
'--mg-bg-aside-2': '#060a0d', '--mg-bg-aside-2': '#060a0d',
'--mg-bg-aside-rgb': '18, 26, 32', '--mg-bg-aside-rgb': '18, 26, 32',
'--mg-bg-card-rgb': '30, 42, 50', '--mg-bg-card-rgb': '30, 42, 50',
'--mg-bg-card-hi-rgb': '42, 56, 66', '--mg-bg-card-hi-rgb': '42, 56, 66',
'--mg-bg-card-darker-rgb': '16, 22, 28', '--mg-bg-card-darker-rgb': '16, 22, 28',
'--mg-shadow-rgb': '4, 6, 10', '--mg-shadow-rgb': '4, 6, 10',
'--mg-text-tint-rgb': '226, 232, 238', '--mg-text-tint-rgb': '226, 232, 238',
'--mg-text-hi-rgb': '186, 198, 210' '--mg-text-hi-rgb': '186, 198, 210'
} }
@@ -282,9 +377,10 @@ export const THEMES: ThemePreset[] = [
{ {
id: 'amber-forge', id: 'amber-forge',
name: '琥珀金', name: '琥珀金',
description: '高对比的橙金调,警示与活力并存', description: '橙金调(兼容)',
preview: '#e65100', preview: '#e65100',
previewAccent: '#ffb74d', previewAccent: '#ffb74d',
outpostShell: false,
vars: { vars: {
'--mg-primary': '#e65100', '--mg-primary': '#e65100',
'--mg-primary-rgb': '230, 81, 0', '--mg-primary-rgb': '230, 81, 0',
@@ -293,44 +389,69 @@ export const THEMES: ThemePreset[] = [
'--mg-primary-active': '#bf360c', '--mg-primary-active': '#bf360c',
'--mg-accent': '#ffb74d', '--mg-accent': '#ffb74d',
'--mg-accent-rgb': '255, 183, 77', '--mg-accent-rgb': '255, 183, 77',
'--mg-primary-light-3': '#ec8344', '--mg-primary-light-3': '#ec8344',
'--mg-primary-light-5': '#f2a474', '--mg-primary-light-5': '#f2a474',
'--mg-primary-light-7': '#f7c4a3', '--mg-primary-light-7': '#f7c4a3',
'--mg-primary-light-8': '#fad6bc', '--mg-primary-light-8': '#fad6bc',
'--mg-primary-light-9': '#fdeadd', '--mg-primary-light-9': '#fdeadd',
'--mg-primary-dark-2': '#bf360c', '--mg-primary-dark-2': '#bf360c',
'--mg-bg-app-1': '#2a1404', '--mg-bg-app-1': '#2a1404',
'--mg-bg-app-2': '#4a2a0c', '--mg-bg-app-2': '#4a2a0c',
'--mg-bg-app-3': '#150a02', '--mg-bg-app-3': '#150a02',
'--mg-bg-app-deep-rgb': '20, 10, 4', '--mg-bg-app-deep-rgb': '20, 10, 4',
'--mg-bg-aside-1': '#200f04', '--mg-bg-aside-1': '#200f04',
'--mg-bg-aside-2': '#0c0602', '--mg-bg-aside-2': '#0c0602',
'--mg-bg-aside-rgb': '34, 16, 6', '--mg-bg-aside-rgb': '34, 16, 6',
'--mg-bg-card-rgb': '62, 32, 8', '--mg-bg-card-rgb': '62, 32, 8',
'--mg-bg-card-hi-rgb': '82, 44, 12', '--mg-bg-card-hi-rgb': '82, 44, 12',
'--mg-bg-card-darker-rgb': '34, 16, 4', '--mg-bg-card-darker-rgb': '34, 16, 4',
'--mg-shadow-rgb': '10, 4, 1', '--mg-shadow-rgb': '10, 4, 1',
'--mg-text-tint-rgb': '252, 232, 210', '--mg-text-tint-rgb': '252, 232, 210',
'--mg-text-hi-rgb': '240, 196, 140' '--mg-text-hi-rgb': '240, 196, 140'
} }
} }
] ]
export function findTheme(id: string | undefined | null): ThemePreset { /** 全量(查找用):精选 + 兼容 */
if (!id) return THEMES[0] export const THEMES: ThemePreset[] = [...PRIMARY_THEMES, ...LEGACY_THEMES]
return THEMES.find((t) => t.id === id) ?? THEMES[0]
/** 旧 id → 新精选 id */
const THEME_ALIASES: Record<string, string> = {
'fame-lavender': 'outpost-purple',
'outpost-fairyland': 'outpost-light'
} }
/** 把主题变量写入 :root;多次调用幂等,会覆盖同名属性 */ export function resolveThemeId(id: string | undefined | null): string {
if (!id) return DEFAULT_THEME_ID
const aliased = THEME_ALIASES[id] ?? id
if (THEMES.some((t) => t.id === aliased)) return aliased
return DEFAULT_THEME_ID
}
export function findTheme(id: string | undefined | null): ThemePreset {
const resolved = resolveThemeId(id)
return THEMES.find((t) => t.id === resolved) ?? PRIMARY_THEMES[0]
}
export function isLegacyThemeId(id: string | undefined | null): boolean {
if (!id) return false
const resolved = THEME_ALIASES[id] ?? id
return LEGACY_THEMES.some((t) => t.id === resolved)
}
/** 把主题变量写入 :rootOutpost 壳写 data-shell="outpost" 供浅色 CSS 命中 */
export function applyThemeVars(preset: ThemePreset): void { export function applyThemeVars(preset: ThemePreset): void {
const root = document.documentElement const root = document.documentElement
for (const [key, value] of Object.entries(preset.vars)) { // preset.vars 在 outpostPreset 里已跑过 withMapEditorChrome;再跑一次以补全缺省 --me-*
const vars = withMapEditorChrome({ ...preset.vars })
for (const [key, value] of Object.entries(vars)) {
if (key === '--me-chrome-mode') continue
root.style.setProperty(key, value) root.style.setProperty(key, value)
} }
root.setAttribute('data-theme', preset.id) root.setAttribute('data-theme', preset.id)
if (preset.outpostShell) {
root.setAttribute('data-shell', 'outpost')
} else {
root.removeAttribute('data-shell')
}
} }
@@ -4,17 +4,10 @@
<div class="bg-overlay" /> <div class="bg-overlay" />
<div class="bg-orb bg-orb-a" /> <div class="bg-orb bg-orb-a" />
<div class="bg-orb bg-orb-b" /> <div class="bg-orb bg-orb-b" />
<div class="bg-orb bg-orb-c" />
<div class="bg-stars" aria-hidden="true">
<span v-for="(s, i) in starStyles" :key="i" :style="s" />
</div>
<div class="login-card"> <div class="login-card">
<!-- 左侧品牌 hero --> <!-- 左侧品牌 hero -->
<div class="hero"> <div class="hero">
<div class="hero-stars" aria-hidden="true">
<span v-for="(s, i) in heroStarStyles" :key="i" :style="s" />
</div>
<div class="hero-brand"> <div class="hero-brand">
<span class="hero-mark"> <span class="hero-mark">
<img src="/FRLD-logo-white-no_title.png" alt="迷毂" class="hero-mark-img" /> <img src="/FRLD-logo-white-no_title.png" alt="迷毂" class="hero-mark-img" />
@@ -212,25 +205,6 @@ const rules: FormRules = {
const year = computed(() => new Date().getFullYear()) const year = computed(() => new Date().getFullYear())
// 生成 n 个随机分布、随机大小与闪烁节奏的小白点,营造梦幻星点。
function makeStars(n: number): Record<string, string>[] {
return Array.from({ length: n }, () => {
const size = 1.5 + Math.random() * 2.8
return {
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
width: `${size}px`,
height: `${size}px`,
opacity: `${0.35 + Math.random() * 0.5}`,
animationDelay: `${-Math.random() * 6}s`,
animationDuration: `${2.6 + Math.random() * 3.4}s`
} as Record<string, string>
})
}
// 背景层(卡片之外)+ 品牌区(卡片内深色区)两组星点
const starStyles = makeStars(48)
const heroStarStyles = makeStars(22)
async function submit() { async function submit() {
if (!formRef.value) return if (!formRef.value) return
await formRef.value.validate(async (ok) => { await formRef.value.validate(async (ok) => {
@@ -269,13 +243,8 @@ async function submit() {
</script> </script>
<style scoped> <style scoped>
/* ════════════════════════════════════════════════════════════════════════ /* Outpost 登录:纸白主区 + plum 品牌柱(对齐 fairylandtech.amerc.ai */
* 登录页固定配色(不随全局主题切换变化)。
* 统一用一组本地 --lg-* 变量(午夜靛蓝 + 青色霓虹),保证无论用户切到哪个
* 主题,登录界面始终是同一套高级、克制的深色质感。
* ════════════════════════════════════════════════════════════════════════ */
.login-page { .login-page {
/* 梦幻晨曦渐变:粉紫(左上) → 蓝紫(中) → 淡蓝(右下),高明度柔光,参考产品视觉稿。 */
position: relative; position: relative;
min-height: 100vh; min-height: 100vh;
display: flex; display: flex;
@@ -283,11 +252,11 @@ async function submit() {
justify-content: center; justify-content: center;
overflow: hidden; overflow: hidden;
background: background:
radial-gradient(ellipse 75% 55% at 16% 14%, #dcbef7 0%, transparent 55%), radial-gradient(ellipse 70% 50% at 15% 10%, rgba(117, 67, 232, 0.14), transparent 55%),
radial-gradient(ellipse 80% 65% at 86% 90%, #abc9f5 0%, transparent 58%), radial-gradient(ellipse 60% 45% at 90% 85%, rgba(155, 124, 255, 0.1), transparent 50%),
linear-gradient(135deg, #cbb2f0 0%, #b7adec 46%, #a7c6f2 100%); #f6f3fb;
color: var(--lg-text); color: #28213a;
font-family: 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', sans-serif; font-family: 'Inter', 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', sans-serif;
} }
/* ===== 背景层 ===== */ /* ===== 背景层 ===== */
@@ -329,25 +298,18 @@ async function submit() {
animation: drift 20s ease-in-out infinite; animation: drift 20s ease-in-out infinite;
} }
.bg-orb-a { .bg-orb-a {
width: 460px; height: 460px; width: 420px; height: 420px;
top: -150px; left: -130px; top: -140px; left: -100px;
background: radial-gradient(circle, var(--lg-primary-hover) 0%, var(--lg-primary) 52%, transparent 80%); background: radial-gradient(circle, rgba(155, 124, 255, 0.55) 0%, rgba(117, 67, 232, 0.35) 55%, transparent 80%);
opacity: 0.24; opacity: 0.22;
} }
.bg-orb-b { .bg-orb-b {
width: 600px; height: 600px; width: 480px; height: 480px;
bottom: -220px; right: -190px; bottom: -180px; right: -140px;
background: radial-gradient(circle, var(--lg-accent) 0%, var(--lg-primary-deep) 58%, transparent 86%); background: radial-gradient(circle, rgba(117, 67, 232, 0.4) 0%, rgba(155, 124, 255, 0.2) 55%, transparent 85%);
opacity: 0.18; opacity: 0.16;
animation-delay: -7s; animation-delay: -7s;
} }
.bg-orb-c {
width: 320px; height: 320px;
top: 32%; right: 9%;
background: radial-gradient(circle, var(--lg-primary-hover) 0%, var(--lg-primary) 60%, transparent 90%);
opacity: 0.14;
animation-delay: -13s;
}
@keyframes drift { @keyframes drift {
0%, 100% { transform: translate(0, 0) scale(1); } 0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(18px, -28px) scale(1.05); } 50% { transform: translate(18px, -28px) scale(1.05); }
@@ -368,7 +330,7 @@ async function submit() {
50% { opacity: 0.95; transform: scale(1.2); } 50% { opacity: 0.95; transform: scale(1.2); }
} }
/* ===== 卡片:双栏 hero ===== */ /* ===== 卡片:Outpost 纸白 + plum 品牌柱 ===== */
.login-card { .login-card {
position: relative; position: relative;
z-index: 2; z-index: 2;
@@ -378,34 +340,19 @@ async function submit() {
display: grid; display: grid;
grid-template-columns: 360px 1fr; grid-template-columns: 360px 1fr;
overflow: hidden; overflow: hidden;
border-radius: 22px; border-radius: 20px;
border: 1px solid rgba(255, 255, 255, 0.08); border: 1px solid rgba(40, 33, 58, 0.08);
/* 登录框本体:紫 → 靛蓝的「紫蓝混合」渐变,梦幻且不发灰;高不透明度保证文字清晰 */ background: #fffefd;
background:
linear-gradient(140deg,
rgba(48, 26, 96, 0.95) 0%,
rgba(33, 26, 92, 0.955) 50%,
rgba(22, 26, 84, 0.96) 100%);
backdrop-filter: blur(26px) saturate(150%);
-webkit-backdrop-filter: blur(26px) saturate(150%);
box-shadow: box-shadow:
0 44px 110px rgba(0, 0, 0, 0.62), 0 1px 2px rgba(40, 33, 58, 0.04),
0 0 70px rgba(var(--lg-primary-rgb), 0.24), 0 24px 56px rgba(54, 35, 78, 0.12);
0 0 0 1px rgba(var(--lg-accent-rgb), 0.16) inset,
0 1px 0 rgba(255, 255, 255, 0.18) inset;
} }
/* 顶部霓虹高光线 */
.login-card::before { .login-card::before {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; left: 12%; right: 12%; top: 0; left: 12%; right: 12%;
height: 1px; height: 1px;
background: linear-gradient(90deg, background: linear-gradient(90deg, transparent 0%, rgba(117, 67, 232, 0.35) 50%, transparent 100%);
transparent 0%,
rgba(var(--lg-accent-rgb), 0.55) 30%,
rgba(255, 255, 255, 0.92) 50%,
rgba(var(--lg-accent-rgb), 0.55) 70%,
transparent 100%);
pointer-events: none; pointer-events: none;
z-index: 3; z-index: 3;
} }
@@ -415,12 +362,8 @@ async function submit() {
position: relative; position: relative;
padding: 44px 34px 34px; padding: 44px 34px 34px;
color: #fff; color: #fff;
/* 品牌区紫蓝混合:左上紫光球 + 右下蓝光球,叠在深紫底上 */ background: linear-gradient(165deg, #3f2454 0%, #241530 100%);
background: border-right: 1px solid rgba(255, 255, 255, 0.06);
radial-gradient(ellipse at 16% 18%, rgba(123, 77, 255, 0.34) 0%, transparent 56%),
radial-gradient(ellipse at 88% 84%, rgba(94, 124, 255, 0.32) 0%, transparent 58%),
linear-gradient(170deg, rgba(var(--lg-primary-rgb), 0.26) 0%, rgba(var(--lg-aside-rgb), 0.42) 60%, rgba(var(--lg-deep-rgb), 0.55) 100%);
border-right: 1px solid rgba(255, 255, 255, 0.08);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -550,56 +493,53 @@ async function submit() {
padding: 44px 44px 32px; padding: 44px 44px 32px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
/* 表单侧:紫 → 蓝的洗光,与品牌区一致的「紫蓝混合」基调 */ background: #fffefd;
background: color: #28213a;
linear-gradient(135deg, rgba(123, 77, 255, 0.14) 0%, rgba(94, 124, 255, 0.10) 100%);
} }
.panel-head { margin-bottom: 26px; } .panel-head { margin-bottom: 26px; }
.panel-title { .panel-title {
font-size: 30px; font-weight: 700; font-size: 28px; font-weight: 700;
letter-spacing: 12px; letter-spacing: 8px;
background: linear-gradient(135deg, #ffffff 0%, var(--lg-accent) 100%); color: #28213a;
-webkit-background-clip: text; background: none;
background-clip: text; -webkit-text-fill-color: unset;
color: transparent; text-shadow: none;
text-shadow: 0 0 22px rgba(var(--lg-primary-hover-rgb), 0.5);
} }
.panel-sub { .panel-sub {
margin-top: 6px; margin-top: 6px;
font-size: 12.5px; font-size: 12.5px;
color: var(--lg-text-dim); color: #756d85;
letter-spacing: 1px; letter-spacing: 1px;
} }
.glass-form { flex: 1; display: flex; flex-direction: column; } .glass-form { flex: 1; display: flex; flex-direction: column; }
.glass-form :deep(.el-form-item) { margin-bottom: 18px; } .glass-form :deep(.el-form-item) { margin-bottom: 18px; }
/* 输入框 透明玻璃 */
.glass-form :deep(.el-input__wrapper) { .glass-form :deep(.el-input__wrapper) {
background: rgba(255, 255, 255, 0.06) !important; background: #f6f3fb !important;
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16) inset !important; box-shadow: 0 0 0 1px rgba(40, 33, 58, 0.1) inset !important;
border-radius: 10px !important; border-radius: 12px !important;
transition: all .25s; transition: all .2s;
} }
.glass-form :deep(.el-input__wrapper:hover) { .glass-form :deep(.el-input__wrapper:hover) {
background: rgba(255, 255, 255, 0.11) !important; background: #fbf9fd !important;
box-shadow: 0 0 0 1px rgba(var(--lg-accent-rgb), 0.5) inset !important; box-shadow: 0 0 0 1px rgba(117, 67, 232, 0.35) inset !important;
} }
.glass-form :deep(.el-input__wrapper.is-focus) { .glass-form :deep(.el-input__wrapper.is-focus) {
background: rgba(255, 255, 255, 0.13) !important; background: #fff !important;
box-shadow: box-shadow:
0 0 0 1px rgba(var(--lg-accent-rgb), 0.85) inset, 0 0 0 1px rgba(117, 67, 232, 0.55) inset,
0 0 20px rgba(var(--lg-primary-hover-rgb), 0.45) !important; 0 0 0 3px rgba(117, 67, 232, 0.12) !important;
} }
.glass-form :deep(.el-input__inner) { .glass-form :deep(.el-input__inner) {
color: #fff !important; color: #28213a !important;
-webkit-text-fill-color: #fff !important; -webkit-text-fill-color: #28213a !important;
font-size: 14px; font-size: 14px;
height: 42px; height: 42px;
} }
.glass-form :deep(.el-input__inner::placeholder) { color: rgba(255, 255, 255, 0.42); } .glass-form :deep(.el-input__inner::placeholder) { color: #a8a0c0; }
.glass-form :deep(.el-input__prefix-inner > :first-child), .glass-form :deep(.el-input__prefix-inner > :first-child),
.glass-form :deep(.el-input__suffix-inner) { color: rgba(255, 255, 255, 0.55); } .glass-form :deep(.el-input__suffix-inner) { color: #756d85; }
/* scope 双卡 */ /* scope 双卡 */
.scope-item :deep(.el-form-item__content) { width: 100%; } .scope-item :deep(.el-form-item__content) { width: 100%; }
@@ -611,45 +551,42 @@ async function submit() {
display: flex; align-items: center; gap: 6px; display: flex; align-items: center; gap: 6px;
margin-bottom: 8px; margin-bottom: 8px;
font-size: 12px; font-size: 12px;
color: var(--lg-text-soft); color: #756d85;
letter-spacing: 1px; letter-spacing: 1px;
} }
.launch-mode-title { font-weight: 500; } .launch-mode-title { font-weight: 500; }
.launch-mode-help { .launch-mode-help {
color: var(--lg-text-dim); color: #a8a0c0;
cursor: help; cursor: help;
font-size: 14px; font-size: 14px;
} }
.launch-mode-help:hover { color: var(--lg-accent); } .launch-mode-help:hover { color: #7543e8; }
.launch-wrap { margin-bottom: 4px; } .launch-wrap { margin-bottom: 4px; }
.launch-tip { max-width: 280px; line-height: 1.6; font-size: 12px; } .launch-tip { max-width: 280px; line-height: 1.6; font-size: 12px; }
.launch-tip > div + div { margin-top: 6px; } .launch-tip > div + div { margin-top: 6px; }
.scope-tab { .scope-tab {
appearance: none; appearance: none;
background: rgba(255, 255, 255, 0.05); background: #f6f3fb;
border: 1px solid rgba(255, 255, 255, 0.16); border: 1px solid rgba(40, 33, 58, 0.1);
border-radius: 12px; border-radius: 12px;
padding: 14px 14px; padding: 14px 14px;
display: flex; align-items: center; gap: 12px; display: flex; align-items: center; gap: 12px;
color: rgba(255, 255, 255, 0.78); color: #28213a;
cursor: pointer; cursor: pointer;
transition: all .2s; transition: all .2s;
text-align: left; text-align: left;
} }
.scope-tab:hover { .scope-tab:hover {
background: rgba(255, 255, 255, 0.11); background: #fbf9fd;
color: #fff; border-color: rgba(117, 67, 232, 0.3);
color: #28213a;
transform: translateY(-1px); transform: translateY(-1px);
} }
.scope-tab.active { .scope-tab.active {
background: linear-gradient(135deg, background: linear-gradient(135deg, #7543e8 0%, #9b7cff 100%);
rgba(var(--lg-primary-rgb), 0.55) 0%, border-color: transparent;
rgba(var(--lg-accent-rgb), 0.28) 100%);
border-color: rgba(var(--lg-accent-rgb), 0.75);
color: #fff; color: #fff;
box-shadow: box-shadow: 0 8px 20px rgba(117, 67, 232, 0.28);
0 0 22px rgba(var(--lg-primary-hover-rgb), 0.45),
0 0 0 1px rgba(var(--lg-accent-rgb), 0.45) inset;
} }
.scope-tab .el-icon { font-size: 20px; } .scope-tab .el-icon { font-size: 20px; }
.scope-meta { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; } .scope-meta { display: flex; flex-direction: column; line-height: 1.3; min-width: 0; }
@@ -660,107 +597,83 @@ async function submit() {
.row { display: flex; align-items: center; } .row { display: flex; align-items: center; }
.row-between { justify-content: space-between; margin: -6px 0 10px; } .row-between { justify-content: space-between; margin: -6px 0 10px; }
.remember :deep(.el-checkbox__label) { .remember :deep(.el-checkbox__label) {
color: rgba(255, 255, 255, 0.9) !important; color: #28213a !important;
font-size: 12.5px; font-size: 12.5px;
text-shadow: 0 1px 2px rgba(6, 9, 18, 0.4); text-shadow: none;
} }
.remember :deep(.el-checkbox__inner) { .remember :deep(.el-checkbox__inner) {
background: rgba(255, 255, 255, 0.1); background: #fff;
border-color: rgba(255, 255, 255, 0.32); border-color: rgba(40, 33, 58, 0.25);
} }
.remember :deep(.el-checkbox.is-checked .el-checkbox__inner) { .remember :deep(.el-checkbox.is-checked .el-checkbox__inner) {
background: var(--lg-primary); background: #7543e8;
border-color: var(--lg-primary); border-color: #7543e8;
} }
.adv-link { color: var(--lg-text-soft) !important; font-size: 12.5px; } .adv-link { color: #756d85 !important; font-size: 12.5px; }
.adv-link:hover { color: #fff !important; } .adv-link:hover { color: #7543e8 !important; }
/* 高级折叠 */ /* 高级折叠 */
.adv-collapse { background: transparent !important; border: none !important; margin-bottom: 16px; } .adv-collapse { background: transparent !important; border: none !important; margin-bottom: 16px; }
.adv-collapse :deep(.el-collapse-item__wrap), .adv-collapse :deep(.el-collapse-item__wrap),
.adv-collapse :deep(.el-collapse-item__header) { .adv-collapse :deep(.el-collapse-item__header) {
background: transparent !important; background: transparent !important;
border-color: rgba(255, 255, 255, 0.14) !important; border-color: rgba(40, 33, 58, 0.1) !important;
color: var(--lg-text-soft) !important; color: #756d85 !important;
} }
.adv-title { font-size: 12px; letter-spacing: 1px; } .adv-title { font-size: 12px; letter-spacing: 1px; }
.adv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; padding-top: 6px; } .adv-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; padding-top: 6px; }
.adv-grid :deep(.el-form-item__label) { .adv-grid :deep(.el-form-item__label) {
color: var(--lg-text-soft); color: #756d85;
font-size: 12px; font-size: 12px;
} }
.adv-grid :deep(.el-input-number) { width: 100%; } .adv-grid :deep(.el-input-number) { width: 100%; }
.adv-grid :deep(.el-input-number .el-input__inner) { text-align: left; } .adv-grid :deep(.el-input-number .el-input__inner) { text-align: left; }
/* 登录按钮:靛蓝→青 霓虹 · 扫光动效 */
.btn-login { .btn-login {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
height: 46px !important; height: 46px !important;
border-radius: 10px; border-radius: 12px;
margin-top: 6px; margin-top: 6px;
font-size: 15px; font-size: 15px;
letter-spacing: 6px; letter-spacing: 6px;
font-weight: 600; font-weight: 600;
color: #fff !important; color: #fff !important;
background: linear-gradient(135deg, var(--lg-primary) 0%, var(--lg-primary-hover) 100%) !important; background: #7543e8 !important;
border: none !important; border: none !important;
box-shadow: box-shadow: 0 10px 24px rgba(117, 67, 232, 0.28) !important;
0 12px 28px rgba(var(--lg-primary-rgb), 0.5), transition: all .2s ease;
0 0 26px rgba(var(--lg-primary-hover-rgb), 0.36),
0 0 0 1px rgba(255, 255, 255, 0.20) inset !important;
transition: all .28s cubic-bezier(.25, .8, .25, 1);
}
.btn-login::after {
content: '';
position: absolute;
top: 0; left: -120%;
width: 80%; height: 100%;
background: linear-gradient(110deg,
transparent 0%,
rgba(255, 255, 255, 0.0) 30%,
rgba(255, 255, 255, 0.38) 50%,
rgba(255, 255, 255, 0.0) 70%,
transparent 100%);
transition: left .6s cubic-bezier(.25, .8, .25, 1);
pointer-events: none;
} }
.btn-login::after { display: none; }
.btn-login:hover { .btn-login:hover {
transform: translateY(-2px); transform: translateY(-1px);
background: linear-gradient(135deg, var(--lg-primary-hover) 0%, var(--lg-accent) 130%) !important; background: #8660f0 !important;
box-shadow: box-shadow: 0 14px 28px rgba(117, 67, 232, 0.35) !important;
0 18px 38px rgba(var(--lg-primary-rgb), 0.6),
0 0 42px rgba(var(--lg-accent-rgb), 0.5),
0 0 0 1px rgba(var(--lg-accent-rgb), 0.5) inset !important;
} }
.btn-login:hover::after { left: 120%; }
.bottom-note { .bottom-note {
margin-top: 16px; margin-top: 16px;
font-size: 11.5px; font-size: 11.5px;
color: var(--lg-text-dim); color: #756d85;
text-align: center; text-align: center;
line-height: 1.6; line-height: 1.6;
} }
/* 底部水印 */
.footer-stamp { .footer-stamp {
position: absolute; position: absolute;
bottom: 18px; left: 50%; bottom: 18px; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
font-size: 11px; font-size: 11px;
letter-spacing: 2px; letter-spacing: 2px;
color: rgba(45, 32, 78, 0.6); color: rgba(40, 33, 58, 0.45);
z-index: 2; z-index: 2;
} }
/* 错误提示 */
.glass-form :deep(.el-form-item__error) { .glass-form :deep(.el-form-item__error) {
color: #ff9bb8; color: #c23a5c;
padding-top: 4px; padding-top: 4px;
} }
/* 小屏自适应 */
@media (max-width: 760px) { @media (max-width: 760px) {
.login-card { grid-template-columns: 1fr; min-height: auto; } .login-card { grid-template-columns: 1fr; min-height: auto; }
.hero { border-right: none; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding: 28px; } .hero { border-right: none; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding: 28px; }
File diff suppressed because it is too large Load Diff
@@ -1740,8 +1740,14 @@ onUnmounted(() => {
.map-editor-page { .map-editor-page {
height: calc(100vh - 56px); height: calc(100vh - 56px);
display: flex; flex-direction: column; display: flex; flex-direction: column;
background: radial-gradient(ellipse at 50% 40%, #2d1456 0%, #1a0930 50%, #0f0420 100%); /* 画布区保持深色;色相跟当前主题侧栏 (--me-chrome-*) */
color: rgba(232,215,245,0.9); background: radial-gradient(
ellipse at 50% 40%,
var(--me-chrome-1) 0%,
var(--me-chrome-2) 52%,
#080610 100%
);
color: var(--me-text);
} }
.me-body { .me-body {
flex: 1; flex: 1;
@@ -1767,13 +1773,13 @@ onUnmounted(() => {
border-radius: 12px 0 0 12px; border-radius: 12px 0 0 12px;
cursor: pointer; cursor: pointer;
color: #fff; color: #fff;
background: linear-gradient(135deg, rgba(150, 90, 240, 0.96) 0%, rgba(255, 90, 200, 0.92) 100%); background: var(--mg-primary);
box-shadow: -4px 0 16px rgba(120, 40, 200, 0.45); box-shadow: -4px 0 16px var(--me-active-glow);
transition: right 0.26s cubic-bezier(0.25, 0.8, 0.25, 1), filter 0.15s ease, box-shadow 0.15s ease; transition: right 0.26s cubic-bezier(0.25, 0.8, 0.25, 1), filter 0.15s ease, box-shadow 0.15s ease;
} }
.ai-assistant-fab:hover { .ai-assistant-fab:hover {
filter: brightness(1.08); filter: brightness(1.08);
box-shadow: -6px 0 22px rgba(150, 60, 230, 0.6); box-shadow: -6px 0 22px var(--me-active-glow);
} }
/* 展开时 right 偏移由内联样式按面板实际宽度设置(默认 360);此处仅作兜底。 */ /* 展开时 right 偏移由内联样式按面板实际宽度设置(默认 360);此处仅作兜底。 */
.ai-assistant-fab.is-open { .ai-assistant-fab.is-open {
File diff suppressed because it is too large Load Diff