页面美化
This commit is contained in:
@@ -17,7 +17,7 @@
|
|||||||
"height": 600,
|
"height": 600,
|
||||||
"minWidth": 900,
|
"minWidth": 900,
|
||||||
"minHeight": 600,
|
"minHeight": 600,
|
||||||
"decorations": true
|
"decorations": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"security": {
|
"security": {
|
||||||
|
|||||||
130
src/App.vue
130
src/App.vue
@@ -11,7 +11,9 @@ const wordCount = ref(0);
|
|||||||
const charCount = ref(0);
|
const charCount = ref(0);
|
||||||
|
|
||||||
type DocumentMode = "preview" | "edit" | "mixed";
|
type DocumentMode = "preview" | "edit" | "mixed";
|
||||||
|
type AppTheme = "system" | "warm" | "white" | "black" | "gruvbox" | "gray";
|
||||||
const DEFAULT_DOCUMENT_MODE_KEY = "yurou:default-document-mode";
|
const DEFAULT_DOCUMENT_MODE_KEY = "yurou:default-document-mode";
|
||||||
|
const APP_THEME_KEY = "yurou:app-theme";
|
||||||
|
|
||||||
function isDocumentMode(value: string | null): value is DocumentMode {
|
function isDocumentMode(value: string | null): value is DocumentMode {
|
||||||
return value === "preview" || value === "edit" || value === "mixed";
|
return value === "preview" || value === "edit" || value === "mixed";
|
||||||
@@ -33,6 +35,30 @@ function saveDefaultDocumentMode(mode: DocumentMode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isAppTheme(value: string | null): value is AppTheme {
|
||||||
|
return value === "system" || value === "warm" || value === "white" || value === "black" || value === "gruvbox" || value === "gray";
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAppTheme(): AppTheme {
|
||||||
|
try {
|
||||||
|
const stored = localStorage.getItem(APP_THEME_KEY);
|
||||||
|
if (isAppTheme(stored)) return stored;
|
||||||
|
} catch { /* localStorage unavailable */
|
||||||
|
}
|
||||||
|
return "warm";
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAppTheme(theme: AppTheme) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(APP_THEME_KEY, theme);
|
||||||
|
} catch { /* noop */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppTheme(theme: AppTheme) {
|
||||||
|
document.documentElement.dataset.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
// File state
|
// File state
|
||||||
const currentFilePath = ref<string | null>(null);
|
const currentFilePath = ref<string | null>(null);
|
||||||
const saveStatus = ref("");
|
const saveStatus = ref("");
|
||||||
@@ -104,6 +130,7 @@ function onEditorWheel(e: WheelEvent) {
|
|||||||
const showOutline = ref(false);
|
const showOutline = ref(false);
|
||||||
const defaultDocumentMode = ref<DocumentMode>(loadDefaultDocumentMode());
|
const defaultDocumentMode = ref<DocumentMode>(loadDefaultDocumentMode());
|
||||||
const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
const documentMode = ref<DocumentMode>(defaultDocumentMode.value);
|
||||||
|
const appTheme = ref<AppTheme>(loadAppTheme());
|
||||||
const showMoreMenu = ref(false);
|
const showMoreMenu = ref(false);
|
||||||
const settingsRequest = ref(0);
|
const settingsRequest = ref(0);
|
||||||
const settingsInitialPage = ref<"about" | "editor">("about");
|
const settingsInitialPage = ref<"about" | "editor">("about");
|
||||||
@@ -127,6 +154,12 @@ function updateDefaultDocumentMode(mode: DocumentMode) {
|
|||||||
saveDefaultDocumentMode(mode);
|
saveDefaultDocumentMode(mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateAppTheme(theme: AppTheme) {
|
||||||
|
appTheme.value = theme;
|
||||||
|
applyAppTheme(theme);
|
||||||
|
saveAppTheme(theme);
|
||||||
|
}
|
||||||
|
|
||||||
function handleExportPdf() {
|
function handleExportPdf() {
|
||||||
showMoreMenu.value = false;
|
showMoreMenu.value = false;
|
||||||
// TODO: implement PDF export
|
// TODO: implement PDF export
|
||||||
@@ -145,6 +178,7 @@ watch(showMoreMenu, (val) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
applyAppTheme(appTheme.value);
|
||||||
applyZoomCssVars();
|
applyZoomCssVars();
|
||||||
window.addEventListener("wheel", onEditorWheel, {passive: false});
|
window.addEventListener("wheel", onEditorWheel, {passive: false});
|
||||||
void restoreLastWorkspace();
|
void restoreLastWorkspace();
|
||||||
@@ -423,9 +457,9 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-screen bg-[#faf9f6] text-[#38342e] flex flex-col">
|
<div class="h-screen app-root flex flex-col">
|
||||||
<!-- Three-column body -->
|
<!-- Three-column body -->
|
||||||
<div class="flex-1 flex overflow-hidden bg-[#faf9f6]">
|
<div class="flex-1 flex overflow-hidden app-root">
|
||||||
<!-- Left: Directory sidebar -->
|
<!-- Left: Directory sidebar -->
|
||||||
<DirectorySidebar
|
<DirectorySidebar
|
||||||
:folder-path="folderPath"
|
:folder-path="folderPath"
|
||||||
@@ -434,10 +468,12 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
:settings-request="settingsRequest"
|
:settings-request="settingsRequest"
|
||||||
:settings-initial-page="settingsInitialPage"
|
:settings-initial-page="settingsInitialPage"
|
||||||
:default-document-mode="defaultDocumentMode"
|
:default-document-mode="defaultDocumentMode"
|
||||||
|
:app-theme="appTheme"
|
||||||
@open-folder="onOpenFolder"
|
@open-folder="onOpenFolder"
|
||||||
@toggle-folder="onToggleFolder"
|
@toggle-folder="onToggleFolder"
|
||||||
@open-file="onOpenFile"
|
@open-file="onOpenFile"
|
||||||
@update-default-document-mode="updateDefaultDocumentMode"
|
@update-default-document-mode="updateDefaultDocumentMode"
|
||||||
|
@update-app-theme="updateAppTheme"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- Right: Editor area -->
|
<!-- Right: Editor area -->
|
||||||
@@ -445,7 +481,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
<!-- ════════════════ Toolbar ════════════════ -->
|
<!-- ════════════════ Toolbar ════════════════ -->
|
||||||
<div
|
<div
|
||||||
v-if="!showStartActions"
|
v-if="!showStartActions"
|
||||||
class="flex w-full items-center h-10 px-3 bg-[#faf9f6] border-b border-[#e0dbcf] select-none shrink-0"
|
class="editor-toolbar flex w-full items-center h-10 px-3 select-none shrink-0"
|
||||||
>
|
>
|
||||||
<!-- Left: Outline toggle -->
|
<!-- Left: Outline toggle -->
|
||||||
<button
|
<button
|
||||||
@@ -458,7 +494,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Center: Filename -->
|
<!-- Center: Filename -->
|
||||||
<div class="flex-1 text-center text-sm text-[#8c877d] font-medium truncate px-3 select-none">
|
<div class="app-muted-text flex-1 text-center text-sm font-medium truncate px-3 select-none">
|
||||||
{{ showStartActions ? '未选择工作目录' : currentFilePath ? getFileName(currentFilePath) : '未命名文档' }}
|
{{ showStartActions ? '未选择工作目录' : currentFilePath ? getFileName(currentFilePath) : '未命名文档' }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -561,14 +597,14 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
<Transition name="zoom-fade">
|
<Transition name="zoom-fade">
|
||||||
<div
|
<div
|
||||||
v-if="showZoomIndicator"
|
v-if="showZoomIndicator"
|
||||||
class="fixed top-4 right-4 z-50 px-2.5 py-1 rounded-md bg-[#38342e]/80 text-white text-xs font-medium pointer-events-none select-none backdrop-blur-sm"
|
class="zoom-indicator fixed top-4 right-4 z-50 px-2.5 py-1 rounded-md text-xs font-medium pointer-events-none select-none backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
{{ Math.round(editorZoom * 100) }}%
|
{{ Math.round(editorZoom * 100) }}%
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
<footer class="border-t border-[#e8e4da] bg-[#f4f1eb]/60 backdrop-blur-sm">
|
<footer class="app-footer backdrop-blur-sm">
|
||||||
<div class="flex items-center gap-4 px-4 py-1.5">
|
<div class="flex items-center gap-4 px-4 py-1.5">
|
||||||
|
|
||||||
<!-- Current file / save status -->
|
<!-- Current file / save status -->
|
||||||
@@ -577,16 +613,16 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
class="text-xs"
|
class="text-xs"
|
||||||
:class="saveStatus.startsWith('保存失败') ? 'text-red-500' : 'text-emerald-600'"
|
:class="saveStatus.startsWith('保存失败') ? 'text-red-500' : 'text-emerald-600'"
|
||||||
>{{ saveStatus }}</span>
|
>{{ saveStatus }}</span>
|
||||||
<span v-else-if="currentFilePath" class="text-xs text-[#8c877d]">
|
<span v-else-if="currentFilePath" class="app-muted-text text-xs">
|
||||||
{{ currentFilePath.split(/[/\\]/).pop() }}
|
{{ currentFilePath.split(/[/\\]/).pop() }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="text-xs text-[#b8b3a8]">未保存</span>
|
<span v-else class="app-subtle-text text-xs">未保存</span>
|
||||||
|
|
||||||
<span class="flex-1"/>
|
<span class="flex-1"/>
|
||||||
|
|
||||||
<span class="text-xs text-[#8c877d]">{{ wordCount.toLocaleString() }} 词</span>
|
<span class="app-muted-text text-xs">{{ wordCount.toLocaleString() }} 词</span>
|
||||||
<span class="w-px h-3 bg-[#d4cfc4]"/>
|
<span class="app-divider w-px h-3"/>
|
||||||
<span class="text-xs text-[#8c877d]">{{ charCount.toLocaleString() }} 字</span>
|
<span class="app-muted-text text-xs">{{ charCount.toLocaleString() }} 字</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -616,6 +652,38 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-root {
|
||||||
|
background: var(--app-bg);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-toolbar {
|
||||||
|
background: var(--app-bg);
|
||||||
|
border-bottom: 1px solid var(--app-border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-footer {
|
||||||
|
border-top: 1px solid var(--app-border);
|
||||||
|
background: color-mix(in srgb, var(--app-sidebar-bg) 72%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-muted-text {
|
||||||
|
color: var(--app-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-subtle-text {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-divider {
|
||||||
|
background: var(--app-border-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-indicator {
|
||||||
|
background: var(--app-toast-bg);
|
||||||
|
color: var(--app-control-contrast);
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Toolbar ───────────────────────────────────────────────────── */
|
/* ── Toolbar ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.toolbar-btn {
|
.toolbar-btn {
|
||||||
@@ -625,7 +693,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -634,13 +702,13 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-btn:hover {
|
.toolbar-btn:hover {
|
||||||
background-color: #ede8dd;
|
background-color: var(--app-hover);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toolbar-btn-active {
|
.toolbar-btn-active {
|
||||||
color: #bf6a3b;
|
color: var(--app-accent);
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-toggle {
|
.mode-toggle {
|
||||||
@@ -648,9 +716,9 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -664,7 +732,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -675,13 +743,13 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.mode-toggle-btn:hover {
|
.mode-toggle-btn:hover {
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mode-toggle-btn-active {
|
.mode-toggle-btn-active {
|
||||||
background-color: #fffdfa;
|
background-color: var(--app-surface);
|
||||||
color: #bf6a3b;
|
color: var(--app-accent);
|
||||||
box-shadow: 0 1px 3px rgba(56, 52, 46, 0.12);
|
box-shadow: 0 1px 3px var(--app-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
@@ -689,8 +757,8 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
right: 0;
|
right: 0;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
background: white;
|
background: var(--app-surface);
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
@@ -706,7 +774,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -714,19 +782,19 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-item:hover {
|
.dropdown-item:hover {
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-actions {
|
.start-actions {
|
||||||
background:
|
background:
|
||||||
linear-gradient(180deg, rgba(255, 253, 250, 0.35), rgba(244, 241, 235, 0.2)),
|
linear-gradient(180deg, color-mix(in srgb, var(--app-surface) 35%, transparent), color-mix(in srgb, var(--app-sidebar-bg) 20%, transparent)),
|
||||||
#faf9f6;
|
var(--app-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.start-action-btn {
|
.start-action-btn {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -742,7 +810,7 @@ registerTauriListener("menu-event", (event) => {
|
|||||||
|
|
||||||
.start-action-btn:hover,
|
.start-action-btn:hover,
|
||||||
.start-action-btn:focus-visible {
|
.start-action-btn:focus-visible {
|
||||||
color: #bf6a3b;
|
color: var(--app-accent);
|
||||||
text-decoration-color: currentColor;
|
text-decoration-color: currentColor;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import TreeEntry from "./TreeEntry.vue";
|
|||||||
|
|
||||||
type SettingsPage = "about" | "editor";
|
type SettingsPage = "about" | "editor";
|
||||||
type DocumentMode = "preview" | "edit" | "mixed";
|
type DocumentMode = "preview" | "edit" | "mixed";
|
||||||
|
type AppTheme = "system" | "warm" | "white" | "black" | "gruvbox" | "gray";
|
||||||
|
|
||||||
interface DirEntry {
|
interface DirEntry {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -21,12 +22,14 @@ const props = withDefaults(
|
|||||||
settingsRequest?: number;
|
settingsRequest?: number;
|
||||||
settingsInitialPage?: SettingsPage;
|
settingsInitialPage?: SettingsPage;
|
||||||
defaultDocumentMode?: DocumentMode;
|
defaultDocumentMode?: DocumentMode;
|
||||||
|
appTheme?: AppTheme;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
currentFilePath: null,
|
currentFilePath: null,
|
||||||
settingsRequest: 0,
|
settingsRequest: 0,
|
||||||
settingsInitialPage: "about",
|
settingsInitialPage: "about",
|
||||||
defaultDocumentMode: "edit",
|
defaultDocumentMode: "edit",
|
||||||
|
appTheme: "warm",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -37,6 +40,7 @@ const emit = defineEmits<{
|
|||||||
createFile: [];
|
createFile: [];
|
||||||
createFolder: [];
|
createFolder: [];
|
||||||
updateDefaultDocumentMode: [mode: DocumentMode];
|
updateDefaultDocumentMode: [mode: DocumentMode];
|
||||||
|
updateAppTheme: [theme: AppTheme];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
// Wrap the workspace root as a top-level tree node
|
// Wrap the workspace root as a top-level tree node
|
||||||
@@ -82,14 +86,14 @@ function openSettings(page: SettingsPage) {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside
|
<aside
|
||||||
:class="['flex flex-row border-r border-[#e8e4da] bg-[#f4f1eb]/80 transition-all duration-300 ease-in-out overflow-hidden shrink-0', expanded ? 'w-72' : 'w-12']"
|
:class="['directory-sidebar flex flex-row transition-all duration-300 ease-in-out overflow-hidden shrink-0', expanded ? 'w-72' : 'w-12']"
|
||||||
>
|
>
|
||||||
<!-- ============ Icon bar ============ -->
|
<!-- ============ Icon bar ============ -->
|
||||||
<div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3">
|
<div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3">
|
||||||
<!-- Folder -->
|
<!-- Folder -->
|
||||||
<button
|
<button
|
||||||
class="px-2 py-1 cursor-pointer rounded-md transition-colors"
|
class="sidebar-icon-btn px-2 py-1 cursor-pointer rounded-md transition-colors"
|
||||||
:class="activeView === 'folder' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
|
:class="{ 'sidebar-icon-btn-active': activeView === 'folder' }"
|
||||||
title="文件"
|
title="文件"
|
||||||
@click="switchView('folder')"
|
@click="switchView('folder')"
|
||||||
>
|
>
|
||||||
@@ -97,8 +101,8 @@ function openSettings(page: SettingsPage) {
|
|||||||
</button>
|
</button>
|
||||||
<!-- Search -->
|
<!-- Search -->
|
||||||
<button
|
<button
|
||||||
class="px-2 py-1 cursor-pointer rounded-md transition-colors"
|
class="sidebar-icon-btn px-2 py-1 cursor-pointer rounded-md transition-colors"
|
||||||
:class="activeView === 'search' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
|
:class="{ 'sidebar-icon-btn-active': activeView === 'search' }"
|
||||||
title="搜索"
|
title="搜索"
|
||||||
@click="switchView('search')"
|
@click="switchView('search')"
|
||||||
>
|
>
|
||||||
@@ -106,8 +110,8 @@ function openSettings(page: SettingsPage) {
|
|||||||
</button>
|
</button>
|
||||||
<!-- ===== Settings ===== -->
|
<!-- ===== Settings ===== -->
|
||||||
<button
|
<button
|
||||||
class="px-2 py-1 cursor-pointer rounded-md transition-colors mt-auto"
|
class="sidebar-icon-btn px-2 py-1 cursor-pointer rounded-md transition-colors mt-auto"
|
||||||
:class="showSettings ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
|
:class="{ 'sidebar-icon-btn-active': showSettings }"
|
||||||
title="设置"
|
title="设置"
|
||||||
@click="openSettings('about')"
|
@click="openSettings('about')"
|
||||||
>
|
>
|
||||||
@@ -116,7 +120,7 @@ function openSettings(page: SettingsPage) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ Expanded panel ============ -->
|
<!-- ============ Expanded panel ============ -->
|
||||||
<div v-show="expanded" class="flex-1 flex flex-col overflow-hidden border-l border-[#e8e4da]">
|
<div v-show="expanded" class="directory-sidebar-panel flex-1 flex flex-col overflow-hidden">
|
||||||
<!-- ===== FOLDER VIEW ===== -->
|
<!-- ===== FOLDER VIEW ===== -->
|
||||||
<div v-if="activeView === 'folder'" class="flex flex-col flex-1 overflow-hidden">
|
<div v-if="activeView === 'folder'" class="flex flex-col flex-1 overflow-hidden">
|
||||||
<!-- File tree -->
|
<!-- File tree -->
|
||||||
@@ -134,11 +138,11 @@ function openSettings(page: SettingsPage) {
|
|||||||
<!-- Empty state -->
|
<!-- Empty state -->
|
||||||
<div
|
<div
|
||||||
v-if="!folderPath"
|
v-if="!folderPath"
|
||||||
class="px-2 py-4 text-xs text-[#b8b3a8] text-center"
|
class="sidebar-empty px-2 py-4 text-xs text-center"
|
||||||
>
|
>
|
||||||
<p class="mb-3">暂无打开的文件夹</p>
|
<p class="mb-3">暂无打开的文件夹</p>
|
||||||
<button
|
<button
|
||||||
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs cursor-pointer rounded-md bg-[#fdf0e5] text-[#bf6a3b] border border-[#d4a574]/40 hover:bg-[#fdf0e5]/80 hover:text-[#bf6a3b] transition-colors"
|
class="sidebar-empty-btn inline-flex items-center gap-1.5 px-3 py-1.5 text-xs cursor-pointer rounded-md transition-colors"
|
||||||
@click="$emit('openFolder')"
|
@click="$emit('openFolder')"
|
||||||
>
|
>
|
||||||
<i class="ri-folder-open-line"></i>
|
<i class="ri-folder-open-line"></i>
|
||||||
@@ -152,16 +156,16 @@ function openSettings(page: SettingsPage) {
|
|||||||
<div v-if="activeView === 'search'" class="flex flex-col flex-1 overflow-hidden">
|
<div v-if="activeView === 'search'" class="flex flex-col flex-1 overflow-hidden">
|
||||||
<div class="px-3 py-3">
|
<div class="px-3 py-3">
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
<i class="ri-search-line absolute left-2.5 top-1/2 -translate-y-1/2 text-sm text-[#b8b3a8]"></i>
|
<i class="sidebar-search-icon ri-search-line absolute left-2.5 top-1/2 -translate-y-1/2 text-sm"></i>
|
||||||
<input
|
<input
|
||||||
class="w-full pl-8 pr-3 py-1.5 text-xs bg-[#ede8de] border border-[#e0dbcf] rounded-md text-[#38342e] outline-none focus:border-[#bf6a3b] placeholder-[#b8b3a8]"
|
class="sidebar-search-input w-full pl-8 pr-3 py-1.5 text-xs rounded-md outline-none"
|
||||||
placeholder="搜索文件内容..."
|
placeholder="搜索文件内容..."
|
||||||
disabled
|
disabled
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1 flex items-center justify-center px-3 pb-8">
|
<div class="flex-1 flex items-center justify-center px-3 pb-8">
|
||||||
<p class="text-xs text-[#b8b3a8] text-center leading-relaxed">
|
<p class="sidebar-empty text-xs text-center leading-relaxed">
|
||||||
搜索功能即将上线
|
搜索功能即将上线
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -173,6 +177,59 @@ function openSettings(page: SettingsPage) {
|
|||||||
v-model:show="showSettings"
|
v-model:show="showSettings"
|
||||||
:initial-page="settingsPage"
|
:initial-page="settingsPage"
|
||||||
:default-document-mode="defaultDocumentMode"
|
:default-document-mode="defaultDocumentMode"
|
||||||
|
:app-theme="appTheme"
|
||||||
@update-default-document-mode="(mode: DocumentMode) => emit('updateDefaultDocumentMode', mode)"
|
@update-default-document-mode="(mode: DocumentMode) => emit('updateDefaultDocumentMode', mode)"
|
||||||
|
@update-app-theme="(theme: AppTheme) => emit('updateAppTheme', theme)"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.directory-sidebar {
|
||||||
|
border-right: 1px solid var(--app-border);
|
||||||
|
background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.directory-sidebar-panel {
|
||||||
|
border-left: 1px solid var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-icon-btn {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-icon-btn:hover,
|
||||||
|
.sidebar-icon-btn-active {
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-empty,
|
||||||
|
.sidebar-search-icon {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-empty-btn {
|
||||||
|
border: 1px solid color-mix(in srgb, var(--app-accent-muted) 42%, transparent);
|
||||||
|
background: var(--app-active-bg);
|
||||||
|
color: var(--app-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-empty-btn:hover {
|
||||||
|
background: color-mix(in srgb, var(--app-active-bg) 84%, var(--app-surface));
|
||||||
|
color: var(--app-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-search-input {
|
||||||
|
border: 1px solid var(--app-border-strong);
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-search-input:focus {
|
||||||
|
border-color: var(--app-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-search-input::placeholder {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -501,8 +501,7 @@ defineExpose({
|
|||||||
class="
|
class="
|
||||||
markdown-source-editor
|
markdown-source-editor
|
||||||
w-full min-h-[60vh] resize-none overflow-hidden bg-transparent outline-none
|
w-full min-h-[60vh] resize-none overflow-hidden bg-transparent outline-none
|
||||||
font-serif text-[#38342e] leading-relaxed
|
font-serif leading-relaxed
|
||||||
placeholder:text-[#b8b3a8]
|
|
||||||
"
|
"
|
||||||
@input="onEditorInput"
|
@input="onEditorInput"
|
||||||
@keydown="onEditorKeydown"
|
@keydown="onEditorKeydown"
|
||||||
@@ -583,7 +582,7 @@ defineExpose({
|
|||||||
.markdown-preview {
|
.markdown-preview {
|
||||||
font-family: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
font-family: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;
|
||||||
line-height: 1.625;
|
line-height: 1.625;
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,6 +591,11 @@ defineExpose({
|
|||||||
.markdown-source-editor {
|
.markdown-source-editor {
|
||||||
display: block;
|
display: block;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.markdown-source-editor::placeholder {
|
||||||
|
color: var(--app-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-mixed > .markdown-block:first-child .markdown-block-preview > *:first-child {
|
.markdown-mixed > .markdown-block:first-child .markdown-block-preview > *:first-child {
|
||||||
@@ -605,7 +609,7 @@ defineExpose({
|
|||||||
.markdown-preview h5,
|
.markdown-preview h5,
|
||||||
.markdown-preview h6 {
|
.markdown-preview h6 {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #2d2a26;
|
color: var(--app-text-strong);
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
margin-top: 1.5em;
|
margin-top: 1.5em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
@@ -617,13 +621,13 @@ defineExpose({
|
|||||||
|
|
||||||
.markdown-preview p { margin: 0.5em 0; }
|
.markdown-preview p { margin: 0.5em 0; }
|
||||||
|
|
||||||
.markdown-preview strong { font-weight: 700; color: #2d2a26; }
|
.markdown-preview strong { font-weight: 700; color: var(--app-text-strong); }
|
||||||
.markdown-preview em { font-style: italic; }
|
.markdown-preview em { font-style: italic; }
|
||||||
.markdown-preview del { text-decoration: line-through; color: #8c877d; }
|
.markdown-preview del { text-decoration: line-through; color: var(--app-muted); }
|
||||||
|
|
||||||
.markdown-preview code {
|
.markdown-preview code {
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
color: #bf6a3b;
|
color: var(--app-accent);
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
padding: 0.125rem 0.375rem;
|
padding: 0.125rem 0.375rem;
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
@@ -632,7 +636,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview pre {
|
.markdown-preview pre {
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -641,19 +645,19 @@ defineExpose({
|
|||||||
.markdown-preview pre code {
|
.markdown-preview pre code {
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
font-size: 0.875em;
|
font-size: 0.875em;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview a { color: #bf6a3b; text-decoration: underline; }
|
.markdown-preview a { color: var(--app-accent); text-decoration: underline; }
|
||||||
|
|
||||||
.markdown-preview blockquote {
|
.markdown-preview blockquote {
|
||||||
border-left: 4px solid #bf6a3b;
|
border-left: 4px solid var(--app-accent);
|
||||||
padding-left: 1rem;
|
padding-left: 1rem;
|
||||||
margin: 0.75em 0;
|
margin: 0.75em 0;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
.markdown-preview blockquote p { margin: 0.25em 0; }
|
.markdown-preview blockquote p { margin: 0.25em 0; }
|
||||||
@@ -664,12 +668,12 @@ defineExpose({
|
|||||||
|
|
||||||
.markdown-preview li input[type="checkbox"] {
|
.markdown-preview li input[type="checkbox"] {
|
||||||
margin-right: 0.375rem;
|
margin-right: 0.375rem;
|
||||||
accent-color: #bf6a3b;
|
accent-color: var(--app-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-preview hr {
|
.markdown-preview hr {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 1px solid #e0dbcf;
|
border-top: 1px solid var(--app-border-strong);
|
||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,16 +684,16 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
.markdown-preview th,
|
.markdown-preview th,
|
||||||
.markdown-preview td {
|
.markdown-preview td {
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
.markdown-preview th {
|
.markdown-preview th {
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #2d2a26;
|
color: var(--app-text-strong);
|
||||||
}
|
}
|
||||||
.markdown-preview tr:nth-child(even) td { background-color: #faf9f6; }
|
.markdown-preview tr:nth-child(even) td { background-color: var(--app-panel-bg); }
|
||||||
|
|
||||||
.markdown-preview img {
|
.markdown-preview img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@@ -716,7 +720,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-block:not(.markdown-block-editing):hover > .markdown-block-preview > * {
|
.markdown-block:not(.markdown-block-editing):hover > .markdown-block-preview > * {
|
||||||
background-color: #f4f1ea;
|
background-color: var(--app-surface-muted);
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -730,13 +734,13 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markdown-block-preview:focus-visible > * {
|
.markdown-block-preview:focus-visible > * {
|
||||||
outline: 2px solid #d6aa8e;
|
outline: 2px solid var(--app-accent-muted);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-empty-placeholder:focus-visible {
|
.markdown-empty-placeholder:focus-visible {
|
||||||
outline: 2px solid #d6aa8e;
|
outline: 2px solid var(--app-accent-muted);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
@@ -747,22 +751,22 @@ defineExpose({
|
|||||||
min-height: calc((1em * 1.6) + 1.5rem + 2px);
|
min-height: calc((1em * 1.6) + 1.5rem + 2px);
|
||||||
resize: none;
|
resize: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #d6aa8e;
|
border: 1px solid var(--app-accent-muted);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
background-color: #fffdfa;
|
background-color: var(--app-surface);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
font-size: 0.92em;
|
font-size: 0.92em;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin: 0.375rem 0;
|
margin: 0.375rem 0;
|
||||||
padding: 0.75rem 0.875rem;
|
padding: 0.75rem 0.875rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: 0 0 0 3px rgba(191, 106, 59, 0.08);
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent) 12%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-block-editor::placeholder,
|
.markdown-block-editor::placeholder,
|
||||||
.markdown-empty-placeholder {
|
.markdown-empty-placeholder {
|
||||||
color: #b8b3a8;
|
color: var(--app-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
.markdown-empty-placeholder {
|
.markdown-empty-placeholder {
|
||||||
|
|||||||
@@ -46,11 +46,11 @@ const onHeadingClick = (item: OutlineItem) => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<aside
|
<aside
|
||||||
class="flex flex-col border-r border-[#e8e4da] bg-[#f4f1eb]/80 w-52 shrink-0 overflow-hidden"
|
class="outline-panel flex flex-col w-52 shrink-0 overflow-hidden"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex items-center px-3 py-3 border-b border-[#e8e4da]">
|
<div class="outline-panel-header flex items-center px-3 py-3">
|
||||||
<span class="text-xs font-semibold text-[#8c877d] uppercase tracking-wider">
|
<span class="outline-panel-title text-xs font-semibold uppercase tracking-wider">
|
||||||
大纲
|
大纲
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -58,7 +58,7 @@ const onHeadingClick = (item: OutlineItem) => {
|
|||||||
<!-- Outline content -->
|
<!-- Outline content -->
|
||||||
<div class="flex-1 overflow-y-auto px-2 py-2">
|
<div class="flex-1 overflow-y-auto px-2 py-2">
|
||||||
<template v-if="headings.length === 0">
|
<template v-if="headings.length === 0">
|
||||||
<p class="text-xs text-[#b8b3a8] px-2 py-4 text-center leading-relaxed">
|
<p class="outline-empty text-xs px-2 py-4 text-center leading-relaxed">
|
||||||
暂无标题<br />使用 H1-H3 标题<br />自动生成大纲
|
暂无标题<br />使用 H1-H3 标题<br />自动生成大纲
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
@@ -67,12 +67,12 @@ const onHeadingClick = (item: OutlineItem) => {
|
|||||||
v-for="item in headings"
|
v-for="item in headings"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:class="[
|
:class="[
|
||||||
'px-2 py-1 rounded-md text-sm cursor-pointer hover:bg-[#ede8de] transition-colors truncate',
|
'outline-item px-2 py-1 rounded-md text-sm cursor-pointer transition-colors truncate',
|
||||||
item.level === 1
|
item.level === 1
|
||||||
? 'text-[#38342e] font-medium ml-0'
|
? 'outline-item-level-1 font-medium ml-0'
|
||||||
: item.level === 2
|
: item.level === 2
|
||||||
? 'text-[#8c877d] ml-3'
|
? 'outline-item-level-2 ml-3'
|
||||||
: 'text-[#b8b3a8] ml-6 text-xs',
|
: 'outline-item-level-3 ml-6 text-xs',
|
||||||
]"
|
]"
|
||||||
:title="item.text"
|
:title="item.text"
|
||||||
@click="onHeadingClick(item)"
|
@click="onHeadingClick(item)"
|
||||||
@@ -83,3 +83,32 @@ const onHeadingClick = (item: OutlineItem) => {
|
|||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.outline-panel {
|
||||||
|
border-right: 1px solid var(--app-border);
|
||||||
|
background: color-mix(in srgb, var(--app-sidebar-bg) 82%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-panel-header {
|
||||||
|
border-bottom: 1px solid var(--app-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-panel-title,
|
||||||
|
.outline-item-level-2 {
|
||||||
|
color: var(--app-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-empty,
|
||||||
|
.outline-item-level-3 {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-item:hover {
|
||||||
|
background: var(--app-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.outline-item-level-1 {
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -2,24 +2,28 @@
|
|||||||
import { getVersion } from "@tauri-apps/api/app";
|
import { getVersion } from "@tauri-apps/api/app";
|
||||||
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
||||||
|
|
||||||
type SettingsPage = "about" | "editor";
|
type SettingsPage = "about" | "editor" | "appearance";
|
||||||
type DocumentMode = "preview" | "edit" | "mixed";
|
type DocumentMode = "preview" | "edit" | "mixed";
|
||||||
|
type AppTheme = "system" | "warm" | "white" | "black" | "gruvbox" | "gray";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
show: boolean;
|
show: boolean;
|
||||||
initialPage?: SettingsPage;
|
initialPage?: SettingsPage;
|
||||||
defaultDocumentMode?: DocumentMode;
|
defaultDocumentMode?: DocumentMode;
|
||||||
|
appTheme?: AppTheme;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
initialPage: "about",
|
initialPage: "about",
|
||||||
defaultDocumentMode: "edit",
|
defaultDocumentMode: "edit",
|
||||||
|
appTheme: "warm",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
"update:show": [value: boolean];
|
"update:show": [value: boolean];
|
||||||
updateDefaultDocumentMode: [mode: DocumentMode];
|
updateDefaultDocumentMode: [mode: DocumentMode];
|
||||||
|
updateAppTheme: [theme: AppTheme];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const APP_NAME = "yurou";
|
const APP_NAME = "yurou";
|
||||||
@@ -34,7 +38,11 @@ const language = ref(loadStringPref(LANGUAGE_KEY, "zh-Hans"));
|
|||||||
const updateStatus = ref("");
|
const updateStatus = ref("");
|
||||||
let updateStatusTimer: ReturnType<typeof setTimeout> | null = null;
|
let updateStatusTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
const pageTitle = computed(() => (activePage.value === "about" ? "关于" : "编辑器"));
|
const pageTitle = computed(() => {
|
||||||
|
if (activePage.value === "about") return "关于";
|
||||||
|
if (activePage.value === "editor") return "编辑器";
|
||||||
|
return "外观";
|
||||||
|
});
|
||||||
|
|
||||||
const menuItems: Array<{
|
const menuItems: Array<{
|
||||||
id: SettingsPage;
|
id: SettingsPage;
|
||||||
@@ -43,6 +51,7 @@ const menuItems: Array<{
|
|||||||
}> = [
|
}> = [
|
||||||
{ id: "about", label: "关于", icon: "ri-information-line" },
|
{ id: "about", label: "关于", icon: "ri-information-line" },
|
||||||
{ id: "editor", label: "编辑器", icon: "ri-edit-2-line" },
|
{ id: "editor", label: "编辑器", icon: "ri-edit-2-line" },
|
||||||
|
{ id: "appearance", label: "外观", icon: "ri-palette-line" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const documentModeOptions: Array<{
|
const documentModeOptions: Array<{
|
||||||
@@ -54,6 +63,19 @@ const documentModeOptions: Array<{
|
|||||||
{ value: "mixed", label: "混合" },
|
{ value: "mixed", label: "混合" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const themeOptions: Array<{
|
||||||
|
value: AppTheme;
|
||||||
|
label: string;
|
||||||
|
swatch: string;
|
||||||
|
}> = [
|
||||||
|
{ value: "system", label: "跟随系统", swatch: "linear-gradient(135deg, #ffffff 0 50%, #17191d 50% 100%)" },
|
||||||
|
{ value: "warm", label: "柔和暖色", swatch: "#bf6a3b" },
|
||||||
|
{ value: "white", label: "白色", swatch: "#2563eb" },
|
||||||
|
{ value: "black", label: "黑色", swatch: "#8ab4ff" },
|
||||||
|
{ value: "gruvbox", label: "Gruvbox", swatch: "#fe8019" },
|
||||||
|
{ value: "gray", label: "灰色", swatch: "#4f6f93" },
|
||||||
|
];
|
||||||
|
|
||||||
function loadBooleanPref(key: string, fallback: boolean) {
|
function loadBooleanPref(key: string, fallback: boolean) {
|
||||||
try {
|
try {
|
||||||
const stored = localStorage.getItem(key);
|
const stored = localStorage.getItem(key);
|
||||||
@@ -118,6 +140,15 @@ function onDefaultDocumentModeChange(event: Event) {
|
|||||||
emit("updateDefaultDocumentMode", mode);
|
emit("updateDefaultDocumentMode", mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onThemeChange(event: Event) {
|
||||||
|
const theme = (event.target as HTMLSelectElement).value as AppTheme;
|
||||||
|
emit("updateAppTheme", theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetTheme() {
|
||||||
|
emit("updateAppTheme", "warm");
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => props.show,
|
||||||
(show) => {
|
(show) => {
|
||||||
@@ -242,7 +273,7 @@ onBeforeUnmount(() => {
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="settings-content">
|
<div v-else-if="activePage === 'editor'" class="settings-content">
|
||||||
<section class="settings-about-card" aria-label="编辑器设置">
|
<section class="settings-about-card" aria-label="编辑器设置">
|
||||||
<div class="settings-row settings-row-top">
|
<div class="settings-row settings-row-top">
|
||||||
<div class="settings-copy">
|
<div class="settings-copy">
|
||||||
@@ -266,6 +297,59 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="settings-content">
|
||||||
|
<section class="settings-about-card" aria-label="外观设置">
|
||||||
|
<div class="settings-row settings-row-top">
|
||||||
|
<div class="settings-copy">
|
||||||
|
<h3>基础颜色</h3>
|
||||||
|
<p>设置 {{ APP_NAME }} 的基础颜色。</p>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
class="settings-select"
|
||||||
|
aria-label="基础颜色"
|
||||||
|
:value="appTheme"
|
||||||
|
@change="onThemeChange"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="option in themeOptions"
|
||||||
|
:key="option.value"
|
||||||
|
:value="option.value"
|
||||||
|
>
|
||||||
|
{{ option.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-row">
|
||||||
|
<div class="settings-copy">
|
||||||
|
<h3>主题色</h3>
|
||||||
|
<p>为 {{ APP_NAME }} 选择一个主题色。主题色将影响选中、链接等元素的颜色。</p>
|
||||||
|
</div>
|
||||||
|
<div class="theme-swatch-group" aria-label="主题色">
|
||||||
|
<button
|
||||||
|
class="theme-reset-btn"
|
||||||
|
type="button"
|
||||||
|
title="重置主题色"
|
||||||
|
@click="resetTheme"
|
||||||
|
>
|
||||||
|
<i class="ri-reset-left-line text-lg"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-for="option in themeOptions.filter((item) => item.value !== 'system')"
|
||||||
|
:key="option.value"
|
||||||
|
class="theme-swatch"
|
||||||
|
:class="{ 'theme-swatch-active': appTheme === option.value }"
|
||||||
|
type="button"
|
||||||
|
:title="option.label"
|
||||||
|
:aria-label="option.label"
|
||||||
|
:style="{ background: option.swatch }"
|
||||||
|
@click="emit('updateAppTheme', option.value)"
|
||||||
|
></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -281,7 +365,7 @@ onBeforeUnmount(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 28px;
|
padding: 28px;
|
||||||
background: rgba(56, 52, 46, 0.22);
|
background: var(--app-overlay);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,12 +376,12 @@ onBeforeUnmount(() => {
|
|||||||
min-height: 430px;
|
min-height: 430px;
|
||||||
max-height: min(680px, calc(100vh - 48px));
|
max-height: min(680px, calc(100vh - 48px));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #fffdfa;
|
background: var(--app-surface);
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 24px 70px rgba(56, 52, 46, 0.2),
|
0 24px 70px var(--app-shadow),
|
||||||
0 2px 10px rgba(56, 52, 46, 0.08);
|
0 2px 10px color-mix(in srgb, var(--app-shadow) 46%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu {
|
.settings-menu {
|
||||||
@@ -305,13 +389,13 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
padding: 14px 10px;
|
padding: 14px 10px;
|
||||||
border-right: 1px solid #e8e4da;
|
border-right: 1px solid var(--app-border);
|
||||||
background: #f4f1eb;
|
background: var(--app-sidebar-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu-title {
|
.settings-menu-title {
|
||||||
padding: 0 10px 8px;
|
padding: 0 10px 8px;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -326,7 +410,7 @@ onBeforeUnmount(() => {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #5c574e;
|
color: var(--app-text-soft);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -337,13 +421,13 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu-item:hover {
|
.settings-menu-item:hover {
|
||||||
background: #ede8dd;
|
background: var(--app-hover);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu-item-active {
|
.settings-menu-item-active {
|
||||||
background: #fdf0e5;
|
background: var(--app-active-bg);
|
||||||
color: #bf6a3b;
|
color: var(--app-accent);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,7 +435,7 @@ onBeforeUnmount(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background: #faf9f6;
|
background: var(--app-panel-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
@@ -360,13 +444,13 @@ onBeforeUnmount(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
min-height: 54px;
|
min-height: 54px;
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
border-bottom: 1px solid #e8e4da;
|
border-bottom: 1px solid var(--app-border);
|
||||||
background: rgba(255, 253, 250, 0.72);
|
background: color-mix(in srgb, var(--app-surface) 72%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header h2 {
|
.settings-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -380,7 +464,7 @@ onBeforeUnmount(() => {
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
background-color 0.15s ease,
|
background-color 0.15s ease,
|
||||||
@@ -388,8 +472,8 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-close-btn:hover {
|
.settings-close-btn:hover {
|
||||||
background: #ede8dd;
|
background: var(--app-hover);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-content {
|
.settings-content {
|
||||||
@@ -402,8 +486,8 @@ onBeforeUnmount(() => {
|
|||||||
.settings-about-card {
|
.settings-about-card {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #fffdfa;
|
background: var(--app-surface);
|
||||||
box-shadow: inset 0 0 0 1px #eee9de;
|
box-shadow: inset 0 0 0 1px var(--app-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-row {
|
.settings-row {
|
||||||
@@ -413,7 +497,7 @@ onBeforeUnmount(() => {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 78px;
|
min-height: 78px;
|
||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-top: 1px solid #e0dbcf;
|
border-top: 1px solid var(--app-border-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-row-top {
|
.settings-row-top {
|
||||||
@@ -427,7 +511,7 @@ onBeforeUnmount(() => {
|
|||||||
.settings-copy h3,
|
.settings-copy h3,
|
||||||
.settings-empty-page h3 {
|
.settings-empty-page h3 {
|
||||||
margin: 0 0 6px;
|
margin: 0 0 6px;
|
||||||
color: #2d2a26;
|
color: var(--app-text-strong);
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
@@ -436,7 +520,7 @@ onBeforeUnmount(() => {
|
|||||||
.settings-copy p,
|
.settings-copy p,
|
||||||
.settings-empty-page p {
|
.settings-empty-page p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #5c574e;
|
color: var(--app-text-soft);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
@@ -447,7 +531,7 @@ onBeforeUnmount(() => {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #7a55d9;
|
color: var(--app-link);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@@ -457,7 +541,7 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-link:hover {
|
.settings-link:hover {
|
||||||
color: #6342bd;
|
color: var(--app-link-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-action-stack {
|
.settings-action-stack {
|
||||||
@@ -487,29 +571,29 @@ onBeforeUnmount(() => {
|
|||||||
.settings-primary-btn {
|
.settings-primary-btn {
|
||||||
min-width: 78px;
|
min-width: 78px;
|
||||||
border: none;
|
border: none;
|
||||||
background: #8c6be8;
|
background: var(--app-control);
|
||||||
color: #fff;
|
color: var(--app-control-contrast);
|
||||||
box-shadow: 0 2px 7px rgba(100, 65, 190, 0.26);
|
box-shadow: 0 2px 7px color-mix(in srgb, var(--app-control) 32%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-primary-btn:hover {
|
.settings-primary-btn:hover {
|
||||||
background: #7a55d9;
|
background: var(--app-control-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-secondary-btn {
|
.settings-secondary-btn {
|
||||||
min-width: 50px;
|
min-width: 50px;
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
background: #fff;
|
background: var(--app-surface);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
box-shadow: 0 1px 4px rgba(56, 52, 46, 0.1);
|
box-shadow: 0 1px 4px color-mix(in srgb, var(--app-shadow) 58%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-secondary-btn:hover {
|
.settings-secondary-btn:hover {
|
||||||
background: #f4f1ea;
|
background: var(--app-surface-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-status {
|
.settings-status {
|
||||||
color: #6f6a60;
|
color: var(--app-muted);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
@@ -532,7 +616,7 @@ onBeforeUnmount(() => {
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
inset: 0;
|
inset: 0;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: #d4cfc4;
|
background: var(--app-border-strong);
|
||||||
transition: background-color 0.16s ease;
|
transition: background-color 0.16s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,14 +627,14 @@ onBeforeUnmount(() => {
|
|||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: #fff;
|
background: var(--app-surface);
|
||||||
box-shadow: 0 1px 3px rgba(56, 52, 46, 0.24);
|
box-shadow: 0 1px 3px var(--app-shadow);
|
||||||
content: "";
|
content: "";
|
||||||
transition: transform 0.16s ease;
|
transition: transform 0.16s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-switch input:checked + span {
|
.settings-switch input:checked + span {
|
||||||
background: #8c6be8;
|
background: var(--app-control);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-switch input:checked + span::after {
|
.settings-switch input:checked + span::after {
|
||||||
@@ -558,34 +642,76 @@ onBeforeUnmount(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.settings-switch input:focus-visible + span {
|
.settings-switch input:focus-visible + span {
|
||||||
outline: 2px solid #d6aa8e;
|
outline: 2px solid var(--app-accent-muted);
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-select {
|
.settings-select {
|
||||||
width: 158px;
|
width: 158px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
border: 1px solid #e0dbcf;
|
border: 1px solid var(--app-border-strong);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: #fff;
|
background: var(--app-surface);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
box-shadow: 0 1px 4px rgba(56, 52, 46, 0.1);
|
box-shadow: 0 1px 4px color-mix(in srgb, var(--app-shadow) 58%, transparent);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-select:focus {
|
.settings-select:focus {
|
||||||
border-color: #d6aa8e;
|
border-color: var(--app-accent-muted);
|
||||||
box-shadow: 0 0 0 3px rgba(191, 106, 59, 0.1);
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--app-accent) 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-swatch-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-reset-btn,
|
||||||
|
.theme-swatch {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-reset-btn {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--app-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-reset-btn:hover {
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-swatch {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border-radius: 999px;
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px color-mix(in srgb, var(--app-text) 18%, transparent),
|
||||||
|
0 2px 5px color-mix(in srgb, var(--app-shadow) 70%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-swatch-active {
|
||||||
|
outline: 2px solid var(--app-accent);
|
||||||
|
outline-offset: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-empty-page {
|
.settings-empty-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #8c877d;
|
color: var(--app-muted);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +735,7 @@ onBeforeUnmount(() => {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid #e8e4da;
|
border-bottom: 1px solid var(--app-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-menu-title {
|
.settings-menu-title {
|
||||||
|
|||||||
@@ -57,15 +57,15 @@ function onClick() {
|
|||||||
<template v-if="entry.is_dir">
|
<template v-if="entry.is_dir">
|
||||||
<!-- Folder label -->
|
<!-- Folder label -->
|
||||||
<div
|
<div
|
||||||
class="flex items-center gap-1 px-2 py-1 rounded-md text-sm text-[#5c574e] hover:bg-[#ede8de] cursor-pointer select-none"
|
class="tree-folder flex items-center gap-1 px-2 py-1 rounded-md text-sm cursor-pointer select-none"
|
||||||
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
class="ri-arrow-right-s-line text-sm text-[#b8b3a8] transition-transform shrink-0"
|
class="tree-arrow ri-arrow-right-s-line text-sm transition-transform shrink-0"
|
||||||
:class="expanded && entry.children ? 'rotate-90' : ''"
|
:class="expanded && entry.children ? 'rotate-90' : ''"
|
||||||
></i>
|
></i>
|
||||||
<i class="ri-folder-line text-base text-[#bf6a3b]/70 shrink-0"></i>
|
<i class="tree-entry-icon ri-folder-line text-base shrink-0"></i>
|
||||||
<span class="truncate">{{ entry.name }}</span>
|
<span class="truncate">{{ entry.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -87,15 +87,47 @@ function onClick() {
|
|||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
:class="[
|
:class="[
|
||||||
'flex items-center gap-1.5 px-2 py-1 rounded-md text-sm cursor-pointer transition-colors',
|
'tree-file flex items-center gap-1.5 px-2 py-1 rounded-md text-sm cursor-pointer transition-colors',
|
||||||
isActive
|
isActive
|
||||||
? 'bg-[#ede8de] text-[#bf6a3b] font-medium'
|
? 'tree-file-active font-medium'
|
||||||
: 'text-[#8c877d] hover:bg-[#ede8de] hover:text-[#38342e]',
|
: 'tree-file-idle',
|
||||||
]"
|
]"
|
||||||
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
:style="{ paddingLeft: `${depth * 16 + 8}px` }"
|
||||||
@click="onClick"
|
@click="onClick"
|
||||||
>
|
>
|
||||||
<i class="ri-file-line text-sm text-[#bf6a3b]/60 shrink-0"></i>
|
<i class="tree-entry-icon ri-file-line text-sm shrink-0"></i>
|
||||||
<span class="truncate">{{ entry.name }}</span>
|
<span class="truncate">{{ entry.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.tree-folder {
|
||||||
|
color: var(--app-text-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-folder:hover {
|
||||||
|
background: var(--app-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-arrow {
|
||||||
|
color: var(--app-subtle);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-entry-icon {
|
||||||
|
color: color-mix(in srgb, var(--app-accent) 68%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-idle {
|
||||||
|
color: var(--app-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-idle:hover {
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-file-active {
|
||||||
|
background: var(--app-hover);
|
||||||
|
color: var(--app-accent);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
202
src/tailwind.css
202
src/tailwind.css
@@ -2,9 +2,193 @@
|
|||||||
@source "../src/**/*.vue";
|
@source "../src/**/*.vue";
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
Global base styles — "Soft Stone" light theme
|
Global base styles and app themes
|
||||||
============================================ */
|
============================================ */
|
||||||
|
|
||||||
|
:root,
|
||||||
|
:root[data-theme="warm"],
|
||||||
|
:root[data-theme="system"] {
|
||||||
|
color-scheme: light;
|
||||||
|
--app-bg: #faf9f6;
|
||||||
|
--app-text: #38342e;
|
||||||
|
--app-text-strong: #2d2a26;
|
||||||
|
--app-text-soft: #5c574e;
|
||||||
|
--app-muted: #8c877d;
|
||||||
|
--app-subtle: #b8b3a8;
|
||||||
|
--app-border: #e8e4da;
|
||||||
|
--app-border-strong: #e0dbcf;
|
||||||
|
--app-sidebar-bg: #f4f1eb;
|
||||||
|
--app-panel-bg: #faf9f6;
|
||||||
|
--app-surface: #fffdfa;
|
||||||
|
--app-surface-muted: #f4f1ea;
|
||||||
|
--app-hover: #ede8dd;
|
||||||
|
--app-active-bg: #fdf0e5;
|
||||||
|
--app-accent: #bf6a3b;
|
||||||
|
--app-accent-muted: #d6aa8e;
|
||||||
|
--app-accent-strong: #9f4f28;
|
||||||
|
--app-link: #7a55d9;
|
||||||
|
--app-link-hover: #6342bd;
|
||||||
|
--app-control: #8c6be8;
|
||||||
|
--app-control-hover: #7a55d9;
|
||||||
|
--app-control-contrast: #ffffff;
|
||||||
|
--app-selection: #e8d5b0;
|
||||||
|
--app-overlay: rgba(56, 52, 46, 0.22);
|
||||||
|
--app-toast-bg: rgba(56, 52, 46, 0.8);
|
||||||
|
--app-shadow: rgba(56, 52, 46, 0.16);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="white"] {
|
||||||
|
color-scheme: light;
|
||||||
|
--app-bg: #ffffff;
|
||||||
|
--app-text: #202124;
|
||||||
|
--app-text-strong: #111315;
|
||||||
|
--app-text-soft: #4f5660;
|
||||||
|
--app-muted: #767d86;
|
||||||
|
--app-subtle: #a4aab2;
|
||||||
|
--app-border: #e8eaed;
|
||||||
|
--app-border-strong: #d7dbe0;
|
||||||
|
--app-sidebar-bg: #f7f8fa;
|
||||||
|
--app-panel-bg: #ffffff;
|
||||||
|
--app-surface: #ffffff;
|
||||||
|
--app-surface-muted: #f4f6f8;
|
||||||
|
--app-hover: #eef1f4;
|
||||||
|
--app-active-bg: #e9f1ff;
|
||||||
|
--app-accent: #2563eb;
|
||||||
|
--app-accent-muted: #93b4f8;
|
||||||
|
--app-accent-strong: #1d4ed8;
|
||||||
|
--app-link: #2563eb;
|
||||||
|
--app-link-hover: #1e40af;
|
||||||
|
--app-control: #2563eb;
|
||||||
|
--app-control-hover: #1d4ed8;
|
||||||
|
--app-control-contrast: #ffffff;
|
||||||
|
--app-selection: #bfdbfe;
|
||||||
|
--app-overlay: rgba(17, 24, 39, 0.2);
|
||||||
|
--app-toast-bg: rgba(17, 24, 39, 0.82);
|
||||||
|
--app-shadow: rgba(17, 24, 39, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="gray"] {
|
||||||
|
color-scheme: light;
|
||||||
|
--app-bg: #f3f4f6;
|
||||||
|
--app-text: #2f3337;
|
||||||
|
--app-text-strong: #1f2328;
|
||||||
|
--app-text-soft: #555b62;
|
||||||
|
--app-muted: #737a82;
|
||||||
|
--app-subtle: #a3a9b0;
|
||||||
|
--app-border: #dde1e6;
|
||||||
|
--app-border-strong: #cfd5dc;
|
||||||
|
--app-sidebar-bg: #e9ecef;
|
||||||
|
--app-panel-bg: #f3f4f6;
|
||||||
|
--app-surface: #ffffff;
|
||||||
|
--app-surface-muted: #eceff2;
|
||||||
|
--app-hover: #dfe3e8;
|
||||||
|
--app-active-bg: #e5ecf5;
|
||||||
|
--app-accent: #4f6f93;
|
||||||
|
--app-accent-muted: #8aa1ba;
|
||||||
|
--app-accent-strong: #3b5674;
|
||||||
|
--app-link: #4f6f93;
|
||||||
|
--app-link-hover: #334e68;
|
||||||
|
--app-control: #4f6f93;
|
||||||
|
--app-control-hover: #3b5674;
|
||||||
|
--app-control-contrast: #ffffff;
|
||||||
|
--app-selection: #cbd5e1;
|
||||||
|
--app-overlay: rgba(31, 35, 40, 0.24);
|
||||||
|
--app-toast-bg: rgba(31, 35, 40, 0.82);
|
||||||
|
--app-shadow: rgba(31, 35, 40, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="black"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--app-bg: #101113;
|
||||||
|
--app-text: #e7e9ed;
|
||||||
|
--app-text-strong: #f6f7f9;
|
||||||
|
--app-text-soft: #c8ccd2;
|
||||||
|
--app-muted: #9aa1aa;
|
||||||
|
--app-subtle: #6f7782;
|
||||||
|
--app-border: #2b2f35;
|
||||||
|
--app-border-strong: #383d45;
|
||||||
|
--app-sidebar-bg: #17191d;
|
||||||
|
--app-panel-bg: #101113;
|
||||||
|
--app-surface: #1b1e23;
|
||||||
|
--app-surface-muted: #242830;
|
||||||
|
--app-hover: #242830;
|
||||||
|
--app-active-bg: #253049;
|
||||||
|
--app-accent: #8ab4ff;
|
||||||
|
--app-accent-muted: #5d7fac;
|
||||||
|
--app-accent-strong: #bdd4ff;
|
||||||
|
--app-link: #9dbdff;
|
||||||
|
--app-link-hover: #c6d8ff;
|
||||||
|
--app-control: #7c9cff;
|
||||||
|
--app-control-hover: #9db8ff;
|
||||||
|
--app-control-contrast: #101113;
|
||||||
|
--app-selection: #30415f;
|
||||||
|
--app-overlay: rgba(0, 0, 0, 0.48);
|
||||||
|
--app-toast-bg: rgba(0, 0, 0, 0.82);
|
||||||
|
--app-shadow: rgba(0, 0, 0, 0.36);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="gruvbox"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--app-bg: #282828;
|
||||||
|
--app-text: #ebdbb2;
|
||||||
|
--app-text-strong: #fbf1c7;
|
||||||
|
--app-text-soft: #d5c4a1;
|
||||||
|
--app-muted: #bdae93;
|
||||||
|
--app-subtle: #928374;
|
||||||
|
--app-border: #3c3836;
|
||||||
|
--app-border-strong: #504945;
|
||||||
|
--app-sidebar-bg: #32302f;
|
||||||
|
--app-panel-bg: #282828;
|
||||||
|
--app-surface: #1d2021;
|
||||||
|
--app-surface-muted: #3c3836;
|
||||||
|
--app-hover: #3c3836;
|
||||||
|
--app-active-bg: #4a3627;
|
||||||
|
--app-accent: #fe8019;
|
||||||
|
--app-accent-muted: #d79921;
|
||||||
|
--app-accent-strong: #fabd2f;
|
||||||
|
--app-link: #83a598;
|
||||||
|
--app-link-hover: #8ec07c;
|
||||||
|
--app-control: #d65d0e;
|
||||||
|
--app-control-hover: #fe8019;
|
||||||
|
--app-control-contrast: #1d2021;
|
||||||
|
--app-selection: #665c54;
|
||||||
|
--app-overlay: rgba(29, 32, 33, 0.52);
|
||||||
|
--app-toast-bg: rgba(29, 32, 33, 0.9);
|
||||||
|
--app-shadow: rgba(0, 0, 0, 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root[data-theme="system"] {
|
||||||
|
color-scheme: dark;
|
||||||
|
--app-bg: #101113;
|
||||||
|
--app-text: #e7e9ed;
|
||||||
|
--app-text-strong: #f6f7f9;
|
||||||
|
--app-text-soft: #c8ccd2;
|
||||||
|
--app-muted: #9aa1aa;
|
||||||
|
--app-subtle: #6f7782;
|
||||||
|
--app-border: #2b2f35;
|
||||||
|
--app-border-strong: #383d45;
|
||||||
|
--app-sidebar-bg: #17191d;
|
||||||
|
--app-panel-bg: #101113;
|
||||||
|
--app-surface: #1b1e23;
|
||||||
|
--app-surface-muted: #242830;
|
||||||
|
--app-hover: #242830;
|
||||||
|
--app-active-bg: #253049;
|
||||||
|
--app-accent: #8ab4ff;
|
||||||
|
--app-accent-muted: #5d7fac;
|
||||||
|
--app-accent-strong: #bdd4ff;
|
||||||
|
--app-link: #9dbdff;
|
||||||
|
--app-link-hover: #c6d8ff;
|
||||||
|
--app-control: #7c9cff;
|
||||||
|
--app-control-hover: #9db8ff;
|
||||||
|
--app-control-contrast: #101113;
|
||||||
|
--app-selection: #30415f;
|
||||||
|
--app-overlay: rgba(0, 0, 0, 0.48);
|
||||||
|
--app-toast-bg: rgba(0, 0, 0, 0.82);
|
||||||
|
--app-shadow: rgba(0, 0, 0, 0.36);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
/* ── Font stack: refined system serif for Chinese + English ── */
|
/* ── Font stack: refined system serif for Chinese + English ── */
|
||||||
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",
|
||||||
@@ -16,14 +200,14 @@ html {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background: #faf9f6;
|
background: var(--app-bg);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Selection ── */
|
/* ── Selection ── */
|
||||||
::selection {
|
::selection {
|
||||||
background: #e8d5b0;
|
background: var(--app-selection);
|
||||||
color: #38342e;
|
color: var(--app-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ============================================
|
/* ============================================
|
||||||
@@ -33,7 +217,7 @@ body {
|
|||||||
/* Firefox */
|
/* Firefox */
|
||||||
* {
|
* {
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
scrollbar-color: #d4cfc4 transparent;
|
scrollbar-color: var(--app-border-strong) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* WebKit (Chrome, Edge, Safari, Tauri WebView2) */
|
/* WebKit (Chrome, Edge, Safari, Tauri WebView2) */
|
||||||
@@ -47,17 +231,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #d4cfc4;
|
background: var(--app-border-strong);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
transition: background 0.2s ease;
|
transition: background 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #b0a99b;
|
background: var(--app-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:active {
|
::-webkit-scrollbar-thumb:active {
|
||||||
background: #9a9282;
|
background: var(--app-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
::-webkit-scrollbar-corner {
|
||||||
|
|||||||
Reference in New Issue
Block a user