feat: 实现笔记编辑器的自动保存功能与UI优化
refactor: 重构用户登录注册逻辑与数据验证 fix: 修复图片上传安全漏洞与路径处理问题 perf: 优化笔记列表分页加载与滚动性能 style: 改进侧边栏菜单的视觉设计与交互体验 chore: 更新环境变量与数据库连接配置 docs: 添加用户信息视图对象的Swagger文档 test: 增加用户注册登录的输入验证测试 ci: 配置JWT密钥环境变量与安全设置 build: 调整前端构建配置与模块加载方式
This commit is contained in:
@@ -56,12 +56,19 @@
|
||||
@upload-markdown="handleMarkdownUpload"
|
||||
@toggle-collapse="isCollapsed = !isCollapsed"
|
||||
/>
|
||||
<div class="note-list-wrapper">
|
||||
<div class="note-list-wrapper" ref="noteListWrapper" @scroll="handleScroll">
|
||||
<NoteList
|
||||
:files="groupMarkdownFiles"
|
||||
:files="displayedFiles"
|
||||
:is-user-logged-in="userStore.isLoggedIn"
|
||||
@preview="previewFile"
|
||||
/>
|
||||
<div v-if="isLoadingMore" class="loading-more">
|
||||
<el-icon class="is-loading"><Loading /></el-icon>
|
||||
<span>加载中...</span>
|
||||
</div>
|
||||
<div v-else-if="hasMoreFiles && !showEditor && !selectedFile" class="load-more-trigger">
|
||||
<el-button @click="loadMoreFiles" type="primary" plain>加载更多</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-main>
|
||||
@@ -157,7 +164,7 @@ import MoveNoteDialog from './home/dialogs/MoveNoteDialog.vue';
|
||||
import SystemSettingsDialog from './home/dialogs/SystemSettingsDialog.vue';
|
||||
import UpdatePasswordDialog from './home/dialogs/UpdatePasswordDialog.vue';
|
||||
import PrivacyDialog from './home/dialogs/PrivacyDialog.vue';
|
||||
import { Plus } from "@element-plus/icons-vue";
|
||||
import { Plus, Loading } from "@element-plus/icons-vue";
|
||||
|
||||
// Basic Setup
|
||||
const userStore = useUserStore();
|
||||
@@ -167,6 +174,11 @@ const router = useRouter();
|
||||
const searchKeyword = ref('');
|
||||
const categoryTree = ref([]);
|
||||
const groupMarkdownFiles = ref([]);
|
||||
const displayedFiles = ref([]);
|
||||
const currentPage = ref(0);
|
||||
const pageSize = ref(16);
|
||||
const isLoadingMore = ref(false);
|
||||
const noteListWrapper = ref(null);
|
||||
const showEditor = ref(false);
|
||||
const selectedFile = ref(null);
|
||||
const editData = ref(null);
|
||||
@@ -188,6 +200,10 @@ const showPrivacyDialog = ref(false);
|
||||
const itemToRename = ref(null);
|
||||
const fileToImport = ref(null);
|
||||
|
||||
const hasMoreFiles = computed(() => {
|
||||
return displayedFiles.value.length < groupMarkdownFiles.value.length;
|
||||
});
|
||||
|
||||
const resetEdit = () => {
|
||||
editData.value = null;
|
||||
};
|
||||
@@ -234,11 +250,38 @@ const resetToHomeView = async () => {
|
||||
showEditor.value = false;
|
||||
searchKeyword.value = '';
|
||||
activeMenu.value = 'all';
|
||||
currentPage.value = 0;
|
||||
try {
|
||||
groupMarkdownFiles.value = await getRecentFiles() || [];
|
||||
groupMarkdownFiles.value = await getRecentFiles(100) || [];
|
||||
updateDisplayedFiles();
|
||||
} catch (error) {
|
||||
ElMessage.error('获取最近文件失败: ' + error.message);
|
||||
groupMarkdownFiles.value = [];
|
||||
displayedFiles.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
const updateDisplayedFiles = () => {
|
||||
const start = 0;
|
||||
const end = (currentPage.value + 1) * pageSize.value;
|
||||
displayedFiles.value = groupMarkdownFiles.value.slice(start, end);
|
||||
};
|
||||
|
||||
const loadMoreFiles = () => {
|
||||
if (isLoadingMore.value || !hasMoreFiles.value) return;
|
||||
isLoadingMore.value = true;
|
||||
|
||||
setTimeout(() => {
|
||||
currentPage.value++;
|
||||
updateDisplayedFiles();
|
||||
isLoadingMore.value = false;
|
||||
}, 300);
|
||||
};
|
||||
|
||||
const handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
if (scrollHeight - scrollTop - clientHeight < 100 && hasMoreFiles.value && !isLoadingMore.value) {
|
||||
loadMoreFiles();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -248,12 +291,15 @@ const handleSelectFile = async (data) => {
|
||||
try {
|
||||
const files = await markdownList(data.id);
|
||||
groupMarkdownFiles.value = files || [];
|
||||
currentPage.value = 0;
|
||||
updateDisplayedFiles();
|
||||
selectedFile.value = null;
|
||||
showEditor.value = false;
|
||||
activeMenu.value = `group-${data.id}`;
|
||||
} catch (error) {
|
||||
ElMessage.error('获取笔记列表失败: ' + error.message);
|
||||
groupMarkdownFiles.value = [];
|
||||
displayedFiles.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -272,30 +318,45 @@ const handleCreateNote = (payload) => {
|
||||
|
||||
const handleEditorBack = (data) => {
|
||||
showEditor.value = false;
|
||||
previewFile(data);
|
||||
if (data && data.id) {
|
||||
const fileWithGrouping = {
|
||||
...data,
|
||||
groupingName: data.groupingName || getCategoryName(data.groupingId)
|
||||
};
|
||||
selectedFile.value = fileWithGrouping;
|
||||
} else {
|
||||
selectedFile.value = null;
|
||||
resetToHomeView();
|
||||
}
|
||||
};
|
||||
|
||||
const getCategoryName = (groupId) => {
|
||||
if (!groupId) return '';
|
||||
const findName = (items) => {
|
||||
for (const item of items) {
|
||||
if (item.id === groupId) return item.grouping;
|
||||
if (item.children) {
|
||||
const name = findName(item.children);
|
||||
if (name) return name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
return findName(categoryTree.value) || '';
|
||||
};
|
||||
|
||||
const handleSaveSuccess = (updatedFile) => {
|
||||
selectedFile.value = updatedFile;
|
||||
// 修复:保存成功后不退出编辑页面
|
||||
// showEditor.value = false;
|
||||
|
||||
const index = groupMarkdownFiles.value.findIndex(f => f.id === updatedFile.id);
|
||||
if (index !== -1) {
|
||||
groupMarkdownFiles.value[index] = updatedFile;
|
||||
} else {
|
||||
// 如果是新创建的笔记(之前ID为null),添加到列表开头
|
||||
groupMarkdownFiles.value.unshift(updatedFile);
|
||||
}
|
||||
|
||||
|
||||
updateDisplayedFiles();
|
||||
fetchGroupings();
|
||||
|
||||
// 延迟清空 editData,确保所有响应式更新完成后再清理状态
|
||||
// Delay clearing editData to ensure all reactive updates are complete before cleaning up the state.
|
||||
setTimeout(() => {
|
||||
// 修复:保存成功后不清空editData,保持编辑状态
|
||||
// resetEdit();
|
||||
}, 100); // A short delay is usually sufficient
|
||||
};
|
||||
|
||||
const previewFile = async (file) => {
|
||||
@@ -390,6 +451,8 @@ const handleSearch = async () => {
|
||||
}
|
||||
try {
|
||||
groupMarkdownFiles.value = await searchMarkdown(searchKeyword.value) || [];
|
||||
currentPage.value = 0;
|
||||
updateDisplayedFiles();
|
||||
selectedFile.value = null;
|
||||
showEditor.value = false;
|
||||
activeMenu.value = 'search';
|
||||
@@ -569,4 +632,19 @@ watch([selectedFile, showEditor], ([newFile, newShowEditor]) => {
|
||||
height: 56px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,.15);
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
gap: 8px;
|
||||
color: var(--text-color-secondary);
|
||||
}
|
||||
|
||||
.load-more-trigger {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user