基本功能完成

master
barney 2 years ago
parent f36f731994
commit c2c6beba70
  1. 5
      article/serializers.py
  2. 4
      article/views.py
  3. BIN
      db.sqlite3
  4. 2
      drf_vue_blog/settings.py
  5. 3
      frontend/.gitignore
  6. 4
      frontend/src/App.vue
  7. 129
      frontend/src/components/ArticleList.vue
  8. 10
      frontend/src/components/BlogFooter.vue
  9. 36
      frontend/src/components/BlogHeader.vue
  10. 170
      frontend/src/components/CommentView.vue
  11. 7
      frontend/src/router/index.js
  12. 8
      frontend/src/store/index.js
  13. 9
      frontend/src/utils/authorization.js
  14. 156
      frontend/src/views/ArticleCreate.vue
  15. 123
      frontend/src/views/ArticleDetailView.vue
  16. 275
      frontend/src/views/ArticleEdit.vue
  17. 12
      frontend/src/views/LoginView.vue
  18. 4
      frontend/src/views/UserCenterView.vue
  19. BIN
      media/avatar/20220928/apple_06.png
  20. BIN
      media/avatar/20220929/apple_02.png
  21. BIN
      media/avatar/20220929/apple_02_XwdRCGC.png
  22. BIN
      media/avatar/20220929/apple_03.png
  23. BIN
      media/avatar/20220929/apple_03_UySCYTE.png
  24. BIN
      media/avatar/20220929/apple_03_ZIeet1U.png
  25. BIN
      media/avatar/20220929/apple_04.png
  26. BIN
      media/avatar/20220929/apple_06.png
  27. BIN
      media/avatar/20220929/apple_06_wFiaqpZ.png
  28. BIN
      media/avatar/20220929/apple_08.png
  29. BIN
      media/avatar/20220929/apple_08_MJp2UUi.png
  30. BIN
      media/avatar/20220929/apple_09.png
  31. BIN
      media/avatar/20220930/apple_02.png
  32. BIN
      media/avatar/20220930/apple_03.png
  33. BIN
      media/avatar/20221001/apple_00.png
  34. BIN
      media/avatar/20221001/apple_03.png
  35. BIN
      requirements.txt

@ -154,7 +154,6 @@ class TagSerializer(serializers.ModelSerializer):
# 显示url
# url = serializers.HyperlinkedIdentityField(view_name='tag-detail')
class Meta:
model = Tag
fields = '__all__'
@ -167,11 +166,11 @@ class TagSerializer(serializers.ModelSerializer):
def create(self, validated_data):
self.check_tag_obj_exists(validated_data)
return super(TagSerializer, self).create(validated_data)
return super().create(validated_data)
def update(self, instance, validated_data):
self.check_tag_obj_exists(validated_data)
return super(TagSerializer, self).update(instance, validated_data)
return super().update(instance, validated_data)

@ -1,7 +1,7 @@
from rest_framework import viewsets
from article.serializers import ArticleSerializer, CategorySerializer, CategoryDetailSerializer
from article.serializers import TagSerializer,ArticleDetailSerializer, AvatarSerializer
from article.models import Article, Category, Tag , Avatar
from article.serializers import TagSerializer, ArticleDetailSerializer, AvatarSerializer
from article.models import Article, Category, Tag, Avatar
from article.permissions import IsAdminUserOrReadOnly
from rest_framework.filters import SearchFilter

Binary file not shown.

@ -139,7 +139,7 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# simple_jwt配置
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=1), # 令牌有效时间为1分钟,便于测试
'ACCESS_TOKEN_LIFETIME': timedelta(days=1), # 令牌有效时间为1
'REFRESH_TOKEN_LIFETIME': timedelta(days=10), # 令牌每10天刷新一次
}

@ -1,6 +1,7 @@
.DS_Store
node_modules
/dist
# /dist文件夹保留
# /dist
# local env files

@ -1,10 +1,12 @@
<template>
<router-view />
</template>
<script>
export default {
name: "App",
name: "App",
};
</script>

