feat(笔记): 添加笔记移动功能并优化分类管理
- 后端:修改创建分组接口,支持接收 parent_id 参数 -前端:实现笔记移动功能,增加移动按钮和对话框- 优化分类列表渲染逻辑,支持点击分类名称查看笔记 - 调整笔记列表显示样式,增加分类名称
This commit is contained in:
@@ -24,6 +24,9 @@ public class GroupingController {
|
|||||||
@Operation(summary = "创建分组")
|
@Operation(summary = "创建分组")
|
||||||
@PostMapping
|
@PostMapping
|
||||||
public R<Grouping> createGrouping(@RequestBody Grouping grouping) {
|
public R<Grouping> createGrouping(@RequestBody Grouping grouping) {
|
||||||
|
if (grouping.getParentId() == null) {
|
||||||
|
grouping.setParentId(0L);
|
||||||
|
}
|
||||||
Grouping created = groupingService.createGrouping(grouping);
|
Grouping created = groupingService.createGrouping(grouping);
|
||||||
return R.success(created);
|
return R.success(created);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,14 +11,8 @@ export const markdownAll = () => axiosApi.get(`/api/markdown`);
|
|||||||
export const Preview = (id) => axiosApi.get(`/api/markdown/${id}`);
|
export const Preview = (id) => axiosApi.get(`/api/markdown/${id}`);
|
||||||
|
|
||||||
// 创建分类分组
|
// 创建分类分组
|
||||||
export const addGroupings = (name) => {
|
export const addGroupings = (group) => {
|
||||||
const formData = new FormData()
|
return axiosApi.post('/api/groupings', group);
|
||||||
if (name) formData.append('grouping', name)
|
|
||||||
return axiosApi.post('/api/groupings', formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
//更新Markdown文件
|
//更新Markdown文件
|
||||||
export const updateMarkdown = (data) => {
|
export const updateMarkdown = (data) => {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
</h2>
|
</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 && userStore.isLoggedIn" type="warning" @click="showMoveNoteDialog = true">移动</el-button>
|
||||||
<el-button v-if="!showEditor && userStore.isLoggedIn" type="primary" @click="editNote(selectedFile); isCollapsed = true">编辑</el-button>
|
<el-button v-if="!showEditor && userStore.isLoggedIn" type="primary" @click="editNote(selectedFile); isCollapsed = true">编辑</el-button>
|
||||||
<el-button v-if="!showEditor && userStore.isLoggedIn" type="danger" @click="deleteNote(selectedFile)">删除</el-button>
|
<el-button v-if="!showEditor && userStore.isLoggedIn" type="danger" @click="deleteNote(selectedFile)">删除</el-button>
|
||||||
<el-button v-if="showEditor" type="primary" @click="showEditor = !showEditor; previewFile(editData)">返回</el-button>
|
<el-button v-if="showEditor" type="primary" @click="showEditor = !showEditor; previewFile(editData)">返回</el-button>
|
||||||
@@ -93,7 +94,10 @@
|
|||||||
|
|
||||||
<div v-if="groupMarkdownFiles.length > 0" class="file-list">
|
<div v-if="groupMarkdownFiles.length > 0" class="file-list">
|
||||||
<el-card v-for="file in groupMarkdownFiles" :key="file.id" shadow="hover" class="file-item">
|
<el-card v-for="file in groupMarkdownFiles" :key="file.id" shadow="hover" class="file-item">
|
||||||
<div @click="previewFile(file)" class="file-title">{{ file.title }}</div>
|
<div @click="previewFile(file)" class="file-title">
|
||||||
|
<span>{{ file.title }}</span>
|
||||||
|
<span class="file-group-name">{{ currentGroupName }}</span>
|
||||||
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
</div>
|
</div>
|
||||||
<el-empty v-else description="暂无笔记,请创建或上传" />
|
<el-empty v-else description="暂无笔记,请创建或上传" />
|
||||||
@@ -169,6 +173,22 @@
|
|||||||
<el-button type="primary" @click="confirmImport">确定</el-button>
|
<el-button type="primary" @click="confirmImport">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 移动笔记对话框 -->
|
||||||
|
<el-dialog v-model="showMoveNoteDialog" title="移动笔记到" width="400px">
|
||||||
|
<el-cascader
|
||||||
|
v-model="moveToGroupId"
|
||||||
|
:options="categoryTree"
|
||||||
|
:props="{ checkStrictly: true, emitPath: false }"
|
||||||
|
clearable
|
||||||
|
placeholder="请选择目标分类"
|
||||||
|
style="width: 100%;"
|
||||||
|
></el-cascader>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="showMoveNoteDialog = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleMoveNote">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</el-main>
|
</el-main>
|
||||||
</el-container>
|
</el-container>
|
||||||
</el-container>
|
</el-container>
|
||||||
@@ -213,6 +233,9 @@ const newName = ref('');
|
|||||||
const showSelectGroupDialog = ref(false);
|
const showSelectGroupDialog = ref(false);
|
||||||
const importGroupId = ref(null);
|
const importGroupId = ref(null);
|
||||||
const fileToImport = ref(null);
|
const fileToImport = ref(null);
|
||||||
|
const showMoveNoteDialog = ref(false);
|
||||||
|
const moveToGroupId = ref(null);
|
||||||
|
const currentGroupName = ref('');
|
||||||
|
|
||||||
const groupFormRef = ref(null);
|
const groupFormRef = ref(null);
|
||||||
const newGroupForm = ref({ name: '', parentId: null });
|
const newGroupForm = ref({ name: '', parentId: null });
|
||||||
@@ -242,9 +265,7 @@ const previewHtml = ref('');
|
|||||||
const saveStatus = ref('空闲');
|
const saveStatus = ref('空闲');
|
||||||
let debounceTimer = null;
|
let debounceTimer = null;
|
||||||
|
|
||||||
const categoryCascaderOptions = computed(() => {
|
const categoryCascaderOptions = computed(() => categoryTree.value);
|
||||||
return [{ id: 0, grouping: '根分类', value: 0, label: '根分类' }, ...categoryTree.value];
|
|
||||||
});
|
|
||||||
|
|
||||||
const initVditor = () => {
|
const initVditor = () => {
|
||||||
vditor.value = new Vditor('vditor', {
|
vditor.value = new Vditor('vditor', {
|
||||||
@@ -274,11 +295,11 @@ const buildTree = (items) => {
|
|||||||
// First, map all items by their id
|
// First, map all items by their id
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
itemMap.set(String(item.id), {
|
itemMap.set(String(item.id), {
|
||||||
...item,
|
...item,
|
||||||
value: item.id,
|
value: item.id,
|
||||||
label: item.grouping,
|
label: item.grouping,
|
||||||
children: [], // Initialize children array
|
children: [], // Initialize children array
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then, build the tree structure
|
// Then, build the tree structure
|
||||||
@@ -323,6 +344,7 @@ const fetchGroupings = async () => {
|
|||||||
const selectFile = async (data) => {
|
const selectFile = async (data) => {
|
||||||
const promise = await markdownList(data.id);
|
const promise = await markdownList(data.id);
|
||||||
groupMarkdownFiles.value = promise.data;
|
groupMarkdownFiles.value = promise.data;
|
||||||
|
currentGroupName.value = data.grouping;
|
||||||
selectedFile.value = null;
|
selectedFile.value = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -344,7 +366,7 @@ const createGrouping = async () => {
|
|||||||
if (valid) {
|
if (valid) {
|
||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
name: newGroupForm.value.name,
|
grouping: newGroupForm.value.name, // 将 name 映射到 grouping
|
||||||
parentId: newGroupForm.value.parentId || 0
|
parentId: newGroupForm.value.parentId || 0
|
||||||
};
|
};
|
||||||
await addGroupings(payload);
|
await addGroupings(payload);
|
||||||
@@ -406,7 +428,7 @@ const resetNoteForm = () => {
|
|||||||
const renderMenu = (item) => {
|
const renderMenu = (item) => {
|
||||||
if (item.children && item.children.length > 0) {
|
if (item.children && item.children.length > 0) {
|
||||||
return h(ElSubMenu, { index: `group-${item.id}` }, {
|
return h(ElSubMenu, { index: `group-${item.id}` }, {
|
||||||
title: () => h('div', { class: 'menu-item-title' }, [
|
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))
|
||||||
@@ -710,6 +732,31 @@ const handleExportMd = () => {
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMoveNote = async () => {
|
||||||
|
if (!moveToGroupId.value) {
|
||||||
|
ElMessage.error('请选择目标分类');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!selectedFile.value) {
|
||||||
|
ElMessage.error('没有选中的笔记');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const updatedFile = {
|
||||||
|
...selectedFile.value,
|
||||||
|
groupingId: moveToGroupId.value,
|
||||||
|
};
|
||||||
|
await updateMarkdown(updatedFile);
|
||||||
|
ElMessage.success('笔记移动成功');
|
||||||
|
showMoveNoteDialog.value = false;
|
||||||
|
selectedFile.value = null; // 返回列表页
|
||||||
|
await chushihua(); // 刷新数据
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('移动失败: ' + error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -932,6 +979,17 @@ const handleExportMd = () => {
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-group-name {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-color-secondary);
|
||||||
|
background-color: var(--bg-color-tertiary);
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- File Preview --- */
|
/* --- File Preview --- */
|
||||||
|
|||||||
BIN
mydatabase.db
BIN
mydatabase.db
Binary file not shown.
Reference in New Issue
Block a user