fix(biji): 优化笔记编辑器自动保存机制并修复相关问题
- 实现了更可靠的自动保存功能,仅在用户停止输入后触发保存操作 - 修复了切换笔记时意外触发自动保存的问题 - 优化了重命名文件后的预览更新逻辑 - 调整了保存成功后的状态清理策略,提高了用户体验
This commit is contained in:
@@ -84,10 +84,12 @@ export const updateGroupingName = (id, newName) => {
|
||||
export const deleteGrouping = (id) => axiosApi.delete(`/api/groupings/${id}`);
|
||||
|
||||
// 更新Markdown文件标题
|
||||
export const updateMarkdownTitle = (id, newTitle) => {
|
||||
return axiosApi.put(`/api/markdown/${id}/title`, newTitle, {
|
||||
export const updateMarkdownTitle = (id, newName) => {
|
||||
const formData = new FormData()
|
||||
if (newName) formData.append('title', newName)
|
||||
return axiosApi.post(`/api/markdown/${id}/title`,formData, {
|
||||
headers: {
|
||||
'Content-Type': 'text/plain'
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
v-if="showEditor"
|
||||
:edit-data="editData"
|
||||
@back="handleEditorBack"
|
||||
@save-success="handleSaveSuccess"
|
||||
@update:edit-data="handleSaveSuccess"
|
||||
/>
|
||||
|
||||
<!-- Note Preview View -->
|
||||
@@ -188,6 +188,10 @@ const showPrivacyDialog = ref(false);
|
||||
const itemToRename = ref(null);
|
||||
const fileToImport = ref(null);
|
||||
|
||||
const resetEdit = () => {
|
||||
editData.value = null;
|
||||
};
|
||||
|
||||
// --- Core Logic ---
|
||||
|
||||
// Data Fetching
|
||||
@@ -225,6 +229,7 @@ const buildTree = (items) => {
|
||||
};
|
||||
|
||||
const resetToHomeView = async () => {
|
||||
resetEdit();
|
||||
selectedFile.value = null;
|
||||
showEditor.value = false;
|
||||
searchKeyword.value = '';
|
||||
@@ -239,6 +244,7 @@ const resetToHomeView = async () => {
|
||||
|
||||
// Event Handlers from Components
|
||||
const handleSelectFile = async (data) => {
|
||||
resetEdit();
|
||||
try {
|
||||
const files = await markdownList(data.id);
|
||||
groupMarkdownFiles.value = files || [];
|
||||
@@ -257,6 +263,7 @@ const handleGroupDeleted = async () => {
|
||||
};
|
||||
|
||||
const handleCreateNote = (payload) => {
|
||||
resetEdit();
|
||||
editData.value = payload;
|
||||
showEditor.value = true;
|
||||
selectedFile.value = null; // Ensure preview is hidden
|
||||
@@ -268,24 +275,27 @@ const handleEditorBack = (data) => {
|
||||
};
|
||||
|
||||
const handleSaveSuccess = (updatedFile) => {
|
||||
editData.value = null; // Clear edit data
|
||||
selectedFile.value = updatedFile; // Update the selected file to show the preview
|
||||
showEditor.value = false; // Hide the editor
|
||||
selectedFile.value = updatedFile;
|
||||
showEditor.value = false;
|
||||
|
||||
// Find the file in the current list and update it
|
||||
const index = groupMarkdownFiles.value.findIndex(f => f.id === updatedFile.id);
|
||||
if (index !== -1) {
|
||||
groupMarkdownFiles.value[index] = updatedFile;
|
||||
} else {
|
||||
// If the file is new (or not in the current list), add it to the top
|
||||
groupMarkdownFiles.value.unshift(updatedFile);
|
||||
}
|
||||
|
||||
// Also refresh the category tree to reflect new file counts or changes
|
||||
fetchGroupings();
|
||||
|
||||
// 延迟清空 editData,确保所有响应式更新完成后再清理状态
|
||||
// Delay clearing editData to ensure all reactive updates are complete before cleaning up the state.
|
||||
setTimeout(() => {
|
||||
resetEdit();
|
||||
}, 100); // A short delay is usually sufficient
|
||||
};
|
||||
|
||||
const previewFile = async (file) => {
|
||||
resetEdit();
|
||||
if (!file || file.id === null) {
|
||||
editData.value = file;
|
||||
selectedFile.value = null;
|
||||
@@ -328,12 +338,20 @@ const openRenameDialog = (item, type) => {
|
||||
showRenameDialog.value = true;
|
||||
};
|
||||
|
||||
const handleRenamed = async () => {
|
||||
const handleRenamed = async (newName) => {
|
||||
await fetchGroupings();
|
||||
if (selectedFile.value && itemToRename.value.type === 'file' && selectedFile.value.id === itemToRename.value.id) {
|
||||
previewFile(selectedFile.value); // Refresh preview
|
||||
// 直接更新当前选中文件的标题
|
||||
selectedFile.value.title = newName;
|
||||
// 更新笔记列表中的对应项
|
||||
const index = groupMarkdownFiles.value.findIndex(f => f.id === selectedFile.value.id);
|
||||
if (index !== -1) {
|
||||
groupMarkdownFiles.value[index].title = newName;
|
||||
}
|
||||
// 重新获取文件内容以确保是最新的
|
||||
previewFile(selectedFile.value); // Refresh preview
|
||||
} else {
|
||||
resetToHomeView();
|
||||
resetToHomeView();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ const trashItems = ref([]);
|
||||
const fetchTrashItems = async () => {
|
||||
try {
|
||||
const response = await getTrash();
|
||||
trashItems.value = response.data || [];
|
||||
trashItems.value = response || [];
|
||||
} catch (error) {
|
||||
ElMessage.error('获取回收站内容失败');
|
||||
}
|
||||
|
||||
@@ -26,4 +26,132 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits
|
||||
const emit = defineEmits(['back', 'update:editData']);
|
||||
|
||||
const vditor = ref(null);
|
||||
const saveStatus = ref('');
|
||||
let saveTimeout = null;
|
||||
const isProgrammaticChange = ref(false);
|
||||
|
||||
const initVditor = () => {
|
||||
vditor.value = new Vditor('vditor-editor', {
|
||||
height: 'calc(100vh - 120px)',
|
||||
mode: 'ir',
|
||||
cache: {
|
||||
enable: false,
|
||||
},
|
||||
after: () => {
|
||||
if (props.editData && props.editData.content) {
|
||||
isProgrammaticChange.value = true;
|
||||
vditor.value.setValue(props.editData.content);
|
||||
isProgrammaticChange.value = false;
|
||||
}
|
||||
vditor.value.focus();
|
||||
},
|
||||
input: (value) => {
|
||||
if (isProgrammaticChange.value) {
|
||||
return;
|
||||
}
|
||||
// 启动定时器,延迟5秒后执行保存
|
||||
clearTimeout(saveTimeout);
|
||||
saveStatus.value = '正在输入...';
|
||||
saveTimeout = setTimeout(() => {
|
||||
save(value);
|
||||
}, 5000);
|
||||
},
|
||||
upload: {
|
||||
accept: 'image/*',
|
||||
handler(files) {
|
||||
const file = files; // 必须是 File 对象,而不是 FileList
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file); // 字段名必须是 'file'
|
||||
|
||||
uploadImage(formData).then(res => {
|
||||
if (res.code === 200) {
|
||||
const url = res.data;
|
||||
// 使用 file.name 替代 files.name 保证一致性
|
||||
vditor.value.insertValue(``);
|
||||
} else {
|
||||
ElMessage.error('图片上传失败');
|
||||
}
|
||||
}).catch(() => {
|
||||
ElMessage.error('图片上传失败');
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const save = async (value) => {
|
||||
clearTimeout(saveTimeout);
|
||||
const content = typeof value === 'string' ? value : vditor.value.getValue();
|
||||
try {
|
||||
saveStatus.value = '正在保存...';
|
||||
const res = await updateMarkdown({ id: props.editData.id, content: content });
|
||||
if (res.code === 200) {
|
||||
saveStatus.value = '已保存';
|
||||
emit('update:editData', { ...props.editData, content: content });
|
||||
} else {
|
||||
saveStatus.value = '保存失败';
|
||||
ElMessage.error(res.message || '保存失败');
|
||||
}
|
||||
} catch (error) {
|
||||
saveStatus.value = '保存失败';
|
||||
ElMessage.error('保存失败');
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initVditor();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearTimeout(saveTimeout);
|
||||
if (vditor.value) {
|
||||
vditor.value.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => props.editData, (newVal, oldVal) => {
|
||||
if (vditor.value && newVal && newVal.id !== oldVal?.id) {
|
||||
isProgrammaticChange.value = true;
|
||||
vditor.value.setValue(newVal.content || '');
|
||||
isProgrammaticChange.value = false;
|
||||
saveStatus.value = '';
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.note-editor-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.editor-title {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.actions .save-status {
|
||||
margin-left: 10px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.vditor {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
}
|
||||
</style>
|
||||
@@ -51,13 +51,14 @@ const handleSubmit = async () => {
|
||||
}
|
||||
try {
|
||||
if (props.item.type === 'file') {
|
||||
await updateMarkdownTitle({ id: props.item.id, title: newName.value });
|
||||
await updateMarkdownTitle(props.item.id, newName.value);
|
||||
} else {
|
||||
await updateGroupingName({ id: props.item.id, grouping: newName.value });
|
||||
await updateGroupingName(props.item.id, newName.value);
|
||||
}
|
||||
ElMessage.success('重命名成功');
|
||||
emit('renamed');
|
||||
handleClose();
|
||||
// 传递新名称给父组件
|
||||
emit('renamed', newName.value);
|
||||
} catch (error) {
|
||||
ElMessage.error('重命名失败: ' + error.message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user