feat(image): 添加图片删除功能并优化 Markdown 编辑器

- 在 ImageController 中添加删除图片的接口
- 在 ImageService 中实现删除图片和批量更新图片 ID 的方法
- 在前端集成复制代码插件并优化 Markdown 编辑器配置
- 修复后端分组相关接口的参数类型问题
This commit is contained in:
ikmkj
2025-06-20 09:35:19 +08:00
parent df848fed23
commit 12ba82eaa1
8 changed files with 145 additions and 27 deletions

View File

@@ -47,7 +47,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId> <artifactId>spring-boot-starter-aop</artifactId>
</dependency> </dependency>
<!-- sqlite --> <!-- sqlite -->
<dependency> <dependency>
<groupId>org.xerial</groupId> <groupId>org.xerial</groupId>

View File

@@ -35,17 +35,20 @@ public class GroupingController {
@Operation(summary = "更新分组名称") @Operation(summary = "更新分组名称")
@PutMapping("/{id}") @PutMapping("/{id}")
public R<Grouping> updateGrouping( public R<Grouping> updateGrouping(
@PathVariable Long id, @PathVariable String id,
@RequestBody Grouping grouping) { @RequestBody Grouping grouping) {
grouping.setId(id);
long l = Long.parseLong(id);
grouping.setId(l);
Grouping updated = groupingService.updateGrouping(grouping); Grouping updated = groupingService.updateGrouping(grouping);
return R.success(updated); return R.success(updated);
} }
@Operation(summary = "删除分组") @Operation(summary = "删除分组")
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
public R<Void> deleteGrouping(@PathVariable Long id) { public R<Void> deleteGrouping(@PathVariable String id) {
groupingService.deleteGrouping(id); Long idLong = Long.parseLong(id);
groupingService.deleteGrouping(idLong);
return R.success(); return R.success();
} }
} }

View File

@@ -13,6 +13,8 @@ import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.util.List;
@Tag(name = "markdown接口") @Tag(name = "markdown接口")
@RestController @RestController
@RequestMapping("/api/images") @RequestMapping("/api/images")
@@ -28,14 +30,39 @@ public class ImageController {
}) })
@PostMapping @PostMapping
public R<Image> uploadImage( public R<Image> uploadImage(
@RequestParam(required = false) Long markdownId, @RequestParam(required = false) String markdownId,
@RequestParam("file") MultipartFile file) { @RequestParam("file") MultipartFile file) {
try { try {
Image image = imageService.uploadImage(markdownId, file); Long markdownIdLong = Long.parseLong(markdownId);
Image image = imageService.uploadImage(markdownIdLong, file);
return R.success(image); return R.success(image);
} catch (IOException e) { } catch (IOException e) {
return R.fail(); return R.fail();
} }
} }
@Operation(summary = "根据id删除图片")
@DeleteMapping("/{id}")
public R<Void> deleteImage(@PathVariable Long id) {
boolean result = imageService.deleteImage(id);
if (result) {
return R.success();
} else {
return R.fail();
}
}
@Operation(summary = "根据url删除图片")
@DeleteMapping
public R<Void> deleteImageByUrl(@RequestParam String url) {
boolean result = imageService.deleteImageByUrl(url);
if (result) {
return R.success();
} else {
return R.fail();
}
}
} }

View File

@@ -31,4 +31,19 @@ public interface ImageService extends IService<Image> {
* @return 图片列表 * @return 图片列表
*/ */
List<Image> getMarkdownImages(Long markdownId); List<Image> getMarkdownImages(Long markdownId);
/**
* 根据URL删除图片
* @param url
* @return
*/
boolean deleteImageByUrl(String url);
/**
* 根据URL批量更新图片ID
* @param list
* @param markdownId
* @return
*/
boolean updateImageId(List<String> list, Long markdownId);
} }

View File

@@ -1,5 +1,7 @@
package com.test.bijihoudaun.service.impl; package com.test.bijihoudaun.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.stream.CollectorUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -87,4 +89,38 @@ public class ImageServiceImpl
.orderByDesc("created_at"); .orderByDesc("created_at");
return this.list(queryWrapper); return this.list(queryWrapper);
} }
@Override
public boolean deleteImageByUrl(String url) {
Image image = imageMapper.selectOne(new QueryWrapper<Image>().eq("url", url));
if (image == null) {
return false;
}
try {
// 删除文件系统中的图片
Path filePath = Paths.get(uploadDir, image.getStoredName());
Files.deleteIfExists(filePath);
// 删除数据库记录
this.removeById(image.getId());
return true;
} catch (IOException e) {
throw new RuntimeException("删除图片失败", e);
}
}
@Override
public boolean updateImageId(List<String> list, Long markdownId) {
if (CollUtil.isEmpty( list)) {
return false;
}
for (String url : list) {
Image image = imageMapper.selectOne(new QueryWrapper<Image>().eq("url", url));
if (image != null) {
image.setMarkdownId(markdownId);
this.updateById(image);
}
}
return true;
}
} }

View File

