显示当前文集的文章列表
视图,代码:
from rest_framework.generics import ListAPIView
from .serializers import ArticleModelSerializer
from .models import Article
from rest_framework.response import Response
from rest_framework import status
class ArticleOfCollectionAPIView(ListAPIView):
"""文集下的文章"""
serializer_class = ArticleModelSerializer
permission_classes = [IsAuthenticated] # 必须是登陆用户才能访问过来
def list(self, request, *args, **kwargs):
user = request.user
collection_id = request.query_params.get("collection_id")
try:
ArticleCollection.objects.get(pk=collection_id)
except ArticleCollection.DoesNotExist:
return Response({"message":"错误的文集ID"}, status=status.HTTP_400_BAD_REQUEST)
queryset = Article.objects.filter(user=user,collection_id=collection_id).order_by("orders", "-id")
queryset = self.filter_queryset(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
序列化器,代码:
from .models import Article
class ArticleModelSerializer(serializers.ModelSerializer):
"""文章模型序列化器"""
class Meta:
model = Article
fields = ["id","name","content","html_content","collection","pub_date","is_public"]
路由代码:
from django.urls import path,re_path
from . import views
urlpatterns = [
path("collection/", views.CollecionAPIView.as_view()),
re_path("^collection/(?P<pk>\d+)/$", views.CollecionAPIView.as_view()),
re_path("^collection/article/$", views.ArticleOfCollectionAPIView.as_view()),
]
现有的接口实现了以后,会发现没有文章,所以我们需要把文章相关模型注册到xadmin中,让我们可以在xadmin中添加测试文章数据。
article/adminx.py,代码:
import xadmin
from .models import ArticleCollection
class ArticleCollectionModelAdmin(object):
"""文集"""
list_display = ["id","name"]
list_editable = ["name"]
xadmin.site.register(ArticleCollection,ArticleCollectionModelAdmin)
from .models import Article
class ArticleModelAdmin(object):
"""文章"""
list_display=["id","name"]
xadmin.site.register(Article, ArticleModelAdmin)
完成上面注册以后,修改文章子应用在xadmin中的名称显示,
article/apps.py,代码:
from django.apps import AppConfig
class ArticleConfig(AppConfig):
name = 'article'
verbose_name="文章管理"
article/__init__.py
,代码:
default_app_config = "article.apps.ArticleConfig"
访问xadmin的时候报错,错误提示:__str__
方法相关错误。解决方案:
- users/models.py下模型的
__str__
方法代码调整:
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
mobile = models.CharField(max_length=15, null=True, unique=True, help_text="手机号码",verbose_name="手机号码")
wxchat = models.CharField(max_length=100, null=True, unique=True, help_text="微信账号", verbose_name="微信账号")
qq_number = models.CharField(max_length=11, null=True, unique=True, help_text="QQ号", verbose_name="QQ号")
alipay = models.CharField(max_length=100, null=True, unique=True, help_text="支付宝账号", verbose_name="支付宝账号")
# 保存文件的子目录 ImageField和FileField字段类型内置了文件上传处理类
avatar = models.ImageField(upload_to="avatar", null=True, default=None, verbose_name="头像")
money = models.DecimalField(max_digits=8, decimal_places=2,default=0, help_text="账户余额", verbose_name="账户余额")
nickname = models.CharField(max_length=20, null=True, unique=True, help_text="用户昵称",verbose_name="用户昵称")
class Meta:
db_table = "rr_users"
verbose_name = "用户信息"
verbose_name_plural = verbose_name
def __str__(self):
return self.nickname if self.nickname else self.username
在utils/models.py,代码:
from django.db import models
class BaseModel(models.Model):
"""公共模型"""
name = models.CharField(null=True, default="暂无", blank=True, max_length=150, verbose_name='名称')
orders = models.IntegerField(default=0, verbose_name='显示顺序')
is_show = models.BooleanField(default=False, verbose_name="是否上架")
is_delete = models.BooleanField(default=False, verbose_name="逻辑删除")
created_time = models.DateTimeField(null=True,blank=True, auto_now_add=True, verbose_name="添加时间")
updated_time = models.DateTimeField(null=True,blank=True, auto_now=True, verbose_name="更新时间")
class Meta:
# 设置当前模型在数据迁移的时候不要为它创建表
abstract = True
def __str__(self):
return self.name if self.name else ""
前端展示当前文集的文章列表,代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn">
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title=""><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" title="随笔"><span class="">随笔</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
}
},
filters:{
truncate(content){
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
切换文集以后,重新获取新的文集对应的文章列表。
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn">
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title=""><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" title="随笔"><span class="">随笔</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
}
},
filters:{
truncate(content){
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
显示当前文章的操作菜单【扩展】
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title=""><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
}
},
filters:{
truncate(content){
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
<style scoped>
/* 在原有css代码基础上调整以下样式 */
.rQQG7{
height: 100%;
display: block;
width: 40%;
border-right: 1px solid #d9d9d9;
}
._3x4X_{
top: 0;
right: 100%;
position: absolute;
}
._24i7u {
flex-shrink: 0;
padding: 0 80px 10px 40px;
margin-bottom: 0;
border: none;
font-size: 30px;
font-weight: 400;
line-height: 30px;
box-shadow: none;
color: #595959;
background-color: transparent;
outline: none;
border-radius: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: absolute;
top: 0;
right: 0;
width: 60%;
}
#editor {
margin: auto;
width: 60%;
position: absolute;
right: 0;
top: 44px;
height: 580px;
}
</style>
作业:完善当前文集下的文章列表的BUG
当前文集如果默认是0,那么在用户新建文件以后,显示的文章列表错误的。
所以,我们需要在用户创建文集以后,设置当前文章列表为空列表。
添加文章
视图代码:
from rest_framework.generics import ListAPIView
from .serializers import ArticleModelSerializer
from .models import Article
from rest_framework.response import Response
from rest_framework import status
class ArticleOfCollectionAPIView(ListAPIView, CreateAPIView):
"""文集下的文章"""
serializer_class = ArticleModelSerializer
permission_classes = [IsAuthenticated] # 必须是登陆用户才能访问过来
def list(self, request, *args, **kwargs):
user = request.user
collection_id = request.query_params.get("collection_id")
try:
ArticleCollection.objects.get(pk=collection_id)
except ArticleCollection.DoesNotExist:
return Response({"message":"错误的文集ID"}, status=status.HTTP_400_BAD_REQUEST)
queryset = Article.objects.filter(user=user,collection_id=collection_id).order_by("orders", "-id")
queryset = self.filter_queryset(queryset)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
序列化器,代码:
from .models import Article
class ArticleModelSerializer(serializers.ModelSerializer):
"""文章模型序列化器"""
position = serializers.IntegerField(write_only=True, default=0, allow_null=True, label="添加文章的位置", help_text="在文章列表的前面插入添加则为0, 在文章列表的后面追加添加则为1")
class Meta:
model = Article
fields = ["id","position","name","content","html_content","collection","pub_date","is_public"]
read_only_fields = ["id","content","html_content","pub_date","is_public"]
def validate(self, data):
name = data.get("name")
if len(name)<1:
raise serializers.ValidationError("对不起,文章标题不能为空!")
return data
def create(self, validated_data):
"""添加文章"""
try:
article = Article.objects.create(
name=validated_data.get("name"),
user=self.context["request"].user,
content="",
html_content="",
collection=validated_data.get("collection"),
is_public=False,
is_show=True,
orders=0
)
except:
raise serializers.ValidationError("对不起,文章添加失败!")
if validated_data.get("position"):
"""
如果用户设置在文章列表后面追加添加文章,则让文章的排序值跟着id来变化越来越大
排序值越大的文章,越往后排列
"""
article.orders = article.id
article.save()
return article
路由代码与上面的文集的文章列表共用一个路由。
客户端发送ajax,代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title=""><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
文章发布
默认情况下,我们的文章属于未发布状态下的,这时候除了作者本人和后台管理员,其他人看不到的。
- 在写文章页面,我们需要给用户区分哪些文章已经发布了,哪些文章没有发布,在前面的文章列表中我们已经完成了这个功能。
- 在写文章页面,提供一个api,用于修改发布状态。同时,需要提供发布后的文章页面。
切换文章的发布状态
服务端提供切换文章发布状态的功能。、
视图代码:
from rest_framework.views import APIView
class ArticleAPIView(APIView):
permission_classes = [IsAuthenticated]
def patch(self,request,pk):
"""切换文章的发布状态"""
try:
article = Article.objects.get(user=request.user,pk=pk)
except Article.DoesNotExist:
return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)
article.is_public = not article.is_public
article.save()
return Response(status=status.HTTP_200_OK)
路由代码:
from django.urls import path,re_path
from . import views
urlpatterns = [
path("collection/", views.CollecionAPIView.as_view()),
re_path("^collection/(?P<pk>\d+)/$", views.CollecionAPIView.as_view()),
path("collection/article/", views.ArticleOfCollectionAPIView.as_view()),
re_path("^public/(?P<pk>\d+)/$", views.ArticleAPIView.as_view()),
]
前端代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title="" v-if="!article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public"><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public"><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title="" v-if="article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>取消发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
pub_article(){
// 切换文章的发布状态
let article = this.article_list[this.current_article];
this.$axios.patch(`${this.$settings.Host}/article/public/${article.id}/`,{},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
// 切换成功
article.is_public=!article.is_public;
// 关闭菜单
this.is_show_article_menu = false;
}).catch(error=>{
this.$message.error("切换文章发布状态失败!");
})
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
移动文章
首先修复文章菜单中的文集列表,在css代码中这个位置放置以下代码:
._2_WAp ._2KzJx, ._2_WAp ._3x4X_ {
position: absolute;
right: 100%;
top: 0;
display: none;
}
._2_WAp:hover ._2KzJx, ._2_WAp:hover ._3x4X_ {
display: block;
}
服务端提供修改文章的文集ID的接口,在上面切换文章发布状态的视图类中,增加代码:
from rest_framework.views import APIView
class ArticleAPIView(APIView):
permission_classes = [IsAuthenticated]
def patch(self,request,pk):
"""切换文章的发布状态"""
try:
article = Article.objects.get(user=request.user,pk=pk)
except Article.DoesNotExist:
return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)
article.is_public = not article.is_public
article.save()
return Response(status=status.HTTP_200_OK)
def put(self,request,pk):
"""移动文章"""
try:
article = Article.objects.get(user=request.user, pk=pk)
except Article.DoesNotExist:
return Response({"message": "当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)
collection_id = request.data.get("collection_id")
try:
collection = ArticleCollection.objects.get(user=request.user, pk=collection_id)
except ArticleCollection.DoesNotExist:
return Response({"message": "当前文集不存在!"}, status=status.HTTP_400_BAD_REQUEST)
article.collection = collection
article.save()
return Response(status=status.HTTP_200_OK)
路由与上面发布文章状态切换接口一致。客户端实现切换文集代码效果:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title="" v-if="!article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public"><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public"><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title="" v-if="article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>取消发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list" @click.stop="move_article(collection.id)"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
pub_article(){
// 切换文章的发布状态
let article = this.article_list[this.current_article];
this.$axios.patch(`${this.$settings.Host}/article/public/${article.id}/`,{},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
// 切换成功
article.is_public=!article.is_public;
// 关闭菜单
this.is_show_article_menu = false;
}).catch(error=>{
this.$message.error("切换文章发布状态失败!");
})
},
move_article(collection_id){
// 移动文章
this.is_show_article_menu = false;
let article = this.article_list[this.current_article];
this.$axios.put(`${this.$settings.Host}/article/public/${article.id}/`,{
collection_id, // collection_id: collection_id的简写,
},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
this.article_list.splice(this.current_article,1);
}).catch(error=>{
this.$message.error("移动文章失败!");
});
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
定时发布[扩展知识点]
原理:使用celery完成定时任务!
步骤:
1. 当用户点选了定时发布, 页面中弹出一个选择时间的窗口。
2. 当用户设置完成发布时间以后,点击“确认”以后,把这个时间和文章id发送到服务端。
3. 服务端中文章模型的pub_date记录这个定时发布时间。
4. 在celery中创建一个定时任务,在每个固定时间段,检查文章表中,对应时间段的pub_date把对应的文章进行发布。
前端增加一个选择时间的弹窗,代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title="" v-if="!article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public" @click.stop="is_show_interval_windows=true;is_show_article_menu=false;"><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!article.is_public"><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title="" v-if="article.is_public" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>取消发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list" @click.stop="move_article(collection.id)"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
<!-- 定时发布弹窗 -->
<div class="interval_box" v-if="is_show_interval_windows">
<transition name="el-fade-in-linear">
<div class="transition-box">
<div class="_2tIvb">
<div class="ZTNas" aria-label="Close" @click.stop="is_show_interval_windows=false"><i class="fa fa-close"></i></div>
<div class="_1KgC3">
<div class="-K8Re">定时发文 </div>
</div>
<div class="_1LROK PWCkH">
<div class="wzwGh">选择定时发布的时间:</div>
<div class="On4jq">
<el-date-picker
v-model="pub_date"
type="datetime"
format="yyyy-MM-dd HH:mm"
placeholder="选择日期时间">
</el-date-picker>
</div>
<div class="_3mEYS">本文章将于<span class="yfqan">{{timeformat(pub_date)}}</span>发布。
</div>
</div>
<div class="RzhZ5 pCffa">
<button type="button" class="_3zXcJ" @click.stop="is_show_interval_windows=false;"><span>取 消</span></button>
<button type="button" class="_3zXcJ _3QfkW" @click.stop="interval_article"><span>确 认</span></button>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
is_show_interval_windows: false, // 选择定时发布文章的时间窗口
pub_date: new Date().toLocaleDateString(), // 定时发布当前文章的时间默认值为当前时间
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
interval_article(){
// 定时发布文章
let pub_date = this.pub_date;
let article_id = this.article_list[this.current_article].id;
// 发送ajax
},
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
timeformat(time){
// 获取客户端日期时间格式
time = new Date(time);
return `${time.getFullYear()}-${time.getMonth()+1}-${time.getDate()} ${time.getHours()}:${time.getMinutes()}`;
},
pub_article(){
// 切换文章的发布状态
let article = this.article_list[this.current_article];
this.$axios.patch(`${this.$settings.Host}/article/public/${article.id}/`,{},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
// 切换成功
article.is_public=!article.is_public;
// 关闭菜单
this.is_show_article_menu = false;
}).catch(error=>{
this.$message.error("切换文章发布状态失败!");
})
},
move_article(collection_id){
// 移动文章
this.is_show_article_menu = false;
let article = this.article_list[this.current_article];
this.$axios.put(`${this.$settings.Host}/article/public/${article.id}/`,{
collection_id, // collection_id: collection_id的简写,
},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
this.article_list.splice(this.current_article,1);
}).catch(error=>{
this.$message.error("移动文章失败!");
});
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
<style scoped>
/* 页面原来的样式不要删除 */
/* 新增以下定时发布文章的相关样式 */
.interval_box{
display: flex;
height: 280px;
width: 400px;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
background: #fff;
border-radius: 4px;
z-index: 2000;
}
.NVdZF{display:block}
._20JYe {
position: fixed;
top: 0;
right: 0;
left: 0;
bottom: 0;
background-color: hsla(0, 0%, 100%, .7);
height: 100%;
z-index: 1000;
filter: alpha(opacity=30)
}
body.reader-night-mode ._20JYe {
background-color: hsla(0, 0%, 40%, .7)
}
._3WS2u {
display: none
}
._23VW8 {
position: fixed;
overflow: auto;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1010;
-webkit-overflow-scrolling: touch;
outline: 0
}
._3cgNz {
position: relative;
background-color: #fff;
width: auto;
border: 0;
border-radius: 6px;
margin: 0 auto 24px;
background-clip: padding-box;
-webkit-box-shadow: 0 2px 8px rgba(0, 0, 0, .2);
box-shadow: 0 2px 8px rgba(0, 0, 0, .2)
}
body.reader-night-mode ._3cgNz {
background-color: #3d3d3d
}
.cjahm {
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%)
}
.cjahm.move-up-appear.move-up-appear-active, .cjahm.move-up-enter.move-up-enter-active {
-webkit-animation-name: _2oyZp;
animation-name: _2oyZp
}
.cjahm.move-up-leave.move-up-leave-active {
-webkit-animation-name: _3g8pf;
animation-name: _3g8pf
}
._2tIvb {
position: relative;
border-radius: 6px;
background-color: #eee;
}
body.reader-night-mode ._2tIvb {
background-color: #3d3d3d
}
.ZTNas {
position: absolute;
right: 0;
top: 0;
width: 48px;
height: 48px;
line-height: 46px;
text-align: center;
font-size: 16px;
cursor: pointer;
color: #999;
-webkit-transition: color .3s ease;
-o-transition: color .3s ease;
transition: color .3s ease
}
.ZTNas:hover {
color: #4d4d4d
}
._1KgC3 {
padding: 13px 16px;
border-radius: 4px 4px 0 0;
color: #595959;
border-bottom: 1px solid #d9d9d9
}
body.reader-night-mode ._1KgC3 {
border-color: #2e2e2e
}
.-K8Re {
margin: 0;
font-size: 16px;
line-height: 21px;
font-weight: 500;
color: #4d4d4d
}
body.reader-night-mode .-K8Re {
color: #b3b3b3
}
._3O4M2 {
font-size: 13px;
padding-left: 10px;
color: #999
}
._3O4M2 a {
color: #3294d0
}
.PWCkH {
padding: 16px;
font-size: 12px;
line-height: 1.5;
color: #595959
}
body.reader-night-mode .PWCkH {
color: #b3b3b3
}
.pCffa {
padding: 0 16px 16px;
text-align: right;
border-radius: 0 0 2px 2px
}
._26mjB {
display: block
}
.HhIYk {
zoom: 1
}
.HhIYk:after, .HhIYk:before {
content: " ";
display: table
}
.HhIYk:after {
clear: both;
visibility: hidden;
font-size: 0;
height: 0
}
._37SYn {
font-size: 13px;
line-height: 20px;
padding: 30px 30px 20px;
color: #333
}
body.reader-night-mode ._37SYn {
color: #b3b3b3
}
._2BmdS {
padding: 0 16px 16px;
text-align: right;
border-radius: 0 0 2px 2px
}
._26mjB .PWCkH {
padding: 0;
border-radius: 2px
}
._26mjB .PWCkH a {
color: #3194d0
}
._26mjB .PWCkH a:active, ._26mjB .PWCkH a:hover {
color: #2b86bc
}
._26mjB ._38pIX {
padding: 30px 16px;
border: 0
}
._26mjB ._38pIX input {
display: block;
border-radius: 4px;
width: 100%;
line-height: 20px;
padding: 5px 10px;
font-size: 15px;
background-color: transparent;
border: 1px solid #ccc
}
body.reader-night-mode ._26mjB ._38pIX input {
border-color: #2e2e2e
}
._26mjB .ZTNas {
width: 32px;
height: 32px;
line-height: 32px
}
.HSpeJ .PWCkH {
border: 0
}
</style>
服务端提供修改pub_date发布时间的api接口
配置文件中, 调整市区,代码:
# 必须设置为True,否则后面提交发布时间到服务端会报错!
USE_TZ = True
视图代码:
class ArticleIntervalAPIView(APIView):
"""定时发布文章"""
permission_classes = [IsAuthenticated]
def put(self,request,pk):
try:
article = Article.objects.get(pk=pk)
except Article.DoesNotExist:
return Response("对不起,当前文章不存在!", status=status.HTTP_400_BAD_REQUEST)
pub_date = request.data.get("pub_date")
article.pub_date = pub_date
article.save()
return Response("操作成功!")
路由,代码:
urlpatterns = [
....
re_path("^interval/(?P<pk>\d+)/$", views.ArticleIntervalAPIView.as_view()),
]
客户端发送ajax请求,代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public,interval:article.pub_date}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)" @click.stop="is_show_interval_windows=true;is_show_article_menu=false;"><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)"><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title="" v-if="article.is_public||article.pub_date" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>取消发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list" @click.stop="move_article(collection.id)"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
<!-- 定时发布弹窗 -->
<div class="interval_box" v-if="is_show_interval_windows">
<transition name="el-fade-in-linear">
<div class="transition-box">
<div class="_2tIvb">
<div class="ZTNas" aria-label="Close" @click.stop="is_show_interval_windows=false"><i class="fa fa-close"></i></div>
<div class="_1KgC3">
<div class="-K8Re">定时发文 </div>
</div>
<div class="_1LROK PWCkH">
<div class="wzwGh">选择定时发布的时间:</div>
<div class="On4jq">
<el-date-picker
v-model="pub_date"
type="datetime"
format="yyyy-MM-dd HH:mm"
placeholder="选择日期时间">
</el-date-picker>
</div>
<div class="_3mEYS">本文章将于<span class="yfqan">{{timeformat(pub_date)}}</span>发布。
</div>
</div>
<div class="RzhZ5 pCffa">
<button type="button" class="_3zXcJ" @click.stop="is_show_interval_windows=false;"><span>取 消</span></button>
<button type="button" class="_3zXcJ _3QfkW" @click.stop="interval_article"><span>确 认</span></button>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
is_show_interval_windows: false, // 选择定时发布文章的时间窗口
pub_date: new Date().toLocaleDateString(), // 定时发布当前文章的时间默认值为当前时间
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
interval_article(){
// 定时发布文章
let article_id = this.article_list[this.current_article].id;
// 发送ajax
this.$axios.put(`${this.$settings.Host}/article/interval/${article_id}/`,{
pub_date: this.pub_date,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.article_list[this.current_article].pub_date = this.pub_date;
this.$message.success("文章设置成功!");
}).catch(error=>{
this.$message.error("操作失败!请联系客服工作人员!");
});
this.is_show_interval_windows=false;// 关闭定时窗口
},
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
timeformat(time){
// 获取客户端日期时间格式
time = new Date(time);
return `${time.getFullYear()}-${time.getMonth()+1}-${time.getDate()} ${time.getHours()}:${time.getMinutes()}`;
},
pub_article(){
// 切换文章的发布状态
let article = this.article_list[this.current_article];
this.$axios.patch(`${this.$settings.Host}/article/public/${article.id}/`,{},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
// 切换成功
article.is_public=!article.is_public;
// 关闭菜单
this.is_show_article_menu = false;
}).catch(error=>{
this.$message.error("切换文章发布状态失败!");
})
},
move_article(collection_id){
// 移动文章
this.is_show_article_menu = false;
let article = this.article_list[this.current_article];
this.$axios.put(`${this.$settings.Host}/article/public/${article.id}/`,{
collection_id, // collection_id: collection_id的简写,
},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
this.article_list.splice(this.current_article,1);
}).catch(error=>{
this.$message.error("移动文章失败!");
});
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
<style>
/* 注意:在素材目录下找到sprite.9d24217.png放到static/image目录中 */
._25Ilv .interval {
background: url(/static/image/sprite.9d24217.png) no-repeat -74px -25px;
background-size: 250px;
}
</style>
当用户取消发布时,我们应该把定时发布也取消掉,也就是说把当前文章的pub_date清空!
视图中切换文章发布状态视图接口代码进行调整,代码:
from rest_framework.views import APIView
class ArticleAPIView(APIView):
permission_classes = [IsAuthenticated]
def patch(self,request,pk):
"""切换文章的发布状态"""
try:
article = Article.objects.get(user=request.user,pk=pk)
except Article.DoesNotExist:
return Response({"message":"当前文章不存在!"}, status=status.HTTP_400_BAD_REQUEST)
# 判断如果是取消发布,则取消定时发布
# 1. 定时发布
if article.is_public == False and article.pub_date is not None:
# 原来定时发布的,现在取消
article.pub_date = None
elif article.is_public == False and article.pub_date is None:
# 原来没有发布的,现在立即发布
article.is_public = True
elif article.is_public == True:
# 原来发布的,现在取消
article.is_public = False
article.save()
return Response(status=status.HTTP_200_OK)
客户端状态在取消发布以后调整显示状态,代码:
<template>
<div class="write" v-if="is_show_page">
<div class="_2v5v5">
<div class="_3zibT"><router-link to="/">回首页</router-link></div>
<div class="_1iZMb">
<div class="_33Zlg" @click="collection_form=true"><i class="fa fa-plus"></i><span>新建文集</span></div>
<div class="_2G97m">
<form class="M8J6Q" :class="collection_form?'_2a1Rp':'_1mU5v'">
<input type="text" placeholder="请输入文集名..." v-model="collection_name" class="_1CtV4">
<button type="submit" class="dwU8Q _3zXcJ _3QfkW" @click.stop.prevent="add_collection"><span>提 交</span></button>
<button type="button" class="vIzwB _3zXcJ" @click.stop.prevent="collection_form=false"><span>取 消</span></button>
</form>
</div>
</div>
<ul class="_3MbJ4 _3t059">
<li class="_3DM7w" :class="{_31PCv:current_collection==key}" @click.stop="current_collection=key;is_show_collection_menu=false;" :title="collection.name" v-for="collection,key in collection_list">
<div class="_3P4JX _2VLy-" v-if="current_collection==key" @click.stop="is_show_collection_menu=!is_show_collection_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_collection_menu}">
<li class="_2po2r cRfUr" title="">
<span class="" @click.stop="edit_collection"><i class="fa fa-pencil-square-o _22XWG"></i>修改文集</span>
</li>
<li class="_2po2r cRfUr" title="">
<span class=""><i class="fa fa-trash-o _22XWG"></i>删除文集</span>
</li>
</ul>
</span>
</div>
<span>{{collection.name}}</span>
</li>
</ul>
<div style="height: 50px;"></div>
<div role="button" class="h-5Am">
<span class="ant-dropdown-trigger"><i class="fa fa-bars"></i><span>设置</span></span>
<span class="Yv5Zx">遇到问题<i class="fa fa-question-circle-o"></i></span>
</div>
</div>
<div class="rQQG7">
<div class="_3revO _2mnPN">
<div class="_3br9T">
<div>
<div class="_1GsW5" @click.stop="add_article(0)"><i class="fa fa-plus-circle"></i><span> 新建文章</span></div>
<ul class="_2TxA-">
<li class="_25Ilv" :class="{_33nt7:key==current_article}" @click.stop="current_article=key;" :title="article.name" v-for="article,key in article_list">
<i class="_13kgp" :class="{_2m93u:article.is_public,interval:article.pub_date}"></i>
<div class="_3P4JX poOXI" v-if="key==current_article" @click.stop="is_show_article_menu=!is_show_article_menu">
<i class="fa fa-gear"></i>
<span>
<ul class="_2V8zt _3FcHm _2w9pn" :class="{NvfK4:is_show_article_menu}">
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>直接发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)" @click.stop="is_show_interval_windows=true;is_show_article_menu=false;"><span class=""><i class="fa fa-clock-o _22XWG"></i>定时发布</span></li>
<li class="_2po2r cRfUr" title="" v-if="!(article.is_public||article.pub_date)"><span class="_20tIi"><i class="iconfont ic-paid _22XWG"></i>发布为付费文章</span></li>
<li class="_2po2r cRfUr" title="" v-if="article.is_public||article.pub_date" @click.stop="pub_article"><span class=""><i class="fa fa-share _22XWG"></i>取消发布</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="iconfont ic-set _22XWG"></i>设置发布样式</span></li>
<li class="_3nZXj _2_WAp _3df2u _2po2r cRfUr" title=""><span class=""><i class="fa fa-folder-open _22XWG"></i>移动文章
<div class="_3x4X_">
<ul class="_2KzJx oGKRI _3DXDE _2w9pn">
<li class="_2po2r cRfUr" :title="collection.name" v-if="key!=current_collection" v-for="collection,key in collection_list" @click.stop="move_article(collection.id)"><span class="">{{collection.name}}</span></li>
</ul>
</div>
</span>
</li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-history _22XWG"></i>历史版本</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-trash-o _22XWG"></i>删除文章</span></li>
<li class="_2po2r cRfUr" title=""><span class=""><i class="fa fa-ban _22XWG"></i>设置禁止转载</span></li>
</ul>
</span>
</div>
<span class="NariC">{{article.name}}</span>
<span class="hLzJv">{{article.content|truncate}}</span>
<span class="_29C-V" v-if="key==current_article">字数:{{article.content.length}}</span>
</li>
<!-- <li class="_25Ilv" title="2020-01-12">-->
<!-- <i class="_13kgp"></i>-->
<!-- <span class="NariC">2020-01-12</span>-->
<!-- <span class="hLzJv">题目:有四个数字:1、2、3、4,能组成多少个互不相同且无重复数字的三位数?各是多少?-->
<!--题目:企业发放的奖金根据利润提成</span>-->
<!-- </li>-->
</ul>
<div class="_2cVn3" @click.stop="add_article(1)"><i class="fa fa-plus"></i><span> 在下方新建文章</span></div>
</div>
</div>
</div>
<input type="text" class="_24i7u" value="2020-01-12">
<div id="editor">
<mavon-editor
style="height: 100%"
v-model="editorContent"
:ishljs="true"
ref=md
@imgAdd="imgAdd"
@imgDel="imgDel"
></mavon-editor>
</div>
</div>
<!-- 定时发布弹窗 -->
<div class="interval_box" v-if="is_show_interval_windows">
<transition name="el-fade-in-linear">
<div class="transition-box">
<div class="_2tIvb">
<div class="ZTNas" aria-label="Close" @click.stop="is_show_interval_windows=false"><i class="fa fa-close"></i></div>
<div class="_1KgC3">
<div class="-K8Re">定时发文 </div>
</div>
<div class="_1LROK PWCkH">
<div class="wzwGh">选择定时发布的时间:</div>
<div class="On4jq">
<el-date-picker
v-model="pub_date"
type="datetime"
format="yyyy-MM-dd HH:mm"
placeholder="选择日期时间">
</el-date-picker>
</div>
<div class="_3mEYS">本文章将于<span class="yfqan">{{timeformat(pub_date)}}</span>发布。
</div>
</div>
<div class="RzhZ5 pCffa">
<button type="button" class="_3zXcJ" @click.stop="is_show_interval_windows=false;"><span>取 消</span></button>
<button type="button" class="_3zXcJ _3QfkW" @click.stop="interval_article"><span>确 认</span></button>
</div>
</div>
</div>
</transition>
</div>
</div>
</template>
<script>
import { mavonEditor } from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'
import "../../static/font-awesome/css/font-awesome.css";
export default {
name: "Writer",
data(){
return {
is_show_page: false, // 是否显示页面
collection_list:[], // 文集列表
article_list:[], // 文章列表
current_collection: 0, // 当前选中的文集下标,默认为0
current_article:0, // 当前选中的文章下标,默认为0
editorContent:"",
img_file:[],
collection_form:false,
collection_name:"",
is_show_collection_menu: false, // 是否显示文集的菜单
is_show_article_menu: false, // 是否显示文章的菜单
position: 0, // 添加文章的位置,默认前面插入,值为0
is_show_interval_windows: false, // 选择定时发布文章的时间窗口
pub_date: new Date().toLocaleDateString(), // 定时发布当前文章的时间默认值为当前时间
}
},
filters:{
truncate(content){
if(content === null){
return 0;
}
return content.substr(0,30);
}
},
watch:{
editorContent(){
console.log(this.editorContent);
},
current_collection(){
// 切换文集
this.get_article_of_collection();
// 关闭文章菜单
this.is_show_article_menu = false;
},
current_article(){
// 切换文章
this.is_show_article_menu = false;
}
},
created(){
// 判断登录
this.$settings.check_user_login(this,"警告","您尚未登录!", "跳转到登录", "/login");
if(this.token){
// 显示页面
this.is_show_page = true;
// 获取当前用户的文集列表
this.get_collection();
}
},
mounted(){
if(this.is_show_page){
document.querySelector("#editor").style.height = document.documentElement.clientHeight - document.querySelector("._24i7u").clientHeight + "px";
// 点选页面其他位置,关闭菜单
document.onclick = (event)=>{
// 关闭文集菜单
this.is_show_collection_menu = false;
// 关闭文章菜单
this.is_show_article_menu = false;
}
}
},
components: {
mavonEditor
},
methods:{
interval_article(){
// 定时发布文章
let article_id = this.article_list[this.current_article].id;
// 发送ajax
this.$axios.put(`${this.$settings.Host}/article/interval/${article_id}/`,{
pub_date: this.pub_date,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.article_list[this.current_article].pub_date = this.pub_date;
this.$message.success("文章设置成功!");
}).catch(error=>{
this.$message.error("操作失败!请联系客服工作人员!");
});
this.is_show_interval_windows=false;// 关闭定时窗口
},
edit_collection(){
// 修改文集
this.is_show_collection_menu=false; // 关闭文集的操作菜单
this.$prompt('请输入新文集名', '提示', {
confirmButtonText: '保存',
cancelButtonText: '取消',
inputPattern: /.{1,}/,
inputErrorMessage: '文集名称不能为空!',
inputValue: this.collection_list[this.current_collection].name,
}).then(({ value }) => {
// 点击确定,需要把当前文集名称提交到服务端进行修改
let collection_id = this.collection_list[this.current_collection].id;
this.$axios.put(`${this.$settings.Host}/article/collection/${collection_id}/`,{
name: value,
},{
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
// 服务端ajax请求操作成功,则客户端的name也要发生改变
this.collection_list[this.current_collection].name = value;
}).catch(error=>{
this.$message.error(error.response.data);
})
}).catch(() => {
});
},
add_collection(){
// 添加文集
if(this.collection_name.length<1){
this.$message.error("文集名称不能为空!");
return;
}
// 发送ajax请求
this.$axios.post(`${this.$settings.Host}/article/collection/`,{
name: this.collection_name
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("添加文集成功!");
this.collection_name = "";
this.collection_form = false; // 隐藏添加文集的表单
// 把服务端中添加返回的文集信息,保存到collection_list中
this.collection_list.unshift(response.data);
}).catch(error=>{
this.$message.error(error.response.data);
});
},
get_collection(){
// 获取用户的文集列表
this.$axios.get(`${this.$settings.Host}/article/collection/`,{
headers:{
Authorization: "jwt " + this.token, // 必须在左边加上 "jwt ",空格!!!
}
}).then(response=>{
this.collection_list = response.data;
// 获取当前选中文集的文章列表
this.get_article_of_collection();
}).catch(error=>{
this.$message.error("对不起,无法获取当前用户的文集列表!");
});
},
get_article_of_collection(){
// 获取当前文集的文章列表
this.$axios.get(`${this.$settings.Host}/article/collection/article/`,{
params:{
collection_id: this.collection_list[this.current_collection].id,
},
headers:{
Authorization:"jwt " + this.token,
}
}).then(response=>{
this.article_list = response.data;
}).catch(error=>{
this.$message.error(error.response.data.message);
})
},
add_article(position){
// 添加文章
this.$axios.post(`${this.$settings.Host}/article/collection/article/`,{
collection: this.collection_list[this.current_collection].id,
name: this.get_datetime(),
position: position,
},{
headers:{
Authorization: "jwt " + this.token,
}
}).then(response=>{
this.$message.success("文章添加成功!");
// 给本地的aticle追加/插入新建的文章
if(position){
// 追加
this.article_list.push(response.data);
}else{
// 插入
this.article_list.unshift(response.data);
}
}).catch(error=>{
this.$message.error("对不起,添加文章失败!");
});
},
get_datetime(){
// 获取客户端日期格式
let datetime = new Date();
let Y = datetime.getFullYear();
let m = datetime.getMonth()+1;
m = m<10?"0"+m:m;
let d = datetime.getDate();
d = d<10?"0"+d:d;
return `${Y}-${m}-${d}`;
},
timeformat(time){
// 获取客户端日期时间格式
time = new Date(time);
return `${time.getFullYear()}-${time.getMonth()+1}-${time.getDate()} ${time.getHours()}:${time.getMinutes()}`;
},
pub_article(){
// 切换文章的发布状态
let article = this.article_list[this.current_article];
this.$axios.patch(`${this.$settings.Host}/article/public/${article.id}/`,{},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
// 切换成功
if(article.pub_date || article.is_public){
// 取消发布
article.is_public=false;
article.pub_date=null;
}else{
// 立即发布
article.is_public=true;
}
// 关闭菜单
this.is_show_article_menu = false;
}).catch(error=>{
this.$message.error("切换文章发布状态失败!");
})
},
move_article(collection_id){
// 移动文章
this.is_show_article_menu = false;
let article = this.article_list[this.current_article];
this.$axios.put(`${this.$settings.Host}/article/public/${article.id}/`,{
collection_id, // collection_id: collection_id的简写,
},{
headers:{
Authorization: "jwt "+this.token,
}
}).then(response=>{
this.article_list.splice(this.current_article,1);
}).catch(error=>{
this.$message.error("移动文章失败!");
});
},
// 绑定@imgAdd event
imgAdd(pos, $file){
// 添加文件
},
imgDel(pos) {
// 删除文件
}
}
}
</script>
celery的定时任务,每分钟执行一次定时发布操作,让pub_date时间到了,则更新对应文章的发布状态。
在mycelery中创建article任务目录,在目录下创建任务文件tasks.py,编写异步任务:
from mycelery.main import app
from article.models import Article
from datetime import datetime
@app.task(name="interval_pub_article")
def interval_pub_article():
"""定时发布文章"""
article_list = Article.objects.exclude(pub_date=None)
for article in article_list:
pub_date_timestamp = int(article.pub_date.timestamp())
current_timestamp = int(datetime.now().timestamp() +8 * 60 * 60)
if pub_date_timestamp <= current_timestamp:
article.pub_date = None
article.is_public = True
article.save()
注册异步任务到main.py中。并重启celery,代码:
from celery import Celery
# 初始化celery对象
app = Celery("renran")
# 初始化django
import os
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'renranapi.settings.dev')
import django
django.setup()
# 加载配置
app.config_from_object("mycelery.config")
# 注册异步任务
# 任务以包进行管理,每一个包必须里面包含了一个tasks.py文件
app.autodiscover_tasks(["mycelery.sms","mycelery.article"])
# 在终端下面运行celery,将来在线上服务器中,可以使用supervisor以守护进程的方式启动
# celery -A mycelery.main worker --loglevel=info
接下来,我们就可以使用celery的定时任务调度器,让celery定时执行异步任务。
Celery官方文档中关于定时任务使用的说明:
http://docs.celeryproject.org/en/latest/userguide/periodic-tasks.html
在mycelery中config.py配置异步任务定时执行,代码:
# 任务队列的链接地址
broker_url = 'redis://127.0.0.1:6379/15'
# 结果队列的链接地址
result_backend = 'redis://127.0.0.1:6379/14'
# 定时任务调度器相关配置
from .main import app
from celery.schedules import crontab
app.conf.beat_schedule = {
# 定时任务列表
'pub-article-every-one-minute': {
'task': 'interval_pub_article', # 指定定时执行的的异步任务
# 'schedule': crontab(), # 时间间隔,一分钟
'schedule': 30.0, # 时间间隔,默认:秒
# 'args': (16, 16) # 如果任务有固定参数,则可以写在args
},
}
# 和django框架同步时区
from django.conf import settings
app.conf.timezone = settings.TIME_ZONE
# 完成上面的配置以后,重启celery并在新的终端窗口执行命令,运行定时任务的调度器
# celery -A mycelery.main beat # mycelery.main 是celery的主应用文件
接下来,我们就可以重启Celery并启用Celery的定时任务调度器
先在终端下,运行celery的定时任务程序,以下命令:
celery -A mycelery.main beat # mycelery.main 是celery的主应用文件
然后再新建一个终端,运行以下命令,上面的命令必须先指定:
celery -A mycelery.main worker --loglevel=info
注意,使用的时候,如果有时区必须先配置好系统时区。