XieJava's blog

记录最好的自己


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于

Django自定义manage.py命令实现hexo博客迁移

发表于 2022-07-27 | 更新于: 2025-10-23 | 分类于 技术 | | 阅读次数:
字数统计: 1.2k | 阅读时长 ≈ 4

实现了hexo的md文件格式解析,通过什么方式怎么来迁移hexo的博客到django的博客呢?开始想到的是通过Django的manage.py的shell命令,通过shell可以执行写好的python脚本进行hexo的md文件格式解析并入库。后来想想为啥不直接自定义一个manage.py的命令直接进行迁移呢?就如我们新建Django工程迁移数据库一样,执行python manage.py migrate来迁移数据库。我们可以定义python manage.py xxx来执行的迁移hexo博客。

自定义Django-admin命令分三步:创建management文件夹、编写命令代码、测试验证

一、创建management文件夹

自定义的Django-admin管理命令本质上是一个python脚本文件,它的存放路径必须遵循一定的规范,一般位于app/management/commands目录。整个文件夹的布局如下所示:
注意app要在setting中注册
在blog/management/commands包下面创建transblog.py文件

在这里插入图片描述

这里有两个要注意的地方:

1、app要在setting中注册。如blog在setting中注册了所以可以在blog/management/commands包下面创建transblog.py文件,api和common都没有在setting中注册所以在这两个目录下创建的不会作为管理命令生效。
2、创建的management/commands是Python包,不是单纯的目录,区别就是目录里必须有init.py文件,空文件都可以。

二、编写命令代码

创建命令管理文件后就可以在该文件中编写命令代码了。
每一个自定义的管理命令本质是一个Command类, 它继承了Django的Basecommand或其子类, 主要通过重写handle()方法实现自己的业务逻辑代码,而add_arguments()则用于帮助处理命令行的参数,如果运行命令时不需要额外参数,可以不写这个方法。
transblog.py的参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# -*- coding: utf-8 -*-
"""
:author: XieJava
:url: http://ishareread.com
:copyright: © 2021 XieJava <xiejava@ishareread.com>
:license: MIT, see LICENSE for more details.
"""
import os

from blog.models import BlogPost,BlogCategory,Tag
from utils.parseblog import parseblog
from django.core.management.base import BaseCommand

class Command(BaseCommand):
help='读取指定目录的.md文件解析至ishareblog'

def add_arguments(self, parser):
parser.add_argument('--path',help='输入.md文件的目录')
parser.add_argument('--file',help='输入.md文件的路径')

def handle(self, *args, **options):
inputfile = input('请输入指定的目录路径或.md文件路径:')
if inputfile.find('.md') > 0:
self.transblogbyfile(inputfile)
else:
self.transblogbypath(inputfile)
if options['path']:
self.transblogbypath(options['path'])
if options['file']:
self.transblogbyfile(options['file'])

'''读取目录解析md文件并写入数据库'''
def transblogbypath(self,filepath='',):
try:
files = os.listdir(filepath)
for file in files:
if file.find('.md') > 0:
blog_file = os.path.join(filepath, file)
self.transblogbyfile(blog_file)
except FileNotFoundError as e:
print('请确认输入是否正确。',e)

'''读取md文件入库'''
def transblogbyfile(self,blogfile=''):
blog_info = parseblog(blogfile)
filename=os.path.basename(blogfile)
#判断是否存在相同的title
qs=BlogPost.objects.filter(title=blog_info['title'])
if len(qs)==0:
category = None
tag_objs = []
if 'categories' in blog_info:
categories=blog_info['categories']
if len(categories)>0:
category_title = categories[0]
category_qs=BlogCategory.objects.filter(title=category_title)
if len(category_qs)>0:
category=category_qs[0]
if 'tags' in blog_info:
tags=blog_info['tags']
for tag in tags:
tag_obj,b=Tag.objects.get_or_create(tag=tag)
print(tag_obj.id)
tag_objs.append(tag_obj)
blog = BlogPost()
blog.title = blog_info['title']
blog.content = blog_info['content']
blog.isShow = 1 #默认显示
blog.summary=blog.content[0:200] #默认提取内容的前200个字作为摘要
blog.category=category
blog.blogSource = filename
blog.pubTime=blog_info['date']
blog.save()
if len(tag_objs)>0:
blog.tags.add(*tag_objs)
print(filename + '读取解析入库成功!')
else:
print(blog_info['title']+'已经存在!')

代码很简单,就是根据读取命令行的参数,这个参数就是需要迁移hexo的.md文件的目录或文件路径,读取目录或文件路径进行文件的解析,并写入到数据库。
.md文件的解析参考:Python二十行代码实现hexo的md文件格式解析

三、测试验证

命令代码写完后就可以进行测试了。
在命令行输入python manage.py 可以看到自定义的transblog已经加入到管理命令了

transblog已经加入到管理命令

输入 python manage.py transblog -h
会提示命令的参数和用法:
管理命令帮助

如执行 python manage.py transblog 会提示”请输入指定的目录路径或.md文件路径:”

1
2
(venv) PS D:\Python\study\pythonproject\ishareblog\ishareblog> python manage.py transblog
请输入指定的目录路径或.md文件路径:

我们输入需要迁移的.md文件或路径
如:E:\CloudStation\personal\xiejavablog\myhexo\myblog\source_posts\2022-07-27-Python二十行代码实现hexo的md文件格式解析.md
不出意外的情况下控制台会打印“XXXX.md读取解析入库成功!”的信息
执行效果

访问博客,可以看到文章已经迁移过来了
执行效果
全部代码仓库:https://gitee.com/ishareblog/ishareblog


作者博客:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

Python二十行代码实现hexo的md文件格式解析

发表于 2022-07-27 | 更新于: 2025-10-23 | 分类于 技术 | | 阅读次数:
字数统计: 521 | 阅读时长 ≈ 2

最近用django+Vue实现了一个博客应用,原来的hexo的博客用着也挺好,想继续留着用,于是就想将hexo生成的.md的博客内容文件解析后直接写到django的博客数据库里做同步显示。

hexo生成的.md文件内容主要分两部分,一部分是博客的信息、一部分是博客内容,博客信息包括标题、目录、标签、发布日期等,博客内容就是具体博客写的内容主体了。
hexo生成的.md文件内容

其中博客信息通过”—“来区分,夹在两个”—“块之间。博客的信息是yaml来描叙的需要解析并提取出相应的字段及内容,博客内容就更简单了直接是markdown描叙的不需要再解析了。
这里要做的事情就是提取两个”—“符号之间的内容,并解析相应的字段,提取两个”—“符号后面的内容作为博客的内容,形成字典,便于后面的入库。

代码示例如下:
yaml的解析可以直接用Python的PyYAML库

1
pip install PyYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# -*- coding: utf-8 -*-
"""
:author: XieJava
:url: http://ishareread.com
:copyright: © 2021 XieJava <xiejava@ishareread.com>
:license: MIT, see LICENSE for more details.
"""
import yaml

