Files
biji/biji-qianduan/src/components/home/NoteEditor.vue
ikmkj a0991db83e feat(database): 添加 MySQL 数据库支持并迁移数据表结构
- 新增 MySQL 5.7.44 建表脚本,包含分组、图片、Markdown文件等7张表
- 添加 SQLite 转 MySQL 的数据库迁移脚本
- 配置开发环境使用 MySQL 数据源连接
- 更新生产环境配置注释 SQLite 配置并预留 MySQL 配置位置
- 修改前端笔记编辑器保存逻辑,完善笔记更新功能
- 替换 SQLite 驱动为 MySQL 连接器依赖
- 添加 commons-codec 依赖用于 SHA256 计算功能
2026-01-06 21:18:06 +08:00

175 lines
4.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="note-editor-wrapper">
<el-header class="editor-header">
<h2 class="editor-title">{{ editData.title }}</h2>
<div class="actions">
<el-button type="primary" @click="$emit('back', editData)">返回</el-button>
<el-button type="success" @click="save">保存</el-button>
<span class="save-status">{{ saveStatus }}</span>
</div>
</el-header>
<div id="vditor-editor" class="vditor" />
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
import Vditor from 'vditor';
import 'vditor/dist/index.css';
import { ElMessage } from 'element-plus';
import { updateMarkdown, uploadImage } from '@/api/CommonApi.js';
const props = defineProps({
editData: {
type: Object,
required: true,
},
});
const emit = defineEmits(['back', 'update:editData']);
const vditor = ref(null);
const bijiId=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[0];
if (!file) return;
uploadImage(file).then(res => {
const url = res.url;
// 使用 file.name 替代 files.name 保证一致性
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
vditor.value.insertValue(`![${file.name}](${baseUrl}${url})`);
}).catch(() => {
ElMessage.error('图片上传失败');
});
},
},
});
};
const save = async (value) => {
clearTimeout(saveTimeout);
const content = typeof value === 'string' ? value : vditor.value.getValue();
try {
saveStatus.value = '正在保存...';
// 发送完整的笔记对象,确保包含所有必要字段
const response = await updateMarkdown({
id: props.editData.id? props.editData.id : bijiId.value,
content: content,
title: props.editData.title,
groupingId: props.editData.groupingId,
fileName: props.editData.fileName,
isPrivate: props.editData.isPrivate
});
// 确保获取到后端返回的数据包括可能的新ID
bijiId.value = response.id;
// 保存成功,更新状态
saveStatus.value = '已保存';
// 发送更新后的笔记数据包含可能的新ID
emit('update:editData', { ...props.editData, content: content });
} catch (error) {
// 保存失败,更新状态并显示错误消息
saveStatus.value = '保存失败';
ElMessage.error('保存失败: ' + (error.message || '未知错误'));
}
};
onMounted(() => {
initVditor();
});
onBeforeUnmount(() => {
clearTimeout(saveTimeout);
if (vditor.value) {
vditor.value.destroy();
}
if (bijiId.value){
// 发送完整的笔记对象,确保包含所有必要字段
updateMarkdown({
id: bijiId.value,
content: vditor.value.getValue(),
title: props.editData.title,
groupingId: props.editData.groupingId,
fileName: props.editData.fileName,
isPrivate: props.editData.isPrivate
});
}
// 离开页面后清空 bijiId.value 变量
bijiId.value = null;
});
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>