Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions ui/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,76 @@
<link rel="icon" type="image/x-icon" href="/favicon.ico?v=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Kanban</title>
<script>
(() => {
const LIGHT_BODY = '#f7f8fa';
const DARK_BODY = '#1e1e1e';
let bodyColor = LIGHT_BODY;
let textColor = '#333333';
let surfaceColor = '#ffffff';
let dark = false;
let followsSystem = false;
let activeTheme = null;

try {
const settings = JSON.parse(localStorage.getItem('general_settings') || 'null');
followsSystem = settings?.followSystemTheme === 1;
activeTheme = settings?.customTheme || settings?.theme;
if (!settings?.customTheme && settings?.currentPresetId === 'dark') {
activeTheme = {
bodyColor: DARK_BODY,
surfaceColor: '#252526',
textColor: '#d4d4d4',
};
} else if (!settings?.customTheme && settings?.currentPresetId === 'light') {
activeTheme = {
bodyColor: LIGHT_BODY,
surfaceColor: '#ffffff',
textColor: '#333333',
};
}
bodyColor = followsSystem
? window.matchMedia('(prefers-color-scheme: dark)').matches
? DARK_BODY
: LIGHT_BODY
: activeTheme?.bodyColor || LIGHT_BODY;
const hex = bodyColor.replace('#', '');
if (/^[0-9a-f]{6}$/i.test(hex)) {
const red = parseInt(hex.slice(0, 2), 16);
const green = parseInt(hex.slice(2, 4), 16);
const blue = parseInt(hex.slice(4, 6), 16);
dark = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 < 0.5;
}
textColor = followsSystem
? dark
? '#d4d4d4'
: '#333333'
: activeTheme?.textColor || (dark ? '#d4d4d4' : '#333333');
surfaceColor = followsSystem
? dark
? '#252526'
: '#ffffff'
: activeTheme?.surfaceColor || (dark ? '#252526' : '#ffffff');
} catch {
bodyColor = LIGHT_BODY;
textColor = '#333333';
surfaceColor = '#ffffff';
dark = false;
}

const scheme = dark ? 'dark' : 'light';
document.documentElement.dataset.colorScheme = scheme;
document.documentElement.style.colorScheme = scheme;
document.documentElement.style.backgroundColor = bodyColor;
document.documentElement.style.setProperty('--app-body-color', bodyColor);
document.documentElement.style.setProperty('--app-canvas', bodyColor);
document.documentElement.style.setProperty('--color-background', bodyColor);
document.documentElement.style.setProperty('--app-surface-color', surfaceColor);
document.documentElement.style.setProperty('--app-surface', surfaceColor);
document.documentElement.style.setProperty('--app-text-color', textColor);
document.documentElement.style.setProperty('--app-text-primary', textColor);
})();
</script>
</head>

<body>
Expand Down
75 changes: 68 additions & 7 deletions ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { useSettingsStore } from '@/stores/settings';
import { useProjectStore } from '@/stores/project';
import { useResponsive } from '@/composables/useResponsive';
import { useAiStatusSummary } from '@/composables/useAiStatusSummary';
import { darkenColor, lightenColor, isDarkHex } from '@/utils/color';
import { isDarkHex } from '@/utils/color';
import { formatBrowserTabTitle } from '@/utils/browserTitle';
import { createThemeOverrides } from '@/utils/themeOverrides';
import { createThemeSemanticPalette } from '@/utils/themeSemanticPalette';
import { getPresetById } from '@/constants/themes';
import { APP_NAME } from '@/constants/app';

Expand Down Expand Up @@ -89,15 +90,14 @@ const resolvedTextColor = computed(() => {
if (textColor && textColor.trim().length > 0) {
return textColor;
}
return isDarkTheme.value ? '#FFFFFFD9' : '#000000E0';
return isDarkTheme.value ? '#D9D9D9' : '#333333';
});

