feat: 新增侧边栏宽度调整、文件树排序与隐藏项显示功能
- 允许用户通过拖拽调整侧边栏宽度,并双击重置为默认宽度 - 文件树目录项现在按类型(目录优先)和名称(自然排序)自动排序 - 新增“显示/隐藏隐藏项”按钮,可过滤以“.”开头及“node_modules”的项 - 为编辑器、预览及整体应用统一设置字体大小变量,提升视觉一致性 - 移除未使用的导入以优化代码结构
This commit is contained in:
parent
7096b7f6c1
commit
fd6aad27f8
112
src/App.vue
112
src/App.vue
|
|
@ -1,5 +1,5 @@
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, onUnmounted, ref, watch } from "vue";
|
||||||
import Toolbar from "./components/Toolbar.vue";
|
import Toolbar from "./components/Toolbar.vue";
|
||||||
import FileTree from "./components/FileTree.vue";
|
import FileTree from "./components/FileTree.vue";
|
||||||
import TabContainer from "./components/TabContainer.vue";
|
import TabContainer from "./components/TabContainer.vue";
|
||||||
|
|
@ -22,6 +22,7 @@ const showGit = ref(false);
|
||||||
const showSettings = ref(false);
|
const showSettings = ref(false);
|
||||||
const showHistory = ref(false); // 历史记录弹窗
|
const showHistory = ref(false); // 历史记录弹窗
|
||||||
const historyDirs = ref([]); // 历史目录列表
|
const historyDirs = ref([]); // 历史目录列表
|
||||||
|
const lastOpenedDirCache = ref('');
|
||||||
|
|
||||||
const themeColors = [
|
const themeColors = [
|
||||||
'#1890ff', // 默认蓝
|
'#1890ff', // 默认蓝
|
||||||
|
|
@ -36,6 +37,58 @@ const themeColors = [
|
||||||
|
|
||||||
const antdTheme = ref(getAntdTheme());
|
const antdTheme = ref(getAntdTheme());
|
||||||
|
|
||||||
|
const DEFAULT_SIDEBAR_WIDTH = 250;
|
||||||
|
const sidebarWidth = ref(DEFAULT_SIDEBAR_WIDTH);
|
||||||
|
const isResizingSidebar = ref(false);
|
||||||
|
const sidebarResizeState = ref({ startX: 0, startWidth: DEFAULT_SIDEBAR_WIDTH });
|
||||||
|
|
||||||
|
const clampSidebarWidth = (width) => {
|
||||||
|
const minWidth = 180;
|
||||||
|
const maxWidth = 720;
|
||||||
|
return Math.min(maxWidth, Math.max(minWidth, Math.round(width)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopSidebarResize = () => {
|
||||||
|
if (!isResizingSidebar.value) return;
|
||||||
|
isResizingSidebar.value = false;
|
||||||
|
window.removeEventListener('pointermove', handleSidebarPointerMove);
|
||||||
|
window.removeEventListener('pointerup', handleSidebarPointerUp);
|
||||||
|
window.removeEventListener('pointercancel', handleSidebarPointerUp);
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSidebarPointerMove = (e) => {
|
||||||
|
if (!isResizingSidebar.value) return;
|
||||||
|
const deltaX = e.clientX - sidebarResizeState.value.startX;
|
||||||
|
sidebarWidth.value = clampSidebarWidth(sidebarResizeState.value.startWidth + deltaX);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSidebarPointerUp = () => {
|
||||||
|
stopSidebarResize();
|
||||||
|
saveGlobalConfig();
|
||||||
|
};
|
||||||
|
|
||||||
|
const startSidebarResize = (e) => {
|
||||||
|
if (e.button !== 0) return;
|
||||||
|
e.preventDefault();
|
||||||
|
isResizingSidebar.value = true;
|
||||||
|
sidebarResizeState.value = { startX: e.clientX, startWidth: sidebarWidth.value };
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
window.addEventListener('pointermove', handleSidebarPointerMove);
|
||||||
|
window.addEventListener('pointerup', handleSidebarPointerUp);
|
||||||
|
window.addEventListener('pointercancel', handleSidebarPointerUp);
|
||||||
|
try {
|
||||||
|
e.currentTarget?.setPointerCapture?.(e.pointerId);
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetSidebarWidth = () => {
|
||||||
|
sidebarWidth.value = DEFAULT_SIDEBAR_WIDTH;
|
||||||
|
saveGlobalConfig();
|
||||||
|
};
|
||||||
|
|
||||||
// 加载全局配置
|
// 加载全局配置
|
||||||
const loadGlobalConfig = async () => {
|
const loadGlobalConfig = async () => {
|
||||||
if (window.services?.loadGlobalData) {
|
if (window.services?.loadGlobalData) {
|
||||||
|
|
@ -51,6 +104,14 @@ const loadGlobalConfig = async () => {
|
||||||
historyDirs.value = data.historyDirs;
|
historyDirs.value = data.historyDirs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.lastOpenedDir) {
|
||||||
|
lastOpenedDirCache.value = data.lastOpenedDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.layout?.sidebarWidth) {
|
||||||
|
sidebarWidth.value = clampSidebarWidth(data.layout.sidebarWidth);
|
||||||
|
}
|
||||||
|
|
||||||
// 自动加载上次打开的目录
|
// 自动加载上次打开的目录
|
||||||
if (data.lastOpenedDir && window.services.pathExists(data.lastOpenedDir)) {
|
if (data.lastOpenedDir && window.services.pathExists(data.lastOpenedDir)) {
|
||||||
await openDirectory(data.lastOpenedDir);
|
await openDirectory(data.lastOpenedDir);
|
||||||
|
|
@ -67,8 +128,11 @@ const loadGlobalConfig = async () => {
|
||||||
const saveGlobalConfig = async () => {
|
const saveGlobalConfig = async () => {
|
||||||
if (window.services?.saveGlobalData) {
|
if (window.services?.saveGlobalData) {
|
||||||
await window.services.saveGlobalData({
|
await window.services.saveGlobalData({
|
||||||
lastOpenedDir: state.rootPath, // from useFileTree
|
lastOpenedDir: lastOpenedDirCache.value,
|
||||||
historyDirs: historyDirs.value,
|
historyDirs: historyDirs.value,
|
||||||
|
layout: {
|
||||||
|
sidebarWidth: sidebarWidth.value
|
||||||
|
},
|
||||||
theme: {
|
theme: {
|
||||||
primaryColor: primaryColor.value,
|
primaryColor: primaryColor.value,
|
||||||
isDark: isDark.value
|
isDark: isDark.value
|
||||||
|
|
@ -81,6 +145,7 @@ const saveGlobalConfig = async () => {
|
||||||
const openDirectory = async (path) => {
|
const openDirectory = async (path) => {
|
||||||
await setRootPath(path);
|
await setRootPath(path);
|
||||||
await loadConfig(path);
|
await loadConfig(path);
|
||||||
|
lastOpenedDirCache.value = path;
|
||||||
|
|
||||||
// 更新历史记录
|
// 更新历史记录
|
||||||
if (!historyDirs.value.includes(path)) {
|
if (!historyDirs.value.includes(path)) {
|
||||||
|
|
@ -126,6 +191,10 @@ onMounted(() => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
stopSidebarResize();
|
||||||
|
});
|
||||||
|
|
||||||
const handleSelectDirectory = async () => {
|
const handleSelectDirectory = async () => {
|
||||||
if (window.services?.selectDirectory) {
|
if (window.services?.selectDirectory) {
|
||||||
const paths = window.services.selectDirectory();
|
const paths = window.services.selectDirectory();
|
||||||
|
|
@ -157,10 +226,17 @@ const { state } = useFileTree(); // 需要获取 rootPath 用于保存
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<div class="sidebar">
|
<div class="sidebar" :style="{ width: `${sidebarWidth}px` }">
|
||||||
<FileTree />
|
<FileTree />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="sidebar-resizer"
|
||||||
|
:class="{ resizing: isResizingSidebar }"
|
||||||
|
@pointerdown="startSidebarResize"
|
||||||
|
@dblclick="resetSidebarWidth"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="editor-area">
|
<div class="editor-area">
|
||||||
<TabContainer />
|
<TabContainer />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -231,13 +307,39 @@ const { state } = useFileTree(); // 需要获取 rootPath 用于保存
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar {
|
.sidebar {
|
||||||
width: 250px;
|
flex: 0 0 auto;
|
||||||
background-color: var(--card-background, #fff);
|
background-color: var(--card-background, #fff);
|
||||||
border-right: 1px solid var(--border-color, #e8e8e8);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-resizer {
|
||||||
|
width: 6px;
|
||||||
|
cursor: col-resize;
|
||||||
|
position: relative;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-resizer::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 2px;
|
||||||
|
width: 1px;
|
||||||
|
background: var(--border-color, #e8e8e8);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-resizer:hover::before,
|
||||||
|
.sidebar-resizer.resizing::before {
|
||||||
|
left: 1px;
|
||||||
|
width: 2px;
|
||||||
|
background: var(--primary-color, #1890ff);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.editor-area {
|
.editor-area {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
<codemirror
|
<codemirror
|
||||||
v-model="code"
|
v-model="code"
|
||||||
placeholder="请输入 Markdown 内容..."
|
placeholder="请输入 Markdown 内容..."
|
||||||
:style="{ height: '100%', fontSize: '14px' }"
|
:style="{ height: '100%', fontSize: 'var(--editor-font-size)' }"
|
||||||
:autofocus="true"
|
:autofocus="true"
|
||||||
:indent-with-tab="true"
|
:indent-with-tab="true"
|
||||||
:tab-size="2"
|
:tab-size="2"
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,22 @@
|
||||||
<div class="file-tree-container">
|
<div class="file-tree-container">
|
||||||
<div class="tree-header">
|
<div class="tree-header">
|
||||||
<span class="title">资源管理器</span>
|
<span class="title">资源管理器</span>
|
||||||
|
<div class="header-actions">
|
||||||
<a-button type="text" size="small" @click="handleRefresh" title="刷新">
|
<a-button type="text" size="small" @click="handleRefresh" title="刷新">
|
||||||
<template #icon><ReloadOutlined /></template>
|
<template #icon><ReloadOutlined /></template>
|
||||||
</a-button>
|
</a-button>
|
||||||
|
<a-button
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
@click="toggleShowHiddenItems"
|
||||||
|
:title="showHiddenItems ? '不显示隐藏项' : '显示隐藏项'"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<EyeOutlined v-if="showHiddenItems" />
|
||||||
|
<EyeInvisibleOutlined v-else />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!state.rootPath" class="empty-tip">
|
<div v-if="!state.rootPath" class="empty-tip">
|
||||||
|
|
@ -67,7 +80,7 @@
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
import { ReloadOutlined } from '@ant-design/icons-vue';
|
import { EyeInvisibleOutlined, EyeOutlined, ReloadOutlined } from '@ant-design/icons-vue';
|
||||||
import { useFileTree } from '../composables/useFileTree';
|
import { useFileTree } from '../composables/useFileTree';
|
||||||
import { useTabs } from '../composables/useTabs';
|
import { useTabs } from '../composables/useTabs';
|
||||||
import { useConfig } from '../composables/useConfig';
|
import { useConfig } from '../composables/useConfig';
|
||||||
|
|
@ -75,7 +88,7 @@ import { message, Modal } from 'ant-design-vue';
|
||||||
|
|
||||||
const { state, loadDirectory, refresh } = useFileTree();
|
const { state, loadDirectory, refresh } = useFileTree();
|
||||||
const { openFile } = useTabs();
|
const { openFile } = useTabs();
|
||||||
const { config, addPinnedFile, removePinnedFile } = useConfig();
|
const { config, addPinnedFile, removePinnedFile, setShowHiddenItems } = useConfig();
|
||||||
|
|
||||||
const modalVisible = ref(false);
|
const modalVisible = ref(false);
|
||||||
const modalTitle = ref('');
|
const modalTitle = ref('');
|
||||||
|
|
@ -89,11 +102,41 @@ const transformNodes = (nodes) => {
|
||||||
return nodes.map(node => ({
|
return nodes.map(node => ({
|
||||||
...node,
|
...node,
|
||||||
isLeaf: node.type === 'file',
|
isLeaf: node.type === 'file',
|
||||||
children: node.children ? transformNodes(node.children) : undefined
|
children: node.children === undefined ? undefined : transformNodes(node.children)
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const treeData = computed(() => transformNodes(state.nodes));
|
const showHiddenItems = computed(() => !!config.fileTree?.showHiddenItems);
|
||||||
|
|
||||||
|
const isHiddenItemName = (name) => {
|
||||||
|
if (!name) return false;
|
||||||
|
if (name.startsWith('.')) return true;
|
||||||
|
if (name === 'node_modules') return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterNodes = (nodes) => {
|
||||||
|
if (!Array.isArray(nodes) || nodes.length === 0) return [];
|
||||||
|
if (showHiddenItems.value) {
|
||||||
|
return nodes.map(node => ({
|
||||||
|
...node,
|
||||||
|
children: node.children === undefined ? undefined : filterNodes(node.children)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
.filter(node => !isHiddenItemName(node?.name))
|
||||||
|
.map(node => ({
|
||||||
|
...node,
|
||||||
|
children: node.children === undefined ? undefined : filterNodes(node.children)
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const treeData = computed(() => transformNodes(filterNodes(state.nodes)));
|
||||||
|
|
||||||
|
const toggleShowHiddenItems = async () => {
|
||||||
|
if (!state.rootPath) return;
|
||||||
|
await setShowHiddenItems(state.rootPath, !showHiddenItems.value);
|
||||||
|
};
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
await refresh();
|
await refresh();
|
||||||
|
|
@ -102,7 +145,7 @@ const handleRefresh = async () => {
|
||||||
const onLoadData = (treeNode) => {
|
const onLoadData = (treeNode) => {
|
||||||
return new Promise(async (resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
// 使用 key (path) 来加载
|
// 使用 key (path) 来加载
|
||||||
if (treeNode.dataRef.children && treeNode.dataRef.children.length > 0) {
|
if (treeNode.dataRef.children !== undefined) {
|
||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -253,6 +296,12 @@ const handleModalOk = async () => {
|
||||||
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
border-bottom: 1px solid var(--border-color, #f0f0f0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
@ -284,7 +333,8 @@ const handleModalOk = async () => {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex: 1; /* allow it to take remaining space but not force width */
|
flex: 1; /* 让文字占满剩余空间 */
|
||||||
|
min-width: 0; /* 关键:允许在 flex 容器中收缩,从而触发省略号 */
|
||||||
margin-left: 4px; /* spacing between icon and text */
|
margin-left: 4px; /* spacing between icon and text */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,6 +348,21 @@ const handleModalOk = async () => {
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 关键:把 Title 容器也变成可收缩的 flex 子项,否则长文件名会把布局“挤歪” */
|
||||||
|
:deep(.ant-tree-title) {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.ant-dropdown-trigger) {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.ant-tree-node-content-wrapper:hover) {
|
:deep(.ant-tree-node-content-wrapper:hover) {
|
||||||
background-color: var(--hover-background, rgba(0, 0, 0, 0.04));
|
background-color: var(--hover-background, rgba(0, 0, 0, 0.04));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ const handleClick = (event) => {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
font-size: var(--preview-font-size);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
background-color: var(--card-background);
|
background-color: var(--card-background);
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +124,7 @@ const handleClick = (event) => {
|
||||||
.markdown-preview code {
|
.markdown-preview code {
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 85%;
|
font-size: var(--preview-code-font-size);
|
||||||
background-color: var(--app-background);
|
background-color: var(--app-background);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
font-family: SFMono-Regular, Consolas, "Liberation Mono", Menlo, monospace;
|
||||||
|
|
@ -132,7 +133,7 @@ const handleClick = (event) => {
|
||||||
.markdown-preview pre {
|
.markdown-preview pre {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
font-size: 85%;
|
font-size: var(--preview-code-font-size);
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
background-color: var(--app-background);
|
background-color: var(--app-background);
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
import { ref, reactive } from 'vue';
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
|
const defaultFileTreeConfig = {
|
||||||
|
showHiddenItems: false,
|
||||||
|
};
|
||||||
|
|
||||||
const config = reactive({
|
const config = reactive({
|
||||||
pinnedFiles: [], // Array of string (file paths)
|
pinnedFiles: [], // Array of string (file paths)
|
||||||
recentFiles: [], // Array of objects { path, lastOpened }
|
recentFiles: [], // Array of objects { path, lastOpened }
|
||||||
|
fileTree: { ...defaultFileTreeConfig },
|
||||||
});
|
});
|
||||||
|
|
||||||
export function useConfig() {
|
export function useConfig() {
|
||||||
|
|
@ -12,9 +17,11 @@ export function useConfig() {
|
||||||
if (data) {
|
if (data) {
|
||||||
config.pinnedFiles = data.pinnedFiles || [];
|
config.pinnedFiles = data.pinnedFiles || [];
|
||||||
config.recentFiles = data.recentFiles || [];
|
config.recentFiles = data.recentFiles || [];
|
||||||
|
config.fileTree = { ...defaultFileTreeConfig, ...(data.fileTree || {}) };
|
||||||
} else {
|
} else {
|
||||||
config.pinnedFiles = [];
|
config.pinnedFiles = [];
|
||||||
config.recentFiles = [];
|
config.recentFiles = [];
|
||||||
|
config.fileTree = { ...defaultFileTreeConfig };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -23,6 +30,7 @@ export function useConfig() {
|
||||||
await window.services.saveConfig(rootPath, {
|
await window.services.saveConfig(rootPath, {
|
||||||
pinnedFiles: config.pinnedFiles,
|
pinnedFiles: config.pinnedFiles,
|
||||||
recentFiles: config.recentFiles,
|
recentFiles: config.recentFiles,
|
||||||
|
fileTree: config.fileTree,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -56,12 +64,19 @@ export function useConfig() {
|
||||||
await saveConfig(rootPath);
|
await saveConfig(rootPath);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setShowHiddenItems = async (rootPath, showHiddenItems) => {
|
||||||
|
if (!rootPath) return;
|
||||||
|
config.fileTree.showHiddenItems = !!showHiddenItems;
|
||||||
|
await saveConfig(rootPath);
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
config,
|
config,
|
||||||
loadConfig,
|
loadConfig,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
addPinnedFile,
|
addPinnedFile,
|
||||||
removePinnedFile,
|
removePinnedFile,
|
||||||
addRecentFile
|
addRecentFile,
|
||||||
|
setShowHiddenItems
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive } from 'vue';
|
||||||
|
|
||||||
// 全局单例状态
|
// 全局单例状态
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
|
|
@ -9,6 +9,15 @@ const state = reactive({
|
||||||
loading: false
|
loading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const compareFileTreeItems = (a, b) => {
|
||||||
|
const aIsDir = a?.type === 'directory';
|
||||||
|
const bIsDir = b?.type === 'directory';
|
||||||
|
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1;
|
||||||
|
const aName = a?.name || '';
|
||||||
|
const bName = b?.name || '';
|
||||||
|
return aName.localeCompare(bName, undefined, { numeric: true, sensitivity: 'base' });
|
||||||
|
};
|
||||||
|
|
||||||
export function useFileTree() {
|
export function useFileTree() {
|
||||||
|
|
||||||
// 设置根目录并加载
|
// 设置根目录并加载
|
||||||
|
|
@ -25,6 +34,9 @@ export function useFileTree() {
|
||||||
state.loading = true;
|
state.loading = true;
|
||||||
try {
|
try {
|
||||||
const items = await window.services.readDirectory(path);
|
const items = await window.services.readDirectory(path);
|
||||||
|
if (Array.isArray(items)) {
|
||||||
|
items.sort(compareFileTreeItems);
|
||||||
|
}
|
||||||
|
|
||||||
if (path === state.rootPath) {
|
if (path === state.rootPath) {
|
||||||
// 如果是根目录,直接替换
|
// 如果是根目录,直接替换
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ export function useTheme() {
|
||||||
colorTextSecondary: textColorSecondary,
|
colorTextSecondary: textColorSecondary,
|
||||||
colorBorder: borderColor,
|
colorBorder: borderColor,
|
||||||
colorFillTertiary: hoverBackground,
|
colorFillTertiary: hoverBackground,
|
||||||
|
fontSize: 15,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@
|
||||||
--border-color: #f0f0f0;
|
--border-color: #f0f0f0;
|
||||||
--hover-background: rgba(0, 0, 0, 0.04);
|
--hover-background: rgba(0, 0, 0, 0.04);
|
||||||
--scrollbar-thumb: rgba(0, 0, 0, 0.28);
|
--scrollbar-thumb: rgba(0, 0, 0, 0.28);
|
||||||
|
--app-font-size: 15px;
|
||||||
|
--editor-font-size: 15px;
|
||||||
|
--preview-font-size: 15px;
|
||||||
|
--preview-code-font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
|
|
@ -21,6 +25,8 @@ body {
|
||||||
body {
|
body {
|
||||||
background-color: var(--app-background);
|
background-color: var(--app-background);
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
|
font-size: var(--app-font-size);
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue