feat(grouping): 添加删除分组功能
- 在前端增加删除分组的 API 接口和相应的方法 - 在后端实现删除分组的接口 - 修改分组实体类,使用 Snowflake算法生成 ID - 在首页组件中添加删除分组的按钮和相关逻辑 - 删除分组时,将分组下的所有笔记移动到"未分类"目录
This commit is contained in:
@@ -13,7 +13,7 @@ import lombok.Data;
|
|||||||
@TableName("grouping")
|
@TableName("grouping")
|
||||||
public class Grouping {
|
public class Grouping {
|
||||||
@Schema(description = "分组id",implementation = Long.class)
|
@Schema(description = "分组id",implementation = Long.class)
|
||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.ASSIGN_ID)
|
||||||
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
@JsonFormat(shape = JsonFormat.Shape.STRING) // 仅作用于此字段
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||||||
import com.test.bijihoudaun.entity.Grouping;
|
import com.test.bijihoudaun.entity.Grouping;
|
||||||
import com.test.bijihoudaun.mapper.GroupingMapper;
|
import com.test.bijihoudaun.mapper.GroupingMapper;
|
||||||
import com.test.bijihoudaun.service.GroupingService;
|
import com.test.bijihoudaun.service.GroupingService;
|
||||||
|
import com.test.bijihoudaun.util.SnowflakeIdGenerator;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -17,10 +18,14 @@ public class GroupingServiceImpl
|
|||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private GroupingMapper groupingMapper;
|
private GroupingMapper groupingMapper;
|
||||||
|
@Resource
|
||||||
|
private SnowflakeIdGenerator snowflakeIdGenerator;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Grouping createGrouping(Grouping grouping) {
|
public Grouping createGrouping(Grouping grouping) {
|
||||||
|
long id = snowflakeIdGenerator.nextId();
|
||||||
|
grouping.setId(id);
|
||||||
this.save(grouping);
|
this.save(grouping);
|
||||||
return grouping;
|
return grouping;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,9 @@ export const updateGroupingName = (id, newName) => {
|
|||||||
return axiosApi.put(`/api/groupings/${id}`, { grouping: newName });
|
return axiosApi.put(`/api/groupings/${id}`, { grouping: newName });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 删除分组
|
||||||
|
export const deleteGrouping = (id) => axiosApi.delete(`/api/groupings/${id}`);
|
||||||
|
|
||||||
// 更新Markdown文件标题
|
// 更新Markdown文件标题
|
||||||
export const updateMarkdownTitle = (id, newTitle) => {
|
export const updateMarkdownTitle = (id, newTitle) => {
|
||||||
return axiosApi.put(`/api/markdown/${id}/title`, newTitle, {
|
return axiosApi.put(`/api/markdown/${id}/title`, newTitle, {
|
||||||
|
|||||||
@@ -208,9 +208,10 @@ import {
|
|||||||
updateMarkdown, uploadImage,
|
updateMarkdown, uploadImage,
|
||||||
searchMarkdown,
|
searchMarkdown,
|
||||||
updateGroupingName,
|
updateGroupingName,
|
||||||
updateMarkdownTitle
|
updateMarkdownTitle,
|
||||||
|
deleteGrouping as apiDeleteGrouping
|
||||||
} from '@/api/CommonApi.js'
|
} 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 { useUserStore } from '../stores/user';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
|
|
||||||
@@ -431,7 +432,8 @@ const renderMenu = (item) => {
|
|||||||
title: () => h('div', { class: 'menu-item-title', onClick: () => selectFile(item) }, [
|
title: () => h('div', { class: 'menu-item-title', onClick: () => selectFile(item) }, [
|
||||||
h(ElIcon, () => h(Folder)),
|
h(ElIcon, () => h(Folder)),
|
||||||
h('span', null, item.grouping),
|
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))
|
default: () => item.children.map(child => renderMenu(child))
|
||||||
});
|
});
|
||||||
@@ -440,7 +442,8 @@ const renderMenu = (item) => {
|
|||||||
default: () => h('div', { class: 'menu-item-title' }, [
|
default: () => h('div', { class: 'menu-item-title' }, [
|
||||||
h(ElIcon, () => h(Folder)),
|
h(ElIcon, () => h(Folder)),
|
||||||
h('span', null, item.grouping),
|
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);
|
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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -900,19 +938,24 @@ const handleMoveNote = async () => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-icon {
|
.edit-icon, .delete-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity var(--transition-duration) ease;
|
transition: opacity var(--transition-duration) ease;
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
}
|
}
|
||||||
|
.delete-icon:hover {
|
||||||
|
color: var(--el-color-danger);
|
||||||
|
}
|
||||||
.edit-icon:hover {
|
.edit-icon:hover {
|
||||||
color: var(--primary-color);
|
color: var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-sub-menu__title:hover .edit-icon,
|
.el-sub-menu__title:hover .edit-icon,
|
||||||
|
.el-sub-menu__title:hover .delete-icon,
|
||||||
.el-menu-item:hover .edit-icon,
|
.el-menu-item:hover .edit-icon,
|
||||||
|
.el-menu-item:hover .delete-icon,
|
||||||
.preview-title:hover .edit-icon {
|
.preview-title:hover .edit-icon {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user