'''将md文件转成blog对象'''
def parseblog(blog_md_file):
#读md文件
md_f = open(blog_md_file, "r",encoding='utf-8')
md_f_str=md_f.read()
#解析两个---之间的内容
pattern='---'
blog_data={}
pattern_list=list(pattern_search(md_f_str, pattern))
if len(pattern_list)>=2:
blog_info_str=md_f_str[pattern_list[0]+len(pattern):pattern_list[1]]
blog_data=yaml.load(blog_info_str,Loader=yaml.SafeLoader)
blog_data['content']=md_f_str[pattern_list[1]+len(pattern):]
md_f.close()
return blog_data

'''分割符号匹配检索'''
def pattern_search(string,pattern):
index=0
while index<len(string)-len(pattern):
index=string.find(pattern,index,len(string))
if index==-1:
break
yield index
index+=len(pattern)-1

if __name__ == '__main__':
blog_data=parseblog('E:\\CloudStation\\personal\\xiejavablog\\myhexo\\myblog\\source\\_posts\\2022-07-19-Vue3解析markdown解析并实现代码高亮显示.md')
print(blog_data)

效果:
可以看到hexo的md文件正确解析出来,形成字典。后续可以直接进行入库操作了。

解析效果

Python的库还是很丰富实用的,用Python二十行代码就实现hexo的md文件格式解析。

源代码仓库:https://gitee.com/ishareblog/ishareblog

Django+Vue快速实现博客网站

发表于 2022-07-26 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 2.4k | 阅读时长 ≈ 12

Django是一个开放源代码的Web应用框架,由Python写成。采用了MTV的框架模式,即模型M,视图V和模版T。它最初是被开发来用于管理劳伦斯出版集团旗下的一些以新闻内容为主的网站的,即是CMS(内容管理系统)软件。对于博客网站来说是典型的CMS应用。本文介绍通过Django+Vue的博客模版快速实现一个可用的博客网站。

这里用的博客模板是Gblog是一款nice的基于 vue 的博客模板。界面简洁轻快,非常适合用作个人博客。https://gitee.com/fengziy/Gblog 后台的接口和管理界面就通过Django框架来实现了。

这里数据库用mysql,接口框架主要用到的是Django的djangorestframework,内容编辑器用的是markdown通过django-mdedior库实现。

一、依赖库

1
2
3
4
5
6
7
8
9
10
11
asgiref==3.5.2
Django==4.0.6
django-cors-headers==3.13.0
django-filter==22.1
django-mdeditor==0.1.20
djangorestframework==3.13.1
mysqlclient==2.1.1
Pillow==9.2.0
pytz==2022.1
sqlparse==0.4.2
tzdata==2022.1

二、工程目录组织结构

工程目录组织结构

三、代码实现

1、模型

模型很简单,根据Gblog前台要显示的内容包括有‘文章分类’、‘文章标签’、‘博客文章’、‘站点信息’、‘社交信息’、‘聚焦’,模型定义分别如下:
这里要说明的是因为博客文章内容准备用markdown编写,所以引入了mdeditor from mdeditor.fields import MDTextField
内容字段content=MDTextField(verbose_name='内容')
模型代码示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
from django.db import models
from common.basemodel import BaseModel
from mdeditor.fields import MDTextField
# Create your models here.
'''文章分类'''
class BlogCategory(BaseModel):
id = models.AutoField(primary_key=True)
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(BaseModel):
id=models.AutoField(primary_key=True)
tag=models.CharField(max_length=20, verbose_name='标签')

def __str__(self):
return self.tag

class Meta:
verbose_name='标签'
verbose_name_plural='标签'


'''博客文章'''
class BlogPost(BaseModel):
id = models.AutoField(primary_key=True)
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)
isTop=models.BooleanField(default=False,verbose_name='是否置顶')
isHot=models.BooleanField(default=False,verbose_name='是否热门')
summary=models.CharField(max_length=500,verbose_name='内容摘要',default='')
content=MDTextField(verbose_name='内容')
viewsCount= models.IntegerField(default=0, verbose_name="查看数")
commentsCount=models.IntegerField(default=0, verbose_name="评论数")
tags=models.ManyToManyField(to=Tag, related_name="tag_post", blank=True, default=None,verbose_name="标签")


@property
def tag_list(self):
return ','.join([i.tag for i in self.tags.all()])

def __str__(self):
return self.title

class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'


'''站点信息'''
class Site(BaseModel):
id = models.AutoField(primary_key=True)
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='站点标语')
domain=models.CharField(max_length=200, verbose_name='站点域名')
notice=models.CharField(max_length=200, verbose_name='站点备注')
desc=models.CharField(max_length=200, verbose_name='站点描述')

def __str__(self):
return self.name

class Meta:
verbose_name = '站点信息'
verbose_name_plural = '站点信息'


'''社交信息'''
class Social(BaseModel):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=20, verbose_name='标题')
icon=models.CharField(max_length=200, verbose_name='图标')
color=models.CharField(max_length=20, verbose_name='颜色')
href=models.CharField(max_length=100, verbose_name='路径')

def __str__(self):
return self.title

class Meta:
verbose_name = '社交信息'
verbose_name_plural = '社交信息'

'''聚焦'''
class Focus(BaseModel):
id=models.AutoField(primary_key=True)
title=models.CharField(max_length=20, verbose_name='标题')
img=models.CharField(max_length=100, verbose_name='路径')

def __str__(self):
return self.title

class Meta:
verbose_name='聚焦'
verbose_name_plural='聚焦'

'''友链'''
class Friend(BaseModel):
id=models.AutoField(primary_key=True)
siteName=models.CharField(max_length=20, verbose_name='友链站点名称')
path=models.CharField(max_length=100, verbose_name='地址路径')
desc=models.CharField(max_length=200, verbose_name='描述')

def __str__(self):
return self.siteName

class Meta:
verbose_name='友链'
verbose_name_plural='友链'

2、admin管理

实际上只要把模型注册到admin就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
from django.contrib import admin
from blog.models import *
# Register your models here.
@admin.register(BlogCategory)
class BlogCategoryAdmin(admin.ModelAdmin):
admin.site.site_title="ishareblog后台"
admin.site.site_header="ishareblog后台"
admin.site.index_title="ishareblog管理"

list_display = ['id', 'title', 'href']

@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ['title','category','isTop','isHot']
search_fields = ('title',)

@admin.register(Site)
class SiteAdmin(admin.ModelAdmin):
list_display = ['name','slogan','domain','desc']

@admin.register(Social)
class SocialAdmin(admin.ModelAdmin):
list_display = ['title','href']

@admin.register(Focus)
class FoucusAdmin(admin.ModelAdmin):
list_display = ['title','img']

@admin.register(Friend)
class FoucusAdmin(admin.ModelAdmin):
list_display = ['siteName','path','desc']

@admin.register(Tag)
class TagAdmin(admin.ModelAdmin):
list_display = ['id','tag']

3、接口

前端是Vue模板展示的,所以要为前端Vue提供相应的接口。通过djangorestframework将模型通过restful接口提供是非常easy的。

1)首先将需要暴露的模型通过序列化类序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
serializers.py

