构建一个使用Vue.js与Django的博客网站,同时支持使用Markdown格式编写文章,是一个集成前后端技术的综合性项目。Vue.js提供了动态、响应式的前端用户界面,而Django则作为强大的后端框架,负责数据管理与API服务。Markdown的引入,使得内容创建更加简洁和高效。
确保已安装Python和pip。然后,创建并激活一个虚拟环境,并安装必要的依赖包:
bash
pip install django
pip install djangorestframework
pip install django-cors-headers
pip install markdown
pip install django-mdeditor # 或者使用 django-markdownx
启动Django项目并创建一个新的应用来管理博客文章:
bash
django-admin startproject blog_backend
cd blog_backend
python manage.py startapp blog
在blog_backend/settings.py
中,添加已安装的应用和配置中间件:
python
INSTALLED_APPS = [
# Django默认应用
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 第三方应用
'rest_framework',
'corsheaders',
'mdeditor', # 如果使用 django-mdeditor
# 'markdownx', # 如果使用 django-markdownx
# 自定义应用
'blog',
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # CORS中间件需放在最上面
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# 配置CORS
CORS_ALLOW_ALL_ORIGINS = True # 开发阶段允许所有来源,生产环境应具体设置
# 配置静态文件和媒体文件
STATIC_URL = '/static/'
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')
# Django Rest Framework配置
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
在blog/models.py
中定义博客文章相关的模型,包括分类、标签和文章内容:
python
from django.db import models
from mdeditor.fields import MDTextField # 如果使用 django-mdeditor
class BlogCategory(models.Model):
title = models.CharField(max_length=50, verbose_name='分类名称', default='')
href = models.CharField(max_length=100, verbose_name='分类路径', default='')
def __str__(self):
return self.title
class Meta:
verbose_name = '文章分类'
verbose_name_plural = '文章分类'
class Tag(models.Model):
tag = models.CharField(max_length=20, verbose_name='标签')
def __str__(self):
return self.tag
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
class BlogPost(models.Model):
title = models.CharField(max_length=200, verbose_name='文章标题', unique=True)
category = models.ForeignKey(BlogCategory, blank=True, null=True, verbose_name='文章分类', on_delete=models.DO_NOTHING)
is_top = models.BooleanField(default=False, verbose_name='是否置顶')
is_hot = models.BooleanField(default=False, verbose_name='是否热门')
summary = models.CharField(max_length=500, verbose_name='内容摘要', default='')
content = MDTextField(verbose_name='内容') # 使用Markdown字段
views_count = models.IntegerField(default=0, verbose_name="查看数")
comments_count = models.IntegerField(default=0, verbose_name="评论数")
tags = models.ManyToManyField(to=Tag, related_name="tag_post", blank=True, verbose_name="标签")
@property
def tag_list(self):
return ','.join([tag.tag for tag in self.tags.all()])
def __str__(self):
return self.title
class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'
class Site(models.Model):
name = models.CharField(max_length=50, verbose_name='站点名称', unique=True)
avatar = models.CharField(max_length=200, verbose_name='站点图标')
slogan = models.CharField(max_length=200, verbose_name='站点标语')
def __str__(self):
return self.name
class Meta:
verbose_name = '站点信息'
verbose_name_plural = '站点信息'
在blog/admin.py
中注册模型,以便通过Django管理界面进行管理:
python
from django.contrib import admin
from .models import BlogCategory, Tag, BlogPost, Site
@admin.register(BlogCategory)
class BlogCategoryAdmin(admin.ModelAdmin):
list_display = ['id', 'title', 'href']
@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['id', 'tag']
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ['title', 'category', 'is_top', 'is_hot', 'views_count', 'comments_count']
search_fields = ('title',)
@admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
list_display = ['name', 'avatar', 'slogan']
执行迁移命令以应用模型到数据库:
bash
python manage.py makemigrations
python manage.py migrate
在blog/serializers.py
中定义序列化器,将模型数据转换为JSON格式:
python
from rest_framework import serializers
from .models import BlogPost, BlogCategory, Tag, Site
class BlogCategorySerializer(serializers.ModelSerializer):
class Meta:
model = BlogCategory
fields = ['id', 'title', 'href']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'tag']
class BlogPostSerializer(serializers.ModelSerializer):
category = BlogCategorySerializer()
tags = TagSerializer(many=True)
class Meta:
model = BlogPost
fields = ['id', 'title', 'category', 'is_top', 'is_hot', 'summary', 'content', 'views_count', 'comments_count', 'tags']
class SiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = ['id', 'name', 'avatar', 'slogan']
在blog/views.py
中定义API视图,使用Django Rest Framework的泛型视图:
python
from rest_framework import generics
from .models import BlogPost, BlogCategory, Tag, Site
from .serializers import BlogPostSerializer, BlogCategorySerializer, TagSerializer, SiteSerializer
class BlogPostListCreateView(generics.ListCreateAPIView):
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
class BlogPostDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = BlogPost.objects.all()
serializer_class = BlogPostSerializer
class BlogCategoryListCreateView(generics.ListCreateAPIView):
queryset = BlogCategory.objects.all()
serializer_class = BlogCategorySerializer
class TagListCreateView(generics.ListCreateAPIView):
queryset = Tag.objects.all()
serializer_class = TagSerializer
class SiteDetailView(generics.RetrieveUpdateAPIView):
queryset = Site.objects.all()
serializer_class = SiteSerializer
在blog/urls.py
中定义应用的URL路由:
python
from django.urls import path
from .views import (
BlogPostListCreateView,
BlogPostDetailView,
BlogCategoryListCreateView,
TagListCreateView,
SiteDetailView
)
urlpatterns = [
path('posts/', BlogPostListCreateView.as_view(), name='post-list-create'),
path('posts//', BlogPostDetailView.as_view(), name='post-detail'),
path('categories/', BlogCategoryListCreateView.as_view(), name='category-list-create'),
path('tags/', TagListCreateView.as_view(), name='tag-list-create'),
path('site/', SiteDetailView.as_view(), name='site-detail'),
]
然后,在主项目的blog_backend/urls.py
中包含应用的URL:
python
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('blog.urls')),
path('mdeditor/', include('mdeditor.urls')), # 如果使用 django-mdeditor
# path('markdownx/', include('markdownx.urls')), # 如果使用 django-markdownx
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
运行以下命令启动Django开发服务器:
bash
python manage.py runserver
访问http://localhost:8000/admin/
,使用超级用户账户登录管理界面,管理博客内容。
使用Vue CLI创建一个新的Vue.js项目:
bash
npm install -g @vue/cli
vue create blog_frontend
cd blog_frontend
安装Axios用于HTTP请求,mavon-editor作为Markdown编辑器,以及Vue Router进行路由管理:
bash
npm install axios mavon-editor
npm install vue-router
在src/router/index.js
中设置路由:
javascript
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home.vue';
import ArticleDetail from '@/views/ArticleDetail.vue';
import CreateArticle from '@/views/CreateArticle.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/article/:id',
name: 'ArticleDetail',
component: ArticleDetail
},
{
path: '/create',
name: 'CreateArticle',
component: CreateArticle
}
]
});
在src/main.js
中引入路由:
javascript
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import mavonEditor from 'mavon-editor';
import 'mavon-editor/dist/css/index.css';
Vue.use(mavonEditor);
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
在src/views/Home.vue
中实现文章列表:
vue
博客文章
创建新文章
-
{{ post.title }}
在src/views/ArticleDetail.vue
中实现文章详情展示:
vue
{{ post.title }}
返回首页
加载中...
在src/views/CreateArticle.vue
中实现文章创建功能:
vue
创建新文章
返回首页
使用
已在项目初始化阶段安装mavon-editor,并在src/main.js
中引入和使用。
在文章详情页面,通过v-html
指令将渲染后的HTML内容显示出来,同时确保内容的安全性已被后端处理:
vue
启动前端开发服务器:
bash
npm run serve
访问http://localhost:8080/
,应能看到博客的首页,列出所有文章,并能通过链接访问详情页或创建新文章。
确保Django后端服务器(http://localhost:8000/
)和Vue前端服务器(http://localhost:8080/
)均已启动。前端通过Axios与后端API进行通信,实现数据的获取和提交。
由于前端使用v-html
渲染后端提供的HTML内容,需确保后端在存储和提供内容时,对Markdown内容进行了适当的过滤和清理,防止恶意脚本注入。
在settings.py
中正确配置CORS,限制允许的前端域名,避免跨域安全问题。
当前项目示例未涉及用户认证,建议在生产环境中添加JWT或其他认证机制,保护API端点,确保只有授权用户可以创建、编辑或删除文章。
引入Django的认证系统或使用django-rest-framework-simplejwt
实现JWT认证,保护API并管理用户权限。
在后端API中添加分页支持,提高数据加载效率;在前端实现分页控件。此外,增加搜索功能,使用户能够按标题或内容搜索文章。
如果需要更丰富的编辑功能,可以考虑集成更高级的Markdown编辑器,如vue3-markdown-editor
,提供实时预览、语法高亮等功能。
在生产环境中,需优化前后端的部署,使用Nginx或其他服务器代理,配置HTTPS,优化静态资源加载,确保网站的性能与安全。
以下是关键文件的完整代码示例:
python
from django.db import models
from mdeditor.fields import MDTextField
class BlogCategory(models.Model):
title = models.CharField(max_length=50, verbose_name='分类名称', default='')
href = models.CharField(max_length=100, verbose_name='分类路径', default='')
def __str__(self):
return self.title
class Meta:
verbose_name = '文章分类'
verbose_name_plural = '文章分类'
class Tag(models.Model):
tag = models.CharField(max_length=20, verbose_name='标签')
def __str__(self):
return self.tag
class Meta:
verbose_name = '标签'
verbose_name_plural = '标签'
class BlogPost(models.Model):
title = models.CharField(max_length=200, verbose_name='文章标题', unique=True)
category = models.ForeignKey(BlogCategory, blank=True, null=True, verbose_name='文章分类', on_delete=models.DO_NOTHING)
is_top = models.BooleanField(default=False, verbose_name='是否置顶')
is_hot = models.BooleanField(default=False, verbose_name='是否热门')
summary = models.CharField(max_length=500, verbose_name='内容摘要', default='')
content = MDTextField(verbose_name='内容')
views_count = models.IntegerField(default=0, verbose_name="查看数")
comments_count = models.IntegerField(default=0, verbose_name="评论数")
tags = models.ManyToManyField(to=Tag, related_name="tag_post", blank=True, verbose_name="标签")
@property
def tag_list(self):
return ','.join([tag.tag for tag in self.tags.all()])
def __str__(self):
return self.title
class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'
class Site(models.Model):
name = models.CharField(max_length=50, verbose_name='站点名称', unique=True)
avatar = models.CharField(max_length=200, verbose_name='站点图标')
slogan = models.CharField(max_length=200, verbose_name='站点标语')
def __str__(self):
return self.name
class Meta:
verbose_name = '站点信息'
verbose_name_plural = '站点信息'
python
from rest_framework import serializers
from .models import BlogPost, BlogCategory, Tag, Site
class BlogCategorySerializer(serializers.ModelSerializer):
class Meta:
model = BlogCategory
fields = ['id', 'title', 'href']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = ['id', 'tag']
class BlogPostSerializer(serializers.ModelSerializer):
category = BlogCategorySerializer()
tags = TagSerializer(many=True)
class Meta:
model = BlogPost
fields = ['id', 'title', 'category', 'is_top', 'is_hot', 'summary', 'content', 'views_count', 'comments_count', 'tags']
class SiteSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = ['id', 'name', 'avatar', 'slogan']
javascript
import Vue from 'vue';
import Router from 'vue-router';
import Home from '@/views/Home.vue';
import ArticleDetail from '@/views/ArticleDetail.vue';
import CreateArticle from '@/views/CreateArticle.vue';
Vue.use(Router);
export default new Router({
mode: 'history',
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/article/:id',
name: 'ArticleDetail',
component: ArticleDetail
},
{
path: '/create',
name: 'CreateArticle',
component: CreateArticle
}
]
});
javascript
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import mavonEditor from 'mavon-editor';
import 'mavon-editor/dist/css/index.css';
Vue.use(mavonEditor);
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App),
}).$mount('#app');
vue
博客文章
创建新文章
-
{{ post.title }}
vue
{{ post.title }}
返回首页
加载中...
vue
创建新文章
返回首页
通过上述步骤,您已成功搭建了一个基于Vue和Django的博客网站,支持使用Markdown格式编写和展示文章。前后端分离的架构增强了项目的可维护性和扩展性,Markdown的使用则简化了内容的编辑与管理。未来,您可以根据需求进一步优化和扩展功能,如集成用户认证、添加评论系统、实现文章分类与标签的高级管理等。