feattrash: 优化删除功能和回收站逻辑
- 修改 Markdown 文件和分组的删除逻辑,使用软删除方式 - 更新回收站相关接口和页面展示 -优化前端保存逻辑,支持新建文件和更新文件 - 调整后端 API 接口,使用更合适的 HTTP 方法
This commit is contained in:
@@ -61,10 +61,9 @@ public class MarkdownController {
|
|||||||
@Parameters({
|
@Parameters({
|
||||||
@Parameter(name = "id", description = "Markdown文件ID", required = true),
|
@Parameter(name = "id", description = "Markdown文件ID", required = true),
|
||||||
})
|
})
|
||||||
@PostMapping("/delete")
|
@DeleteMapping("/{id}")
|
||||||
public R<Void> deleteMarkdown(String id) {
|
public R<Void> deleteMarkdown(@PathVariable Long id) {
|
||||||
long l = Long.parseLong(id);
|
if (markdownFileService.deleteMarkdownFile(id)) {
|
||||||
if (markdownFileService.deleteMarkdownFile(l)) {
|
|
||||||
return R.success();
|
return R.success();
|
||||||
}
|
}
|
||||||
return R.fail();
|
return R.fail();
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public class TrashItemVo {
|
|||||||
private String id;
|
private String id;
|
||||||
|
|
||||||
@Schema(description = "项目名称(笔记标题或分组名称)")
|
@Schema(description = "项目名称(笔记标题或分组名称)")
|
||||||
private String name;
|
private String title;
|
||||||
|
|
||||||
@Schema(description = "项目类型(note 或 group)")
|
@Schema(description = "项目类型(note 或 group)")
|
||||||
private String type;
|
private String type;
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ package com.test.bijihoudaun.mapper;
|
|||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.test.bijihoudaun.entity.Grouping;
|
import com.test.bijihoudaun.entity.Grouping;
|
||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface GroupingMapper extends BaseMapper<Grouping> {
|
public interface GroupingMapper extends BaseMapper<Grouping> {
|
||||||
|
|
||||||
|
@Select("SELECT * FROM grouping WHERE is_deleted = 1")
|
||||||
|
List<Grouping> selectDeleted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
|||||||
@Select("SELECT mf.*, g.grouping as groupingName " +
|
@Select("SELECT mf.*, g.grouping as groupingName " +
|
||||||
"FROM markdown_file mf " +
|
"FROM markdown_file mf " +
|
||||||
"LEFT JOIN grouping g ON mf.grouping_id = g.id " +
|
"LEFT JOIN grouping g ON mf.grouping_id = g.id " +
|
||||||
|
"WHERE mf.is_deleted = 0 " +
|
||||||
"ORDER BY mf.updated_at DESC " +
|
"ORDER BY mf.updated_at DESC " +
|
||||||
"LIMIT #{limit}")
|
"LIMIT #{limit}")
|
||||||
List<MarkdownFileVO> selectRecentWithGrouping(@Param("limit") int limit);
|
List<MarkdownFileVO> selectRecentWithGrouping(@Param("limit") int limit);
|
||||||
@@ -22,7 +23,10 @@ public interface MarkdownFileMapper extends BaseMapper<MarkdownFile> {
|
|||||||
@Select("SELECT mf.*, g.grouping as groupingName " +
|
@Select("SELECT mf.*, g.grouping as groupingName " +
|
||||||
"FROM markdown_file mf " +
|
"FROM markdown_file mf " +
|
||||||
"LEFT JOIN grouping g ON mf.grouping_id = g.id " +
|
"LEFT JOIN grouping g ON mf.grouping_id = g.id " +
|
||||||
"WHERE mf.grouping_id = #{groupingId} " +
|
"WHERE mf.grouping_id = #{groupingId} AND mf.is_deleted = 0 " +
|
||||||
"ORDER BY mf.updated_at DESC")
|
"ORDER BY mf.updated_at DESC")
|
||||||
List<MarkdownFileVO> selectByGroupingIdWithGrouping(@Param("groupingId") String groupingId);
|
List<MarkdownFileVO> selectByGroupingIdWithGrouping(@Param("groupingId") String groupingId);
|
||||||
|
|
||||||
|
@Select("SELECT * FROM markdown_file WHERE is_deleted = 1")
|
||||||
|
List<MarkdownFile> selectDeleted();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,19 @@ public class GroupingServiceImpl
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void deleteGrouping(Long id) {
|
public void deleteGrouping(Long id) {
|
||||||
LambdaUpdateWrapper<MarkdownFile> updateWrapper = new LambdaUpdateWrapper<>();
|
// 1. 使用 LambdaUpdateWrapper 软删除分组本身,确保 isDeleted 和 deletedAt 都被更新
|
||||||
updateWrapper.eq(MarkdownFile::getGroupingId, id)
|
LambdaUpdateWrapper<Grouping> groupingUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
.set(MarkdownFile::getGroupingId, 999L);
|
groupingUpdateWrapper.eq(Grouping::getId, id)
|
||||||
markdownFileMapper.update(null, updateWrapper);
|
.set(Grouping::getIsDeleted, 1)
|
||||||
|
.set(Grouping::getDeletedAt, new java.util.Date());
|
||||||
|
this.update(groupingUpdateWrapper);
|
||||||
|
|
||||||
this.removeById(id);
|
// 2. 将该分组下的所有笔记也一并软删除
|
||||||
|
LambdaUpdateWrapper<MarkdownFile> markdownFileUpdateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
markdownFileUpdateWrapper.eq(MarkdownFile::getGroupingId, id)
|
||||||
|
.set(MarkdownFile::getIsDeleted, 1)
|
||||||
|
.set(MarkdownFile::getDeletedAt, new java.util.Date());
|
||||||
|
markdownFileMapper.update(null, markdownFileUpdateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.test.bijihoudaun.service.impl;
|
package com.test.bijihoudaun.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.test.bijihoudaun.entity.MarkdownFile;
|
import com.test.bijihoudaun.entity.MarkdownFile;
|
||||||
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
import com.test.bijihoudaun.entity.MarkdownFileVO;
|
||||||
@@ -52,7 +53,11 @@ public class MarkdownFileServiceImpl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean deleteMarkdownFile(Long id) {
|
public boolean deleteMarkdownFile(Long id) {
|
||||||
return this.removeById(id);
|
LambdaUpdateWrapper<MarkdownFile> updateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
updateWrapper.eq(MarkdownFile::getId, id)
|
||||||
|
.set(MarkdownFile::getIsDeleted, 1)
|
||||||
|
.set(MarkdownFile::getDeletedAt, new Date());
|
||||||
|
return this.update(updateWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ public class TrashServiceImpl implements TrashService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<TrashItemVo> getTrashItems() {
|
public List<TrashItemVo> getTrashItems() {
|
||||||
// 查询已删除的笔记
|
// 调用自定义的Mapper方法查询已删除的笔记,绕过全局逻辑删除过滤器
|
||||||
List<TrashItemVo> deletedNotes = markdownFileMapper.selectList(new QueryWrapper<MarkdownFile>().eq("is_deleted", 1))
|
List<TrashItemVo> deletedNotes = markdownFileMapper.selectDeleted()
|
||||||
.stream()
|
.stream()
|
||||||
.map(file -> {
|
.map(file -> {
|
||||||
TrashItemVo vo = new TrashItemVo();
|
TrashItemVo vo = new TrashItemVo();
|
||||||
vo.setId(String.valueOf(file.getId()));
|
vo.setId(String.valueOf(file.getId()));
|
||||||
vo.setName(file.getTitle());
|
vo.setTitle(file.getTitle());
|
||||||
vo.setType("note");
|
vo.setType("note");
|
||||||
vo.setDeletedAt(file.getDeletedAt());
|
vo.setDeletedAt(file.getDeletedAt());
|
||||||
vo.setDeletedBy(String.valueOf(file.getDeletedBy()));
|
vo.setDeletedBy(String.valueOf(file.getDeletedBy()));
|
||||||
@@ -40,13 +40,13 @@ public class TrashServiceImpl implements TrashService {
|
|||||||
})
|
})
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
// 查询已删除的分组
|
// 调用自定义的Mapper方法查询已删除的分组
|
||||||
List<TrashItemVo> deletedGroups = groupingMapper.selectList(new QueryWrapper<Grouping>().eq("is_deleted", 1))
|
List<TrashItemVo> deletedGroups = groupingMapper.selectDeleted()
|
||||||
.stream()
|
.stream()
|
||||||
.map(group -> {
|
.map(group -> {
|
||||||
TrashItemVo vo = new TrashItemVo();
|
TrashItemVo vo = new TrashItemVo();
|
||||||
vo.setId(String.valueOf(group.getId()));
|
vo.setId(String.valueOf(group.getId()));
|
||||||
vo.setName(group.getGrouping());
|
vo.setTitle(group.getGrouping());
|
||||||
vo.setType("group");
|
vo.setType("group");
|
||||||
vo.setDeletedAt(group.getDeletedAt());
|
vo.setDeletedAt(group.getDeletedAt());
|
||||||
vo.setDeletedBy(String.valueOf(group.getDeletedBy()));
|
vo.setDeletedBy(String.valueOf(group.getDeletedBy()));
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const uploadImage = (file) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 删除Markdown文件
|
// 删除Markdown文件
|
||||||
export const deleteMarkdown = (id) => axiosApi.post(`/api/markdown/delete?id=${id}`);
|
export const deleteMarkdown = (id) => axiosApi.delete(`/api/markdown/${id}`);
|
||||||
// 根据分组ID获取Markdown文件列表
|
// 根据分组ID获取Markdown文件列表
|
||||||
export const markdownList = (groupingId) => axiosApi.get(`/api/markdown/grouping/${groupingId}`);
|
export const markdownList = (groupingId) => axiosApi.get(`/api/markdown/grouping/${groupingId}`);
|
||||||
|
|
||||||
|
|||||||
@@ -548,29 +548,41 @@ const debouncedSave = (value) => {
|
|||||||
const handleSave = async (content) => {
|
const handleSave = async (content) => {
|
||||||
saveStatus.value = '正在保存...';
|
saveStatus.value = '正在保存...';
|
||||||
try {
|
try {
|
||||||
const newImageUrls = extractImageUrls(content);
|
// 构造一个干净的、只包含必要字段的 payload
|
||||||
const deletedImages = originalImages.value.filter(url => !newImageUrls.includes(url));
|
const payload = {
|
||||||
|
id: editData.value.id, // 可能是 null,用于创建
|
||||||
|
title: editData.value.title,
|
||||||
|
groupingId: editData.value.groupingId,
|
||||||
|
content: content,
|
||||||
|
fileName: editData.value.fileName || `${editData.value.title}.md`,
|
||||||
|
};
|
||||||
|
|
||||||
if (deletedImages.length > 0) {
|
// 调用后端接口
|
||||||
await deleteImages({ imageUrls: deletedImages });
|
const response = await updateMarkdown(payload);
|
||||||
|
|
||||||
|
// 使用后端返回的完整、最新的数据更新前端状态
|
||||||
|
// 这对于新创建的笔记至关重要,因为它会获得一个新的 ID
|
||||||
|
editData.value = response.data;
|
||||||
|
|
||||||
|
// 如果当前正在预览这个文件,也更新 selectedFile
|
||||||
|
if (selectedFile.value && (!selectedFile.value.id || selectedFile.value.id === response.data.id)) {
|
||||||
|
selectedFile.value = response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
originalImages.value = newImageUrls;
|
|
||||||
|
|
||||||
const payload = {
|
|
||||||
...editData.value,
|
|
||||||
content: content,
|
|
||||||
};
|
|
||||||
const response = await updateMarkdown(payload);
|
|
||||||
editData.value = response.data;
|
|
||||||
selectedFile.value = editData.value;
|
|
||||||
saveStatus.value = '已保存';
|
saveStatus.value = '已保存';
|
||||||
ElMessage.success('保存成功');
|
ElMessage.success('保存成功');
|
||||||
await fetchGroupings();
|
|
||||||
|
// 刷新文件列表以反映更改(例如,新文件出现)
|
||||||
await fetchMarkdownFiles();
|
await fetchMarkdownFiles();
|
||||||
|
// 如果当前在某个分类下,也刷新该分类的列表
|
||||||
|
if (activeMenu.value.startsWith('group-')) {
|
||||||
|
const groupId = activeMenu.value.split('-');
|
||||||
|
await selectFile({ id: groupId, grouping: currentGroupName.value });
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
saveStatus.value = '保存失败';
|
saveStatus.value = '保存失败';
|
||||||
ElMessage.error('保存失败: ' + error.message);
|
ElMessage.error('保存失败: ' + (error.response?.data?.message || error.message));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
BIN
mydatabase.db
BIN
mydatabase.db
Binary file not shown.
Reference in New Issue
Block a user