from blog.models import *
from rest_framework import serializers
class BlogCategoryModelSerializer(serializers.ModelSerializer):
class Meta:
model=BlogCategory
fields = "__all__"

class TagModelSerializer(serializers.ModelSerializer):
class Meta:
model = Tag
fields = "__all__"


class BlogPostModelSerializer(serializers.ModelSerializer):
create_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
update_time = serializers.DateTimeField(format="%Y-%m-%d %H:%M:%S", required=False, read_only=True)
category_id = serializers.CharField(max_length=32, source='category.id')
pubTime=update_time
category=BlogCategoryModelSerializer()
tags=serializers.SerializerMethodField()

# 多对多,钩子函数序列化,必须是以get_开头的
def get_tags(self, obj):
tags = obj.tags.all()
tag = TagModelSerializer(tags, many=True)
return tag.data

class Meta:
model=BlogPost
fields="__all__"

class SiteModelSerializer(serializers.ModelSerializer):
class Meta:
model = Site
fields = "__all__"

class SocialModelSerializer(serializers.ModelSerializer):
class Meta:
model = Social
fields = "__all__"

class FocusModelSerializer(serializers.ModelSerializer):
class Meta:
model = Focus
fields = "__all__"

class FriendModelSerializer(serializers.ModelSerializer):
class Meta:
model = Friend
fields = "__all__"

2)将序列化的对象通过视图类提供接口

custommodelviewset.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from rest_framework import status
from rest_framework import viewsets
from common.customresponse import CustomResponse

class CustomModelViewSet(viewsets.ModelViewSet):

#CreateModelMixin->create
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return CustomResponse(data=serializer.data, code=201,msg="OK", status=status.HTTP_201_CREATED,headers=headers)
#ListModelMixin->list
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)

serializer = self.get_serializer(queryset, many=True)
return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

#RetrieveModelMixin->retrieve
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)
#UpdateModelMixin->update
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)

if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}

return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

#DestroyModelMixin->destroy
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return CustomResponse(data=[], code=204, msg="OK", status=status.HTTP_204_NO_CONTENT)

views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Create your views here.
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import viewsets, status
from rest_framework import filters
from api.myfilter import BlogPostFilter
from api.serializers import *
from blog.models import BlogCategory, BlogPost,Site,Social,Focus,Friend,Tag
from api.mypage import MyPage
from common.custommodelviewset import CustomModelViewSet
from common.customresponse import CustomResponse

class BlogCategoryViewset(CustomModelViewSet):
queryset = BlogCategory.objects.all()
serializer_class = BlogCategoryModelSerializer

class BlogsView(CustomModelViewSet):
queryset = BlogPost.objects.order_by('-isTop','-update_time')
serializer_class = BlogPostModelSerializer
pagination_class = MyPage
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter)
filterset_class = BlogPostFilter
#搜索
search_fields=('title',)
#排序
ordering_fields = ('isTop', 'update_time')
#自定义获取详情接口
def retrieve(self,request,*args, **kwargs):
instance=self.get_object()
instance.viewsCount+=1
instance.save()
serializer=self.get_serializer(instance)
return CustomResponse(data=serializer.data,code=200,msg="success",status=status.HTTP_200_OK)


class SiteView(CustomModelViewSet):
queryset = Site.objects.all()
serializer_class = SiteModelSerializer

class SocialView(CustomModelViewSet):
queryset = Social.objects.all()
serializer_class = SocialModelSerializer

class FocusView(CustomModelViewSet):
queryset = Focus.objects.all()
serializer_class = FocusModelSerializer

class FriendView(viewsets.ModelViewSet):
queryset = Friend.objects.all()
serializer_class = FriendModelSerializer

class TagView(viewsets.ModelViewSet):
queryset = Tag.objects.all()
serializer_class = TagModelSerializer

3)通过路由来实现接口地址和视图的绑定和访问

urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# -*- coding: utf-8 -*-
"""
:author: XieJava
:url: http://ishareread.com
:copyright: © 2021 XieJava <xiejava@ishareread.com>
:license: MIT, see LICENSE for more details.
"""
from api import views
from django.urls import path,include
from rest_framework.routers import DefaultRouter
blogcategory_list=views.BlogCategoryViewset.as_view({'get':'list',})
blogcategory_detail=views.BlogCategoryViewset.as_view({ 'get': 'retrieve',})
blog_list=views.BlogsView.as_view({'get':'list',})
blog_detail=views.BlogsView.as_view({ 'get': 'retrieve',})
site_list=views.SiteView.as_view({'get':'list',})
site_detail=views.SiteView.as_view({'get':'retrieve',})
social_list=views.SocialView.as_view({'get':'list',})
social_detail=views.SocialView.as_view({'get':'retrieve',})
focus_list=views.FocusView.as_view({'get':'list',})
focus_detail=views.FocusView.as_view({'get':'retrieve'})
friend_list=views.FriendView.as_view({'get':'list',})
friend_detail=views.FriendView.as_view({'get':'retrieve'})
tags_list=views.TagView.as_view({'get':'list',})
# router=DefaultRouter()
# router.register('blogs',views.BlogsView)
urlpatterns = [
path('category/',blogcategory_list),
path('category/<pk>/',blogcategory_detail),
path('post/list',blog_list),
path('post/<pk>',blog_detail),
path('social/',social_list),
path('site/<pk>',site_detail),
path('focus/list',focus_list),
path('comment/',blog_list),
path('friend/',friend_list),
path('tags/',tags_list),
]

4)自定义接口返回格式

接口需要根据Glog定义的格式进行定义和返回,这里就需要自定义接口返回格式。
具体实现参见:https://xiejava.blog.csdn.net/article/details/125773730
–自定义返回响应类customresponse.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from rest_framework.response import Response
from rest_framework.serializers import Serializer

class CustomResponse(Response):
def __init__(self,data=None,code=None,msg=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None,**kwargs):
super().__init__(None, status=status)

if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
self.data={'code':code,'msg':msg,'data':data}
self.data.update(kwargs)
self.template_name=template_name
self.exception=exception
self.content_type=content_type

if headers:
for name, value in headers.items():
self[name] = value

–翻页实现类mypage.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from common.customresponse import CustomResponse

class MyPage(PageNumberPagination):
page_size = 8 #每页显示数量
max_page_size = 50 #每页最大显示数量。
page_size_query_param = 'size' #每页数量的参数名称
page_query_param = 'page' #页码的参数名称

#自定义分页器的返回参数
def get_paginated_response(self, data):
ret_data = dict()
ret_data['items'] = data
# 加入自定义分页信息
ret_data['total'] = self.page.paginator.count
ret_data['hasNextPage'] = self.get_next_link()
ret_data['size'] = self.page_size
ret_data['page'] = self.page.number
return CustomResponse(data=ret_data,code=200,msg="OK",status=status.HTTP_200_OK)

全部代码:
后台代码:https://gitee.com/xiejava/ishareblog
前台代码:https://gitee.com/xiejava/Gblog

四、效果

1、后台管理

