侧边栏样式调整完成

This commit is contained in:
2026-07-03 11:33:40 +08:00
parent bc96296670
commit 891cb7df54
6 changed files with 103 additions and 122 deletions

View File

@@ -1,3 +1,4 @@
{ {
"topic_20260630-144952_fd0584fe8f266d6a": 1782830992641 "topic_20260630-144952_fd0584fe8f266d6a": 1782830992641,
"topic_20260703-022850_50330b4eb93f116d": 1783045730616
} }

View File

@@ -12,5 +12,9 @@
"topic_20260702-161756_b6c7f476849c7ee1": "auto", "topic_20260702-161756_b6c7f476849c7ee1": "auto",
"topic_20260702-161832_f8157b81a77b9bfe": "auto", "topic_20260702-161832_f8157b81a77b9bfe": "auto",
"topic_20260702-163743_d97b7189a52644d4": "auto", "topic_20260702-163743_d97b7189a52644d4": "auto",
"topic_20260702-165156_738d9dd96d08e07c": "auto" "topic_20260702-165156_738d9dd96d08e07c": "auto",
"topic_20260703-022850_50330b4eb93f116d": "auto",
"topic_20260703-025626_29ad0ca0ec1414e3": "auto",
"topic_20260703-031103_9a945dac33ad9fd9": "auto",
"topic_20260703-031922_1d2031ca5f2c0dd5": "auto"
} }

View File

@@ -12,5 +12,9 @@
"topic_20260702-161756_b6c7f476849c7ee1": "打开的应用设置一个最小的高度是600…", "topic_20260702-161756_b6c7f476849c7ee1": "打开的应用设置一个最小的高度是600…",
"topic_20260702-161832_f8157b81a77b9bfe": "左侧的工作目录,我希望除了名称之外…", "topic_20260702-161832_f8157b81a77b9bfe": "左侧的工作目录,我希望除了名称之外…",
"topic_20260702-163743_d97b7189a52644d4": "tiptap-editor 里的区域…", "topic_20260702-163743_d97b7189a52644d4": "tiptap-editor 里的区域…",
"topic_20260702-165156_738d9dd96d08e07c": "帮我引入@tiptap/markdo…" "topic_20260702-165156_738d9dd96d08e07c": "帮我引入@tiptap/markdo…",
"topic_20260703-022850_50330b4eb93f116d": "侧边栏展开 的时候在131行的hea…",
"topic_20260703-025626_29ad0ca0ec1414e3": "第174行用户选中的工作空间目录没…",
"topic_20260703-031103_9a945dac33ad9fd9": "添加一个重要的更新功能,现在侧边栏在…",
"topic_20260703-031922_1d2031ca5f2c0dd5": "我希望第126-228行的内容在用…"
} }

View File

@@ -1,2 +1,2 @@
[permissions] [permissions]
allow = ["Bash(pnpm approve-builds:*)"] allow = ["Bash(pnpm approve-builds:*)", "Edit"]

View File

