feat(LivePreviewEditor): 为目录侧边栏添加宽度拖拽调整功能

添加目录侧边栏的宽度拖拽调整支持,提升用户自定义布局的灵活性。
- 新增拖拽手柄组件,用户可调整侧边栏宽度(150px-600px范围)
- 拖拽时提供视觉反馈(光标变化、手柄高亮)
- 移除侧边栏固定宽度样式,改为JS动态控制
- 拖拽期间禁用过渡动画以避免视觉延迟
This commit is contained in:
cfq 2026-01-30 17:43:13 +08:00
parent e1c97dd8f0
commit 2c6b978d8c
1 changed files with 65 additions and 5 deletions

View File

@ -4,8 +4,12 @@
<div class="editor-main">
<!-- 目录大纲侧边栏 -->
<div class="toc-sidebar" :class="{ collapsed: !showToc }">
<div class="toc-content">
<div
class="toc-sidebar"
:class="{ collapsed: !showToc, resizing: isResizing }"
:style="{ width: showToc ? `${tocWidth}px` : '0px' }"
>
<div class="toc-content" :style="{ width: `${tocWidth}px` }">
<div v-if="toc.length === 0" class="toc-empty">暂无大纲</div>
<div
v-for="(item, index) in toc"
@ -20,6 +24,13 @@
</div>
</div>
<!-- 拖拽手柄 -->
<div
v-if="showToc"
class="toc-resizer"
@mousedown.prevent="startResize"
></div>
<codemirror
v-model="code"
placeholder="请输入 Markdown 内容..."
@ -84,6 +95,36 @@ const { applyHeading, wrapSelection, prefixLines, insertLink } = useMarkdownActi
//
const showToc = ref(true);
const toc = shallowRef([]);
const tocWidth = ref(220);
const isResizing = ref(false);
let startX = 0;
let startWidth = 0;
const startResize = (e) => {
isResizing.value = true;
startX = e.clientX;
startWidth = tocWidth.value;
document.addEventListener('mousemove', handleResize);
document.addEventListener('mouseup', stopResize);
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
};
const handleResize = (e) => {
if (!isResizing.value) return;
const diff = e.clientX - startX;
const newWidth = Math.max(150, Math.min(600, startWidth + diff));
tocWidth.value = newWidth;
};
const stopResize = () => {
isResizing.value = false;
document.removeEventListener('mousemove', handleResize);
document.removeEventListener('mouseup', stopResize);
document.body.style.cursor = '';
document.body.style.userSelect = '';
};
const debounce = (fn, delay) => {
let timer = null;
@ -480,16 +521,20 @@ onUnmounted(() => {
}
.toc-sidebar {
width: 220px;
/* width: 220px; 由 JS 控制 */
flex-shrink: 0;
border-right: 1px solid var(--border-color);
overflow-y: auto;
overflow-x: hidden;
background-color: var(--card-background);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s;
opacity: 1;
}
.toc-sidebar.resizing {
transition: none;
}
.toc-sidebar.collapsed {
width: 0;
opacity: 0;
@ -497,10 +542,25 @@ onUnmounted(() => {
}
.toc-content {
width: 220px; /* 固定宽度,防止内容挤压 */
/* width: 220px; 由 JS 控制 */
padding: 12px 0;
}
.toc-resizer {
width: 4px;
cursor: col-resize;
background-color: transparent;
transition: background-color 0.2s;
flex-shrink: 0;
margin-left: -1px; /* 重叠边框 */
z-index: 10;
position: relative;
}
.toc-resizer:hover, .toc-sidebar.resizing + .toc-resizer {
background-color: var(--primary-color);
}
.toc-empty {
padding: 20px;
text-align: center;