const inputBorderColor = computed(() => (isDarkTheme.value ? '#4B4B4B' : '#D0D5DD'));
const inputBorderHoverColor = computed(() =>
isDarkTheme.value
? lightenColor(inputBorderColor.value, 0.12)
: darkenColor(inputBorderColor.value, 0.12)
const semanticPalette = computed(() =>
createThemeSemanticPalette(theme.value, resolvedTextColor.value)
);
const inputBorderColor = computed(() => semanticPalette.value.controlBorder);
const inputBorderHoverColor = computed(() => semanticPalette.value.controlBorderHover);

// 根据当前语言动态切换 Naive UI 的 locale
const naiveLocale = computed(() => (locale.value === 'zh-CN' ? zhCN : enUS));
Expand Down Expand Up @@ -169,13 +169,74 @@ const cssVarsToSet = computed(() => ({
'--app-text-color': resolvedTextColor.value,
'--app-input-border-color': inputBorderColor.value,
'--app-input-border-hover-color': inputBorderHoverColor.value,
'--app-canvas': semanticPalette.value.canvas,
'--app-surface': semanticPalette.value.surface,
'--app-surface-raised': semanticPalette.value.surfaceRaised,
'--app-surface-sunken': semanticPalette.value.surfaceSunken,
'--app-surface-hover': semanticPalette.value.surfaceHover,
'--app-surface-active': semanticPalette.value.surfaceActive,
'--app-text-primary': semanticPalette.value.textPrimary,
'--app-text-secondary': semanticPalette.value.textSecondary,
'--app-text-muted': semanticPalette.value.textMuted,
'--app-text-inverse': semanticPalette.value.textInverse,
'--app-border': semanticPalette.value.border,
'--app-border-strong': semanticPalette.value.borderStrong,
'--app-control-border': semanticPalette.value.controlBorder,
'--app-control-border-hover': semanticPalette.value.controlBorderHover,
'--app-focus-ring': semanticPalette.value.focusRing,
'--app-accent': semanticPalette.value.accent,
'--app-accent-hover': semanticPalette.value.accentHover,
'--app-accent-pressed': semanticPalette.value.accentPressed,
'--app-accent-soft': semanticPalette.value.accentSoft,
'--app-accent-contrast': semanticPalette.value.accentContrast,
'--app-link': semanticPalette.value.link,
'--app-link-hover': semanticPalette.value.linkHover,
'--app-success': semanticPalette.value.success,
'--app-success-soft': semanticPalette.value.successSoft,
'--app-warning': semanticPalette.value.warning,
'--app-warning-soft': semanticPalette.value.warningSoft,
'--app-error': semanticPalette.value.error,
'--app-error-soft': semanticPalette.value.errorSoft,
'--app-info': semanticPalette.value.info,
'--app-info-soft': semanticPalette.value.infoSoft,
'--app-overlay': semanticPalette.value.overlay,
'--app-shadow': semanticPalette.value.shadow,
'--n-primary-color': semanticPalette.value.accent,
'--n-color-primary': semanticPalette.value.accent,
'--n-primary-color-hover': semanticPalette.value.accentHover,
'--n-primary-color-pressed': semanticPalette.value.accentPressed,
'--n-primary-color-suppl': semanticPalette.value.accentHover,
'--n-text-color-base': semanticPalette.value.textPrimary,
'--n-text-color': semanticPalette.value.textPrimary,
'--n-text-color-1': semanticPalette.value.textPrimary,
'--n-text-color-2': semanticPalette.value.textSecondary,
'--n-text-color-3': semanticPalette.value.textMuted,
'--n-text-color-disabled': semanticPalette.value.textMuted,
'--n-body-color': semanticPalette.value.canvas,
'--n-color': semanticPalette.value.surface,
'--n-color-hover': semanticPalette.value.surfaceHover,
'--n-color-target': semanticPalette.value.accentSoft,
'--n-color-embedded': semanticPalette.value.surfaceSunken,
'--n-card-color': semanticPalette.value.surface,
'--n-modal-color': semanticPalette.value.surfaceRaised,
'--n-popover-color': semanticPalette.value.surfaceRaised,
'--n-border-color': semanticPalette.value.border,
'--n-divider-color': semanticPalette.value.border,
'--n-box-shadow-color': semanticPalette.value.shadow,
'--n-error-color': semanticPalette.value.error,
'--n-warning-color': semanticPalette.value.warning,
'--n-success-color': semanticPalette.value.success,
'--n-info-color': semanticPalette.value.info,
}));

watch(
cssVarsToSet,
vars => {
if (typeof document !== 'undefined') {
const root = document.documentElement;
root.dataset.colorScheme = semanticPalette.value.colorScheme;
root.style.colorScheme = semanticPalette.value.colorScheme;
root.style.backgroundColor = semanticPalette.value.canvas;
Object.entries(vars).forEach(([key, value]) => {
root.style.setProperty(key, value ?? '');
});
Expand Down
18 changes: 2 additions & 16 deletions ui/src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,6 @@
--section-gap: 160px;
}

@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--vt-c-black);
--color-background-soft: var(--vt-c-black-soft);
--color-background-mute: var(--vt-c-black-mute);

--color-border: var(--vt-c-divider-dark-2);
--color-border-hover: var(--vt-c-divider-dark-1);

--color-heading: var(--vt-c-text-dark-1);
--color-text: var(--vt-c-text-dark-2);
}
}