管理界面
管理界面
博客文章列表
博客文章列表
文章内容编辑,支持markdown
文章内容编辑,支持markdown
分类管理
文章分类
标签管理
标签管理
社交信息
社交信息

2、接口

接口清单
接口清单

文章列表接口,支持翻页

文章列表接口

文章详情接口
文章详情接口

3、前台展现

前台展现

文章列表
文章列表

文章详情,支持markdown显示及目录
文章详情

社交信息
社交信息

博客效果地址:http://blog.ishareread.com

后续考虑
1、django原生admin的管理界面还是简陋了一点,后续可能会用其他管理界面的UI给换掉
2、现在有了一个hexo的博客了,后续可能会考虑实现hexo生成的博客内容直接同步到django的博客,或者django博客编辑的内容直接生成hexo的.md文件
有兴趣的话可以关注本博客


博客:http://xiejava.ishareread.com


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

Vue3解析markdown解析并实现代码高亮显示

发表于 2022-07-19 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 479 | 阅读时长 ≈ 2

Vue实现博客前端,需要实现markdown的解析,如果有代码则需要实现代码的高亮。
Vue的markdown解析库有很多,如markdown-it、vue-markdown-loader、marked、vue-markdown等。这些库都大同小异。这里选用的是marked,代码高亮的库选用的是highlight.js。

具体实现步骤如下:

一、安装依赖库

在vue项目下打开命令窗口,并输入以下命令

1
2
npm install marked -save    // marked 用于将markdown转换成html
npm install highlight.js -save //用于代码高亮显示

命令执行完后可以在控制台或package.json文件中看到有安装的版本号
package.json文件中看到有安装的版本号

二、在main.js文件中引入highlight.js及样式并创建一个自定义的全局指令

1
2
3
4
5
6
7
8
9
10
import hljs from 'highlight.js';
import 'highlight.js/styles/atom-one-dark.css' //样式

//创建v-highlight全局指令
Vue.directive('highlight',function (el) {
let blocks = el.querySelectorAll('pre code');
blocks.forEach((block)=>{
hljs.highlightBlock(block)
})
})

这样就可以在vue组件中使用v-highlight引用代码高亮的方法了。

三、在Vue组件中应用marked解析及实现代码高亮

代码示例如下:

1
2
3
4
 <!-- 正文输出 -->
<div class="entry-content">
<div v-highlight v-html="article" id="content"></div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<script>
// 将marked 引入
import { marked }from 'marked';
export default {
name: 'articles',
data(){
return{
article:''
}
},
methods: {
getPostDetail() {
console.log('getPostDetail()'+this.id)
fetchPostDetail(this.id).then(res => {
this.postdetail=res.data
// 调用marked()方法,将markdown转换成html
this.article= marked(this.postdetail.content);
console.log(res.data)
}).catch(err => {
console.log(err)
})

},
created() {
//调用获取文章内容的接口方法
this.getPostDetail()
},
}
</script>

四、显示效果

markdown解析及代码高亮显示效果
在这里插入图片描述

示例中引用的样式是 import 'highlight.js/styles/atom-one-dark.css'
实际highlight.js/styles中提供了很多样式,可以根据自己的喜好选用。

代码高亮样式


博客:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

Python3.9环境安装mysqlclient报python setup.py egg_info did not run successfully错避坑

发表于 2022-07-16 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 434 | 阅读时长 ≈ 2

MySQL是常用的开源数据库,Python环境下django框架连接MySQL数据库用的是mysqlclient库,今天在用pip安装mysqlclient库时报错,特记录一下,避免后续继续踩坑。

环境说明:

操作系统:CentOS Linux 7.2
Python版本:Python 3.9.13
pip版本:pip 22.1.2

报错信息:

执行pip3 install mysqlclient==2.1.1 报错
报错信息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Using cached http://mirrors.aliyun.com/pypi/packages/50/5f/eac919b88b9df39bbe4a855f136d58f80d191cfea34a3dcf96bf5d8ace0a/mysqlclient-2.1.1.tar.gz (88 kB)
Preparing metadata (setup.py) ... error
error: subprocess-exited-with-error

× python setup.py egg_info did not run successfully.
│ exit code: 1
╰─> [16 lines of output]
/bin/sh: mysql_config: command not found
/bin/sh: mariadb_config: command not found
/bin/sh: mysql_config: command not found
Traceback (most recent call last):
File "<string>", line 2, in <module>
File "<pip-setuptools-caller>", line 34, in <module>
File "/tmp/pip-install-i1nt_asj/mysqlclient_1b92535d58cd440b8797686ac8bc9882/setup.py", line 15, in <module>
metadata, options = get_config()
File "/tmp/pip-install-i1nt_asj/mysqlclient_1b92535d58cd440b8797686ac8bc9882/setup_posix.py", line 70, in get_config
libs = mysql_config("libs")
File "/tmp/pip-install-i1nt_asj/mysqlclient_1b92535d58cd440b8797686ac8bc9882/setup_posix.py", line 31, in mysql_config
raise OSError("{} not found".format(_mysql_config_path))
OSError: mysql_config not found
mysql_config --version
mariadb_config --version
mysql_config --libs
[end of output]

note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.

note: This is an issue with the package mentioned above, not pip.
hint: See above for details.

mysqlclient报错

避坑:

从报错信息看是找不到mysql_config
通过whereis mysql_config命令查看mysql_config
发现mysql_confg没有
执行yum install mysql-devel 安装mysql-devel
执行whereis mysql_config命令查看mysql_config这时mysql_config有了

1
mysql_config: /usr/bin/mysql_config /usr/share/man/man1/mysql_config.1.gz

再次执行pip安装命令安装成功!

1
2
3
4
5
6
7
8
9
10
pip3 install mysqlclient==2.1.1

Looking in indexes: http://mirrors.aliyun.com/pypi/simple/
Collecting mysqlclient==2.1.1
Using cached http://mirrors.aliyun.com/pypi/packages/50/5f/eac919b88b9df39bbe4a855f136d58f80d191cfea34a3dcf96bf5d8ace0a/mysqlclient-2.1.1.tar.gz (88 kB)
Preparing metadata (setup.py) ... done
Using legacy 'setup.py install' for mysqlclient, since package 'wheel' is not installed.
Installing collected packages: mysqlclient
Running setup.py install for mysqlclient ... done
Successfully installed mysqlclient-2.1.1

Django加入markdown编辑器及markdown上传图片不回显避坑

发表于 2022-07-15 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 1.3k | 阅读时长 ≈ 5

一般来说一个CMS系统如博客系统都需要一个好的富文本编辑器,现在大家更多的是选择MarkDown编辑器来编辑内容。Django作为python的主流web开发框架当然少不了markdown的插件。本文介绍如何在Django框架中引入markdown编辑器及在使用markdown时的注意事项。

在Django框架中引入markdown编辑器主要是通过安装引入Django-mdeditor库来实现。
Django-mdeditor 是基于 Editor.md 的一个 django Markdown 文本编辑插件应用。
其官方下载地址见 https://pypi.org/project/django-mdeditor/
根据官方指导文档

一、安装使用

1、安装django-mdeditor

1
pip install django-mdeditor

2、在 settings 配置文件 INSTALLED_APPS 中添加 mdeditor

1
2
3
4
5
6
7
8
9
10
11
12
13
INSTALLED_APPS = [
'blog',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'django_filters',#注册条件查询
# 注册markdown的应用
'mdeditor',
]

3、针对django3.0+修改 frame 配置

1
X_FRAME_OPTIONS = 'SAMEORIGIN'  # django 3.0 + 默认为 deny

4、在 settings 中添加媒体文件的路径配置

1
2
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'uploads')

