Skip to content

Commit 2b33266

Browse files
committed
feat: 新增脑图主题切换功能及多主题预设
新增多款脑图主题预设(清晨、极夜、翠林、珊瑚、天空),支持根据暗黑模式自动切换对应主题。 在脑图工具栏添加主题切换下拉菜单,允许用户选择脑图主题。 移除 Jenkinsfile 中容器清理宿主机路径的冗余代码。 调整脑图节点标签样式,优化标签与文字的排列和显示。
1 parent bc5c89d commit 2b33266

5 files changed

Lines changed: 385 additions & 19 deletions

File tree

Jenkinsfile

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,6 @@ pipeline {
113113
"REPO_PRELOADED=${repoPreloaded}",
114114
].join('\n') + '\n'
115115

116-
// 清理容器内 /workspace/repo 的宿主机映射路径(即 reportDir/repo)
117-
// 避免 entrypoint.sh 在容器内 git clone /workspace/repo 时报 "already exists"
118-
// 若目录被历史 root 容器污染,宿主机 rm 可能失败;此时回退到临时容器做 root 清理。
119-
sh """
120-
rm -rf "${reportDir}/repo" || true
121-
if [ -d "${reportDir}/repo" ]; then
122-
echo "⚠️ 宿主机清理 ${reportDir}/repo 失败,尝试容器内 root 清理"
123-
docker run --rm -v "${reportDir}:/workspace" alpine:3.20 rm -rf /workspace/repo
124-
fi
125-
"""
126-
127116
echo "[${new Date().format('HH:mm:ss')}] docker-run start"
128117
def testExitCode = 1
129118
def repoMount = repoUrl ? "-v ${repoDir}:/repo" : ''

src/index.css

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -290,28 +290,50 @@
290290
/* ============================================
291291
* Mind-Elixir 节点标签内联样式
292292
*
293-
* 将 me-tpc 改为 inline-flex 布局,使 .tags 与节点文字
294-
* 水平并排显示在同一行内,节点高度 = 文字行高,
295-
* 不外溢、不遮线,视觉最自然。
293+
* 将 .tags 改为 display:inline-flex,使其与 span.text(inline-block)
294+
* 在同一文字行内水平排列,节点高度 = 文字行高,不外溢、不遮线。
296295
*
297296
* 关键约束:
298-
* - 不修改 me-parent 的 padding/margin,避免影响 mind-elixir SVG 连线坐标计算
299-
* - .tags 恢复正常文档流(不绝对定位),跟在节点文字后面
297+
* - 不修改 me-tpc 的 display(保持 block),避免改变 offsetWidth,
298+
* 从而不影响 mind-elixir generateMainBranch / generateSubBranch 的连线坐标计算
299+
* - 不修改 me-parent 的 padding/margin,避免影响 SVG 连线整体坐标
300300
* ============================================ */
301301
.map-container me-parent me-tpc {
302302
overflow: visible;
303-
display: inline-flex;
304-
align-items: center;
305-
gap: 6px;
306303
}
307304

308305
.map-container me-parent me-tpc .tags {
309306
display: inline-flex;
307+
vertical-align: middle;
310308
flex-wrap: nowrap;
311309
align-items: center;
312310
gap: 4px;
311+
margin-left: 6px;
313312
padding: 0;
314313
pointer-events: none;
315314
white-space: nowrap;
316315
flex-shrink: 0;
317316
}
317+
318+
/* LHS(左侧)节点:
319+
* mind-elixir 原生已设置 .lhs me-tpc { direction: ltr },因此 me-tpc 内部
320+
* span.text 和 .tags 仍按 LTR 顺序排列(文字在前、Tag 在后)。
321+
* 为使 Tag 靠近主节点(即出现在文字左侧),我们借助 me-tpc 本身的 direction:
322+
* 暂时移除原生的 "direction:ltr" 覆盖,让 .lhs 的 direction:rtl 作用到 me-tpc,
323+
* 同时给 .tags 显式设 direction:ltr 保持 Tag 文字可读。
324+
*
325+
* 不改 me-tpc 的 display 属性,offsetWidth 行为不变,连线坐标不受影响。
326+
*/
327+
.map-container .lhs me-parent me-tpc {
328+
direction: rtl; /* 覆盖原生的 direction:ltr,使 inline 子元素从右向左排 */
329+
}
330+
331+
.map-container .lhs me-parent me-tpc .text {
332+
direction: ltr; /* 节点文字保持 LTR 可读 */
333+
}
334+
335+
.map-container .lhs me-parent me-tpc .tags {
336+
direction: ltr; /* Tag 文字保持 LTR 可读 */
337+
margin-left: 0;
338+
margin-right: 6px;
339+
}

