feat(grouping): 添加删除分组功能

- 在前端增加删除分组的 API 接口和相应的方法
- 在后端实现删除分组的接口
- 修改分组实体类,使用 Snowflake算法生成 ID
- 在首页组件中添加删除分组的按钮和相关逻辑
- 删除分组时,将分组下的所有笔记移动到"未分类"目录
This commit is contained in:
2025-07-31 15:21:30 +08:00
parent c660ae5b12
commit 7bab57c59b
4 changed files with 57 additions and 6 deletions

View File

@@ -77,6 +77,9 @@ export const updateGroupingName = (id, newName) => {
return axiosApi.put(`/api/groupings/${id}`, { grouping: newName });
}
// 删除分组
export const deleteGrouping = (id) => axiosApi.delete(`/api/groupings/${id}`);
// 更新Markdown文件标题
export const updateMarkdownTitle = (id, newTitle) => {
return axiosApi.put(`/api/markdown/${id}/title`, newTitle, {

View File

@@ -208,9 +208,10 @@ import {
updateMarkdown, uploadImage,
searchMarkdown,
updateGroupingName,
updateMarkdownTitle
updateMarkdownTitle,
deleteGrouping as apiDeleteGrouping
} from '@/api/CommonApi.js'
import { Plus, Fold, Expand, Folder, Document, Search, Edit } from "@element-plus/icons-vue";
import { Plus, Fold, Expand, Folder, Document, Search, Edit, Delete } from "@element-plus/icons-vue";
import { useUserStore } from '../stores/user';
import { useRouter } from 'vue-router';
@@ -431,7 +432,8 @@ const renderMenu = (item) => {
title: () => h('div', { class: 'menu-item-title', onClick: () => selectFile(item) }, [
h(ElIcon, () => h(Folder)),
h('span', null, item.grouping),
h(ElIcon, { class: 'edit-icon', onClick: (e) => { e.stopPropagation(); openRenameDialog(item, 'group'); } }, () => h(Edit))
h(ElIcon, { class: 'edit-icon', onClick: (e) => { e.stopPropagation(); openRenameDialog(item, 'group'); } }, () => h(Edit)),
h(ElIcon, { class: 'delete-icon', onClick: (e) => { e.stopPropagation(); handleDeleteGroup(item); } }, () => h(Delete))
]),
default: () => item.children.map(child => renderMenu(child))
});
@@ -440,7 +442,8 @@ const renderMenu = (item) => {
default: () => h('div', { class: 'menu-item-title' }, [
h(ElIcon, () => h(Folder)),
h('span', null, item.grouping),
h(ElIcon, { class: 'edit-icon', onClick: (e) => { e.stopPropagation(); openRenameDialog(item, 'group'); } }, () => h(Edit))
h(ElIcon, { class: 'edit-icon', onClick: (e) => { e.stopPropagation(); openRenameDialog(item, 'group'); } }, () => h(Edit)),
h(ElIcon, { class: 'delete-icon', onClick: (e) => { e.stopPropagation(); handleDeleteGroup(item); } }, () => h(Delete))
])
});
};
@@ -757,6 +760,41 @@ const handleMoveNote = async () => {
ElMessage.error('移动失败: ' + error.message);
}
};
const handleDeleteGroup = async (group) => {
try {
await ElMessageBox.confirm(
'确定要删除这个分类吗?分类下的所有笔记将被移动到“未分类”。',
'警告',
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}
);
const unclassifiedGroup = categoryTree.value.find(g => g.grouping === '未分类');
if (!unclassifiedGroup) {
ElMessage.error('未找到“未分类”目录,无法移动笔记。');
return;
}
const notesToMove = await markdownList(group.id);
if (notesToMove.data && notesToMove.data.length > 0) {
for (const note of notesToMove.data) {
await updateMarkdown({ ...note, groupingId: unclassifiedGroup.id });
}
}
await apiDeleteGrouping(group.id);
ElMessage.success('分类删除成功');
await fetchGroupings();
} catch (error) {
if (error !== 'cancel') {
ElMessage.error('删除失败: ' + (error.message || ''));
}
}
};
</script>
<style>
@@ -900,19 +938,24 @@ const handleMoveNote = async () => {
text-overflow: ellipsis;
}
.edit-icon {
.edit-icon, .delete-icon {
cursor: pointer;
margin-left: 8px;
opacity: 0;
transition: opacity var(--transition-duration) ease;
color: var(--text-color-secondary);
}
.delete-icon:hover {
color: var(--el-color-danger);
}
.edit-icon:hover {
color: var(--primary-color);
}
.el-sub-menu__title:hover .edit-icon,
.el-sub-menu__title:hover .delete-icon,
.el-menu-item:hover .edit-icon,
.el-menu-item:hover .delete-icon,
.preview-title:hover .edit-icon {
opacity: 1;
}