feat(笔记): 添加笔记移动功能并优化分类管理

- 后端:修改创建分组接口,支持接收 parent_id 参数
-前端:实现笔记移动功能,增加移动按钮和对话框- 优化分类列表渲染逻辑,支持点击分类名称查看笔记
- 调整笔记列表显示样式,增加分类名称
This commit is contained in:
2025-07-31 15:05:14 +08:00
parent 1bfc45b240
commit c660ae5b12
4 changed files with 74 additions and 19 deletions

View File

@@ -40,6 +40,7 @@
</h2>
<div class="actions">
<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="danger" @click="deleteNote(selectedFile)">删除</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">
<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>
</div>
<el-empty v-else description="暂无笔记,请创建或上传" />
@@ -169,6 +173,22 @@
<el-button type="primary" @click="confirmImport">确定</el-button>
</template>
</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-container>
</el-container>
@@ -213,6 +233,9 @@ const newName = ref('');
const showSelectGroupDialog = ref(false);
const importGroupId = ref(null);
const fileToImport = ref(null);
const showMoveNoteDialog = ref(false);
const moveToGroupId = ref(null);
const currentGroupName = ref('');
const groupFormRef = ref(null);
const newGroupForm = ref({ name: '', parentId: null });
@@ -242,9 +265,7 @@ const previewHtml = ref('');
const saveStatus = ref('空闲');
let debounceTimer = null;
const categoryCascaderOptions = computed(() => {
return [{ id: 0, grouping: '根分类', value: 0, label: '根分类' }, ...categoryTree.value];
});
const categoryCascaderOptions = computed(() => categoryTree.value);
const initVditor = () => {
vditor.value = new Vditor('vditor', {
@@ -274,11 +295,11 @@ const buildTree = (items) => {
// First, map all items by their id
items.forEach(item => {
itemMap.set(String(item.id), {
...item,
value: item.id,
label: item.grouping,
children: [], // Initialize children array
});
...item,
value: item.id,
label: item.grouping,
children: [], // Initialize children array
});
});
// Then, build the tree structure
@@ -323,6 +344,7 @@ const fetchGroupings = async () => {
const selectFile = async (data) => {
const promise = await markdownList(data.id);
groupMarkdownFiles.value = promise.data;
currentGroupName.value = data.grouping;
selectedFile.value = null;
};
@@ -344,7 +366,7 @@ const createGrouping = async () => {
if (valid) {
try {
const payload = {
name: newGroupForm.value.name,
grouping: newGroupForm.value.name, // 将 name 映射到 grouping
parentId: newGroupForm.value.parentId || 0
};
await addGroupings(payload);
@@ -406,7 +428,7 @@ const resetNoteForm = () => {
const renderMenu = (item) => {
if (item.children && item.children.length > 0) {
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('span', null, item.grouping),
h(ElIcon, { class: 'edit-icon', onClick: (e) => { e.stopPropagation(); openRenameDialog(item, 'group'); } }, () => h(Edit))
@@ -710,6 +732,31 @@ const handleExportMd = () => {
document.body.removeChild(link);
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>
<style>
@@ -932,6 +979,17 @@ const handleExportMd = () => {
font-weight: 500;
color: var(--text-color);
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 --- */