在你项目根目录下创建 uploads/editor 目录,用于存放上传的图片。

5、在项目的根 urls.py 中添加扩展url和媒体文件url:

注意是在项目的根 urls.py 中添加扩展url和媒体文件url,而不是在其他项目应用的urls.py中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.conf.urls import url, include
from django.conf.urls.static import static
from django.conf import settings
...

urlpatterns = [
...
url(r'mdeditor/', include('mdeditor.urls'))
]

if settings.DEBUG:
# static files (images, css, javascript, etc.)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

6、在项目model中应用markdown

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'''博客文章'''
class BlogPost(BaseModel):
id = models.AutoField(primary_key=True)
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)
isTop=models.BooleanField(default=False,verbose_name='是否置顶')
isHot=models.BooleanField(default=False,verbose_name='是否热门')
summary=models.CharField(max_length=500,verbose_name='内容摘要',default='')
content=MDTextField(verbose_name='内容')
viewsCount= models.IntegerField(default=0, verbose_name="查看数")
commentsCount=models.IntegerField(default=0, verbose_name="评论数")

def __str__(self):
return self.title

class Meta:
verbose_name = '博客文章'
verbose_name_plural = '博客文章'

见 content=MDTextField(verbose_name='内容') 表示博客文章的内容是MDTextField

7、向 admin.py 中注册model:

1
2
3
4
5
6
from django.contrib import admin
from blog.models import *
# Register your models here.
@admin.register(BlogPost)
class BlogPostAdmin(admin.ModelAdmin):
list_display = ['title','category','isTop','isHot']

8、迁移创建数据表

运行 python manage.py makemigrations 和 python manage.py migrate 来创建你的model 数据库表,可以看到默认创建的content字段是longtext类型的
默认创建的content字段是longtext类型的

9、测试验证

启动应用,访问http://127.0.0.1:8000/admin/ 点击新增博客文章,可以看到内容字段是markdown编辑器输入了。
markdown编辑器

至此django应用中就可以使用markdown编辑器了。

二、markdown上传图片不回显避坑

按照以上步骤配置django-mdeditor,markdown编辑器可以正常使用,但是这里有个大坑,就是有些浏览器在上传图片后上传的图片不回显!
我就碰到了这样的情况。

上传图片后上传的图片不回显

在添加图片界面选择本地上传图片后发现后台接口调到了 /mdeditor/uploads/?guid=1657867564930 接口并且返回了200,但是上传的图片地址不回显,提交报“错误:图片地址不能为空。” 这就奇了怪了。
打开浏览器的调试工具,发现报了一个错,Uncaught SyntaxError: Unexpected token 下 in JSON at position 141

浏览器的调试工具,发现报了一个错

点击详情,具体应该是获取的JSON无法解析。

JSON无法解析

这个JSON为什么无法解析呢?开始进一步调试,这个JSON是上传时调用的后台上传方法返回的。所以来看看是不是后台上传接口返回的JSON串有什么问题。找到/mdeditor/uploads路由所对应的源码

/mdeditor/uploads路由所对应的源码

UploadView的源代码,就是返回一个成功的json报文

1
2
3
4
5
return JsonResponse({'success': 1,
'message': "上传成功!",
'url': os.path.join(settings.MEDIA_URL,
MDEDITOR_CONFIGS['image_folder'],
file_full_name)})

实际打断点debug也是正常返回上传成功的json报文。

打断点debug也是正常返回上传成功的json报文

这就有点奇怪了,接口返回了正常的json报文怎么就解析不了了呢?接着继续调前台js代码,看究竟是什么原因。

json串里多了几个字“下载视频”!

发现js获取的json串里多了几个字“下载视频”! 这是什么鬼?实在是没有地方有返回“下载视频”这几个字啊?看js代码是通过iframe来处理请求的,再来看看iframe的内容,发现iframe里确实有“下载视频”

iframe里确实有“下载视频”

原来是有个chrome浏览器插件,擅自给加了“下载视频”的内容。再来看浏览器装了些啥插件。原来是有个迅雷插件,应该就是这个插件搞的鬼了,罪魁祸首就是它了!
罪魁祸首迅雷插件

把这个迅雷插件删除或停用,果然一切正常!可以正常回显!!!

可以正常回显

显示插入的图片

显示插入的图片

所以,碰到markdown上传图片不回显的情况,先看下自己的浏览器是不是开启了迅雷插件应用,如果开启了迅雷插件应用先停用或删除!


作者博客:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

Django的restframework接口框架自定义返回数据格式

发表于 2022-07-14 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 1.3k | 阅读时长 ≈ 5

在前后端分离是大趋势的背景下,前端获取数据都是通过调用后台的接口来获取数据微服务的应用越来越多。Django是Python进行web应用开发常用的web框架,用Django框架进行web应用框架减少了很多工作,通常用很少量的代码就可以实现数据的增、删、改、查的业务应用,同样用Django的restframework的框架对外发布接口也是非常的简单方便,几行代码就可以将数据对象通过接口的方式提供服务。因为在实际开发过程中接口的返回数据有一定的格式,本文介绍通过自定义Response返回对象来自定义接口返回数据格式。

以下示例将数据对象Friend通过restframework框架进行接口发布。
只要定义Friend数据对象

1
2
3
4
5
6
7
8
9
10
11
12
class Friend(BaseModel):
id=models.AutoField(primary_key=True)
siteName=models.CharField(max_length=20, verbose_name='友链站点名称')
path=models.CharField(max_length=100, verbose_name='地址路径')
desc=models.CharField(max_length=200, verbose_name='描述')

def __str__(self):
return self.siteName

class Meta:
verbose_name='友链'
verbose_name_plural='友链'

定义一个序列化类将返回的字段序列化

1
2
3
4
class FriendModelSerializer(serializers.ModelSerializer):
class Meta:
model = Friend
fields = "__all__"

定义一个接口视图类获取数据

1
2
3
class FriendView(viewsets.ModelViewSet):
queryset = Friend.objects.all()
serializer_class = FriendModelSerializer

定义接口路由就可以通过httprestfull的接口进行访问了

1
2
3
4
friend_list=views.FriendView.as_view({'get':'list',})
urlpatterns = [
path('friend/',friend_list),
]

接口访问效果如下:
http://localhost:8000/api/friend/
httprestfull的接口