@ -1,47 +1,52 @@
<template>
<div v-for="article in articles" :key="article.url" id="articles">
<!-- 增加了这个 span -->
<span v-if="article.category !== null" class="category">
{{ article.category.title }}
</span>
<span v-for="tag in article.tags" v-bind:key="tag" class="tag">
{{ tag }}
</span>
<!-- 路由链接:to的两个参数分别是: 路由名称和传入的id -->
<router-link :to="{ name: 'detail', params: { id: article.id } }">
<div class="article-title">{{ article.title }}</div>
</router-link>
<div>
{{ formatted_time(article.created) }}
<div class="grid" :style="gridStyle(article)">
<div class="image-container">
<img :src="imageIfExists(article)" alt="" class="image" />
</div>
<div>
</div>
<div>
<span v-if="article.category !== null" class="category">
{{ article.category.title }}
</span>
<span v-for="tag in article.tags" v-bind:key="tag" class="tag">
{{ tag }}
</span>
<!-- 路由链接:to的两个参数分别是: 路由名称和传入的id -->
<router-link :to="{ name: 'detail', params: { id: article.id } }">
<div class="article-title">{{ article.title }}</div>
</router-link>
<div>
{{ formatted_time(article.created) }}
</div>
</div>
</div>
</div>
<!-- 实现分页 -->
<div id="paginator">
<span v-if="is_page_exists('previous')">
<router-link :to="get_path('previous')"> Prev </router-link>
</span>
<span class="current-page">
{{ get_page_param("current") }}
</span>
<span v-if="is_page_exists('next')">
<router-link :to="get_path('next')"> Next </router-link>
</span>
</div>
</div>
<!-- 实现分页 -->
<div id="paginator">
<span v-if="is_page_exists('previous')">
<router-link :to="get_path('previous')"> Prev </router-link>
</span>
<span class="current-page">
{{ get_page_param("current") }}
</span>
<span v-if="is_page_exists('next')">
<router-link :to="get_path('next')"> Next </router-link>
</span>
</div>
</template>
<script>
import $ from "jquery";
import { onMounted, ref } from "vue";
import { onMounted, ref, watch} from "vue";
import { useRoute } from "vue-router";
export default {
name: "ArticleList",
setup() {
onMounted(() => {
get_article_data();
});
let articles = ref([]); //
let previous = ref(null); // url
let next = ref(null); // url
@ -136,21 +141,49 @@ export default {
return url;
};
const imageIfExists = (article) => {
if (article.avatar) {
return article.avatar.content;
}
};
const gridStyle = (article) => {
if(article.avatar) {
return {
display: 'grid',
gridTemplateColumns: '1fr 1fr 8fr',
}
}
};
onMounted(get_article_data);
// watch(()=>router.fullPath, () => {
// console.log("!");
// get_article_data();
// })
//
watch(router,get_article_data);
return {
articles,
formatted_time,
is_page_exists,
get_page_param,
get_article_data,
imageIfExists,
gridStyle,
get_path,
};
},
watch: {
//
$route() {
this.get_article_data();
},
},
// watch: {
// //
// $route() {
// this.get_article_data();
// },
// },
};
</script>
@ -159,12 +192,24 @@ export default {
padding: 1rem;
}
.image {
width: 100px;
border-radius: 10px;
box-shadow: darkslategrey 0 0 12px;
}
.image-container {
width: 100px;
}
.grid {
padding-bottom: 10px;
}
.article-title {
font-size: 1.2rem;
font-size: 1.5rem;
font-weight: bolder;
text-decoration: none;
color: blue;
padding: 5px 0 5px 0;
padding: 15px 0 15px 0;
}
.tag {
@ -179,11 +224,12 @@ export default {
#paginator {
text-align: center;
padding-bottom: 4rem;
position: relative;
}
#paginator a {
color: black;
padding-bottom: 75px;
}
.current-page {
@ -191,6 +237,7 @@ export default {
font-weight: bold;
padding-left: 10px;
padding-right: 10px;
}
.category {

@ -1,15 +1,15 @@
<template>
<div id="footer">
<p>&copy; bnblogs.cc</p>
<div>
<p>&copy; bnblogs.cc</p>
</div>
</div>
</template>
<script>
export default {
name: "BlogFooter",
};
</script>
<style scoped>
@ -22,5 +22,9 @@ export default {
background: whitesmoke;
text-align: center;
font-weight: bold;
display: flex;
justify-content: center;
align-items: center;
}
</style>

@ -2,7 +2,7 @@
<div id="header">
<div class="grid">
<div></div>
<h1>My Drf-Vue Blog</h1>
<h1 @click="homePage">My Drf-Vue Blog</h1>
<SearchButton />
</div>
<hr />
@ -31,7 +31,7 @@
<router-link
:to="{ name: 'ArticleCreate' }"
v-if="isSuperUser"
v-if="$store.state.isSuperUser"
>
发表文章
</router-link>
@ -53,44 +53,56 @@
</template>
<script>
import { ref } from "vue";
import { onMounted, ref, watchEffect } from "vue";
import SearchButton from "@/components/SearchButton.vue";
import authorization from "@/utils/authorization";
import router from "@/router/index";
import { useStore } from "vuex";
const storage = localStorage;
export default {
name: "BlogHeader",
components: {
SearchButton,
},
mounted() {
authorization().then((data) => ([this.haslogin, this.username] = data));
},
setup() {
let username = ref("");
let haslogin = ref(false);
let isSuperUser = ref(false);
const quit = () => {
window.alert("已退出登录!");
console.log("登出成功,过期时间: ", new Date().toLocaleString());
router.push({ name: "home" });
storage.clear(); //
haslogin.value = false;
};
isSuperUser.value = JSON.parse(localStorage.getItem('is_superuser_blog'));
const homePage = () => {
router.push({ name: "home" });
};
watchEffect(() => {
const store = useStore();
store.commit(
"updateIsSuperUser",
JSON.parse(storage.getItem("is_superuser_blog"))
);
});
onMounted(() => {
authorization().then(
(data) => {[haslogin.value, username.value] = data}
);
});
return {
username,
haslogin,
isSuperUser,
homePage,
quit,
};
},
methods: {
refresh() {
this.username = localStorage.getItem("username_blog");

@ -0,0 +1,170 @@
<template>
<br /><br />
<hr />
<h3>发表评论</h3>
<!-- 评论多行文本输入控件 -->
<textarea
v-model="message"
:placeholder="placeholder"
name="comment"
id="comment-area"
cols="60"
rows="10"
></textarea>
<div>
<button @click="submit" class="submitBtn">发布</button>
</div>
<br />
<p>已有 {{ comments.length }} 条评论</p>
<hr />
<!-- 渲染所有评论内容 -->
<div v-for="comment in comments" :key="comment.id">
<div class="comments">
<div>
<span class="username">
{{ comment.author.username }}
</span>
<span class="created">
{{ formatted_time(comment.created) }}
</span>
<span v-if="comment.parent">
<span class="parent">
{{ comment.parent.author.username }}
</span>
</span>
说道
</div>
<div class="content">
{{ comment.content }}
</div>
<div>
<button class="commentBtn" @click="replyTo(comment)">
回复
</button>
</div>
</div>
<hr />
</div>
</template>
<script>
import $ from 'jquery';
import authorization from '@/utils/authorization'
import { ref, watchEffect } from 'vue';
export default {
name: "CommentView",
props: {
article: {
type: Object,
required: true,
}
},
setup(props) {
//
let comments = ref([]);
//
let message = ref('');
let placeholder = ref('说点啥吧...');
//
let parentId = ref(null);
//
const submit = () => {
authorization().then((response) => {
if (response[0]) {
$.ajax({
url: 'http://127.0.0.1:6789/api/comment/',
type: 'POST',
data: {
content: message.value,
article_id: props.article.id,
parent_id: parentId.value,
},
headers: {
authorization: "Bearer " + localStorage.getItem('access_blog')
},
success(resp) {
//
comments.value.unshift(resp);
message.value = '';
window.alert("留言成功");
}
})
}else {
window.alert("请登录后再评论!");
}
});
};
const replyTo = (comment) => {
console.log(comment);
parentId.value = comment.id;
placeholder.value = '对' + comment.author.username + '说';
};
const formatted_time = (iso_date_string) => {
const date = new Date(iso_date_string);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
};
watchEffect(() => {
comments.value = props.article !== null ? props.article.comments : [];
});
return {
comments,
message,
placeholder,
parentId,
submit,
replyTo,
formatted_time,
}
},
};
</script>
<style scoped>
button {
cursor: pointer;
border: none;
outline: none;
color: whitesmoke;
border-radius: 5px;
}
.submitBtn {
height: 35px;
background: steelblue;
width: 60px;
}
.commentBtn {
height: 25px;
background: lightslategray;
width: 40px;
margin-bottom: 40px;
}
.comments {
padding-top: 10px;
}
.username {
font-weight: bold;
color: darkorange;
}
.created {
font-weight: bold;
color: darkblue;
}
.parent {
font-weight: bold;
color: orangered;
}
.content {
font-size: large;
padding: 15px;
}
</style>

@ -5,6 +5,7 @@ import LoginView from '@/views/LoginView.vue'
import RegisterView from '@/views/RegisterView.vue'
import UserCenter from '@/views/UserCenterView.vue'
import ArticleCreate from '@/views/ArticleCreate.vue'
import ArticleEdit from '@/views/ArticleEdit.vue'
const routes = [
{
@ -36,7 +37,11 @@ const routes = [
path : "/article/create/",
name: "ArticleCreate",
component: ArticleCreate,
},
{
path: "/article/edit/:id",
name: "ArticleEdit",
component: ArticleEdit,
}
]

@ -2,14 +2,18 @@ import { createStore } from 'vuex'
export default createStore({
state: {
islogin: false // 用户是否登录,默认没有登录
islogin: false, // 用户是否登录,默认没有登录
isSuperUser: false // 默认为非管理员身份
},
getters: {
},
mutations: {
updateIsLogin(state,islogin) {
state.islogin = islogin;
},
},updateIsSuperUser(state, isSuperUser){
state.isSuperUser = isSuperUser;
}
},
actions: {
},

@ -8,7 +8,6 @@ const authorization = async () => {
let username = storage.getItem('username_blog');
// 过期时间
const expired_time = Number(storage.getItem("expired_time"));
console.log("expired_time ",new Date(expired_time).toLocaleString());
// 当前时间
const current = new Date().getTime();
// 刷新令牌
@ -16,7 +15,7 @@ const authorization = async () => {
// 未过期
if (expired_time > current) {
haslogin = true;
console.log('authorization success');
// console.log('authorization success');
}
// 初始tokeng过期,则由刷新令牌申请新的token
else if (refreshToken !== null) {
@ -29,7 +28,7 @@ const authorization = async () => {
refresh: refreshToken,
},
success(resp) {
const nextExpiredTime = new Date().getTime() + 60000;
const nextExpiredTime = new Date().getTime() + 24*3600*1000;
storage.setItem("access_blog", resp.access);
storage.removeItem("refresh_blog"); // 移除刷新令牌
console.log(
@ -43,13 +42,13 @@ const authorization = async () => {
}catch(err){
storage.clear();
haslogin = false;
console.log('authorization err');
// console.log('authorization err');
}
} else {
storage.clear(); // 清除所有有效信息
haslogin = false;
console.log('authorization exp');
// console.log('authorization exp');
}
console.log('authorization done');

@ -2,6 +2,17 @@
<BlogHeader />
<div id="article-create">
<h3>发表文章</h3>
<form id="image_form">
<div class="form-elem">
<span>文章标题图片:</span>
<input
v-on:change="onFileChange"
type="file"
id="file"
style="padding-bottom: 10px"
/>
</div>
</form>
<form>
<div class="form-elem">
<span>标题</span>
@ -32,12 +43,12 @@
</div>
<div class="form-elem">
<span>正文</span>
<div style="margin-bottom: 10px;">正文</div>
<textarea
v-model="body"
placeholder="输入正文"
rows="20"
cols="80"
cols="100"
></textarea>
</div>
@ -55,26 +66,26 @@ import BlogFooter from "@/components/BlogFooter.vue";
import authorization from "@/utils/authorization";
import $ from "jquery";
import { onMounted } from "@vue/runtime-core";
import { useRouter } from 'vue-router';
import { ref } from "vue";
export default {
name: "ArticleCreate",
components: { BlogHeader, BlogFooter },
data: function () {
return {
//
title: "",
//
body: "",
//
// categories: [],
//
selectedCategory: null,
//
tags: "",
};
},
setup() {
let categories = ref([]);
//
let title = ref("");
//
let body = ref("");
//
let categories = ref([]);
//
let selectedCategory = ref(null);
//
let tags = ref("");
// id
let avatarID = ref(null);
const router = useRouter();
onMounted(() => {
//
@ -86,17 +97,12 @@ export default {
},
});
});
return {
categories,
};
},
methods: {
//
// css vue 便
categoryStyle(category) {
//
const categoryStyle = (category) => {
if (
this.selectedCategory !== null &&
category.id === this.selectedCategory.id
selectedCategory.value !== null &&
category.id === selectedCategory.value.id
) {
return {
backgroundColor: "black",
@ -106,38 +112,61 @@ export default {
backgroundColor: "lightgrey",
color: "black",
};
},
//
chooseCategory(category) {
};
//
const chooseCategory = (category) => {
// selectedCategory
if (
this.selectedCategory !== null &&
this.selectedCategory.id === category.id
selectedCategory.value !== null &&
selectedCategory.value.id === category.id
) {
this.selectedCategory = null;
}
//
else {
this.selectedCategory = category;
selectedCategory.value = null;
} else {
selectedCategory.value = category;
}
},
};
const onFileChange = (e) => {
//
const file = e.target.files[0];
let formData = new FormData();
formData.append("content", file);
$.ajax({
url: "http://127.0.0.1:6789/api/avatar/",
type: "POST",
data: formData,
processData: false,
contentType: false,
headers: {
"Authorization":
"Bearer " + localStorage.getItem("access_blog"),
},
success(resp) {
avatarID.value = resp.id;
}
});
};
//
submit() {
const that = this;
const submit = () => {
//
authorization().then(function (response) {
authorization().then((response) => {
if (response[0]) {
//
let data = {
title: that.title,
body: that.body,
title: title.value,
body: body.value,
};
//
if (that.selectedCategory) {
data.category_id = that.selectedCategory.id;
if (selectedCategory.value) {
data.category_id = selectedCategory.value.id;
}
//
data.tags = that.tags
data.tags = tags.value
//
.split(/[,,]/)
//
@ -145,18 +174,22 @@ export default {
//
.filter((x) => x.charAt(0) !== "");
data.avatar_id = avatarID.value;
//
//
const token = localStorage.getItem("access_blog");
$.ajax({
url: "http://127.0.0.1:6789/api/article/",
type: "POST",
data: data,
data: JSON.stringify(data),
contentType: 'application/json',
dataType: 'json',
headers: {
Authorization: "Bearer " + token,
},
success(resp) {
that.$router.push({
router.push({
name: "detail",
params: { id: resp.id },
});
@ -166,7 +199,22 @@ export default {
window.alert("令牌过期,请重新登录。");
}
});
},
}
return {
categories,
title,
body,
avatarID,
tags,
selectedCategory,
categoryStyle,
chooseCategory,
onFileChange,
submit,
}
},
};
</script>
@ -185,6 +233,14 @@ form {
padding-right: 10px;
padding-bottom: 100px;
}
#image_form {
text-align: left;
padding-left: 100px;
padding-right: 10px;
padding-bottom: 0px;
}
.form-elem {
padding: 10px;
}
@ -203,6 +259,6 @@ button {
background-color: steelblue;
color: whitesmoke;
border-radius: 5px;
width: 60px;
width: 50px;
}
</style>

@ -1,95 +1,114 @@
<template>
<BlogHeader />
<div v-if="article !== null" class="grid-container">
<div>
<h1 id='title'>{{article.title}}</h1>
<p id="subtitle">
本文由{{article.author.username}} 发布于 {{formatted_time(article.created)}}
</p>
<div v-html='article.body_html' class="article-body">
</div>
</div>
<div v-if="article !== null" class="grid-container">
<div>
<h1 id="title">{{ article.title }}</h1>
<p id="subtitle">
本文由{{ article.author.username }} 发布于
{{ formatted_time(article.created) }}
<span v-if="isSuperUser">
<router-link
:to="{
name: 'ArticleEdit',
params: { id: article.id },
}"
>
更新与删除
</router-link>
</span>
</p>
<div v-html="article.body_html" class="article-body"></div>
</div>
<div>
<h3>目录</h3>
<div v-html="article.toc_html" class="toc"></div>
</div>
</div>
<CommentView :article="article" />
<div>
<h3>目录</h3>
<div v-html='article.toc_html' class="toc"></div>
</div>
</div>
<BlogFooter />
</template>
<script>
import BlogHeader from "@/components/BlogHeader.vue";
import BlogFooter from "@/components/BlogFooter.vue";
import $ from 'jquery';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import CommentView from "@/components/CommentView.vue";
import $ from "jquery";
import { ref } from "vue";
import { useRoute } from "vue-router";
export default {
name: "ArticleDetail",
components: {
BlogHeader,
BlogFooter,
CommentView,
},
setup() {
let article = ref(null);
const route = useRoute(); // :id
$.ajax({
url: "http://127.0.0.1:6789/api/article/" + route.params.id,
type: 'GET',
success(resp){
article.value=resp;
},
error(resp) {
console.log(resp)
}
});
let article = ref( );
const route = useRoute(); // :id
$.ajax({
url: "http://127.0.0.1:6789/api/article/" + route.params.id,
type: "GET",
success(resp) {
article.value = resp;
},
error(resp) {
console.log(resp);
},
});
const formatted_time = (iso_date_string) => {
const date = new Date(iso_date_string);
return date.toLocaleDateString();
}
const formatted_time = (iso_date_string) => {
const date = new Date(iso_date_string);
return date.toLocaleDateString();
};
return {
article,
formatted_time,
}
},
return {
article,
formatted_time,
};
},
computed: {
isSuperUser() {
return localStorage.getItem("is_superuser_blog") === "true";
},
},
};
</script>
<style scoped>
.grid-container {
display: grid;
grid-template-columns: 3fr 1fr;
display: grid;
grid-template-columns: 3fr 1fr;
}
#title {
text-align: center;
font-size: x-large;
text-align: center;
font-size: x-large;
}
#subtitle {
text-align: center;
color: gray;
font-size: small;
text-align: center;
color: gray;
font-size: small;
}
</style>
<style>
.article-body p img {
max-width: 100%;
border-radius: 50px;
box-shadow: gray 0 0 20px;
max-width: 100%;
border-radius: 50px;
box-shadow: gray 0 0 20px;
}
.toc ul {
list-style-type: disc;
padding-inline-start: 15px;
list-style-type: disc;
padding-inline-start: 15px;
}
.toc a {
color: gray;
color: gray;
}
</style>

@ -0,0 +1,275 @@
<template>
<BlogHeader />
<div id="article-create">
<h3>更新文章</h3>
<form>
<div class="form-elem">
<span>标题</span>
<input v-model="title" type="text" placeholder="输入标题" />
</div>
<div class="form-elem">
<span>分类</span>
<span v-for="category in categories" :key="category.id">
<!--样式也可以通过 :style 绑定-->
<button
class="category-btn"
:style="categoryStyle(category)"
@click.prevent="chooseCategory(category)"
>
{{ category.title }}
</button>
</span>
</div>
<div class="form-elem">
<span>标签</span>
<input
v-model="tags"
type="text"
placeholder="输入标签,用逗号分隔"
/>
</div>
<div class="form-elem">
<div style="margin-bottom: 10px">正文</div>
<textarea
v-model="body"
placeholder="输入正文"
rows="20"
cols="100"
></textarea>
</div>
<div class="form-elem">
<button v-on:click.prevent="submit">提交</button>
<button
v-on:click.prevent="deleteArticle"
style="background-color: darkred; margin-left: 30px"
>
删除
</button>
</div>
</form>
</div>
<BlogFooter />
</template>
<script>
import BlogHeader from "@/components/BlogHeader.vue";
import BlogFooter from "@/components/BlogFooter.vue";
import authorization from "@/utils/authorization";
import $ from "jquery";
import { onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";
export default {
name: "ArticleEdit",
components: { BlogHeader, BlogFooter },
setup() {
//
let title = ref("");
//
let body = ref("");
//
let selectedCategory = ref(null);
//
let tags = ref("");
// id
let articleID = ref(null);
//
let categories = ref([]);
const route = useRoute();
const router = useRouter();
//
onMounted(() => {
$.ajax({
url: "http://127.0.0.1:6789/api/category",
type: "GET",
success(resp) {
categories.value = resp;
},
});
$.ajax({
url:
"http://127.0.0.1:6789/api/article/" +
route.params.id +
"/",
type: "GET",
success(resp) {
const data = resp;
title.value = data.title;
body.value = data.body;
selectedCategory.value = data.category;
tags.value = data.tags.join(",");
articleID.value = data.id;
console.log(tags.value);
},
});
});
//
const categoryStyle = (category) => {
if (
selectedCategory.value !== null &&
category.id === selectedCategory.value.id
) {
return {
backgroundColor: "black",
};
}
return {
backgroundColor: "lightgrey",
color: "black",
};
};
//
const chooseCategory = (category) => {
// selectedCategory
if (
selectedCategory.value !== null &&
selectedCategory.value.id === category.id
) {
selectedCategory.value = null;
} else {
selectedCategory.value = category;
}
};
//
const submit = () => {
authorization().then((response) => {
if (response[0]) {
let data = {
title: title.value,
body: body.value,
};
data.category_id = selectedCategory.value
? selectedCategory.value.id
: null;
//
data.tags = tags.value
//
.split(/[,,]/)
//
.map((x) => x.trim())
//
.filter((x) => x.charAt(0) !== "");
const token = localStorage.getItem("access_blog");
$.ajax({
url:
"http://127.0.0.1:6789/api/article/" +
articleID.value +
"/",
type: "PUT",
dataType: "json", // json
contentType:"application/json",
data: JSON.stringify(data),
headers: {
Authorization: "Bearer " + token,
},
success(resp) {
router.push({
name: "detail",
params: { id: resp.id },
});
},
error() {
window.alert("令牌过期,请重新登录!");
},
});
} else {
window.alert("令牌过期,请重新登录!");
}
});
};
const deleteArticle = () => {
authorization().then((response) => {
const token = localStorage.getItem("access_blog");
if (response[0]) {
$.ajax({
url:
"http://127.0.0.1:6789/api/article/" +
articleID.value +
"/",
type: "DELETE",
headers: {
Authorization: "Bearer " + token,
},
success() {
router.push({
name: "home",
});
},
error() {
window.alert("令牌过期,请重新登录!");
},
});
} else {
alert("令牌过期,请重新登录。");
}
});
};
return {
categories,
title,
body,
tags,
selectedCategory,
categoryStyle,
articleID,
submit,
chooseCategory,
deleteArticle,
};
},
};
</script>
<style scoped>
.category-btn {
margin-right: 10px;
}
#article-create {
text-align: center;
font-size: large;
}
form {
text-align: left;
padding-left: 100px;
padding-right: 10px;
padding-bottom: 50px;
}
.form-elem {
padding: 10px;
}
.form-elem-1 {
display: inline-block;
padding: 10px;
}
input {
height: 25px;
padding-left: 10px;
width: 50%;
}
button {
height: 35px;
cursor: pointer;
border: none;
outline: none;
background: steelblue;
color: whitesmoke;
border-radius: 5px;
width: 60px;
}
</style>

@ -38,6 +38,7 @@ import BlogHeader from "@/components/BlogHeader.vue";
import BlogFooter from "@/components/BlogFooter.vue";
import $ from "jquery";
import router from "@/router/index";
import { useStore } from 'vuex';
export default {
name: "LoginView",
@ -46,9 +47,12 @@ export default {
BlogFooter,
},
setup() {
let username = ref("");
let password = ref("");
const store = useStore();
const login = () => {
$.ajax({
url: "http://127.0.0.1:6789/api/token/",
@ -61,17 +65,19 @@ export default {
// tokenlocalStorage
const storage = localStorage;
const current = (new Date()).getTime();
const expiredTime = current + 60000;
const expiredTime = current + 24*3600*1000;
storage.setItem('access_blog', resp.access);
storage.setItem('refresh_blog', resp.refresh);
storage.setItem('expired_time', expiredTime);
storage.setItem('username_blog', username.value);
//
$.ajax({
url: 'http://127.0.0.1:6789/api/user/' + username.value + '/',
type: 'GET',
success(resp){
storage.setItem('is_superuser_blog',resp.is_superuser);
//
storage.setItem("is_superuser_blog",resp.is_superuser);
store.commit("updateIsSuperUser",JSON.parse(storage.getItem("is_superuser_blog")));
console.log(store.state);
}
});

@ -71,7 +71,7 @@ export default {
methods: {
confirmDelete() {
const that = this;
authorization().then(function (response) {
authorization().then((response) => {
if (response[0]) {
//
that.token = storage.getItem("access_blog");
@ -92,7 +92,7 @@ export default {
},
changeInfo() {
const that = this;
authorization().then(function (response) {
authorization().then((response) => {
if (response[0] === false) {
window.alert("登录已过期, 请重新登录!");
storage.clear();

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.
Loading…
Cancel
Save