src/lib/aiCaseMindMap.ts

Lines changed: 279 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type { Theme } from 'mind-elixir';
2+
13
import type {
24
AiCaseMindData,
35
AiCaseNode,
@@ -897,3 +899,280 @@ export function computeProgress(data: AiCaseMindData): AiCaseProgress {
897899

898900
return stats;
899901
}
902+
903+
// ============================================================
904+
// 脑图主题预设
905+
// ============================================================
906+
907+
export type AiCaseMindThemeId = 'latte' | 'night' | 'forest' | 'coral' | 'sky';
908+
909+
export interface AiCaseMindThemePreset {
910+
id: AiCaseMindThemeId;
911+
label: string;
912+
/** light 模式下使用的 mind-elixir Theme 对象 */
913+
light: Theme;
914+
/** dark 模式下使用的 mind-elixir Theme 对象 */
915+
dark: Theme;
916+
}
917+
918+
/** 公共 cssVar 基准(不含颜色,各主题复用) */
919+
const BASE_CSS_VAR = {
920+
'--node-gap-x': '30px',
921+
'--node-gap-y': '10px',
922+
'--main-gap-x': '65px',
923+
'--main-gap-y': '45px',
924+
'--root-radius': '30px',
925+
'--main-radius': '20px',
926+
'--topic-padding': '3px',
927+
'--map-padding': '50px 80px',
928+
} as const;
929+
930+
export const AI_CASE_MIND_THEMES: AiCaseMindThemePreset[] = [
931+
{
932+
id: 'latte',
933+
label: '清晨',
934+
light: {
935+
name: 'Latte',
936+
type: 'light',
937+
palette: ['#dd7878', '#ea76cb', '#8839ef', '#e64553', '#fe640b', '#df8e1d', '#40a02b', '#209fb5', '#1e66f5', '#7287fd'],
938+
cssVar: {
939+
...BASE_CSS_VAR,
940+
'--root-color': '#ffffff',
941+
'--root-bgcolor': '#4c4f69',
942+
'--root-border-color': 'rgba(0,0,0,0)',
943+
'--main-color': '#444446',
944+
'--main-bgcolor': '#ffffff',
945+
'--main-bgcolor-transparent': 'rgba(255,255,255,0.8)',
946+
'--color': '#777777',
947+
'--bgcolor': '#f6f6f6',
948+
'--selected': '#4dc4ff',
949+
'--accent-color': '#e64553',
950+
'--panel-color': '#444446',
951+
'--panel-bgcolor': '#ffffff',
952+
'--panel-border-color': '#eaeaea',
953+
},
954+
},
955+
dark: {
956+
name: 'Latte Dark',
957+
type: 'dark',
958+
palette: ['#dd7878', '#ea76cb', '#8839ef', '#e64553', '#fe640b', '#df8e1d', '#40a02b', '#209fb5', '#1e66f5', '#7287fd'],
959+
cssVar: {
960+
...BASE_CSS_VAR,
961+
'--root-color': '#ffffff',
962+
'--root-bgcolor': '#4c4f69',
963+
'--root-border-color': 'rgba(255,255,255,0.1)',
964+
'--main-color': '#e2e8f0',
965+
'--main-bgcolor': '#1e2235',
966+
'--main-bgcolor-transparent': 'rgba(30,34,53,0.8)',
967+
'--color': '#94a3b8',
968+
'--bgcolor': '#0f1117',
969+
'--selected': '#4dc4ff',
970+
'--accent-color': '#e64553',
971+
'--panel-color': '#e2e8f0',
972+
'--panel-bgcolor': '#1e2235',
973+
'--panel-border-color': '#2d3748',
974+
},
975+
},
976+
},
977+
{
978+
id: 'night',
979+
label: '极夜',
980+
light: {
981+
name: 'Night Light',
982+
type: 'light',
983+
palette: ['#848FA0', '#748BE9', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#DA7FBC', '#5B8AF0', '#9B72CF'],
984+
cssVar: {
985+
...BASE_CSS_VAR,
986+
'--root-color': '#ffffff',
987+
'--root-bgcolor': '#2d3748',
988+
'--root-border-color': 'rgba(0,0,0,0)',
989+
'--main-color': '#2d3748',
990+
'--main-bgcolor': '#ffffff',
991+
'--main-bgcolor-transparent': 'rgba(255,255,255,0.8)',
992+
'--color': '#4a5568',
993+
'--bgcolor': '#edf2f7',
994+
'--selected': '#667eea',
995+
'--accent-color': '#789AFA',
996+
'--panel-color': '#2d3748',
997+
'--panel-bgcolor': '#ffffff',
998+
'--panel-border-color': '#e2e8f0',
999+
},
1000+
},
1001+
dark: {
1002+
name: 'Night',
1003+
type: 'dark',
1004+
palette: ['#848FA0', '#748BE9', '#D2F9FE', '#4145A5', '#789AFA', '#706CF4', '#EF987F', '#775DD5', '#FCEECF', '#DA7FBC'],
1005+
cssVar: {
1006+
...BASE_CSS_VAR,
1007+
'--root-color': '#ffffff',
1008+
'--root-bgcolor': '#2d3748',
1009+
'--root-border-color': 'rgba(255,255,255,0.1)',
1010+
'--main-color': '#ffffff',
1011+
'--main-bgcolor': '#4c4f69',
1012+
'--main-bgcolor-transparent': 'rgba(76,79,105,0.8)',
1013+
'--color': '#cccccc',
1014+
'--bgcolor': '#252526',
1015+
'--selected': '#4dc4ff',
1016+
'--accent-color': '#789AFA',
1017+
'--panel-color': '#ffffff',
1018+
'--panel-bgcolor': '#2d3748',
1019+
'--panel-border-color': '#696969',
1020+
},
1021+
},
1022+
},
1023+
{
1024+
id: 'forest',
1025+
label: '翠林',
1026+
light: {
1027+
name: 'Forest Light',
1028+
type: 'light',
1029+
palette: ['#40a02b', '#179299', '#1e66f5', '#8839ef', '#df8e1d', '#fe640b', '#e64553', '#ea76cb', '#04a5e5', '#7287fd'],
1030+
cssVar: {
1031+
...BASE_CSS_VAR,
1032+
'--root-color': '#ffffff',
1033+
'--root-bgcolor': '#166534',
1034+
'--root-border-color': 'rgba(0,0,0,0)',
1035+
'--main-color': '#14532d',
1036+
'--main-bgcolor': '#f0fdf4',
1037+
'--main-bgcolor-transparent': 'rgba(240,253,244,0.8)',
1038+
'--color': '#374151',
1039+
'--bgcolor': '#f8fafc',
1040+
'--selected': '#22c55e',
1041+
'--accent-color': '#16a34a',
1042+
'--panel-color': '#14532d',
1043+
'--panel-bgcolor': '#ffffff',
1044+
'--panel-border-color': '#bbf7d0',
1045+
},
1046+
},
1047+
dark: {
1048+
name: 'Forest Dark',
1049+
type: 'dark',
1050+
palette: ['#4ade80', '#34d399', '#22d3ee', '#818cf8', '#fb923c', '#f472b6', '#a78bfa', '#60a5fa', '#fbbf24', '#86efac'],
1051+
cssVar: {
1052+
...BASE_CSS_VAR,
1053+
'--root-color': '#f0fdf4',
1054+
'--root-bgcolor': '#14532d',
1055+
'--root-border-color': 'rgba(74,222,128,0.2)',
1056+
'--main-color': '#86efac',
1057+
'--main-bgcolor': '#0f2d1a',
1058+
'--main-bgcolor-transparent': 'rgba(15,45,26,0.8)',
1059+
'--color': '#6ee7b7',
1060+
'--bgcolor': '#071a0f',
1061+
'--selected': '#4ade80',
1062+
'--accent-color': '#22c55e',
1063+
'--panel-color': '#86efac',
1064+
'--panel-bgcolor': '#0f2d1a',
1065+
'--panel-border-color': '#166534',
1066+
},
1067+
},
1068+
},
1069+
{
1070+
id: 'coral',
1071+
label: '珊瑚',
1072+
light: {
1073+
name: 'Coral Light',
1074+
type: 'light',
1075+
palette: ['#f43f5e', '#fb923c', '#fbbf24', '#a3e635', '#34d399', '#38bdf8', '#818cf8', '#e879f9', '#fb7185', '#fdba74'],
1076+
cssVar: {
1077+
...BASE_CSS_VAR,
1078+
'--root-color': '#ffffff',
1079+
'--root-bgcolor': '#be123c',
1080+
'--root-border-color': 'rgba(0,0,0,0)',
1081+
'--main-color': '#9f1239',
1082+
'--main-bgcolor': '#fff1f2',
1083+
'--main-bgcolor-transparent': 'rgba(255,241,242,0.8)',
1084+
'--color': '#374151',
1085+
'--bgcolor': '#fafafa',
1086+
'--selected': '#f43f5e',
1087+
'--accent-color': '#fb923c',
1088+
'--panel-color': '#9f1239',
1089+
'--panel-bgcolor': '#ffffff',
1090+
'--panel-border-color': '#fecdd3',
1091+
},
1092+
},
1093+
dark: {
1094+
name: 'Coral Dark',
1095+
type: 'dark',
1096+
palette: ['#fb7185', '#fb923c', '#fbbf24', '#86efac', '#67e8f9', '#a5b4fc', '#f0abfc', '#fda4af', '#fed7aa', '#d9f99d'],
1097+
cssVar: {
1098+
...BASE_CSS_VAR,
1099+
'--root-color': '#fff1f2',
1100+
'--root-bgcolor': '#9f1239',
1101+
'--root-border-color': 'rgba(251,113,133,0.2)',
1102+
'--main-color': '#fda4af',
1103+
'--main-bgcolor': '#2d0a12',
1104+
'--main-bgcolor-transparent': 'rgba(45,10,18,0.8)',
1105+
'--color': '#fca5a5',
1106+
'--bgcolor': '#1a0508',
1107+
'--selected': '#fb7185',
1108+
'--accent-color': '#f43f5e',
1109+
'--panel-color': '#fda4af',
1110+
'--panel-bgcolor': '#2d0a12',
1111+
'--panel-border-color': '#7f1d1d',
1112+
},
1113+
},
1114+
},
1115+
{
1116+
id: 'sky',
1117+
label: '天空',
1118+
light: {
1119+
name: 'Sky Light',
1120+
type: 'light',
1121+
palette: ['#0ea5e9', '#38bdf8', '#7dd3fc', '#1d4ed8', '#6366f1', '#8b5cf6', '#06b6d4', '#0891b2', '#2563eb', '#4f46e5'],
1122+
cssVar: {
1123+
...BASE_CSS_VAR,
1124+
'--root-color': '#ffffff',
1125+
'--root-bgcolor': '#0369a1',
1126+
'--root-border-color': 'rgba(0,0,0,0)',
1127+
'--main-color': '#0c4a6e',
1128+
'--main-bgcolor': '#f0f9ff',
1129+
'--main-bgcolor-transparent': 'rgba(240,249,255,0.8)',
1130+
'--color': '#334155',
1131+
'--bgcolor': '#f8fafc',
1132+
'--selected': '#0ea5e9',
1133+
'--accent-color': '#0284c7',
1134+
'--panel-color': '#0c4a6e',
1135+
'--panel-bgcolor': '#ffffff',
1136+
'--panel-border-color': '#bae6fd',
1137+
},
1138+
},
1139+
dark: {
1140+
name: 'Sky Dark',
1141+
type: 'dark',
1142+
palette: ['#38bdf8', '#7dd3fc', '#93c5fd', '#818cf8', '#a5b4fc', '#67e8f9', '#5eead4', '#6ee7b7', '#fde68a', '#c4b5fd'],
1143+
cssVar: {
1144+
...BASE_CSS_VAR,
1145+
'--root-color': '#f0f9ff',
1146+
'--root-bgcolor': '#0c4a6e',
1147+
'--root-border-color': 'rgba(56,189,248,0.2)',
1148+
'--main-color': '#7dd3fc',
1149+
'--main-bgcolor': '#082032',
1150+
'--main-bgcolor-transparent': 'rgba(8,32,50,0.8)',
1151+
'--color': '#93c5fd',
1152+
'--bgcolor': '#040d18',
1153+
'--selected': '#38bdf8',
1154+
'--accent-color': '#0ea5e9',
1155+
'--panel-color': '#7dd3fc',
1156+
'--panel-bgcolor': '#082032',
1157+
'--panel-border-color': '#0c4a6e',
1158+
},
1159+
},
1160+
},
1161+
];
1162+
1163+
export const DEFAULT_MIND_THEME_ID: AiCaseMindThemeId = 'latte';
1164+
export const MIND_THEME_STORAGE_KEY = 'ai-case-mind-theme';
1165+
1166+
/** 根据 themeId 和当前 dark/light 模式返回对应的 mind-elixir Theme 对象 */
1167+
export function resolveMindTheme(themeId: AiCaseMindThemeId, isDark: boolean): Theme {
1168+
const preset = AI_CASE_MIND_THEMES.find((t) => t.id === themeId) ?? AI_CASE_MIND_THEMES[0];
1169+
return isDark ? preset.dark : preset.light;
1170+
}
1171+
1172+
/** 从 localStorage 读取脑图主题偏好,回退到默认值 */
1173+
export function readMindThemePreference(): AiCaseMindThemeId {
1174+
if (typeof window === 'undefined') return DEFAULT_MIND_THEME_ID;
1175+
const stored = window.localStorage.getItem(MIND_THEME_STORAGE_KEY);
1176+
const valid = AI_CASE_MIND_THEMES.map((t) => t.id);
1177+
return (valid.includes(stored as AiCaseMindThemeId) ? stored : DEFAULT_MIND_THEME_ID) as AiCaseMindThemeId;
1178+
}

0 commit comments

Comments
 (0)