@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed } from "vue"; import {ref, computed} from "vue";
import TreeEntry from "./TreeEntry.vue"; import TreeEntry from "./TreeEntry.vue";
interface DirEntry { interface DirEntry {
@@ -10,16 +10,16 @@ interface DirEntry {
} }
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
folderPath: string; folderPath: string;
entries: DirEntry[]; entries: DirEntry[];
content?: string; content?: string;
currentFilePath?: string | null; currentFilePath?: string | null;
}>(), }>(),
{ {
content: "", content: "",
currentFilePath: null, currentFilePath: null,
}, },
); );
const emit = defineEmits<{ const emit = defineEmits<{
@@ -27,34 +27,36 @@ const emit = defineEmits<{
openFolder: []; openFolder: [];
toggleFolder: [path: string]; toggleFolder: [path: string];
openFile: [path: string]; openFile: [path: string];
createFile: [];
createFolder: [];
}>(); }>();
// Wrap the workspace root as a top-level tree node
const rootEntry = computed<DirEntry | null>(() => {
if (!props.folderPath) return null;
const name = props.folderPath.split(/[/\\]/).pop() || props.folderPath;
return {
name,
path: props.folderPath,
is_dir: true,
children: props.entries,
};
});
// ---- sidebar view mode ---- // ---- sidebar view mode ----
type ViewMode = "folder" | "search" | "outline"; type ViewMode = "folder" | "search" | "outline";
const activeView = ref<ViewMode>("folder"); const activeView = ref<ViewMode>("folder");
const collapsed = ref(true); const expanded = ref(true);
const viewLabels: Record<ViewMode, string> = {
folder: "文件",
search: "搜索",
outline: "大纲",
};
function switchView(view: ViewMode) { function switchView(view: ViewMode) {
if (collapsed.value) { if (activeView.value === view) {
collapsed.value = false; expanded.value = !expanded.value;
activeView.value = view;
} else if (activeView.value === view) {
collapsed.value = true;
} else { } else {
activeView.value = view; activeView.value = view;
expanded.value = true;
} }
} }
function collapseSidebar() {
collapsed.value = true;
}
// ---- outline state ---- // ---- outline state ----
interface OutlineItem { interface OutlineItem {
text: string; text: string;
@@ -72,14 +74,14 @@ const headings = computed<OutlineItem[]>(() => {
// Strip any remaining markdown formatting from the heading text // Strip any remaining markdown formatting from the heading text
const text = match[2].replace(/\*{1,3}|_{1,3}|`+|~~|\[([^\]]*)\]\([^)]*\)/g, (_, linkText) => linkText || "").trim(); const text = match[2].replace(/\*{1,3}|_{1,3}|`+|~~|\[([^\]]*)\]\([^)]*\)/g, (_, linkText) => linkText || "").trim();
if (text) { if (text) {
result.push({ text, level, id: `heading-${result.length}` }); result.push({text, level, id: `heading-${result.length}`});
} }
} }
return result; return result;
}); });
function onHeadingClick(item: OutlineItem) { function onHeadingClick(item: OutlineItem) {
emit("headingClick", { text: item.text, level: item.level }); emit("headingClick", {text: item.text, level: item.level});
} }
// ---- settings modal ---- // ---- settings modal ----
@@ -88,96 +90,77 @@ const showSettings = ref(false);
<template> <template>
<aside <aside
:class="[ :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']"
'flex flex-col border-r border-[#e8e4da] bg-[#f4f1eb]/80 transition-all duration-300 ease-in-out overflow-hidden shrink-0',
collapsed ? 'w-12' : 'w-56',
]"
> >
<!-- ============ COLLAPSED: icon bar ============ --> <!-- ============ Icon bar ============ -->
<template v-if="collapsed"> <div class="w-12 shrink-0 flex flex-col items-center gap-1 py-3">
<div class="flex flex-col items-center gap-1 py-3 flex-1"> <!-- Folder -->
<!-- Folder --> <button
<button
class="p-2 rounded-md transition-colors" class="p-2 rounded-md transition-colors"
:class="activeView === 'folder' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'" :class="activeView === 'folder' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
title="文件" title="文件"
@click="switchView('folder')" @click="switchView('folder')"
> >
<i class="ri-folder-line text-base"></i> <i class="ri-folder-line text-base"></i>
</button> </button>
<!-- Search --> <!-- Search -->
<button <button
class="p-2 rounded-md transition-colors" class="p-2 rounded-md transition-colors"
:class="activeView === 'search' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'" :class="activeView === 'search' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
title="搜索" title="搜索"
@click="switchView('search')" @click="switchView('search')"
> >
<i class="ri-search-line text-base"></i> <i class="ri-search-line text-base"></i>
</button> </button>
<!-- Outline --> <!-- Outline -->
<button <button
class="p-2 rounded-md transition-colors" class="p-2 rounded-md transition-colors"
:class="activeView === 'outline' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'" :class="activeView === 'outline' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
title="大纲" title="大纲"
@click="switchView('outline')" @click="switchView('outline')"
> >
<i class="ri-menu-line text-base"></i> <i class="ri-menu-line text-base"></i>
</button> </button>
</div> <!-- ===== Settings ===== -->
</template> <button
class="p-2 rounded-md transition-colors mt-auto"
<!-- ============ EXPANDED: full sidebar ============ --> :class="activeView === 'outline' ? 'text-[#bf6a3b] bg-[#ede8de]' : 'text-[#b8b3a8] hover:text-[#5c574e] hover:bg-[#ede8de]'"
<template v-else> title="设置"
<!-- Header --> @click="showSettings = true"
<div class="flex items-center justify-between px-3 py-3 border-b border-[#e8e4da]"> >
<span class="text-xs font-semibold text-[#8c877d] uppercase tracking-wider"> <i class="ri-settings-line text-sm"></i>
{{ viewLabels[activeView] }} </button>
</span> </div>
<button
class="p-1 rounded-md text-[#8c877d] hover:text-[#38342e] hover:bg-[#e0dbcf] transition-colors"
title="折叠侧边栏"
@click="collapseSidebar"
>
<i class="ri-arrow-left-double-line text-base"></i>
</button>
</div>
<!-- ============ Expanded panel ============ -->
<div v-show="expanded" class="flex-1 flex flex-col overflow-hidden border-l border-[#e8e4da]">
<!-- ===== 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 -->
<div class="flex-1 overflow-y-auto px-2 py-2 space-y-0.5"> <div class="flex-1 overflow-y-auto px-2 py-2 space-y-0.5">
<!-- Folder path indicator --> <!-- Workspace root as top-level tree node -->
<div <TreeEntry
v-if="folderPath" v-if="rootEntry"
class="px-2 py-1 text-xs text-[#b8b3a8] truncate mb-2 border-b border-[#e8e4da] pb-2" :entry="rootEntry"
:title="folderPath" :depth="0"
> @toggle-folder="(path: string) => emit('toggleFolder', path)"
📁 {{ folderPath.split(/[/\\]/).pop() || folderPath }} @open-file="(path: string) => emit('openFile', path)"
</div> />
<!-- Empty state --> <!-- Empty state -->
<div <div
v-if="!folderPath" v-if="!folderPath"
class="px-2 py-4 text-xs text-[#b8b3a8] text-center" class="px-2 py-4 text-xs text-[#b8b3a8] 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 rounded-md bg-[#fdf0e5] text-[#bf6a3b] border border-[#d4a574]/40 hover:bg-[#fdf0e5]/80 hover:text-[#bf6a3b] transition-colors" class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-[#fdf0e5] text-[#bf6a3b] border border-[#d4a574]/40 hover:bg-[#fdf0e5]/80 hover:text-[#bf6a3b] transition-colors"
@click="$emit('openFolder')" @click="$emit('openFolder')"
> >
<i class="ri-folder-open-line"></i> <i class="ri-folder-open-line"></i>
打开文件夹 打开文件夹
</button> </button>
</div> </div>
<TreeEntry
v-for="entry in entries"
:key="entry.path"
:entry="entry"
:depth="0"
@toggle-folder="(path: string) => emit('toggleFolder', path)"
@open-file="(path: string) => emit('openFile', path)"
/>
</div> </div>
</div> </div>
@@ -187,9 +170,9 @@ const showSettings = ref(false);
<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="ri-search-line absolute left-2.5 top-1/2 -translate-y-1/2 text-sm text-[#b8b3a8]"></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="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]"
placeholder="搜索文件内容..." placeholder="搜索文件内容..."
disabled disabled
/> />
</div> </div>
</div> </div>
@@ -209,14 +192,14 @@ const showSettings = ref(false);
</template> </template>
<template v-else-if="headings.length === 0"> <template v-else-if="headings.length === 0">
<p class="text-xs text-[#b8b3a8] px-2 py-4 text-center leading-relaxed"> <p class="text-xs text-[#b8b3a8] px-2 py-4 text-center leading-relaxed">
暂无标题<br />使用 H1-H3 标题<br />自动生成大纲 暂无标题<br/>使用 H1-H3 标题<br/>自动生成大纲
</p> </p>
</template> </template>
<template v-else> <template v-else>
<div <div
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', 'px-2 py-1 rounded-md text-sm cursor-pointer hover:bg-[#ede8de] transition-colors truncate',
item.level === 1 item.level === 1
? 'text-[#38342e] font-medium ml-0' ? 'text-[#38342e] font-medium ml-0'
@@ -224,11 +207,11 @@ const showSettings = ref(false);
? 'text-[#8c877d] ml-3' ? 'text-[#8c877d] ml-3'
: 'text-[#b8b3a8] ml-6 text-xs', : 'text-[#b8b3a8] ml-6 text-xs',
]" ]"
:title="item.text" :title="item.text"
@click="onHeadingClick(item)" @click="onHeadingClick(item)"
> >
<span <span
:class="[ :class="[
'inline-block w-1.5 h-1.5 rounded-full mr-1.5 align-middle', 'inline-block w-1.5 h-1.5 rounded-full mr-1.5 align-middle',
item.level === 1 item.level === 1
? 'bg-[#bf6a3b]' ? 'bg-[#bf6a3b]'
@@ -241,33 +224,22 @@ const showSettings = ref(false);
</div> </div>
</template> </template>
</div> </div>
</div>
<!-- ===== BOTTOM: Settings ===== -->
<div class="border-t border-[#e8e4da] px-3 py-2">
<button
class="w-full flex items-center gap-2 px-2 py-1.5 text-xs text-[#8c877d] hover:text-[#38342e] hover:bg-[#ede8de] rounded-md transition-colors"
@click="showSettings = true"
>
<i class="ri-settings-line text-sm"></i>
设置
</button>
</div>
</template>
</aside> </aside>
<!-- ===== SETTINGS MODAL ===== --> <!-- ===== SETTINGS MODAL ===== -->
<Teleport to="body"> <Teleport to="body">
<div <div
v-if="showSettings" v-if="showSettings"
class="fixed inset-0 z-50 flex items-center justify-center bg-[#38342e]/25 backdrop-blur-sm" class="fixed inset-0 z-50 flex items-center justify-center bg-[#38342e]/25 backdrop-blur-sm"
@click.self="showSettings = false" @click.self="showSettings = false"
> >
<div class="bg-white border border-[#e0dbcf] rounded-xl shadow-2xl w-80 max-h-[70vh] overflow-y-auto"> <div class="bg-white border border-[#e0dbcf] rounded-xl shadow-2xl w-80 max-h-[70vh] overflow-y-auto">
<div class="flex items-center justify-between px-4 py-3 border-b border-[#e8e4da]"> <div class="flex items-center justify-between px-4 py-3 border-b border-[#e8e4da]">
<span class="text-sm font-semibold text-[#38342e]">设置</span> <span class="text-sm font-semibold text-[#38342e]">设置</span>
<button <button
class="p-1 rounded-md text-[#8c877d] hover:text-[#38342e] hover:bg-[#e0dbcf] transition-colors" class="p-1 rounded-md text-[#8c877d] hover:text-[#38342e] hover:bg-[#e0dbcf] transition-colors"
@click="showSettings = false" @click="showSettings = false"
> >
<i class="ri-close-line text-base"></i> <i class="ri-close-line text-base"></i>
</button> </button>

View File

@@ -23,7 +23,7 @@ const emit = defineEmits<{
openFile: [path: string]; openFile: [path: string];
}>(); }>();
const expanded = ref(false); const expanded = ref(true);
// Auto-expand when children are loaded for the first time // Auto-expand when children are loaded for the first time
watch( watch(