feat(theme): 实现完整的深色主题支持

- 在多个组件中使用 CSS 变量替代硬编码颜色值
- 更新 useTheme 组合式函数以支持完整的深色主题变量
- 修改编辑器组件以根据主题动态切换样式
- 统一应用中的颜色使用方式,确保主题切换的一致性
This commit is contained in:
cfq 2026-01-26 15:17:41 +08:00
parent 3a9a325f80
commit 7096b7f6c1
13 changed files with 130 additions and 74 deletions

View File

@ -275,7 +275,7 @@ const { state } = useFileTree(); // 需要获取 rootPath 用于保存
}
.color-block.active {
border-color: #000;
box-shadow: 0 0 4px rgba(0,0,0,0.2);
border-color: var(--text-color);
box-shadow: 0 0 0 2px var(--primary-color-bg);
}
</style>

View File

@ -13,11 +13,12 @@
</template>
<script setup>
import { ref, watch } from 'vue'
import { computed, ref, watch } from 'vue'
import { Codemirror } from 'vue-codemirror'
import { markdown } from '@codemirror/lang-markdown'
import { oneDark } from '@codemirror/theme-one-dark'
import { EditorView } from "@codemirror/view"
import { useTheme } from '../composables/useTheme'
const props = defineProps({
modelValue: String,
@ -27,7 +28,34 @@ const props = defineProps({
const emit = defineEmits(['update:modelValue'])
const code = ref(props.modelValue || '')
const extensions = [markdown(), oneDark, EditorView.lineWrapping]
const { isDark } = useTheme()
const markdownExtension = markdown()
const editorTheme = EditorView.theme({
'&': {
backgroundColor: 'var(--card-background)',
color: 'var(--text-color)'
},
'.cm-content': {
caretColor: 'var(--primary-color)'
},
'&.cm-focused .cm-cursor': {
borderLeftColor: 'var(--primary-color)'
},
'&.cm-focused .cm-selectionBackground, ::selection': {
backgroundColor: 'var(--primary-color-bg)'
},
'.cm-gutters': {
backgroundColor: 'var(--card-background)',
color: 'var(--text-color-secondary)',
borderRightColor: 'var(--border-color)'
}
}, { dark: false })
const extensions = computed(() => {
const base = [markdownExtension, EditorView.lineWrapping, editorTheme]
if (!isDark.value) return base
return [markdownExtension, oneDark, EditorView.lineWrapping, editorTheme]
})
watch(() => props.modelValue, (val) => {
if (val !== code.value) {

View File

@ -138,7 +138,9 @@ const handlePull = async () => {
}
.status-output {
background-color: #f5f5f5;
background-color: var(--app-background);
color: var(--text-color);
border: 1px solid var(--border-color);
padding: 8px;
border-radius: 4px;
max-height: 200px;

View File

@ -100,7 +100,7 @@ const handleOpenFile = async (path) => {
}
.subtitle {
color: #888;
color: var(--text-color-secondary);
margin-top: 8px;
}
@ -115,7 +115,7 @@ const handleOpenFile = async (path) => {
display: flex;
align-items: center;
gap: 8px;
color: #333;
color: var(--text-color);
}
.file-item {
@ -126,7 +126,7 @@ const handleOpenFile = async (path) => {
}
.file-item:hover {
background-color: #f5f5f5;
background-color: var(--hover-background);
}
.file-info {
@ -138,7 +138,7 @@ const handleOpenFile = async (path) => {
}
.file-path {
color: #333;
color: var(--text-color);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@ -146,11 +146,11 @@ const handleOpenFile = async (path) => {
.file-name {
font-weight: 500;
color: #333;
color: var(--text-color);
}
.file-path-sub {
color: #999;
color: var(--text-color-secondary);
font-size: 12px;
margin-left: 8px;
overflow: hidden;
@ -159,13 +159,13 @@ const handleOpenFile = async (path) => {
}
.file-time {
color: #999;
color: var(--text-color-secondary);
font-size: 12px;
margin-left: 16px;
}
.empty-text {
color: #ccc;
color: var(--text-color-secondary);
text-align: center;
padding: 20px;
}

View File

@ -33,7 +33,7 @@ const fileUrl = computed(() => toFileUrl(props.tab.filePath));
height: 100%;
display: flex;
overflow: auto;
background-color: #fff;
background-color: var(--card-background);
}
.image-container {
@ -43,6 +43,6 @@ const fileUrl = computed(() => toFileUrl(props.tab.filePath));
.empty-state {
margin: auto;
color: #999;
color: var(--text-color-secondary);
}
</style>

View File

@ -90,8 +90,8 @@ const handleClick = (event) => {
height: 100%;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
line-height: 1.6;
color: #24292e;
background-color: #fff;
color: var(--text-color);
background-color: var(--card-background);
}
.markdown-preview h1,
@ -105,13 +105,13 @@ const handleClick = (event) => {
.markdown-preview h1 {
font-size: 2em;
border-bottom: 1px solid #eaecef;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
}
.markdown-preview h2 {
font-size: 1.5em;
border-bottom: 1px solid #eaecef;
border-bottom: 1px solid var(--border-color);
padding-bottom: 0.3em;
}
@ -124,7 +124,7 @@ const handleClick = (event) => {
padding: 0.2em 0.4em;
margin: 0;
font-size: 85%;
background-color: #f6f8fa;
background-color: var(--app-background);
border-radius: 3px;
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
}
@ -134,7 +134,8 @@ const handleClick = (event) => {
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
background-color: var(--app-background);
border: 1px solid var(--border-color);
border-radius: 3px;
margin-bottom: 16px;
}
@ -152,14 +153,24 @@ const handleClick = (event) => {
.markdown-preview blockquote {
padding: 0 1em;
color: #6a737d;
border-left: 0.25em solid #dfe2e5;
color: var(--text-color-secondary);
border-left: 0.25em solid var(--border-color);
margin: 0 0 16px 0;
}
.markdown-preview img {
max-width: 100%;
box-sizing: border-box;
cursor: zoom-in;
}
.markdown-preview a {
color: var(--primary-color);
text-decoration: none;
}
.markdown-preview a:hover {
text-decoration: underline;
}
.markdown-preview table {
@ -174,10 +185,10 @@ const handleClick = (event) => {
.markdown-preview table th,
.markdown-preview table td {
padding: 6px 13px;
border: 1px solid #dfe2e5;
border: 1px solid var(--border-color);
}
.markdown-preview table tr:nth-child(2n) {
background-color: #f6f8fa;
background-color: var(--hover-background);
}
</style>

View File

@ -108,7 +108,7 @@ const handleResultClick = async (item) => {
}
.result-item:hover {
background-color: #f5f5f5;
background-color: var(--hover-background);
}
.file-name {
@ -117,15 +117,15 @@ const handleResultClick = async (item) => {
}
.file-path {
color: #999;
color: var(--text-color-secondary);
font-size: 12px;
}
.match-preview {
margin-top: 4px;
color: #666;
color: var(--text-color-secondary);
font-size: 13px;
background-color: #fafafa;
background-color: var(--app-background);
padding: 4px;
border-radius: 2px;
}
@ -133,6 +133,6 @@ const handleResultClick = async (item) => {
.match-count {
margin-top: 4px;
font-size: 12px;
color: #1890ff;
color: var(--primary-color);
}
</style>

View File

@ -24,8 +24,9 @@ const { activeTab } = useTabs();
<style scoped>
.status-bar-container {
height: 24px;
background-color: #1890ff;
color: #fff;
background-color: var(--card-background);
color: var(--text-color-secondary);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;

View File

@ -37,8 +37,8 @@ const onContextMenu = (e, tab) => {
<style scoped>
.tab-bar {
display: flex;
background-color: #f0f0f0;
border-bottom: 1px solid #e8e8e8;
background-color: var(--app-background);
border-bottom: 1px solid var(--border-color);
height: 36px;
overflow-x: auto;
}
@ -48,24 +48,26 @@ const onContextMenu = (e, tab) => {
align-items: center;
padding: 0 16px;
cursor: pointer;
border-right: 1px solid #e8e8e8;
background-color: #fafafa;
border-right: 1px solid var(--border-color);
background-color: var(--card-background);
min-width: 100px;
max-width: 200px;
font-size: 13px;
user-select: none;
position: relative;
transition: all 0.2s;
color: var(--text-color-secondary);
}
.tab-item:hover {
background-color: #fff;
background-color: var(--hover-background);
}
.tab-item.active {
background-color: #fff;
border-top: 2px solid #1890ff;
background-color: var(--card-background);
border-top: 2px solid var(--primary-color);
font-weight: 500;
color: var(--text-color);
}
.tab-title {

View File

@ -49,7 +49,7 @@ const { state, activeTab } = useTabs();
flex: 1;
overflow: hidden;
position: relative;
background-color: #fff;
background-color: var(--card-background);
}
.file-tab {
@ -61,6 +61,6 @@ const { state, activeTab } = useTabs();
justify-content: center;
align-items: center;
height: 100%;
color: #999;
color: var(--text-color-secondary);
}
</style>

View File

@ -116,8 +116,8 @@ window.addEventListener('keydown', async (e) => {
<style scoped>
.toolbar-container {
height: 48px;
background-color: #fff;
border-bottom: 1px solid #e8e8e8;
background-color: var(--card-background);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
@ -137,12 +137,12 @@ window.addEventListener('keydown', async (e) => {
.logo {
font-weight: bold;
font-size: 16px;
color: #1890ff;
color: var(--primary-color);
white-space: nowrap;
}
.current-path {
color: #666;
color: var(--text-color-secondary);
font-size: 12px;
overflow: hidden;
text-overflow: ellipsis;

View File

@ -17,6 +17,7 @@ export function useTheme() {
const updateCSSVariables = () => {
const root = document.documentElement;
root.dataset.theme = isDark.value ? 'dark' : 'light';
root.style.setProperty('--primary-color', primaryColor.value);
// 生成一个浅色背景用于选中态
@ -24,25 +25,43 @@ export function useTheme() {
root.style.setProperty('--primary-color-bg', primaryColor.value + '1A'); // 10% opacity
if (isDark.value) {
root.style.setProperty('--app-background', '#141414');
root.style.setProperty('--card-background', '#1f1f1f');
root.style.setProperty('--text-color', 'rgba(255, 255, 255, 0.85)');
root.style.setProperty('--border-color', '#303030');
root.style.setProperty('--hover-background', 'rgba(255, 255, 255, 0.08)');
root.style.setProperty('--text-color-secondary', 'rgba(255, 255, 255, 0.45)');
root.style.setProperty('--scrollbar-thumb', 'rgba(255, 255, 255, 0.28)');
} else {
root.style.setProperty('--app-background', '#f4f4f4');
root.style.setProperty('--card-background', '#ffffff');
root.style.setProperty('--text-color', 'rgba(0, 0, 0, 0.85)');
root.style.setProperty('--border-color', '#f0f0f0');
root.style.setProperty('--hover-background', 'rgba(0, 0, 0, 0.04)');
root.style.setProperty('--text-color-secondary', 'rgba(0, 0, 0, 0.45)');
root.style.setProperty('--scrollbar-thumb', 'rgba(0, 0, 0, 0.28)');
}
};
const getAntdTheme = () => {
const layoutBackground = isDark.value ? '#141414' : '#f4f4f4';
const containerBackground = isDark.value ? '#1f1f1f' : '#ffffff';
const textColor = isDark.value ? 'rgba(255, 255, 255, 0.85)' : 'rgba(0, 0, 0, 0.85)';
const textColorSecondary = isDark.value ? 'rgba(255, 255, 255, 0.45)' : 'rgba(0, 0, 0, 0.45)';
const borderColor = isDark.value ? '#303030' : '#f0f0f0';
const hoverBackground = isDark.value ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.04)';
return {
algorithm: isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm,
token: {
colorPrimary: primaryColor.value,
colorBgLayout: layoutBackground,
colorBgContainer: containerBackground,
colorBgElevated: containerBackground,
colorText: textColor,
colorTextSecondary: textColorSecondary,
colorBorder: borderColor,
colorFillTertiary: hoverBackground,
},
};
};

View File

@ -1,6 +1,15 @@
:root {
--blue: rgb(88, 164, 246);
--light: #fff;
--primary-color: #1890ff;
--primary-color-bg: #1890ff1a;
--app-background: #f4f4f4;
--card-background: #ffffff;
--text-color: rgba(0, 0, 0, 0.85);
--text-color-secondary: rgba(0, 0, 0, 0.45);
--border-color: #f0f0f0;
--hover-background: rgba(0, 0, 0, 0.04);
--scrollbar-thumb: rgba(0, 0, 0, 0.28);
}
html,
@ -9,10 +18,15 @@ body {
padding: 0;
}
body {
background-color: var(--app-background);
color: var(--text-color);
}
button {
border: none;
background: none var(--blue);
color: var(--light);
background: none var(--primary-color);
color: #fff;
line-height: 2.5;
cursor: pointer;
transition: opacity .2s;
@ -32,34 +46,13 @@ textarea {
margin: 0;
}
@media (prefers-color-scheme: light) {
body {
background-color: #f4f4f4;
}
::-webkit-scrollbar-track-piece {
background-color: #f4f4f4;
background-color: var(--app-background);
}
::-webkit-scrollbar-thumb {
border-color: #f4f4f4;
}
}
@media (prefers-color-scheme: dark) {
&::-webkit-scrollbar-track-piece {
background-color: #303133;
}
&::-webkit-scrollbar-thumb {
background-color: #666;
border-color: #303133;
}
body {
background-color: #303133;
color: #fff;
}
background-color: var(--scrollbar-thumb);
border-color: var(--app-background);
}
/* 全局主题样式 - 确保 Ant Design Vue 组件使用主题变量 */