资料删除与组件通信,资料更新与异步

master
barney 2 years ago
parent 74808ae9fa
commit 932ed5ed5a
  1. BIN
      db.sqlite3
  2. 2
      frontend/src/components/ArticleList.vue
  3. 224
      frontend/src/components/BlogHeader.vue
  4. 96
      frontend/src/components/SearchButton.vue
  5. 1
      frontend/src/main.js
  6. 8
      frontend/src/router/index.js
  7. 59
      frontend/src/utils/authorization.js
  8. 4
      frontend/src/views/LoginView.vue
  9. 205
      frontend/src/views/UserCenterView.vue
  10. 1
      frontend/vue.config.js

Binary file not shown.

@ -182,7 +182,7 @@ export default {
#paginator {
text-align: center;
padding-bottom: 3rem;
padding-bottom: 4rem;
}
#paginator a {

@ -3,26 +3,39 @@
<div class="grid">
<div></div>
<h1>My Drf-Vue Blog</h1>
<div class="search">
<form>
<input
v-model="searchText"
type="text"
placeholder="输入搜索内容..."
/>
<button v-on:click.prevent="searchArticles"></button>
</form>
</div>
<hr />
<SearchButton />
</div>
<hr />
<div class="grid-1">
<div class="register">
<div v-if="haslogin">
<div v-if="haslogin"></div>
<div v-else>
<router-link to="/register" class="register-link"
>注册</router-link
>
</div>
<div v-else>
<router-link to="/register" class="register-link">注册</router-link>
</div>
</div>
<div class="login">
<div v-if="haslogin">欢迎, {{ username }}</div>
<div v-if="haslogin">
<div class="dropdown">
<button class="dropbtn">欢迎你, {{ username }}!</button>
<div class="dropdown-content">
<router-link
:to="{
name: 'UserCenter',
params: { username: username },
}"
>
用户中心
</router-link>
<router-link to="/" @click="quit"
>
退出
</router-link>
</div>
</div>
</div>
<div v-else>
<router-link to="/login" class="login-link"
>登录</router-link
@ -34,140 +47,109 @@
</template>
<script>
import { ref,onMounted } from "vue";
import router from "@/router/index";
import $ from "jquery";
import { ref } from "vue";
import SearchButton from "@/components/SearchButton.vue";
import authorization from "@/utils/authorization";
export default {
name: "BlogHeader",
components: {
SearchButton,
},
mounted() {
authorization().then((data) => ([this.haslogin, this.username] = data));
},
setup() {
let username = ref("");
let haslogin = ref(false);
let searchText = ref("");
onMounted(() => {
const storage = localStorage;
//
const expired_time = Number(storage.getItem("expired_time"));
//
const current = new Date().getTime();
//
const refreshToken = storage.getItem("refresh_blog");
//
username.value = storage.getItem("username_blog");
//
if (expired_time > current) {
haslogin.value = true;
}
// tokeng,token
else if (refreshToken !== null) {
$.ajax({
url: "http://127.0.0.1:6789/api/token/refresh/",
type: "POST",
data: {
refresh: refreshToken,
},
success(resp) {
const nextExpiredTime = new Date().getTime() + 60000;
storage.setItem("access_blog", resp.access);
storage.removeItem("refresh_blog"); //
console.log("令牌刷新时间: ",new Date().toLocaleString());
storage.setItem("expired_time", nextExpiredTime);
haslogin.value = true;
},
});
} else {
console.log("登录过期时间: ",new Date().toLocaleString());
storage.clear(); //
haslogin.value = false;
}
});
const searchArticles = () => {
const text = searchText.value.trim();
if (text.charAt(0) !== "") {
router.push({ name: "home", query: { search: text } });
}
const quit = () => {
const storage = localStorage;
window.alert("已退出登录!");
console.log("登出成功,过期时间: ", new Date().toLocaleString());
storage.clear(); //
haslogin.value = false;
};
return {
searchText,
searchArticles,
username,
haslogin,
quit,
};
},
},
methods: {
refresh() {
this.username = localStorage.getItem('username_blog');
}
}
};
</script>
<style scoped>
#header {
text-align: center;
margin-top: 20px;
}
.grid {
display: grid;
grid-template-columns: 1fr 4fr 1fr;
/* 样式来源: https://www.runoob.com/css/css-dropdowns.html* /
/* 下拉按钮样式 */
.dropbtn {
width: 10vw;
background-color: mediumslateblue;
color: white;
padding: 8px 8px 30px 8px;
font-size: 1.1vw;
border: none;
cursor: pointer;
height: 2vw;
border-radius: 5px;
text-overflow: ellipsis;
overflow: hidden;
}
.search {
padding-top: 22px;
/* 容器 <div> - 需要定位下拉内容 */
.dropdown {
position: relative;
display: inline-block;
}
/* 搜索框样式 */
* {
box-sizing: border-box;
/* 下拉内容 (默认隐藏) */
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
width: 10vw;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
text-align: center;
}
form {
position: relative;
width: 20vw;
margin: 0 auto;
/* 下拉菜单的链接 */
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
input,
button {
outline: none;
/* 鼠标移上去后修改下拉菜单链接颜色 */
.dropdown-content a:hover {
background-color: #f1f1f1;
}
input {
border-color: gray;
width: 100%;
height: 30px;
padding-left: 13px;
padding-right: 46px;
overflow: hidden;
text-overflow: ellipsis;
/* 在鼠标移上去后显示下拉菜单 */
.dropdown:hover .dropdown-content {
display: block;
}
button {
border: none;
height: 30px;
width: 30px;
cursor: pointer;
position: absolute;
/* 当下拉内容显示后修改下拉按钮的背景颜色 */
.dropdown:hover .dropbtn {
background-color: darkslateblue;
}
</style>
.search input {
border: 2px solid gray;
border-radius: 5px;
background: transparent;
top: 0;
right: 0;
<style scoped>
#header {
text-align: center;
margin-top: 20px;
}
.search button {
background: gray;
border-radius: 0 5px 5px 0;
width: 45px;
top: 0;
right: 0;
.grid {
display: grid;
grid-template-columns: 1fr 4fr 1fr;
}
.search button:before {
content: "搜索";
font-size: 13px;
color: white;
.grid-1 {
display: grid;
grid-template-columns: 8fr 2fr ;
}
.login-link {

@ -0,0 +1,96 @@
<template>
<div class="search">
<form>
<input
v-model="searchText"
type="text"
placeholder="输入搜索内容..."
/>
<button v-on:click.prevent="searchArticles"></button>
</form>
</div>
</template>
<script>
import { ref } from "vue";
import router from "@/router/index";
export default {
name: "SearchButton",
setup() {
let searchText = ref("");
const searchArticles = () => {
const text = searchText.value.trim();
if (text.charAt(0) !== "") {
router.push({ name: "home", query: { search: text } });
}
};
return {
searchText,
searchArticles,
};
},
};
</script>
<style scoped>
.search {
padding-top: 22px;
}
/* 搜索框样式 */
* {
box-sizing: border-box;
}
form {
position: relative;
width: 20vw;
margin: 0 auto;
}
input,
button {
outline: none;
}
input {
border-color: gray;
width: 100%;
height: 30px;
padding-left: 13px;
padding-right: 46px;
overflow: hidden;
text-overflow: ellipsis;
}
button {
border: none;
height: 30px;
width: 30px;
cursor: pointer;
position: absolute;
}
.search input {
border: 2px solid gray;
border-radius: 5px;
background: transparent;
top: 0;
right: 0;
}
.search button {
background: gray;
border-radius: 0 5px 5px 0;
width: 45px;
top: 0;
right: 0;
}
.search button:before {
content: "搜索";
font-size: 13px;
color: white;
}
</style>

@ -10,4 +10,5 @@ URLSearchParams.prototype.appendIfExists = function (key, value) {
};
createApp(App).use(store).use(router).mount('#app')

@ -3,6 +3,7 @@ import HomePage from '@/views/HomePageView.vue'
import ArticleDetail from '@/views/ArticleDetailView.vue'
import LoginView from '@/views/LoginView.vue'
import RegisterView from '@/views/RegisterView.vue'
import UserCenter from '@/views/UserCenterView.vue'
const routes = [
{
@ -24,7 +25,12 @@ const routes = [
path: '/register/',
name: 'register',
component: RegisterView,
}
},
{
path: "/user/:username",
name: "UserCenter",
component: UserCenter,
},
]
const router = createRouter({

@ -0,0 +1,59 @@
import $ from 'jquery';
const authorization = async () => {
const storage = localStorage;
let haslogin = false;
// 用户名
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();
// 刷新令牌
const refreshToken = storage.getItem("refresh_blog");
// 未过期
if (expired_time > current) {
haslogin = true;
console.log('authorization success');
}
// 初始tokeng过期,则由刷新令牌申请新的token
else if (refreshToken !== null) {
try {
$.ajax({
url: "http://127.0.0.1:6789/api/token/refresh/",
type: "POST",
async: false,
data: {
refresh: refreshToken,
},
success(resp) {
const nextExpiredTime = new Date().getTime() + 60000;
storage.setItem("access_blog", resp.access);
storage.removeItem("refresh_blog"); // 移除刷新令牌
console.log(
"令牌刷新时间: ",
new Date().toLocaleString()
);
storage.setItem("expired_time", nextExpiredTime);
haslogin = true;
},
});
}catch(err){
storage.clear();
haslogin = false;
console.log('authorization err');
}
} else {
storage.clear(); // 清除所有有效信息
haslogin = false;
console.log('authorization exp');
}
console.log('authorization done');
return [haslogin, username];
}
export default authorization;

@ -48,8 +48,6 @@ export default {
setup() {
let username = ref("");
let password = ref("");
const current = (new Date()).getTime();
console.log(current);
const login = () => {
$.ajax({
@ -62,7 +60,6 @@ export default {
success(resp) {
// tokenlocalStorage
const storage = localStorage;
const current = (new Date()).getTime();
const expiredTime = current + 60000;
storage.setItem('access_blog', resp.access);
@ -71,7 +68,6 @@ export default {
storage.setItem('username_blog', username.value);
console.log("登录时间: ",new Date().toLocaleString());
router.push({ name: "home" });
},
error(resp) {
console.log(resp);

@ -0,0 +1,205 @@
<template>
<BlogHeader ref="header" />
<div id="user-center">
<h3>更新资料信息</h3>
<form>
<div class="form-elem">
<span>用户名</span>
<input
v-model="username"
type="text"
placeholder="输入用户名"
/>
</div>
<div class="form-elem">
<span>新密码</span>
<input
v-model="password"
type="password"
placeholder="输入密码"
autocomplete="true"
/>
</div>
<div class="form-elem">
<button v-on:click.prevent="changeInfo">更新</button>
</div>
<div class="form-elem">
<button
v-on:click.prevent="showingDeleteAlert=true"
class="delete-btn"
>
删除用户
</button>
<div :class="{ shake: showingDeleteAlert }">
<button
v-if="showingDeleteAlert"
class="confirm-btn"
@click.prevent="confirmDelete"
>
确定
</button>
</div>
</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";
const storage = localStorage;
export default {
name: "UserCenter",
components: { BlogHeader, BlogFooter },
data: function () {
return {
username: "",
password: "",
token: "",
showingDeleteAlert: false,
};
},
mounted() {
this.username = storage.getItem("username_blog");
},
methods: {
confirmDelete() {
const that = this;
authorization().then(function (response) {
if (response[0]) {
//
that.token = storage.getItem("access_blog");
$.ajax({
url: 'http://127.0.0.1:6789/api/user/' + that.username + '/',
type: 'DELETE',
headers: {
Authorization: "Bearer " + that.token
},
success(resp) {
console.log(resp);
storage.clear();
that.$router.push({ name: "home" });
}
})
}
});
},
changeInfo() {
const that = this;
authorization().then(function (response) {
if (response[0] === false) {
window.alert("登录已过期, 请重新登录!");
storage.clear();
that.$router.push({ name: "login" });
return;
}
// 6
if (that.password.length >= 0 && that.password.length < 6) {
window.alert("密码太短了");
return;
}
//
const oldName = storage.getItem("username_blog");
//
let data = {};
if (that.username !== "") {
data.username = that.username;
}
data.password = that.password;
that.token = storage.getItem("access_blog");
//
$.ajax({
url: "http://127.0.0.1:6789/api/user/" + oldName + "/",
type: "PATCH",
data: data,
headers: {
Authorization: "Bearer " + that.token,
},
success(resp) {
window.alert("修改成功");
const name = resp.username;
storage.setItem("username_blog", name);
//
that.$router.push({
name: "UserCenter",
params: { username: name },
});
that.$refs.header.refresh();
},
});
});
},
},
};
</script>
<style scoped>
#user-center {
text-align: center;
}
.form-elem {
padding: 10px;
}
input {
height: 25px;
padding-left: 10px;
}
button {
height: 35px;
cursor: pointer;
border: none;
outline: none;
background: gray;
color: whitesmoke;
border-radius: 5px;
width: 250px;
}
.confirm-btn {
width: 80px;
background-color: darkorange;
}
.delete-btn {
background-color: darkred;
margin-bottom: 10px;
}
.shake {
animation: shake 0.82s cubic-bezier(0.36, 0.07, 0.19, 0.97) both;
transform: translate3d(0, 0, 0);
backface-visibility: hidden;
perspective: 1000px;
}
@keyframes shake {
10%,
90% {
transform: translate3d(-1px, 0, 0);
}
20%,
80% {
transform: translate3d(2px, 0, 0);
}
30%,
50%,
70% {
transform: translate3d(-4px, 0, 0);
}
40%,
60% {
transform: translate3d(4px, 0, 0);
}
}
</style>

@ -2,3 +2,4 @@ const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
transpileDependencies: true
})

Loading…
Cancel
Save