但是在项目中经常会碰到接口格式变化的情况,restframework框架默认的返回数据格式不满足应用的需求。比如一般的接口都会有接口返回的code、msg、data,code用来标识接口返回代码比如200是正常,msg用来记录异常或其信息,data用来返回具体的数据。
通过restframework接口自定义返回数据格式也是很简单方便的。
先自定义Response返回对象,在返回对象中自定义数据返回的格式,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from rest_framework.response import Response
from rest_framework.serializers import Serializer

class CustomResponse(Response):
def __init__(self,data=None,code=None,msg=None,
status=None,
template_name=None, headers=None,
exception=False, content_type=None,**kwargs):
super().__init__(None, status=status)

if isinstance(data, Serializer):
msg = (
'You passed a Serializer instance as data, but '
'probably meant to pass serialized `.data` or '
'`.error`. representation.'
)
raise AssertionError(msg)
#自定义返回格式
self.data={'code':code,'msg':msg,'data':data}
self.data.update(kwargs)
self.template_name=template_name
self.exception=exception
self.content_type=content_type

if headers:
for name, value in headers.items():
self[name] = value

在接口接口视图类获取数据返回时,使用该自定义的Response返回对象。

1
2
3
4
5
6
7
8
class FriendView(viewsets.ModelViewSet):
queryset = Friend.objects.all()
serializer_class = FriendModelSerializer
#自定义list方法,自定义Response返回
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(queryset, many=True)
return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

接口访问效果如下:
可以看到返回数据格式中增加了code,msg 数据放到了data节点
自定义数据返回格式

列表数据通常接口要提供翻页功能,在接口中要有总页数、当前页、是否有下一页的信息。
可以自定义一个分页器,在分页器中自定义需要返回的分页参数
参考示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from common.customresponse import CustomResponse

class MyPage(PageNumberPagination):
page_size = 8 #每页显示数量
max_page_size = 50 #每页最大显示数量。
page_size_query_param = 'size' #每页数量的参数名称
page_query_param = 'page' #页码的参数名称

def get_paginated_response(self, data):
#自定义分页器的返回参数
return CustomResponse(data=data,code=200,msg="OK",status=status.HTTP_200_OK, count=self.page.paginator.count,next=self.get_next_link(),previous=self.get_previous_link(),size=self.page_size,page=self.page.number)

在接口接口视图类获取数据返回时,如果有分页器则使用该分页器自定义的Response返回对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class FriendView(viewsets.ModelViewSet):
queryset = Friend.objects.all()
serializer_class = FriendModelSerializer
pagination_class = MyPage
#自定义list方法,自定义Response返回
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
#如果有分页器,则进行分页后返回
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)

serializer = self.get_serializer(queryset, many=True)
return CustomResponse(data=serializer.data, code=200, msg="OK", status=status.HTTP_200_OK)

接口访问效果如下:
可以看到接口中自定义增加了分页信息。
接口中自定义增加了分页信息

但是有时候可能希望分页的信息数据要放在data节点里面,这样也是可以做到的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from rest_framework import status
from rest_framework.pagination import PageNumberPagination
from common.customresponse import CustomResponse

class MyPage(PageNumberPagination):
page_size = 8 #每页显示数量
max_page_size = 50 #每页最大显示数量。
page_size_query_param = 'size' #每页数量的参数名称
page_query_param = 'page' #页码的参数名称

#自定义分页器的返回参数
def get_paginated_response(self, data):
ret_data = dict()
ret_data['items'] = data
# 加入自定义分页信息
ret_data['total'] = self.page.paginator.count
ret_data['hasNextPage'] = self.get_next_link()
ret_data['size'] = self.page_size
ret_data['page'] = self.page.number
return CustomResponse(data=ret_data,code=200,msg="OK",status=status.HTTP_200_OK)

接口访问效果如下:
可以看到接口中自定义增加了分页信息,分页的信息数据放在data节点里面了
自定义增加了分页信息,分页的信息数据放在data节点里面
至此,本文介绍了通过Django的restframework接口框架自定义Response返回对象来自定义返回数据格式。Django的restframework接口框架使用简单方便,拿来即用,能够很大程度上减少代码开发量。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

Vue3引入vue-router路由并通过vue-wechat-title设置页面title

发表于 2022-07-03 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 1.7k | 阅读时长 ≈ 7

对于用类似Vue前后端分离技术架构的单页应用页面之间的跳转没有非前后端分离那么来得直接,甚至连设置跳转页面的Title都要费一番周折,本文介绍Vue3引入vue-router路由并设置页面Title,通过vue-router实现页面的路由,通过vue-wechat-title来设置页面的title。

一、用vue-router库实现路由管理

vue-router是Vue.js官方推荐的路由管理库。它和Vue.js的核心深度集成,让构建单页应用变得轻松容易。使用Vue.js和vue-router库创建单页应用非常的简单:使用Vue.js开发,整个应用已经被拆分成了独立的组件;使用vue-router库,可以把路由映射到各个组件,并把各个组件渲染到正确的地方。下面就来介绍如何安装引入vue-router库并实现路由管理

1、安装vue-router库

使用如下命令安装vue-router库

1
npm install -save -vue-router

也可以通过 npm install -save vue-router@4 来指定版本号@4表示版本是4
安装成功后,可以在控制台看到了安装成功的信息和版本号
控制台看到了安装成功的信息和版本号
除此之外也可以在工程中的package.json中看到依赖的库中包含有vue-router及版本号。
package.json中看到依赖的库中包含有vue-router及版本号

2、在router文件夹下创建router.js

在工程的src目录下建立router文件夹 在router文件夹下创建router.js,该文件是Vue路由管理的核心文件,所有的各组件的路由在该文件中进行配置。
参考代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { createRouter,createWebHistory } from "vue-router"; //引入vue-router组件
import HelloWorld from '@/components/HelloWorld'; //引入需要路由管理的页面组件HelloWorld
import siteLogin from '@/views/user/login'; //引入需要路由管理的页面组件login
import userInfo from "@/views/user/userinfo"; //引入需要路由管理的页面组件userinfo
const router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/', //路由的路径
name:'Home', //路由的名称
component:HelloWorld, //路由的组件
},
{
path:'/login',
name:'Login',
component:siteLogin,
},
{
path:'/userinfo',
name:'UserInfo',
component:userInfo,
}
]
})
export default router;

代码组织结构如下:
代码组织结构如下

3、在App.vue中加入路由视图

在App.vue中加入<router-view />
App.vue示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
<template>
<div id="app">
<router-view />
</div>
</template>
<script>
export default {
name: 'App',
}
</script>
<style>
</style>

4、在项目的main.js中引入路由

参考代码如下:

1
2
3
4
import { createApp } from 'vue';
import App from './App.vue';
import router from "@/router/router"; //引入路由,会去找router下的router.js的配置文件
createApp(App).use(router).mount('#app') //创建应用的时候应用路由

5、验证效果

为了显示更清楚,将默认创建的src\components\HelloWorld.vue内容稍加调整

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div >
第一个路由组件Home
<p>{{ name }}</p>
</div>
</template>

