feat(grouping): 新增分组功能并优化 Markdown 文件操作

- 新增分组实体、控制器、服务和映射器
- 实现分组创建、获取、更新和删除接口
- 优化 Markdown 文件创建、获取和删除接口- 新增全局异常处理和日志记录
- 更新数据库表结构和字段类型
- 重构前端页面,支持分组和 Markdown 文件展示
This commit is contained in:
ikmkj
2025-06-17 20:46:10 +08:00
parent 8b43b68e62
commit 4557bd49f9
29 changed files with 4286 additions and 97 deletions

View File

@@ -0,0 +1,266 @@
<template>
<div class="home-page">
<el-container style="height: 100vh;">
<!-- 左侧菜单栏 -->
<el-aside width="300px" style="background-color: #f5f7fa; border-right: 1px solid #ebeef5;">
<div class="sidebar-header">
<h3>笔记分类</h3>
<el-button @click="isCollapsed = !isCollapsed" size="small" type="text">
{{ isCollapsed ? '展开' : '收起' }}
</el-button>
</div>
<el-menu
v-if="!isCollapsed"
default-active="all"
class="el-menu-vertical-demo"
@select="handleMenuSelect"
>
<el-menu-item index="all">
<span>全部笔记</span>
</el-menu-item>
<el-submenu v-for="group in groupings" :key="group.id" :index="'group-'+group.id">
<template #title>
<span>{{ group.grouping }}</span>
</template>
<el-menu-item
v-for="file in groupFiles[group.id] || []"
:key="file.id"
:index="'file-'+file.id"
>
{{ file.title }}
</el-menu-item>
</el-submenu>
</el-menu>
</el-aside>
<!-- 右侧内容区 -->
<el-main style="padding: 20px;">
<div class="header" v-if="!selectedFile">
<h1>我的笔记</h1>
<div class="actions">
<el-button type="primary" @click="showEditor = true">新建笔记</el-button>
<el-upload
action=""
:show-file-list="false"
:before-upload="handleMarkdownUpload"
accept=".md"
class="upload-btn"
>
<el-button type="success">上传Markdown</el-button>
</el-upload>
</div>
</div>
<div v-if="selectedFile" class="file-preview">
<div class="preview-header">
<h2>{{ selectedFile.title }}</h2>
<div>
<el-button type="primary" @click="editNote(selectedFile)">编辑</el-button>
<el-button @click="selectedFile = null">返回列表</el-button>
</div>
</div>
<div class="markdown-preview">
<v-md-preview :text="selectedFile.content"></v-md-preview>
</div>
</div>
</el-main>
</el-container>
<el-dialog v-model="showEditor" title="Markdown编辑器" width="80%">
<MarkdownEditor v-if="showEditor" :fileId="currentFileId" @close="showEditor = false" />
</el-dialog>
</div>
</template>
<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';
import { ElMessage } from 'element-plus';
import MarkdownEditor from './MarkdownEditor.vue';
import VMdPreview from '@kangc/v-md-editor/lib/preview';
import '@kangc/v-md-editor/lib/style/preview.css';
import githubTheme from '@kangc/v-md-editor/lib/theme/github.js';
import '@kangc/v-md-editor/lib/theme/style/github.css';
VMdPreview.use(githubTheme);
export default {
components: {
MarkdownEditor,
[VMdPreview.name]: VMdPreview
},
setup() {
const API_BASE_URL = 'http://localhost:8083';
const markdownFiles = ref([]);
const groupings = ref([]);
const groupFiles = ref({});
const showEditor = ref(false);
const currentFileId = ref(null);
const selectedFile = ref(null);
const isCollapsed = ref(false);
// 获取所有分组
const fetchGroupings = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/api/groupings`);
groupings.value = response.data;
// 为每个分组获取文件
for (const group of groupings.value) {
const filesRes = await axios.get(`${API_BASE_URL}/api/markdown/grouping/${group.id}`);
groupFiles.value[group.id] = filesRes.data;
}
} catch (error) {
ElMessage.error('获取分组失败: ' + error.message);
}
};
// 获取所有Markdown文件
const fetchMarkdownFiles = async () => {
try {
const response = await axios.get(`${API_BASE_URL}/api/markdown`);
markdownFiles.value = response.data;
} catch (error) {
ElMessage.error('获取笔记列表失败: ' + error.message);
}
};
// 编辑笔记
const editNote = (file) => {
currentFileId.value = file.id;
showEditor.value = true;
};
// 删除笔记
const deleteNote = async (file) => {
try {
await axios.delete(`${API_BASE_URL}/api/markdown/${file.id}`);
ElMessage.success('删除成功');
fetchMarkdownFiles();
fetchGroupings(); // 重新加载分组和文件
} catch (error) {
ElMessage.error('删除失败: ' + error.message);
}
};
// 菜单选择处理
const handleMenuSelect = (index) => {
if (index === 'all') {
selectedFile.value = null;
} else if (index.startsWith('file-')) {
const fileId = index.split('-')[1];
const file = markdownFiles.value.find(f => f.id == fileId);
if (file) {
selectedFile.value = file;
}
}
};
// 上传Markdown文件处理
const handleMarkdownUpload = (file) => {
const reader = new FileReader();
reader.onload = async (e) => {
const content = e.target.result;
const fileName = file.name.replace('.md', '');
try {
const params = new URLSearchParams();
params.append('userId', '1');
params.append('title', fileName);
params.append('fileName', fileName);
await axios.post(`${API_BASE_URL}/api/markdown`, content, {
params: params,
headers: {
'Content-Type': 'text/plain'
}
});
ElMessage.success('上传成功');
fetchMarkdownFiles();
fetchGroupings(); // 重新加载分组和文件
} catch (error) {
ElMessage.error('上传失败: ' + error.message);
}
};
reader.readAsText(file);
return false; // 阻止默认上传行为
};
onMounted(() => {
fetchMarkdownFiles();
fetchGroupings();
});
return {
markdownFiles,
groupings,
groupFiles,
showEditor,
currentFileId,
selectedFile,
isCollapsed,
editNote,
deleteNote,
handleMarkdownUpload,
handleMenuSelect
};
}
};
</script>
<style scoped>
.home-page {
height: 100vh;
}
.sidebar-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
border-bottom: 1px solid #ebeef5;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.actions {
display: flex;
gap: 10px;
}
.upload-btn {
display: inline-block;
margin-left: 10px;
}
.file-preview {
padding: 20px;
border: 1px solid #ebeef5;
border-radius: 4px;
background: #fff;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.markdown-preview {
min-height: 500px;
padding: 20px;
border: 1px solid #f0f0f0;
border-radius: 4px;
background: #fff;
}
</style>