feat(qianduan): 优化笔记功能和图片处理

- 新增批量删除图片接口和功能- 实现笔记中图片的上传和删除
- 优化笔记保存时的图片处理逻辑
-调整分组展示和Markdown文件加载方式
This commit is contained in:
ikmkj
2025-06-20 15:35:53 +08:00
parent 12ba82eaa1
commit 431e3dea1c
11 changed files with 239 additions and 138 deletions

View File

@@ -4,7 +4,7 @@ import axiosApi from '@/utils/axios.js'
export const groupingId = (data) => axiosApi.get(`/api/markdown/grouping/${data}`)
// 获取所有分组
export const groupingAll = () => axiosApi.get(`/api/groupings`)
export const groupingAll = (data) => axiosApi.get(`/api/groupings?parentId=${data}`);
// 获取所有Markdown文件
export const markdownAll = () => axiosApi.get(`/api/markdown`);
// 预览markdown文件
@@ -30,6 +30,33 @@ export const updateMarkdown = (id, data) => {
}
})
}
// 批量删除图片
export const deleteImages = (list) => {
const formData = new FormData()
formData.append('urls', list)
return axiosApi.post('/api/images/batch', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 上传图片
export const uploadImage = (file) => {
const formData = new FormData()
if (file) formData.append('file', file)
return axiosApi.post('/api/images?markdownId', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
}
// 删除Markdown文件
export const deleteMarkdown = (id) => axiosApi.post(`/api/markdown/delete?id=${id}`);
// 根据分组ID获取Markdown文件列表
export const markdownList = (groupingId) => axiosApi.get(`/api/markdown/grouping/${groupingId}`);
// MD5哈希

View File

@@ -18,24 +18,20 @@
:default-active="activeMenu"
class="el-menu-vertical-demo"
:collapse="isCollapsed"
@select="handleMenuSelect"
popper-effect="light"
collapse-transition
>
<!-- 分组分类 -->
<el-sub-menu v-for="group in groupings" :key="group.id" :index="`group-${group.id}`">
<template #title>
<template #title>
<span>{{ group.grouping }}</span>
</template>
<!-- 分组下的文件 -->
<el-menu-item
v-for="file in groupFiles[group.id] || []"
:key="file.id"
:index="`file-${file.id}`"
>
{{ file.title }}
</el-menu-item>
<el-menu-item v-for="sub in jb22.filter(j => +j.parentId === +group.id)"
:key="sub.id"
:index="`sub-${sub.id}`"
@click="selectFile(sub);selectedFile=null"
>{{ sub.grouping }}</el-menu-item>
</el-sub-menu>
</el-menu>
</div>
@@ -51,7 +47,7 @@
<el-button v-if="!showEditor" type="primary" @click="selectedFile=null">清空</el-button>
<el-button v-if="!showEditor" type="primary" @click="editNote(selectedFile);isCollapsed=true">编辑</el-button>
<el-button v-if="!showEditor" type="danger" @click="deleteNote(selectedFile)">删除</el-button>
<el-button v-if="showEditor" type="primary" @click="showEditor=!showEditor;selectFile(editData)">返回</el-button>
<el-button v-if="showEditor" type="primary" @click="showEditor=!showEditor;previewFile(editData)">返回</el-button>
<el-button v-if="showEditor" type="success" @click="handleSave(editData.content)">保存</el-button>
</div>
</div>
@@ -90,9 +86,9 @@
</div>
</div>
<div v-if="markdownFiles.length > 0" class="file-list">
<div v-for="file in markdownFiles" :key="file.id" class="file-item">
<div @click="selectFile(file)" class="file-title">{{ file.title }}</div>
<div v-if="groupMarkdownFiles.length > 0" class="file-list">
<div v-for="file in groupMarkdownFiles" :key="file.id" class="file-item">
<div @click="previewFile(file)" class="file-title">{{ file.title }}</div>
</div>
</div>
<div v-else class="empty-tip">暂无笔记请创建或上传</div>
@@ -139,18 +135,26 @@
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import {onMounted, ref} from 'vue';
import {ElMessage} from 'element-plus';
import '@kangc/v-md-editor/lib/style/preview.css';
import '@kangc/v-md-editor/lib/theme/style/github.css';
import {groupingId, groupingAll, markdownAll, addGroupings, Preview, updateMarkdown} from '@/api/CommonApi.js'
import {
addGroupings,
deleteImages, deleteMarkdown,
groupingAll,
groupingId,
markdownAll, markdownList,
Preview,
updateMarkdown, uploadImage
} from '@/api/CommonApi.js'
import {DArrowRight} from "@element-plus/icons-vue";
const markdownFiles = ref([]);
const groupings = ref([]);
const groupFiles = ref({});
// 二级分类下的markdown文件
const groupMarkdownFiles = ref({});
const showEditor = ref(false);
const selectedFile = ref(null);
const activeMenu = ref('all');
@@ -162,45 +166,29 @@ const newNoteForm = ref({ title: '', groupingId: null });
const editData=ref(null)
// 笔记中的所有图片url
const imageUrls = ref([]);
// 刚开始笔记中的所有图片url
const originalImages = ref([]);
const jb22=ref([])
// 获取所有分组
const fetchGroupings = async () => {
try {
const response = await groupingAll()
// 确保分组数据是数组
groupings.value = response.data
// 为每个分组获取文件
for (const group of groupings.value) {
try {
const filesRes = await groupingId(group.id);
// 确保文件ID为字符串
groupFiles.value[group.id] = (filesRes.data).map(file => ({
...file,
id: file.id
}));
} catch (fileError) {
console.error(`获取分组 ${group.id} 文件失败:`, fileError);
groupFiles.value[group.id] = [];
}
}
// 添加"全部"分类 (ID=1)
if (!groupings.value.some(g => g.id === 1)) {
// groupings.value.unshift({ id: 1, name: '全部' });
// 获取所有文件作为"全部"分类的内容
try {
const allFilesRes = await markdownAll()
groupFiles.value[1] = (allFilesRes.data).map(file => ({
...file,
id: file.id
}));
} catch (allFilesError) {
console.error('获取全部笔记失败:', allFilesError);
groupFiles.value[1] = [];
}
const response = await groupingAll(0)
const jb1 = []
const jb2 = []
for (let i = 0; i <response.data.length; i++) {
if (+response.data[i].parentId===0){
jb1.push(response.data[i])
}else{
jb2.push(response.data[i])
}
}
groupings.value=jb1
jb22.value=jb2
} catch (error) {
console.error('获取分组失败:', error);
ElMessage.error('获取分组失败: ' + (error.response?.data?.message || error.message));
@@ -208,6 +196,13 @@ const fetchGroupings = async () => {
}
};
// 获取二级分类下的Markdown文件
const selectFile = async (data) => {
const promise = await markdownList(data.id);
groupMarkdownFiles.value=promise.data
};
// 代码块复制成功回调
const handleCopyCodeSuccess = () => {
ElMessage.success('代码已复制到剪贴板');
};
@@ -228,12 +223,13 @@ const fetchMarkdownFiles = async () => {
// 创建新分类
const createGrouping = async () => {
// TODO 添加分类创建逻辑
try {
const response = await addGroupings(newGroupForm.value.name)
ElMessage.success('分类创建成功');
showCreateGroupDialog.value = false;
newGroupForm.value.name = '';
fetchGroupings();
await fetchGroupings();
} catch (error) {
ElMessage.error('创建分类失败: ' + error.message);
}
@@ -241,6 +237,7 @@ const createGrouping = async () => {
// 创建新笔记
const createNote = async () => {
// TODO 添加笔记创建逻辑
try {
await axios.post(`${API_BASE_URL}/api/markdown`, '# 新笔记内容', {
params: {
@@ -253,16 +250,14 @@ const createNote = async () => {
ElMessage.success('笔记创建成功');
showCreateNoteDialog.value = false;
newNoteForm.value = { title: '', groupingId: null };
await fetchMarkdownFiles();
await fetchGroupings();
await chushihua()
} catch (error) {
ElMessage.error('创建笔记失败: ' + error.message);
}
};
// 选择文件预览
const selectFile = async (file) => {
console.log( file)
const previewFile = async (file) => {
try {
const response = await Preview(file.id)
@@ -289,17 +284,26 @@ const selectFile = async (file) => {
// 编辑笔记
const editNote = (file) => {
editData.value = file
originalImages.value = extractImageUrls(file.content);
showEditor.value = true;
};
const handleImageUpload=(event,insertImage,files)=>{
// 图片上传
const handleImageUpload=async (event, insertImage, files) => {
console.log(files)
const promise = await uploadImage(files[0]);
if (promise.code !== 200) {
ElMessage.error(promise.msg);
return;
}
// 插入图片确保URL格式正确
const imageUrl = promise.data.url.startsWith('/')
? `http://127.0.0.1:8084${promise.data.url}`
: promise.data.url;
// 插入图片
insertImage({
// 图片地址
url:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1269952892,3525182336&fm=26&gp=0.jpg'
url: imageUrl
// 图片描述
// desc: '七龙珠',
});
@@ -307,6 +311,8 @@ const handleImageUpload=(event,insertImage,files)=>{
// 在编辑页面按Ctrl+S保存笔记或者点击保存对数据进行保存
const handleSave= async (file) => {
imageUrls.value = extractImageUrls(file);
extractDeletedImageUrls(imageUrls.value)
const filesRes = await updateMarkdown(editData.value.id,file);
if (filesRes.code===200){
ElMessage.success(filesRes.msg);
@@ -316,49 +322,83 @@ const handleSave= async (file) => {
}
}
// 笔记保存后处理
const handleNoteSaved = () => {
showEditor.value = false;
fetchMarkdownFiles();
fetchGroupings();
if (selectedFile.value) {
selectFile(selectedFile.value); // 刷新当前预览内容
// 保存时获取所有的图片url
const extractImageUrls = (data) => {
const content = data
const urls = [];
// 处理 URL 的内部函数
const getPathFromUrl = (url) => {
try {
if (url.startsWith('http://') || url.startsWith('https://')) {
// 使用 URL 对象解析路径
return new URL(url).pathname;
}
return url; // 非 HTTP(S) URL 直接返回(如 base64
} catch (e) {
return url; // 解析失败时返回原始 URL
}
};
// 匹配Markdown图片语法
const mdRegex = /!\[.*?\]\((.*?)\)/g;
let mdMatch;
while ((mdMatch = mdRegex.exec(content)) !== null) {
urls.push(getPathFromUrl(mdMatch[1]))
}
// 匹配HTML img标签
const htmlRegex = /<img[^>]+src="([^">]+)"/g;
let htmlMatch;
while ((htmlMatch = htmlRegex.exec(content)) !== null) {
urls.push(getPathFromUrl(htmlMatch[1]));
}
// 匹配base64图片
const base64Regex = /<img[^>]+src="(data:image\/[^;]+;base64[^">]+)"/g;
let base64Match;
while ((base64Match = base64Regex.exec(content)) !== null) {
urls.push(base64Match[1]);
}
// 过滤和去重
const validUrls = urls.filter(url => {
return url.startsWith('http') || url.startsWith('data:image');
});
return [...new Set(validUrls)]
};
// 笔记保存时获取要删除的图片url
const extractDeletedImageUrls = (data) => {
const delImages = []
// 原来的url originalImages.value
for (let i = 0; i <originalImages.value.length; i++) {
for (let j = 0; j <data.length; j++) {
if (originalImages.value[i] !== data[j]){
delImages.push(originalImages.value[i])
}
}
}
if (delImages.length>0){
deleteImages(delImages)
}
}
// 删除笔记
const deleteNote = async (file) => {
try {
await axios.delete(`${API_BASE_URL}/api/markdown/${file.id}`);
await deleteMarkdown(file.id);
ElMessage.success('删除成功');
selectedFile.value = null;
fetchMarkdownFiles();
fetchGroupings();
await chushihua()
await selectFile(groupingId)
} catch (error) {
ElMessage.error('删除失败: ' + error.message);
}
};
// 菜单选择处理
const handleMenuSelect = (index) => {
if (index === 'all') {
selectedFile.value = null;
activeMenu.value = 'all';
} else if (index.startsWith('file-')) {
const fileId = index.split('-')[1];
// 确保markdownFiles.value是数组
if (Array.isArray(markdownFiles.value)) {
const file = markdownFiles.value.find(f => f.id === fileId);
if (file) {
selectFile(file);
activeMenu.value = index;
}
}
} else if (index.startsWith('group-')) {
activeMenu.value = index;
}
};
// ---------------------------------TODO 下面的还没有还是看
// 上传Markdown文件处理
const handleMarkdownUpload = (file) => {
@@ -380,8 +420,7 @@ const handleMarkdownUpload = (file) => {
});
ElMessage.success('上传成功');
fetchMarkdownFiles();
fetchGroupings();
await chushihua()
} catch (error) {
ElMessage.error('上传失败: ' + error.message);
}