<script>
export default {
name: 'HelloWorld',
data() {
return {
name:"Hello World!"
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

如果上面的步骤没有遗漏,在终端输入 npm run serve 将前端服务启动起来,在浏览器访问localhost:8080可以看到如下页面:

localhost:8080

访问localhost:8080/login

访问localhost:8080/login

访问localhost:8080/userinfo

访问localhost:8080/userinfo
可以看到访问不同的URL路由到了不同的Vue页面,上述login.vue和userinfo.vue示例代码没有给出,大家可以自行随便实现。

二、用vue-wechat-title实现页面title的设置

在上面实现了不同页面的路由管理,但是访问不同的URL看到的页面title所有的页面都是一样的,如何设置不同页面不同的页面Title呢?比较方便的做法是用vue-wechat-title来实现。
同样首先要安装vue-wechat-title库

1、安装vue-wechat-title库

使用如下命令安装vue-wechat-title库

1
npm install vue-wechat-title -save

安装完成后在工程中的package.json中看到依赖的库中包含有vue-wechat-title及版本号
package.json中看到依赖的库中包含有vue-wechat-title及版本号

2、在router文件夹下的router.js中增加title的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { createRouter,createWebHistory } from "vue-router"; //引入vue-router组件
import HelloWorld from '@/components/HelloWorld'; //引入需要路由管理的页面组件HelloWorld
import siteLogin from '@/views/user/login'; //引入需要路由管理的页面组件login
import userInfo from "@/views/user/userinfo"; //引入需要路由管理的页面组件userinfo
const router = createRouter({
history:createWebHistory(),
routes:[
{
path:'/', //路由的路径
name:'Home', //路由的名称
meta:{
title: '首页' //title配置
},
component:HelloWorld, //路由的组件
},
{
path:'/login',
name:'Login',
meta:{
title:'登录'
},
component:siteLogin,
},
{
path:'/userinfo',
name:'UserInfo',
meta:{
title: '用户信息'
},
component:userInfo,
}
]
})
export default router;

主要是在路由配置时设置了meta:{title:'xxxx'}如下图:

router.js中增加title的配置

3、在App.vue页面中使用

App.vue代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div id="app" v-wechat-title="$route.meta.title">
<router-view />
</div>
</template>

<script>
export default {
name: 'App',
}
</script>

<style>
</style>

主要是在<div id="app" v-wechat-title="$route.meta.title"> 加入了v-wechat-title="$route.meta.title"

4、在main.js中引用vue-wechat-title

在main.js中引用vue-wechat-title的时候有个坑,如果按照一般的引用会报错
mian.js代码示例如下:

1
2
3
4
5
import { createApp } from 'vue';
import App from './App.vue';
import router from "@/router/router"; //引入路由,会去找router下的router.js的配置文件
import VueWechatTitle from 'vue-wechat-title'; //引入VueWechatTitle
createApp(App).use(router,VueWechatTitle).mount('#app') //创建应用的时候应用路由

在终端输入 npm run serve 将前端服务启动起来会报错!
Uncaught TypeError: Cannot read properties of undefined (reading ‘deep’)

原因是在挂载app示例前,vue-wechat-title还没有加载好,一定要先应用再挂载app
将createApp(App).use(router,VueWechatTitle).mount(‘#app’)删除或注释掉。改用

1
2
3
4
const app=createApp(App);
app.use(VueWechatTitle);
app.use(router);
app.mount('#app')

main.js的参考示例代码如下:

1
2
3
4
5
6
7
8
9
import { createApp } from 'vue';
import App from './App.vue';
import router from "@/router/router"; //引入路由,会去找router下的router.js的配置文件
import VueWechatTitle from 'vue-wechat-title'; //引入VueWechatTitle
//createApp(App).use(router,VueWechatTitle).mount('#app') //指令定义在 mount('#app')之后,导致自定义指令未挂载到,会报错
const app=createApp(App);
app.use(VueWechatTitle);
app.use(router);
app.mount('#app')

5、验证效果

在终端输入 npm run serve 将前端服务启动起来
看到访问不同的URL会显示不同的title
http://localhost:8080/

http://localhost:8080/的title
http://localhost:8080/login

login的title登录

http://localhost:8080/userinfo

userinfo的title用户信息

本文通过以上实例实现了Vue3引入vue-router路由并设置页面Title,通过vue-router实现页面的路由,通过vue-wechat-title来设置页面的title都还比较方便。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

Vue快速入门

发表于 2022-07-03 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 2.3k | 阅读时长 ≈ 10

一、什么是Vue

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

二、安装

1、独立版本
直接下载并用<script>标签引入
官网下载地址:https://cn.vuejs.org/js/vue.js
2、使用CDN
和独立版本类似,与独立版本的区别就是不用下载到本地应用,直接引用CDN加速以后的地址。缺点是如果是内网封闭环境不能用,国内CDN也不稳定,国外的CDN有时无法访问。如官网的
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script> 就无法访问。
几个比较稳定的CDN
Staticfile CDN(国内) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js
unpkg:https://unpkg.com/vue@2.6.14/dist/vue.min.js。
cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js
3、命令行工具
Vue 提供了一个官方的 CLI,为单页面应用 (SPA) 快速搭建繁杂的脚手架。它为现代前端工作流提供了开箱即用的构建设置。只需要几分钟的时间就可以运行起来并带有热重载、保存时 lint 校验,以及生产环境可用的构建版本。更多详情可查阅 Vue CLI 的文档。

三、第一个Vue

程序员学一门新的语音或框架,都是从hello world!开始的。来看一下Vue的hello world!
将vue.min.js下载到本地,在vue.min.js的目录下新建一个hellovue.html的文件,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!DOCTYPE html>
<html>
<head>
<script src="vue.min.js"></script>
</head>
<body>
<div id="app">
{{ message }}
</div>

<script type="text/javascript">
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
</body>
</html>

用浏览器打开,hello Vue! 成功的展现出来,第一个Vue就这么简单。
hello Vue!

在这里我们通过<script src="vue.min.js"></script>引入了本地的vue.min.js,就可以用vue框架了。
通过<div id="app">构建了一个DOM元素div标签元素,id为app,`{{message}}` 是占位符,类似于大多数的模板语法。

1
2
3
<div id="app">
{{ message }}
</div>

在javascript代码中,定义了一个Vue对象,对象中构造了el和data两个参数。el是元素选择器,通过#app选择了id="app"的div,data用来定义数据属性,这里定义了massage:'hellow Vue!',通过`{{message}}`将数据hellow Vue显示输出。
可以用chrome浏览器的开发者工具打开控制台看到app.message的值为’hellow Vue!’。
chrome浏览器的开发者工具调试

可以通过修改这个变量的值而改变显示在浏览器的值。
修改值

四、常用基本语法

模板语法
Vue.js 使用了基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。
Vue.js 的核心是一个允许你采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统。
结合响应系统,在应用状态改变时, Vue 能够智能地计算出重新渲染组件的最小代价并应用到 DOM 操作上

插值文本

数据绑定最常见的形式就是使用 `{{xxx}}`(双大括号)的文本插值:
正如我们的第一的Vue通过`{{ message }}`将文本值插入到占位符进行数据绑定

1
2
3
<div id="app">
<p>{{ message }}</p>
</div>

绑定输出html

使用v-html 指令用于输出 html 代码:

1
2
3
4
5
6
7
8
9
10
app2:v-html指令输出html代码
<div id="app2">
<div v-html="message"></div>
</div>
var app2 = new Vue({
el: '#app2',
data: {
message: '<b>Hello Vue!</b>'
}
});

效果如下图所示:
v-html

如果不用v-html插入,将<div id="app2">标签内容改成用文本插入

1
2
3
<div id="app2">
<p>{{ message }}</p>
</div>

显示效果如下,直接将html代码给显示出来了。
直接显示HTML代码

绑定属性

HTML 属性中的值应使用 v-bind 指令。
如插入绑定 a 标签的href属性

1
2
3
4
5
6
7
8
9
10
app3:v-bind指令绑定属性值
<div id="app3">
<a target="_blank" v-bind:href="url">click me go to myblog</a>
</div>
var app3 = new Vue({
el: '#app3',
data: {
url: 'http://xiejava.ishareread.com/'
}
});

效果如下:
绑定属性

绑定样式

class 与 style 是 HTML 元素的属性,用于设置元素的样式,可以用 v-bind 来绑定设置样式属性

1
2
3
4
5
6
7
8
9
10
11
12
app4:v-band:class指令绑定样式
<div id="app4">
<div v-bind:class="{ 'active': isActive }"></div>
</div>
<br>
var app4=new Vue(
{
el: '#app4',
data: {
isActive:true
}
});

定义样式

1
2
3
4
5
6
7
<style>
.active {
width: 100px;
height: 100px;
background: red;
}
</style>

效果如下:
v-band:class

插值Javascript表达式

vue.js插值支持javascript表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app5:vue.js插值的javascript表达式支持
<div id="app5">
{{5+5}}<br>
{{ ok ? 'YES' : 'NO' }}<br>
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id">xiejava</div>
</div>
<br>
var app5 = new Vue({
el: '#app5',
data: {
ok: true,
message: 'XIEJAVA',
id : 1
}
});

效果如下:

vue.js插值支持javascript表达式

常用语句

v-if v-else (条件语句)

条件判断使用 v-if 指令,可以用 v-else 指令给 v-if 添加一个 “else” 块:

1
2
3
4
5
6
7
8
9
10
11
app6:v-if条件语句
<div id="app6">
<div v-if="ok">YES</div>
<div v-else>NO</div>
</div>
var app6 = new Vue({
el:"#app6",
data:{
ok:false,
}
});

效果如下:

v-if v-else (条件语句)

for循环语句

循环使用 v-for 指令,v-for 可以绑定数据到数组来渲染一个列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app7">
<ol>
<li v-for="code in codes">
{{ code.name }}
</li>
</ol>
</div>
<br>
var app7=new Vue(
{
el: '#app7',
data: {
codes: [
{ name: 'java' },
{ name: 'python' },
{ name: 'php' }
]
}
});

效果如下:
for循环语句

v-on绑定事件

事件监听可以使用 v-on 指令进行绑定

1
2
3
4
5
6
7
8
9
10
11
<div id="app8">
<button v-on:click="counter += 1">+1</button>
<p>加了 {{ counter }} 次1。</p>
</div>
var app8=new Vue(
{
el: '#app8',
data: {
counter:0
}
});

效果如下:

v-on绑定事件

以上全部示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
</head>
<body>
app1:hello Vue!
<div id="app1">
{{ message }}
</div>
<br>

app2:v-html指令输出html代码
<div id="app2">
<div v-html="message"></div>
</div>
<br>

app3:v-bind指令绑定属性值
<div id="app3">
<a target="_blank" v-bind:href="url">click me go to myblog</a>
</div>
<br>

app4:v-band:class指令绑定样式
<div id="app4">
<div v-bind:class="{ 'active': isActive }"></div>
</div>
<br>

app5:vue.js插值的javascript表达式支持
<div id="app5">
{{5+5}}<br>
{{ ok ? 'YES' : 'NO' }}<br>
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id">xiejava</div>
</div>
<br>

app6:v-if条件语句
<div id="app6">
<div v-if="ok">YES</div>
<div v-else>NO</div>
</div>
<br>

app7:for循环语句
<div id="app7">
<ol>
<li v-for="code in codes">
{{ code.name }}
</li>
</ol>
</div>
<br>

app8:v-on绑定事件
<div id="app8">
<button v-on:click="counter += 1">+1</button>
<p>加了 {{ counter }} 次1。</p>
</div>
<br>

<script type="text/javascript">
var app1 = new Vue({
el: '#app1',
data: {
message: 'Hello Vue!'
}
});

var app2 = new Vue({
el: '#app2',
data: {
message: '<b>Hello Vue!</b>'
}
});

var app3 = new Vue({
el: '#app3',
data: {
url: 'http://xiejava.ishareread.com/'
}
});

var app4=new Vue(
{
el: '#app4',
data: {
isActive:true
}
});

var app5 = new Vue({
el: '#app5',
data: {
ok: true,
message: 'XIEJAVA',
id : 1
}
});

var app6 = new Vue({
el:"#app6",
data:{
ok:false,
}
});

var app7=new Vue(
{
el: '#app7',
data: {
codes: [
{ name: 'java' },
{ name: 'python' },
{ name: 'php' }
]
}
});

var app8=new Vue(
{
el: '#app8',
data: {
counter:0
}
});
</script>
<style>
.active {
width: 100px;
height: 100px;
background: red;
}
</style>
</body>
</html>

通过上面的快速入门,基本了解什么是VUE、VUE的安装及基本的使用,常用的语法。后面还要更深入的学习VUE的组件、路由、后台接口调用等。


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注:微信公众号,一起学习成长!

PyCharm在用Django开发时debug模式启动失败显示can't find '__main__' module的解决方法

发表于 2022-06-06 | 更新于: 2025-10-23 | 分类于 技术 , 开发 | | 阅读次数:
字数统计: 203 | 阅读时长 ≈ 1

初次用Django开发web应用,在试图用Pycharm进行debug的时候,出现了一个奇怪的问题。以正常模式启动或者在terminal启动都没有问题。但是以debug模式启动时,显示can't find '__main__' module”报错。在网上找了很久都没有看到解决方法,最后在某乎上看到一篇文章,在启动时加上--noreload参数,既可以debug模式启动。

报错信息:
报错信息
解决方法:
在启动时加上 --noreload 参数可以正常启动调试
加入不重新加载参数

debug启动正常也可以调试了。
debug正常启动

踩过的坑记录一下,希望能帮到碰到同样问题的人。

感谢大佬的文章 https://zhuanlan.zhihu.com/p/443763989


博客地址:http://xiejava.ishareread.com/


“fullbug”微信公众号

关注微信公众号,一起学习、成长!

<1…131415…22>
XieJava

XieJava

216 日志
11 分类
27 标签
RSS
GitHub
友情链接
  • 爱分享读书
  • CSDN
  • 豆瓣
© 2025 XieJava | Site words total count: 436.7k

主题 — NexT.Muse
0%