@@ -49,13 +49,18 @@
<h2>{{ selectedFile.title }}</h2> <h2>{{ selectedFile.title }}</h2>
<div class="actions"> <div class="actions">
<el-button v-if="!showEditor" type="primary" @click="selectedFile=null">清空</el-button> <el-button v-if="!showEditor" type="primary" @click="selectedFile=null">清空</el-button>
<el-button v-if="!showEditor" type="primary" @click="editNote(selectedFile)">编辑</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="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;selectFile(editData)">返回</el-button>
<el-button v-if="showEditor" type="success" @click="handleSave(editData.content)">保存</el-button> <el-button v-if="showEditor" type="success" @click="handleSave(editData.content)">保存</el-button>
</div> </div>
</div> </div>
<v-md-preview v-if="!showEditor" :text="selectedFile.content" class="markdown-preview"></v-md-preview> <v-md-preview
v-if="!showEditor"
:text="selectedFile.content"
class="markdown-preview"
@copy-code-success="handleCopyCodeSuccess"
></v-md-preview>
<!-- Markdown编辑器 --> <!-- Markdown编辑器 -->
<v-md-editor <v-md-editor
v-if="showEditor" v-if="showEditor"
@@ -63,6 +68,8 @@
height="500px" height="500px"
@upload-image="handleImageUpload" @upload-image="handleImageUpload"
@save="handleSave" @save="handleSave"
:disabled-menus="[]"
@copy-code-success="handleCopyCodeSuccess"
></v-md-editor> ></v-md-editor>
</div> </div>
@@ -139,6 +146,8 @@ import '@kangc/v-md-editor/lib/theme/style/github.css';
import {groupingId, groupingAll, markdownAll, addGroupings, Preview, updateMarkdown} from '@/api/CommonApi.js' import {groupingId, groupingAll, markdownAll, addGroupings, Preview, updateMarkdown} from '@/api/CommonApi.js'
import {DArrowRight} from "@element-plus/icons-vue"; import {DArrowRight} from "@element-plus/icons-vue";
const markdownFiles = ref([]); const markdownFiles = ref([]);
const groupings = ref([]); const groupings = ref([]);
const groupFiles = ref({}); const groupFiles = ref({});
@@ -154,6 +163,8 @@ const newNoteForm = ref({ title: '', groupingId: null });
const editData=ref(null) const editData=ref(null)
// 获取所有分组 // 获取所有分组
const fetchGroupings = async () => { const fetchGroupings = async () => {
try { try {
@@ -197,6 +208,10 @@ const fetchGroupings = async () => {
} }
}; };
const handleCopyCodeSuccess = () => {
ElMessage.success('代码已复制到剪贴板');
};
// 获取所有Markdown文件确保ID为字符串 // 获取所有Markdown文件确保ID为字符串
const fetchMarkdownFiles = async () => { const fetchMarkdownFiles = async () => {
try { try {
@@ -277,8 +292,17 @@ const editNote = (file) => {
showEditor.value = true; showEditor.value = true;
}; };
const handleImageUpload=()=>{ const handleImageUpload=(event,insertImage,files)=>{
console.log(666) console.log(files)
// 插入图片
insertImage({
// 图片地址
url:
'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1269952892,3525182336&fm=26&gp=0.jpg'
// 图片描述
// desc: '七龙珠',
});
} }
// 在编辑页面按Ctrl+S保存笔记或者点击保存对数据进行保存 // 在编辑页面按Ctrl+S保存笔记或者点击保存对数据进行保存

View File

@@ -4,33 +4,47 @@ import router from './router/'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import VMdEditor from '@kangc/v-md-editor'; // 1. 导入编辑器和预览组件
import '@kangc/v-md-editor/lib/style/base-editor.css'; import VMdEditor from '@kangc/v-md-editor'
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js'; import VMdPreview from '@kangc/v-md-editor/lib/preview'
import '@kangc/v-md-editor/lib/theme/style/github.css';
import VMdPreview from '@kangc/v-md-editor/lib/preview'; // 2. 导入基础样式
import '@kangc/v-md-editor/lib/style/preview.css'; import '@kangc/v-md-editor/lib/style/base-editor.css'
import '@kangc/v-md-editor/lib/theme/style/github.css'; import '@kangc/v-md-editor/lib/style/preview.css'
// 3. 导入主题和样式
import githubTheme from '@kangc/v-md-editor/lib/theme/github'
import '@kangc/v-md-editor/lib/theme/style/github.css'
// 4. 导入复制代码插件及其样式
import createCopyCodePlugin from '@kangc/v-md-editor/lib/plugins/copy-code/index'
import '@kangc/v-md-editor/lib/plugins/copy-code/copy-code.css'
// 5. 导入高亮库
import hljs from 'highlight.js'
const app = createApp(App) const app = createApp(App)
// highlightjs // 6. 配置编辑器
import hljs from 'highlight.js';
VMdEditor.use(githubTheme, { VMdEditor.use(githubTheme, {
Hljs: hljs, Hljs: hljs,
}); })
// 7. 配置预览组件
VMdPreview.use(githubTheme, { VMdPreview.use(githubTheme, {
Hljs: hljs, Hljs: hljs,
}); })
// // 配置Markdown编辑器 // 8. 为编辑器和预览分别添加复制插件
app.use(VMdEditor); VMdEditor.use(createCopyCodePlugin())
app.use(VMdPreview); VMdPreview.use(createCopyCodePlugin()) // 注意这里使用同一个插件创建函数
// 使用Element Plus和路由 // 9. 注册组件
app.use(VMdEditor)
app.use(VMdPreview)
// 10. 使用Element Plus和路由
app.use(ElementPlus) app.use(ElementPlus)
app.use(router) app.use(router)
app.mount('#app') app.mount('#app')

Binary file not shown.