大岭山建设网站万网app下载
个人博客系统
一个基于 Spring Boot + Vue.js 的现代化个人博客系统。
功能特点
- 📝 Markdown 编辑器支持
- 🏷️ 文章标签管理
- 🔍 全文搜索功能
- 📱 响应式设计
- 🌓 深色模式支持
- 📊 文章分类统计
- 🖼️ 图片上传功能
- 💾 基于文件系统的存储
技术栈
后端
- Spring Boot 2.3.4
- Hutool 工具包
- 基于文件系统的存储方案
前端
- Vue 3
- Vue Router
- Tailwind CSS
- v-md-editor (Markdown 编辑器)
- Axios
快速开始
环境要求
- JDK 8+
- Node.js 14+
- Maven 3.6+
后端启动
cd 后端
mvn spring-boot:run
前端启动
cd 前端
npm install
npm run serve
项目结构
personal-blog-system/
├── 前端/ # Vue.js 前端项目
│ ├── src/ # 源代码
│ ├── public/ # 静态资源
│ └── package.json # 项目依赖
└── 后端/ # Spring Boot 后端项目├── src/ # 源代码└── pom.xml # Maven 配置
配置说明
后端配置
主要配置文件位于 后端/src/main/resources/application.properties
:
server.port
: 服务器端口base.dir
: 文章存储目录metadata.file
: 元数据文件路径upload.dir
: 图片上传目录
前端配置
主要配置文件位于 前端/vue.config.js
:
- 开发服务器配置
- 构建配置
- 代理配置
主要功能说明
文章管理
- 支持创建、编辑、删除文章
- Markdown 格式支持
- 自动生成文章摘要
- 标签管理
文章展示
- 响应式布局
- 代码高亮
- 标签筛选
- 时间线展示
搜索功能
- 支持按标题搜索
- 支持按标签筛选
- 支持按时间排序
演示截图
1.首页
2.文章书写页面
3.文章阅读页面
调整为个人博客网站操作说明
- 只需要将前台的文章书写页面屏蔽掉,然后将文章列表页面的修改按钮屏蔽掉
- 后台的相应文章修改和新增接口也屏蔽掉,即转换为个人博客网站
源码下载
个人博客系统
核心源码
Home.vue
<template><div style="min-height: 1024px"><header class="fixed top-0 left-0 right-0 bg-white shadow-sm z-50"><div class="max-w-7xl mx-auto px-4"><div class="flex items-center justify-between h-16"><div class="flex items-center"><router-link to="/" class="text-2xl font-['Pacifico'] text-primary">logo</router-link><nav class="ml-10 space-x-8"><a href="#" class="text-gray-900 hover:text-primary">首页</a><a href="#" @click.prevent="scrollToElem('categories')" class="text-gray-600 hover:text-primary">热门分类</a><a href="#" @click.prevent="scrollToElem('latest')" class="text-gray-600 hover:text-primary">最新文章</a><a href="#" @click.prevent="scrollToElem('recommended')" class="text-gray-600 hover:text-primary">推荐阅读</a><a href="#" @click.prevent="scrollToElem('about')" class="text-gray-600 hover:text-primary">关于我们</a></nav></div><div class="flex items-center"><div class="relative"><inputv-model="searchKeyword"type="text"placeholder="搜索文章..."@keyup.enter="handleSearch"class="w-64 h-10 pl-10 pr-4 rounded-full bg-gray-100 border-none focus:outline-none focus:ring-2 focus:ring-primary/20 text-sm"><i class="fas fa-search absolute left-4 top-1/2 -translate-y-1/2 text-gray-400 text-sm cursor-pointer" @click="handleSearch"></i></div><button @click="toCreatePost" class="ml-4 px-4 py-2 bg-primary text-white !rounded-button hover:bg-primary/90 whitespace-nowrap">写文章</button></div></div></div></header><main class="mt-16 min-h-screen"><section class="hero-section relative h-[600px] flex items-center"><div class="max-w-7xl mx-auto px-4 w-full"><div class="max-w-2xl bg-white/90 backdrop-blur-sm p-12 rounded-lg"><h1 class="text-4xl font-bold text-gray-900 mb-6">探索技术与创意的交汇</h1><p class="text-lg text-gray-700 mb-8">在这里,我们分享技术见解、设计灵感和创新思维。让我们一起探索数字世界的无限可能。</p><button class="px-8 py-3 bg-primary text-white !rounded-button hover:bg-primary/90 whitespace-nowrap text-lg" @click="toSearch">开始阅读</button></div></div></section><section id="categories" class="py-16 bg-white"><div class="max-w-7xl mx-auto px-4"><h2 class="text-2xl font-bold text-gray-900 mb-8">热门分类</h2><div class="grid grid-cols-4 gap-6"><div class="category-card group" @click="handleCategoryClick('技术开发')"><div class="aspect-w-4 aspect-h-3 rounded-lg overflow-hidden mb-4"><img src="@/image/8b61d5e137821556fc4f8579832e31a9.jpg" alt="技术"class="w-full h-full object-cover category-image"></div><h3 class="font-medium text-gray-900">data.技术开发</h3><p class="text-sm text-gray-500">{{ data.技术开发 }} 篇文章</p></div><div class="category-card group" @click="handleCategoryClick('设计创意')"><div class="aspect-w-4 aspect-h-3 rounded-lg overflow-hidden mb-4"><img src="@/image/d347ff83edd65ed540ab221eaf6492f5.jpg" alt="设计"class="w-full h-full object-cover category-image"></div><h3 class="font-medium text-gray-900">设计创意</h3><p class="text-sm text-gray-500">{{ data.设计创意 }} 篇文章</p></div><div class="category-card group" @click="handleCategoryClick('产品思维')"><div class="aspect-w-4 aspect-h-3 rounded-lg overflow-hidden mb-4"><img src="@/image/c6c5099dde513aca3cd5b3bb0711cf82.jpg" alt="产品"class="w-full h-full object-cover category-image"></div><h3 class="font-medium text-gray-900">产品思维</h3><p class="text-sm text-gray-500">{{ data.产品思维 }} 篇文章</p></div><div class="category-card group" @click="handleCategoryClick('生活感悟')"><div class="aspect-w-4 aspect-h-3 rounded-lg overflow-hidden mb-4"><img src="@/image/1fb2c11596c1d0e87f38b4ae78434971.jpg" alt="生活"class="w-full h-full object-cover category-image"></div><h3 class="font-medium text-gray-900">生活感悟</h3><p class="text-sm text-gray-500">{{ data.生活感悟 }} 篇文章</p></div></div></div></section><section id="latest" class="py-16 bg-gray-50"><div class="max-w-7xl mx-auto px-4"><div class="flex justify-between items-center mb-8"><h2 class="text-2xl font-bold text-gray-900">最新文章</h2><div class="flex space-x-2"></div></div><div class="grid grid-cols-2 gap-8"><article class="bg-white rounded-lg shadow-sm overflow-hidden article-card" v-if="topPost1.id"><img src="@/image/771d287c74c79cf46d39a0fe0cdaa1f1.jpg" alt="文章配图"class="w-full h-64 object-cover"><div class="p-6"><div class="flex items-center space-x-4 mb-4"><img src="@/image/17c3f9f4f41171ffe7c623beb4627fa8.jpg" alt="作者头像"class="w-10 h-10 rounded-full"><div><h4 class="font-medium text-gray-900">冰冰一号</h4><p class="text-sm text-gray-500">{{ topPost1.createTime }}</p></div></div><h3 class="text-xl font-bold text-gray-900 mb-2"><router-link :to="`/post/${topPost1.id}`" class="hover:text-primary">{{ topPost1.title }}</router-link></h3><p class="text-gray-600 mb-4 line-clamp-2 min-h-[48px]">{{ topPost1.summary }}</p><div class="flex items-center justify-between"><div class="flex items-center space-x-4"><span class="text-sm text-gray-500"></span><span class="text-sm text-gray-500"></span></div><div class="flex items-center space-x-2"><span class="px-3 py-1 text-sm text-primary bg-primary/10 rounded-full">{{ topPost1.tag }}</span></div></div></div></article><article class="bg-white rounded-lg shadow-sm overflow-hidden article-card" v-if="topPost2.id"><img src="@/image/4adb9fb8469c6d5721f141cd78770e33.jpg" alt="文章配图"class="w-full h-64 object-cover"><div class="p-6"><div class="flex items-center space-x-4 mb-4"><img src="@/image/9d1e781d74a8e978da68f67dfad16a43.jpg" alt="作者头像"class="w-10 h-10 rounded-full"><div><h4 class="font-medium text-gray-900">冰冰一号</h4><p class="text-sm text-gray-500">{{ topPost2.createTime }}</p></div></div><h3 class="text-xl font-bold text-gray-900 mb-2"><router-link :to="`/post/${topPost2.id}`" class="hover:text-primary">{{ topPost2.title }}</router-link></h3><p class="text-gray-600 mb-4 line-clamp-2 min-h-[48px]">{{ topPost2.summary }}</p><div class="flex items-center justify-between"><div class="flex items-center space-x-4"><span class="text-sm text-gray-500"></span><span class="text-sm text-gray-500"></span></div><div class="flex items-center space-x-2"><span class="px-3 py-1 text-sm text-primary bg-primary/10 rounded-full">{{ topPost2.tag }}</span></div></div></div></article></div></div></section><section id="recommended" class="py-16 bg-gray-50"><div class="max-w-7xl mx-auto px-4"><h2 class="text-2xl font-bold text-gray-900 mb-8">推荐阅读</h2><div class="grid grid-cols-3 gap-6"><article class="bg-gray-50 rounded-lg p-6 article-card" v-if="recommendedPost1.id"><div class="flex items-center space-x-4 mb-4"><img src="@/image/fee96823961b14e8b6fcc9b91ba91ee0.jpg" alt="作者头像"class="w-10 h-10 rounded-full"><div><h4 class="font-medium text-gray-900">冰冰一号</h4><p class="text-sm text-gray-500">{{ recommendedPost1.createTime }}</p></div></div><h3 class="text-lg font-bold text-gray-900 mb-2"><router-link :to="`/post/${recommendedPost1.id}`" class="hover:text-primary">{{ recommendedPost1.title }}</router-link></h3><p class="text-gray-600 mb-4 line-clamp-2 min-h-[48px]">{{ recommendedPost1.summary }}</p><div class="flex items-center justify-between"><span class="text-sm text-gray-500"></span><span class="px-3 py-1 text-sm text-primary bg-primary/10 rounded-full">{{ recommendedPost1.tag }}</span></div></article><article class="bg-gray-50 rounded-lg p-6 article-card" v-if="recommendedPost2.id"><div class="flex items-center space-x-4 mb-4"><img src="@/image/7ce370d4dc07c7b902604c928cb381b6.jpg" alt="作者头像"class="w-10 h-10 rounded-full"><div><h4 class="font-medium text-gray-900">冰冰一号</h4><p class="text-sm text-gray-500">{{ recommendedPost2.createTime }}</p></div></div><h3 class="text-lg font-bold text-gray-900 mb-2"><router-link :to="`/post/${recommendedPost2.id}`" class="hover:text-primary">{{ recommendedPost2.title }}</router-link></h3><p class="text-gray-600 mb-4 line-clamp-2 min-h-[48px]">{{ recommendedPost2.summary }}</p><div class="flex items-center justify-between"><span class="text-sm text-gray-500"></span><span class="px-3 py-1 text-sm text-primary bg-primary/10 rounded-full">{{ recommendedPost2.tag }}</span></div></article><article class="bg-gray-50 rounded-lg p-6 article-card" v-if="recommendedPost3.id"><div class="flex items-center space-x-4 mb-4"><img src="@/image/ec22dc1fba08639f9a1909e30929c645.jpg" alt="作者头像"class="w-10 h-10 rounded-full"><div><h4 class="font-medium text-gray-900">冰冰一号</h4><p class="text-sm text-gray-500">{{ recommendedPost3.createTime }}</p></div></div><h3 class="text-lg font-bold text-gray-900 mb-2"><router-link :to="`/post/${recommendedPost3.id}`" class="hover:text-primary">{{ recommendedPost3.title }}</router-link></h3><p class="text-gray-600 mb-4 line-clamp-2 min-h-[48px]">{{ recommendedPost3.summary }}</p><div class="flex items-center justify-between"><span class="text-sm text-gray-500"></span><span class="px-3 py-1 text-sm text-primary bg-primary/10 rounded-full">{{ recommendedPost3.tag }}</span></div></article></div></div></section><section class="py-16 bg-gray-50"><div class="max-w-7xl mx-auto px-4"><div class="bg-white rounded-lg p-12 text-center"><h2 class="text-3xl font-bold text-gray-900 mb-4">订阅最新文章</h2><p class="text-gray-600 mb-8 max-w-2xl mx-auto">及时获取最新的技术文章和行业动态,我们会定期发送精选内容到您的邮箱</p><div class="flex items-center justify-center space-x-4"><input type="email" placeholder="请输入您的邮箱地址"class="w-96 h-12 px-4 rounded-lg bg-gray-50 border-none focus:outline-none focus:ring-2 focus:ring-primary/20"><button class="px-8 py-3 bg-primary text-white !rounded-button hover:bg-primary/90 whitespace-nowrap">立即订阅</button></div></div></div></section></main><footer id="about" class="bg-white border-t border-gray-200"><div class="max-w-7xl mx-auto px-4 py-12"><div class="grid grid-cols-4 gap-8 mb-8"><div><h3 class="text-lg font-bold text-gray-900 mb-4">关于我们</h3><p class="text-gray-600">分享技术,连接思想,创造价值</p></div><div><h3 class="text-lg font-bold text-gray-900 mb-4">友情链接</h3><ul class="space-y-2"><li><a href="#" class="text-gray-600 hover:text-primary">掘金</a></li><li><a href="#" class="text-gray-600 hover:text-primary">InfoQ</a></li><li><a href="#" class="text-gray-600 hover:text-primary">开源中国</a></li></ul></div><div><h3 class="text-lg font-bold text-gray-900 mb-4">联系我们</h3><ul class="space-y-2"><li class="text-gray-600">邮箱:contact@example.com</li><li class="text-gray-600">微信:blog_official</li></ul></div><div><h3 class="text-lg font-bold text-gray-900 mb-4">关注我们</h3><div class="flex space-x-4"><a href="#"class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-primary hover:text-white"><i class="fab fa-weixin"></i></a><a href="#"class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-primary hover:text-white"><i class="fab fa-weibo"></i></a><a href="#"class="w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center text-gray-600 hover:bg-primary hover:text-white"><i class="fab fa-github"></i></a></div></div></div><div class="pt-8 border-t border-gray-200 text-center text-gray-600"><p>© 2024 个人博客. All rights reserved.</p></div></div></footer><!-- 回到顶部按钮 --><div v-show="showBackToTop"@click="scrollToTop"class="fixed right-8 bottom-8 bg-white w-10 h-10 rounded-full shadow-lg flex items-center justify-center cursor-pointer hover:bg-gray-50 transition-all duration-300"title="回到顶部"><i class="fas fa-arrow-up text-gray-600"></i></div></div>
</template><script setup>
import {ref, onMounted, onUnmounted, reactive} from 'vue'
import {useRouter} from "vue-router";
import {postApi} from "@/api/post";// 控制回到顶部按钮的显示
const showBackToTop = ref(false)// 监听滚动事件
const handleScroll = () => {showBackToTop.value = window.scrollY > 300
}// 回到顶部方法
const scrollToTop = () => {window.scrollTo({top: 0,behavior: 'smooth'})
}onMounted(() => {// 添加滚动监听window.addEventListener('scroll', handleScroll)
})onUnmounted(() => {// 移除滚动监听window.removeEventListener('scroll', handleScroll)
})function scrollToElem(id) {const elem = document.getElementById(id);if (elem) {elem.scrollIntoView({ behavior: 'smooth' });}
}const router = useRouter();function toCreatePost() {router.push({path: "/createPost",})
}const searchKeyword = ref("");// 添加搜索处理函数
const handleSearch = () => {router.push({path: '/search',query: {keyword: searchKeyword.value.trim()}});
};const data = reactive({技术开发: 0,设计创意: 0,产品思维: 0,生活感悟: 0,
});onMounted(async () => {const countMap = await postApi.getCategoriesData();// 更新各分类的文章数量Object.keys(data).forEach(category => {data[category] = countMap[category] || 0;});
});const handleCategoryClick = (category) => {router.push({path: '/search',query: {tag: category}});
};const topPost1 = reactive({});
const topPost2 = reactive({});function setProperty(target, origin) {target.id = origin.id;target.title = origin.title;target.summary = origin.summary;target.tag = origin.tags[0];target.createTime = formatVisitTime(origin.createTime);
}onMounted(async () => {const topTwoPost = await postApi.getTopTwoPost();setProperty(topPost1, topTwoPost.post1);setProperty(topPost2, topTwoPost.post2);
});// 格式化访问时间
const formatVisitTime = (timestamp) => {const minutes = Math.floor((Date.now() - new Date(timestamp)) / 1000 / 60)if (minutes < 60) {return `${minutes} 分钟前`} else if (minutes < 1440) {return `${Math.floor(minutes / 60)} 小时前`} else {return `${Math.floor(minutes / 1440)} 天前`}
}const toSearch = () => {router.push({path: '/search',});
};const recommendedPost1 = reactive({});
const recommendedPost2 = reactive({});
const recommendedPost3 = reactive({});onMounted(async () => {const recommendedPosts = await postApi.getRecommendedTopThreePost();if (recommendedPosts.length >= 1) {setProperty(recommendedPost1, recommendedPosts[0]);}if (recommendedPosts.length >= 2) {setProperty(recommendedPost2, recommendedPosts[1]);}if (recommendedPosts.length >= 3) {setProperty(recommendedPost3, recommendedPosts[2]);}
});
</script><style scoped>
.hero-section {background-image: url('@/image/5419bc35abe0fb8251a2cdca72c487ce.jpg');background-size: cover;background-position: center;
}.article-card:hover {transform: translateY(-4px);transition: all 0.3s ease;
}.category-image {transition: transform 0.3s ease;
}.category-card:hover .category-image {transform: scale(1.05);
}
</style>
PostDetail.vue
<template><Layout><main class="flex-1 container mx-auto px-4 py-8"><div v-if="!postLoading" class="mx-auto bg-white rounded-lg shadow-lg"><!-- 帖子内容 --><article class="p-6"><h1 class="text-2xl font-bold mb-4">{{ post.title }}</h1><div class="flex justify-end items-center space-x-4 mb-6"><div class="flex items-center space-x-2"><span v-for="(tag, index) in post.tags":key="index"class="px-3 py-1 bg-primary/5 text-primary rounded-full text-sm">{{ tag }}</span></div><span class="text-sm text-gray-500">{{ formatDate(post.createTime) }}</span></div><!-- 帖子正文 --><v-md-editorv-model="post.content"mode="preview"></v-md-editor></article></div></main></Layout><!-- 回到顶部按钮 --><buttonv-show="showBackToTop"@click="scrollToTop"class="fixed bottom-8 right-8 w-10 h-10 bg-primary text-white rounded-full shadow-lg hover:bg-primary/90 transition-all duration-300 flex items-center justify-center"title="回到顶部"><i class="fas fa-angle-up text-lg"></i></button>
</template><script setup>
import {ref, onMounted, onUnmounted} from 'vue'
import { useRoute } from 'vue-router'
import { postApi } from '@/api/post'
import Message from '@/utils/message'
import Layout from '@/components/Layout.vue'const route = useRoute()
const post = ref({})// 格式化日期
const formatDate = (date) => {if (!date) return ''return new Date(date).toLocaleString('zh-CN', {year: 'numeric',month: '2-digit',day: '2-digit',hour: '2-digit',minute: '2-digit'})
}const postLoading = ref(true)// 获取帖子详情和点赞状态
const fetchPostDetail = async () => {try {const res = await postApi.getDetail(route.params.id)post.value = res.datapostLoading.value = false;} catch (error) {Message.error('获取帖子详情失败')}
}onMounted(async () => {await fetchPostDetail()
})// 控制回到顶部按钮显示
const showBackToTop = ref(false)// 监听滚动事件
const handleScroll = () => {showBackToTop.value = window.scrollY > 300
}// 回到顶部
const scrollToTop = () => {window.scrollTo({top: 0,behavior: 'smooth'})
}onMounted(() => {window.addEventListener('scroll', handleScroll)
})// 组件卸载时移除事件监听
onUnmounted(() => {window.removeEventListener('scroll', handleScroll)
})
</script><style scoped></style>
CreatePost.vue
<template><Layout><main class="flex-1 flex items-center justify-center"><div class="container mx-auto px-4 py-8" style="width: 100%; max-width: 100%"><div class="mx-auto bg-white rounded-lg p-6 shadow-lg"><h1 class="text-2xl font-bold mb-6">{{ isEdit ? '编辑帖子' : '发布新帖子' }}</h1><form @submit.prevent="handleSubmit" class="space-y-6"><!-- 标题输入 --><div><label class="block text-sm font-medium text-gray-700 mb-2">标题</label><inputv-model="postForm.title"type="text"requiredclass="w-full px-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary"placeholder="请输入帖子标题"><p v-if="errors.title" class="mt-1 text-sm text-red-600">{{ errors.title }}</p></div><!-- 标签选择 --><div><label class="block text-sm font-medium text-gray-700 mb-2">标签</label><div class="flex flex-wrap gap-2"><divv-for="tag in availableTags":key="tag.id"@click="toggleTag(tag)":class="['px-3 py-1 rounded-full text-sm cursor-pointer transition-colors',postForm.tags.includes(tag.id)? 'bg-primary text-white': 'bg-gray-100 text-gray-700 hover:bg-gray-200']">{{ tag.name }}</div></div><p v-if="errors.tags" class="mt-1 text-sm text-red-600">{{ errors.tags }}</p></div><!-- Markdown编辑器 --><div><label class="block text-sm font-medium text-gray-700 mb-2">内容</label><v-md-editorv-model="postForm.content"height="400px":disabled-menus="[]"@change="handleEditorChange"@upload-image="handleUploadImage"></v-md-editor><p v-if="errors.content" class="mt-1 text-sm text-red-600">{{ errors.content }}</p></div><!-- 提交按钮 --><div class="flex justify-end space-x-4"><button type="button" @click="$router.back()" class="px-6 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50">取消</button><button type="submit" :disabled="loading" class="px-6 py-2 bg-primary text-white rounded-md hover:bg-primary/90 disabled:opacity-50"><i v-if="loading" class="fas fa-circle-notch fa-spin mr-2"></i>{{ loading ? (isEdit ? '保存中...' : '发布中...') : (isEdit ? '保存修改' : '发布帖子') }}</button></div></form></div></div></main></Layout>
</template><script setup>
import { ref, reactive, onMounted } from 'vue';
import {useRoute, useRouter} from 'vue-router';
import { postApi } from '@/api/post';
import {tagList} from '@/api/tag';
import Message from '@/utils/message';
import Layout from "@/components/Layout.vue";
import request, {baseURL} from "@/utils/request";const router = useRouter();
const loading = ref(false);const postForm = reactive({title: '',content: '',tags: []
});const errors = reactive({title: '',content: '',tags: ''
});const availableTags = ref(tagList);const toggleTag = (tag) => {const index = postForm.tags.indexOf(tag.id);if (index === -1) {if (postForm.tags.length < 1) {postForm.tags.push(tag.id);} else {Message.warning('最多只能选择1个标签');}} else {postForm.tags.splice(index, 1);}
};const validateForm = () => {let isValid = true;errors.title = '';errors.content = '';errors.tags = '';if (!postForm.title.trim()) {errors.title = '请输入帖子标题';isValid = false;} else if (postForm.title.length > 100) {errors.title = '标题长度不能超过100个字符';isValid = false;}if (!postForm.content.trim()) {errors.content = '请输入帖子内容';isValid = false;}if (postForm.tags.length === 0) {errors.tags = '请至少选择一个标签';isValid = false;}return isValid;
};const handleEditorChange = (text) => {postForm.content = text;
};const handleSubmit = async () => {if (!validateForm()) return;try {loading.value = true;// 将标签ID转换为标签名称const tagNames = postForm.tags.map(tagId =>availableTags.value.find(tag => tag.id === tagId)?.name).filter(name => name); // 过滤掉可能的undefinedconst data = {title: postForm.title,content: postForm.content,tags: tagNames};if (isEdit.value) {await postApi.update(postId.value, data);Message.success('更新成功');} else {await postApi.create(data);Message.success('发布成功');}await router.push('/');} catch (error) {console.error(isEdit.value ? '更新失败:' : '发布失败:', error);Message.error(error.message || (isEdit.value ? '更新失败,请稍后重试' : '发布失败,请稍后重试'));} finally {loading.value = false;}
};const route = useRoute();
const isEdit = ref(false);
const postId = ref(null);// 在 onMounted 中检查是否是编辑模式
onMounted(async () => {// 检查是否是编辑模式if (route.query.edit === 'true' && route.query.postId) {isEdit.value = true;postId.value = route.query.postId;await fetchPostDetail();}
});// 获取帖子详情
const fetchPostDetail = async () => {try {const res = await postApi.getDetail(postId.value);postForm.title = res.data.title;postForm.content = res.data.content;// 将标签名称转换为对应的标签IDpostForm.tags = res.data.tags.map(tagName =>availableTags.value.find(tag => tag.name === tagName)?.id).filter(id => id); // 过滤掉可能的undefined} catch (error) {console.error('获取帖子详情失败:', error);Message.error('获取帖子详情失败');}
};// 处理图片上传
const handleUploadImage = async (event, insertImage, files) => {try {const file = files[0];// 检查文件类型if (!['image/jpeg', 'image/png', 'image/gif'].includes(file.type)) {Message.error('只支持 jpg、png、gif 格式的图片');return;}// 检查文件大小(最大 5MB)if (file.size > 5 * 1024 * 1024) {Message.error('图片大小不能超过 5MB');return;}const formData = new FormData();formData.append('file', file);const response = await request({url: "/api/upload/image",method: "post",data: formData,headers: {'Content-Type': 'multipart/form-data'}});if (response.data.url) {insertImage({url: baseURL + response.data.url,desc: file.name});Message.success('图片上传成功');}} catch (error) {console.error('图片上传失败:', error);Message.error('图片上传失败,请稍后重试');}
};
</script>