当前位置: 首页 > wzjs >正文

大岭山建设网站万网app下载

大岭山建设网站,万网app下载,安徽省水利厅j建设网站,云服务器价格个人博客系统 一个基于 Spring Boot Vue.js 的现代化个人博客系统。 功能特点 📝 Markdown 编辑器支持🏷️ 文章标签管理🔍 全文搜索功能📱 响应式设计🌓 深色模式支持📊 文章分类统计🖼️ …

个人博客系统

一个基于 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.文章阅读页面
在这里插入图片描述

调整为个人博客网站操作说明

  1. 只需要将前台的文章书写页面屏蔽掉,然后将文章列表页面的修改按钮屏蔽掉
  2. 后台的相应文章修改和新增接口也屏蔽掉,即转换为个人博客网站

源码下载

个人博客系统

核心源码

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>
http://www.dtcms.com/wzjs/200162.html

相关文章:

  • 手机下载工具appseo咨询服务价格
  • dream8网站建设及设计合肥seo快排扣费
  • 网站的标题标签一般是写在公司网络推广该怎么做
  • 长春网站建设百度店铺注册
  • 网站建设那种语言好太原百度快速优化
  • 东莞疫情最新消息2021怎样优化网站关键词排名靠前
  • 青海网站建设策划seo常用的工具
  • 怎么做自己的网站教程企业宣传
  • tornado网站开发网络营销课程培训机构
  • 做影视网站被起诉网络推广是什么
  • 上海专业的网站建培训学校管理系统
  • 做网站的策划需要做什么中国50强企业管理培训机构
  • 便宜网站建设模板网站一手渠道推广平台
  • 投票网站怎么做的广州官方新闻
  • 网站asp代码seo推广系统排名榜
  • 网站设计的原则怎么关闭seo综合查询
  • 环保类网站模板举一个病毒营销的例子
  • 怎样看网站做的好不好百度推广官网网站
  • 贵州做网站公司上海seo推广整站
  • 课程网站开发 预算互联网营销师题库
  • 用r做简易的网站海外网络推广方案
  • 网站隐私条款模板宁波seo外包平台
  • 深圳网站开发哪个好网络营销策划的具体流程是
  • 做网站的图片大全自动推广软件免费
  • 电子商务平台的建设专业的seo外包公司
  • 网站设计制作网络营销与直播电商专业介绍
  • 在线看网站建设全网推广怎么做
  • app 排名网站中国职业培训在线官方网站
  • 做公司+网站建设价格网站seo具体怎么做
  • 怎么看一个网站用什么做的seo外链工具源码