*,
*::before,
*::after {
Expand All @@ -60,8 +46,8 @@

body {
min-height: 100vh;
color: var(--color-text);
background: var(--color-background);
color: var(--app-text-primary, var(--color-text));
background: var(--app-canvas, var(--color-background));
transition:
color 0.5s,
background-color 0.5s;
Expand Down
9 changes: 6 additions & 3 deletions ui/src/assets/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
min-height: 100vh;
margin: 0;
padding: 0;
background-color: var(--app-canvas, var(--app-body-color, #f7f8fa));
color: var(--app-text-primary, var(--app-text-color, #333333));
}

main {
Expand Down Expand Up @@ -125,18 +127,19 @@ main {
left: 0;
right: 0;
bottom: 0;
background-color: var(--app-surface-color, #ffffff);
background-color: var(--app-surface-raised, var(--app-surface-color, #ffffff));
color: var(--app-text-primary, var(--app-text-color, #333333));
z-index: 100;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.15);
box-shadow: 0 -4px 20px var(--app-shadow, rgba(0, 0, 0, 0.15));
}

/* 拖动条 (用于移动端面板) */
.drag-handle {
width: 40px;
height: 4px;
background-color: var(--n-border-color, #e0e0e0);
background-color: var(--app-border-strong, var(--n-border-color, #e0e0e0));
border-radius: 2px;
margin: 8px auto;
}
43 changes: 24 additions & 19 deletions ui/src/components/changes/GitChangesPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -861,9 +861,8 @@ onBeforeUnmount(() => {
min-height: 0;
min-width: 0;
overflow: hidden;
background:
linear-gradient(180deg, rgba(246, 248, 252, 0.98), rgba(238, 242, 247, 0.96)),
radial-gradient(circle at top right, rgba(53, 119, 186, 0.12), transparent 32%);
background: var(--app-canvas);
color: var(--app-text-primary);
}

.git-changes-toolbar {
Expand All @@ -872,7 +871,8 @@ onBeforeUnmount(() => {
gap: 12px;
flex-wrap: wrap;
padding: 12px 16px;
border-bottom: 1px solid rgba(24, 35, 51, 0.08);
border-bottom: 1px solid var(--app-border);
background: var(--app-surface);
}

.git-changes-scope {
Expand Down Expand Up @@ -925,8 +925,8 @@ onBeforeUnmount(() => {
flex: 0 0 360px;
min-width: 280px;
max-width: 440px;
border-right: 1px solid rgba(24, 35, 51, 0.08);
background: rgba(255, 255, 255, 0.72);
border-right: 1px solid var(--app-border);
background: var(--app-surface-sunken);
display: flex;
flex-direction: column;
min-height: 0;
Expand Down Expand Up @@ -959,7 +959,7 @@ onBeforeUnmount(() => {
overflow: auto;
padding: 12px;
scrollbar-width: thin;
scrollbar-color: rgba(37, 90, 143, 0.38) transparent;
scrollbar-color: var(--app-border-strong) transparent;
}

.git-changes-list::-webkit-scrollbar {
Expand All @@ -969,7 +969,7 @@ onBeforeUnmount(() => {

.git-changes-list::-webkit-scrollbar-thumb {
border-radius: 999px;
background: rgba(37, 90, 143, 0.28);
background: var(--app-border-strong);
}

.git-changes-empty,
Expand All @@ -988,9 +988,10 @@ onBeforeUnmount(() => {
display: flex;
width: 100%;
padding: 12px 14px;
border: 1px solid rgba(24, 35, 51, 0.08);
border: 1px solid var(--app-border);
border-radius: 12px;
background: rgba(255, 255, 255, 0.92);
background: var(--app-surface-raised);
color: var(--app-text-primary);
text-align: left;
cursor: pointer;
transition:
Expand All @@ -1001,9 +1002,13 @@ onBeforeUnmount(() => {

.git-change-row:hover,
.git-change-row.is-active {
border-color: rgba(53, 119, 186, 0.32);
background: rgba(240, 247, 255, 0.9);
box-shadow: 0 10px 24px rgba(40, 73, 118, 0.08);
border-color: var(--app-accent);
background: var(--app-surface-hover);
box-shadow: 0 10px 24px color-mix(in srgb, var(--app-accent) 8%, transparent);
}

.git-change-row.is-active {
background: var(--app-surface-active);
}

.git-change-main {
Expand All @@ -1022,7 +1027,7 @@ onBeforeUnmount(() => {
}

.git-change-icon {
color: #255a8f;
color: var(--app-info);
flex: 0 0 auto;
}

Expand All @@ -1042,20 +1047,20 @@ onBeforeUnmount(() => {
}

.git-change-stat-tag {
color: rgba(34, 46, 67, 0.78);
color: var(--app-text-secondary);
}

.git-change-path,
.git-change-previous {
color: rgba(34, 46, 67, 0.7);
color: var(--app-text-muted);
font-size: 12px;
line-height: 1.45;
word-break: break-all;
}

.git-changes-preview {
flex: 1;
background: rgba(255, 255, 255, 0.82);
background: var(--app-surface);
}

.git-changes-mobile-preview-modal {
Expand All @@ -1067,7 +1072,7 @@ onBeforeUnmount(() => {
.git-changes-mobile-preview-surface {
width: 100vw;
min-height: min(100vh, 100dvh);
background: rgba(255, 255, 255, 0.98);
background: var(--app-surface);
border-radius: 0;
overflow: hidden;
}
Expand All @@ -1082,7 +1087,7 @@ onBeforeUnmount(() => {
width: 100%;
max-width: none;
border-right: none;
border-bottom: 1px solid rgba(24, 35, 51, 0.08);
border-bottom: 1px solid var(--app-border);
}

.git-changes-panel:not(.is-mobile-preview) .git-changes-list {
Expand Down
Loading
Loading