- 移除重复的错误提示,统一在axios拦截器中处理 - 优化XSS拦截器,添加请求头白名单 - 修复注册码服务的日期处理问题 - 添加403权限错误处理 - 优化分组查询参数处理
239 lines
6.6 KiB
Vue
239 lines
6.6 KiB
Vue
<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="handleBack">返回</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 currentId = ref(null);
|
||
const isInitialized = ref(false);
|
||
const saveStatus = ref('');
|
||
let saveTimeout = null;
|
||
let lastSavedContent = ref('');
|
||
let isSaving = ref(false);
|
||
// 维护当前最新的笔记数据
|
||
const currentData = ref({ ...props.editData });
|
||
|
||
const initVditor = () => {
|
||
if (vditor.value) {
|
||
vditor.value.destroy();
|
||
}
|
||
|
||
vditor.value = new Vditor('vditor-editor', {
|
||
height: 'calc(100vh - 120px)',
|
||
mode: 'ir',
|
||
cache: {
|
||
enable: false,
|
||
},
|
||
after: () => {
|
||
isInitialized.value = true;
|
||
if (currentData.value && currentData.value.content) {
|
||
vditor.value.setValue(currentData.value.content);
|
||
lastSavedContent.value = currentData.value.content;
|
||
}
|
||
if (currentData.value && currentData.value.id) {
|
||
currentId.value = currentData.value.id;
|
||
}
|
||
vditor.value.focus();
|
||
},
|
||
input: (value) => {
|
||
if (!isInitialized.value) return;
|
||
|
||
clearTimeout(saveTimeout);
|
||
saveStatus.value = '正在输入...';
|
||
saveTimeout = setTimeout(() => {
|
||
if (!isSaving.value && value !== lastSavedContent.value) {
|
||
save(value);
|
||
}
|
||
}, 3000);
|
||
},
|
||
upload: {
|
||
accept: 'image/*',
|
||
handler(files) {
|
||
const file = files[0];
|
||
if (!file) return;
|
||
|
||
uploadImage(file).then(res => {
|
||
const url = res.url;
|
||
const baseUrl = import.meta.env.VITE_API_BASE_URL || '';
|
||
vditor.value.insertValue(``);
|
||
}).catch((error) => {
|
||
// 错误已在 axios 拦截器中显示,这里不再重复显示
|
||
console.error('图片上传失败:', error);
|
||
});
|
||
},
|
||
},
|
||
});
|
||
};
|
||
|
||
const save = async (value) => {
|
||
if (isSaving.value) return;
|
||
|
||
// 修复:添加空值检查
|
||
if (!vditor.value) {
|
||
console.warn('编辑器未初始化');
|
||
return;
|
||
}
|
||
|
||
clearTimeout(saveTimeout);
|
||
const content = typeof value === 'string' ? value : vditor.value?.getValue() || '';
|
||
|
||
if (content === lastSavedContent.value && currentId.value) {
|
||
return;
|
||
}
|
||
|
||
isSaving.value = true;
|
||
try {
|
||
saveStatus.value = '正在保存...';
|
||
|
||
// 确保groupingId不会丢失:优先使用currentData中的值
|
||
const groupingId = currentData.value.groupingId || props.editData.groupingId;
|
||
|
||
// 将ID转为字符串以避免JavaScript精度丢失
|
||
const idString = currentId.value ? String(currentId.value) : (currentData.value.id ? String(currentData.value.id) : null);
|
||
const groupingIdString = groupingId ? String(groupingId) : null;
|
||
|
||
const payload = {
|
||
id: idString,
|
||
content: content,
|
||
title: currentData.value.title || props.editData.title,
|
||
groupingId: groupingIdString,
|
||
fileName: currentData.value.fileName || props.editData.fileName,
|
||
isPrivate: currentData.value.isPrivate !== undefined ? currentData.value.isPrivate : props.editData.isPrivate
|
||
};
|
||
|
||
|
||
|
||
const response = await updateMarkdown(payload);
|
||
|
||
if (response && response.id) {
|
||
currentId.value = response.id;
|
||
lastSavedContent.value = content;
|
||
|
||
// 使用后端返回的数据,但确保groupingId不会丢失
|
||
// 注意:后端返回的ID是字符串,保持字符串格式避免精度丢失
|
||
const updatedFile = {
|
||
...response,
|
||
content: content,
|
||
// 如果后端返回的groupingId为空,使用原来的值(保持字符串格式)
|
||
groupingId: response.groupingId || groupingIdString,
|
||
groupingName: response.groupingName || currentData.value.groupingName
|
||
};
|
||
|
||
// 更新currentData为最新数据
|
||
currentData.value = updatedFile;
|
||
emit('update:editData', updatedFile);
|
||
saveStatus.value = '已保存';
|
||
}
|
||
} catch (error) {
|
||
saveStatus.value = '保存失败';
|
||
// 错误已在 axios 拦截器中显示,这里不再重复显示
|
||
console.error('保存失败:', error);
|
||
} finally {
|
||
isSaving.value = false;
|
||
}
|
||
};
|
||
|
||
const handleBack = async () => {
|
||
const content = vditor.value ? vditor.value.getValue() : '';
|
||
if (content !== lastSavedContent.value && !isSaving.value) {
|
||
await save(content);
|
||
}
|
||
|
||
// 确保groupingId不会丢失(保持字符串格式)
|
||
const groupingId = currentData.value.groupingId || props.editData.groupingId;
|
||
const groupingName = currentData.value.groupingName || props.editData.groupingName;
|
||
|
||
const returnData = {
|
||
...currentData.value,
|
||
...props.editData,
|
||
id: currentId.value ? String(currentId.value) : (currentData.value.id ? String(currentData.value.id) : null),
|
||
content: content,
|
||
groupingId: groupingId ? String(groupingId) : null,
|
||
groupingName: groupingName
|
||
};
|
||
|
||
emit('back', returnData);
|
||
};
|
||
|
||
onMounted(() => {
|
||
initVditor();
|
||
});
|
||
|
||
onBeforeUnmount(() => {
|
||
clearTimeout(saveTimeout);
|
||
if (vditor.value) {
|
||
vditor.value.destroy();
|
||
vditor.value = null;
|
||
}
|
||
currentId.value = null;
|
||
isInitialized.value = false;
|
||
});
|
||
|
||
watch(() => props.editData, (newVal, oldVal) => {
|
||
if (vditor.value && isInitialized.value && newVal && newVal.id !== oldVal?.id) {
|
||
// 更新currentData为最新的props数据
|
||
currentData.value = { ...newVal };
|
||
vditor.value.setValue(newVal.content || '');
|
||
lastSavedContent.value = newVal.content || '';
|
||
currentId.value = newVal.id;
|
||
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> |