commit
9634cc0a91
27 changed files with 20621 additions and 0 deletions
@ -0,0 +1,25 @@ |
|||||||
|
.DS_Store |
||||||
|
node_modules |
||||||
|
/dist |
||||||
|
|
||||||
|
|
||||||
|
# local env files |
||||||
|
.env.local |
||||||
|
.env.*.local |
||||||
|
|
||||||
|
# Log files |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.idea |
||||||
|
.vscode |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
||||||
|
|
||||||
|
.vercel |
@ -0,0 +1,24 @@ |
|||||||
|
# myspace |
||||||
|
|
||||||
|
## Project setup |
||||||
|
``` |
||||||
|
npm install |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and hot-reloads for development |
||||||
|
``` |
||||||
|
npm run serve |
||||||
|
``` |
||||||
|
|
||||||
|
### Compiles and minifies for production |
||||||
|
``` |
||||||
|
npm run build |
||||||
|
``` |
||||||
|
|
||||||
|
### Lints and fixes files |
||||||
|
``` |
||||||
|
npm run lint |
||||||
|
``` |
||||||
|
|
||||||
|
### Customize configuration |
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/). |
@ -0,0 +1,5 @@ |
|||||||
|
module.exports = { |
||||||
|
presets: [ |
||||||
|
'@vue/cli-plugin-babel/preset' |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "es5", |
||||||
|
"module": "esnext", |
||||||
|
"baseUrl": "./", |
||||||
|
"moduleResolution": "node", |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"src/*" |
||||||
|
] |
||||||
|
}, |
||||||
|
"lib": [ |
||||||
|
"esnext", |
||||||
|
"dom", |
||||||
|
"dom.iterable", |
||||||
|
"scripthost" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@ |
|||||||
|
{ |
||||||
|
"name": "myspace", |
||||||
|
"version": "0.1.0", |
||||||
|
"private": true, |
||||||
|
"scripts": { |
||||||
|
"serve": "vue-cli-service serve", |
||||||
|
"build": "vue-cli-service build", |
||||||
|
"lint": "vue-cli-service lint" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"@popperjs/core": "^2.11.5", |
||||||
|
"bootstrap": "^5.2.0", |
||||||
|
"core-js": "^3.8.3", |
||||||
|
"jquery": "^3.6.0", |
||||||
|
"jwt-decode": "^3.1.2", |
||||||
|
"vue": "^3.2.13", |
||||||
|
"vue-router": "^4.0.3", |
||||||
|
"vuex": "^4.0.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@babel/core": "^7.12.16", |
||||||
|
"@babel/eslint-parser": "^7.12.16", |
||||||
|
"@vue/cli-plugin-babel": "~5.0.0", |
||||||
|
"@vue/cli-plugin-eslint": "~5.0.0", |
||||||
|
"@vue/cli-plugin-router": "^5.0.8", |
||||||
|
"@vue/cli-plugin-vuex": "~5.0.0", |
||||||
|
"@vue/cli-service": "~5.0.0", |
||||||
|
"eslint": "^7.32.0", |
||||||
|
"eslint-plugin-vue": "^8.0.3" |
||||||
|
}, |
||||||
|
"eslintConfig": { |
||||||
|
"root": true, |
||||||
|
"env": { |
||||||
|
"node": true |
||||||
|
}, |
||||||
|
"extends": [ |
||||||
|
"plugin:vue/vue3-essential", |
||||||
|
"eslint:recommended" |
||||||
|
], |
||||||
|
"parserOptions": { |
||||||
|
"parser": "@babel/eslint-parser" |
||||||
|
}, |
||||||
|
"rules": {} |
||||||
|
}, |
||||||
|
"browserslist": [ |
||||||
|
"> 1%", |
||||||
|
"last 2 versions", |
||||||
|
"not dead", |
||||||
|
"not ie 11" |
||||||
|
] |
||||||
|
} |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,17 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang=""> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"> |
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0"> |
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<noscript> |
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> |
||||||
|
</noscript> |
||||||
|
<div id="app"></div> |
||||||
|
<!-- built files will be auto injected --> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,20 @@ |
|||||||
|
<template> |
||||||
|
<NavBar/> |
||||||
|
<router-view :key="$route.fullPath"/> |
||||||
|
</template> |
||||||
|
|
||||||
|
|
||||||
|
<script> |
||||||
|
import 'bootstrap/dist/css/bootstrap.css'; |
||||||
|
import 'bootstrap/dist/js/bootstrap'; |
||||||
|
import NavBar from "@/components/NavBar.vue" |
||||||
|
export default { |
||||||
|
name:'app', |
||||||
|
components:{ |
||||||
|
NavBar, |
||||||
|
} |
||||||
|
} |
||||||
|
</Script> |
||||||
|
|
||||||
|
<style> |
||||||
|
</style> |
After Width: | Height: | Size: 6.7 KiB |
@ -0,0 +1,22 @@ |
|||||||
|
<template> |
||||||
|
<div class="container"> |
||||||
|
<div class="card"> |
||||||
|
<div class="card-body"> |
||||||
|
<slot> |
||||||
|
</slot> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name:"ContentBase", |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.container { |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,58 @@ |
|||||||
|
<template> |
||||||
|
<div class="hello"> |
||||||
|
<h1>{{ msg }}</h1> |
||||||
|
<p> |
||||||
|
For a guide and recipes on how to configure / customize this project,<br> |
||||||
|
check out the |
||||||
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>. |
||||||
|
</p> |
||||||
|
<h3>Installed CLI Plugins</h3> |
||||||
|
<ul> |
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li> |
||||||
|
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li> |
||||||
|
</ul> |
||||||
|
<h3>Essential Links</h3> |
||||||
|
<ul> |
||||||
|
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li> |
||||||
|
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li> |
||||||
|
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li> |
||||||
|
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li> |
||||||
|
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li> |
||||||
|
</ul> |
||||||
|
<h3>Ecosystem</h3> |
||||||
|
<ul> |
||||||
|
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li> |
||||||
|
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li> |
||||||
|
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li> |
||||||
|
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li> |
||||||
|
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
name: 'HelloWorld', |
||||||
|
props: { |
||||||
|
msg: String |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only --> |
||||||
|
<style scoped> |
||||||
|
h3 { |
||||||
|
margin: 40px 0 0; |
||||||
|
} |
||||||
|
ul { |
||||||
|
list-style-type: none; |
||||||
|
padding: 0; |
||||||
|
} |
||||||
|
li { |
||||||
|
display: inline-block; |
||||||
|
margin: 0 10px; |
||||||
|
} |
||||||
|
a { |
||||||
|
color: #42b983; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,81 @@ |
|||||||
|
<template> |
||||||
|
<nav class="navbar navbar-expand-lg navbar-light bg-light"> |
||||||
|
<div class="container"> |
||||||
|
<route-link class="navbar-brand" :to="{ name: 'home' }" |
||||||
|
>MySpace</route-link |
||||||
|
> |
||||||
|
<button |
||||||
|
class="navbar-toggler" |
||||||
|
type="button" |
||||||
|
data-bs-toggle="collapse" |
||||||
|
data-bs-target="#navbarText" |
||||||
|
aria-controls="navbarText" |
||||||
|
aria-expanded="false" |
||||||
|
aria-label="Toggle navigation" |
||||||
|
> |
||||||
|
<span class="navbar-toggler-icon"></span> |
||||||
|
</button> |
||||||
|
<div class="collapse navbar-collapse" id="navbarText"> |
||||||
|
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> |
||||||
|
<li class="nav-item"> |
||||||
|
<router-link class="nav-link" :to="{ name: 'home' }" |
||||||
|
>首页</router-link |
||||||
|
> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<router-link class="nav-link" :to="{ name: 'userlist' }" |
||||||
|
>好友列表</router-link |
||||||
|
> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
|
||||||
|
<ul class="navbar-nav" v-if="!$store.state.user.is_login"> |
||||||
|
<li class="nav-item"> |
||||||
|
<router-link class="nav-link" :to="{ name: 'login' }" |
||||||
|
>登陆</router-link |
||||||
|
> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<router-link class="nav-link" :to="{ name: 'register' }" |
||||||
|
>注册</router-link |
||||||
|
> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
<ul class="navbar-nav" v-else> |
||||||
|
<li class="nav-item"> |
||||||
|
<router-link |
||||||
|
class="nav-link" |
||||||
|
:to="{name:'userprofile',params: {userId: $store.state.user.id},}" |
||||||
|
>{{ $store.state.user.username }}</router-link |
||||||
|
> |
||||||
|
</li> |
||||||
|
<li class="nav-item"> |
||||||
|
<a class="nav-link" style="cursor: pointer" @click="logout">退出</a> |
||||||
|
</li> |
||||||
|
</ul> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</nav> |
||||||
|
</template> |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script> |
||||||
|
import { useStore } from "vuex"; |
||||||
|
export default { |
||||||
|
name: "NavBar", |
||||||
|
setup(){ |
||||||
|
const store = useStore(); |
||||||
|
const logout = () => { |
||||||
|
store.commit("logout"); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
logout, |
||||||
|
} |
||||||
|
} |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
</style> |
@ -0,0 +1,113 @@ |
|||||||
|
<template> |
||||||
|
<div class="card"> |
||||||
|
<div class="card-body"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-3 img-field"> |
||||||
|
<img class="img-fluid" :src="userInfo.photo" alt=""> |
||||||
|
</div> |
||||||
|
<div class="col-9"> |
||||||
|
<div class="username">{{ userInfo.username }}</div> |
||||||
|
<div class="fans">粉丝数: {{ userInfo.followerCount }}</div> |
||||||
|
<button @click="follow" v-if="!userInfo.is_followed&&!is_me" type="button" |
||||||
|
class="btn btn-success btn-sm">+关注</button> |
||||||
|
<button @click="unfollow" v-if="userInfo.is_followed&&!is_me" type="button" |
||||||
|
class="btn btn-danger btn-sm">取消关注</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
|
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import {useStore} from "vuex"; |
||||||
|
import {computed} from "vue"; |
||||||
|
import $ from "jquery"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "UserProfileInfo", |
||||||
|
props: { |
||||||
|
userInfo: { |
||||||
|
type: Object, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
setup(props, context) { |
||||||
|
const store = useStore(); |
||||||
|
const follow = () => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/follow/", |
||||||
|
type: "POST", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
target_id: props.userInfo.id, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
if(resp.result === "success") |
||||||
|
// 在前端进行修改 |
||||||
|
context.emit("follow123"); // 触发父组件中的follow123事件 |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
const unfollow = () => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/follow/", |
||||||
|
type: "POST", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
target_id: props.userInfo.id, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
if(resp.result === "success") |
||||||
|
// 在前端进行修改 |
||||||
|
context.emit("unfollow1234"); // 触发父组件中的follow1234事件 |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
// 如果是自己的页面,不展示关注和取消关注按钮 |
||||||
|
let is_me = computed(()=> store.state.user.id === props.userInfo.id); |
||||||
|
|
||||||
|
return { |
||||||
|
follow, |
||||||
|
unfollow, |
||||||
|
is_me, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
}, |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
img { |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
|
||||||
|
.username { |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.fans { |
||||||
|
color: gray; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
padding: 2px 4px; |
||||||
|
font-size: 12px; |
||||||
|
} |
||||||
|
|
||||||
|
.img-field { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
|
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,71 @@ |
|||||||
|
<template> |
||||||
|
<div class="card"> |
||||||
|
<div class="card-body"> |
||||||
|
<div v-for="post in posts.posts" :key = "post.id"> |
||||||
|
<div class="card singlePost"> |
||||||
|
<div class="card-body"> |
||||||
|
{{post.content}} |
||||||
|
<button @click="delete_a_post(post.id)" v-if="is_me" type="button" class="btn btn-danger btn-sm">删除该贴</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import {computed} from "vue"; |
||||||
|
import {useStore} from "vuex"; |
||||||
|
import $ from "jquery"; |
||||||
|
|
||||||
|
|
||||||
|
export default { |
||||||
|
name: "UserProfilePost", |
||||||
|
props: { |
||||||
|
// 父组件发送过来两个属性:posts和user |
||||||
|
posts:{ |
||||||
|
type : Object, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
user: { |
||||||
|
type : Object, |
||||||
|
required: true, |
||||||
|
} |
||||||
|
}, |
||||||
|
setup(props,context) { |
||||||
|
const store = useStore(); |
||||||
|
// 如果不是自己的页面,不展示删除帖子按钮 |
||||||
|
let is_me = computed(() => store.state.user.id === props.user.id); |
||||||
|
const delete_a_post = post_id => $.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/post/", |
||||||
|
type: "DELETE", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data:{ |
||||||
|
post_id: post_id, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
if (resp.result === "success"){ |
||||||
|
context.emit("delete_a_post",post_id); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
return { |
||||||
|
is_me, |
||||||
|
delete_a_post, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.singlePost{ |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
button { |
||||||
|
float: right; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,68 @@ |
|||||||
|
<template> |
||||||
|
<div class="card edit-field"> |
||||||
|
<div class="card-body"> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="edit-input" class="form-label">编辑区</label> |
||||||
|
<textarea v-model="content" class="form-control" id="edit-input" rows="3"></textarea> |
||||||
|
<!--点击button会触发子组件send_msg函数--> |
||||||
|
<button @click="send_msg" type="button" class="btn btn-primary btn-sm">发送</button> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
|
||||||
|
<script> |
||||||
|
|
||||||
|
import { ref } from "vue"; |
||||||
|
import $ from "jquery"; |
||||||
|
import {useStore} from "vuex"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "UserProfileWrite", |
||||||
|
setup(props, context) { |
||||||
|
let content = ref(''); |
||||||
|
const store = useStore(); |
||||||
|
const send_msg = () => { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/post/", |
||||||
|
type: "POST", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
content: content.value, |
||||||
|
}, |
||||||
|
success(resp){ |
||||||
|
// 帖子成功提交数据库,前端页面进行刷新 |
||||||
|
if (resp.result === "success"){ |
||||||
|
// 会触发父组件的send_msg事件(发帖) |
||||||
|
context.emit("send_msg", content.value); |
||||||
|
content.value = ""; |
||||||
|
} |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
content, |
||||||
|
send_msg, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
</script> |
||||||
|
|
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.edit-field { |
||||||
|
margin-top: 20px; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
margin-top: 10px; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,6 @@ |
|||||||
|
import { createApp } from 'vue' |
||||||
|
import App from './App.vue' |
||||||
|
import router from './router' |
||||||
|
import store from './store' |
||||||
|
|
||||||
|
createApp(App).use(store).use(store).use(router).mount('#app') |
@ -0,0 +1,48 @@ |
|||||||
|
import { createRouter, createWebHistory } from 'vue-router' |
||||||
|
import HomeView from '@/views/HomeView.vue' |
||||||
|
import UserList from '@/views/UserList.vue' |
||||||
|
import LoginView from "@/views/LoginView.vue" |
||||||
|
import RegisterView from "@/views/RegisterView.vue" |
||||||
|
import NotFound from "@/views/NotFoundView.vue" |
||||||
|
import UserProfile from '@/views/UserProfile.vue' |
||||||
|
|
||||||
|
const routes = [ |
||||||
|
{ |
||||||
|
path: '/', |
||||||
|
name: 'home', |
||||||
|
component: HomeView |
||||||
|
}, |
||||||
|
{ |
||||||
|
path: '/userlist/', |
||||||
|
name: 'userlist', |
||||||
|
component: UserList |
||||||
|
}, |
||||||
|
{ |
||||||
|
path: '/login/', |
||||||
|
name: 'login', |
||||||
|
component: LoginView |
||||||
|
}, { |
||||||
|
path: '/register/', |
||||||
|
name: 'register', |
||||||
|
component: RegisterView |
||||||
|
}, { |
||||||
|
path: '/userprofile/:userId/', |
||||||
|
name: 'userprofile', |
||||||
|
component: UserProfile |
||||||
|
}, { |
||||||
|
path: '/404/', |
||||||
|
name: 'error', |
||||||
|
component: NotFound |
||||||
|
},{ |
||||||
|
path: '/:catchAll(.*)', |
||||||
|
name: 'other', |
||||||
|
redirect: '/404/', |
||||||
|
} |
||||||
|
] |
||||||
|
|
||||||
|
const router = createRouter({ |
||||||
|
history: createWebHistory(), |
||||||
|
routes |
||||||
|
}) |
||||||
|
|
||||||
|
export default router |
@ -0,0 +1,16 @@ |
|||||||
|
import { createStore } from 'vuex' |
||||||
|
import moduleUser from './user'; |
||||||
|
|
||||||
|
export default createStore({ |
||||||
|
state: { |
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
}, |
||||||
|
actions: { |
||||||
|
}, |
||||||
|
modules: { |
||||||
|
user: moduleUser, |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,100 @@ |
|||||||
|
import $ from 'jquery'; |
||||||
|
import jwt_decode from 'jwt-decode'; |
||||||
|
const moduleUser = { |
||||||
|
state: { |
||||||
|
id: "", |
||||||
|
username: "", |
||||||
|
photo: "", |
||||||
|
folllowerCount: 0, |
||||||
|
access: "", |
||||||
|
refresh: "", |
||||||
|
is_login: false, |
||||||
|
}, |
||||||
|
getters: { |
||||||
|
}, |
||||||
|
mutations: { |
||||||
|
updateUser(state,user) { |
||||||
|
state.id = user.id; |
||||||
|
state.username = user.username; |
||||||
|
state.photo = user.photo; |
||||||
|
state.folllowerCount = user.folllowerCount; |
||||||
|
state.access = user.access; |
||||||
|
state.refresh = user.refresh; |
||||||
|
state.is_login = user.is_login; |
||||||
|
}, |
||||||
|
updateAccess(state,access) { |
||||||
|
state.access = access; |
||||||
|
}, |
||||||
|
|
||||||
|
logout(state) { |
||||||
|
state.id = "", |
||||||
|
state.username = ""; |
||||||
|
state.photo = ""; |
||||||
|
state.folllowerCount = 0; |
||||||
|
state.access = ""; |
||||||
|
state.refresh = ""; |
||||||
|
state.is_login = false; |
||||||
|
} |
||||||
|
}, |
||||||
|
actions: { |
||||||
|
login(context,data) { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/api/token/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
username: data.username, |
||||||
|
password: data.password, |
||||||
|
}, |
||||||
|
|
||||||
|
success(resp) { |
||||||
|
const {access,refresh} = resp; |
||||||
|
const access_obj = jwt_decode(access); |
||||||
|
|
||||||
|
// 每隔4.5分钟刷新依次令牌(access)
|
||||||
|
|
||||||
|
setInterval(()=> { |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/api/token/refresh/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
refresh:refresh, |
||||||
|
}, |
||||||
|
|
||||||
|
success(resp) { |
||||||
|
context.commit("updateAccess",resp.access);
|
||||||
|
} |
||||||
|
}) |
||||||
|
},4.5 * 60 * 1000); |
||||||
|
|
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/", |
||||||
|
type: "get", |
||||||
|
data: { |
||||||
|
user_id: access_obj.user_id, |
||||||
|
}, |
||||||
|
headers:{ |
||||||
|
'Authorization': "Bearer " + access, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
context.commit("updateUser",{ |
||||||
|
...resp, |
||||||
|
access:access, |
||||||
|
refresh:refresh, |
||||||
|
is_login:true, |
||||||
|
}); |
||||||
|
data.success(); |
||||||
|
}, |
||||||
|
}); |
||||||
|
}, |
||||||
|
error() { |
||||||
|
data.error(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}, |
||||||
|
modules: { |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
export default moduleUser; |
@ -0,0 +1,18 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase> |
||||||
|
首页 |
||||||
|
</ContentBase> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ContentBase from '@/components/ContentBase.vue' |
||||||
|
export default { |
||||||
|
name: "HomeView", |
||||||
|
components: { |
||||||
|
ContentBase, |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
</style> |
@ -0,0 +1,85 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase> |
||||||
|
<div class="row justify-content-md-center"> |
||||||
|
<div class="col-3"> |
||||||
|
<form @submit.prevent="login"> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="username" class="form-label">用户名</label> |
||||||
|
<input |
||||||
|
v-model="username" |
||||||
|
type="text" |
||||||
|
class="form-control" |
||||||
|
id="username" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="password" class="form-label">密码</label> |
||||||
|
<input |
||||||
|
v-model="password" |
||||||
|
type="password" |
||||||
|
class="form-control" |
||||||
|
id="password" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="error-msg">{{error_msg}}</div> |
||||||
|
<button type="submit" class="btn btn-primary">登录</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</ContentBase> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ContentBase from "@/components/ContentBase.vue"; |
||||||
|
import { ref } from "vue"; |
||||||
|
import {useStore} from "vuex"; |
||||||
|
import router from "@/router/index"; |
||||||
|
export default { |
||||||
|
name: "LoginView", |
||||||
|
components: { |
||||||
|
ContentBase, |
||||||
|
}, |
||||||
|
|
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let username = ref(""); |
||||||
|
let password = ref(""); |
||||||
|
let error_msg = ref(""); |
||||||
|
const login = ()=> { |
||||||
|
// 登陆前先清空error_msg |
||||||
|
error_msg.value = ""; |
||||||
|
// 调用moduleUser组件中的函数,第一个参数是函数名,第二个参数是一些传入的数据 |
||||||
|
store.dispatch("login",{ |
||||||
|
username: username.value, |
||||||
|
password: password.value, |
||||||
|
success() { // 登陆成功的回调函数 |
||||||
|
router.push({name: "userlist"}); |
||||||
|
}, |
||||||
|
error () { // 登陆失败的回调函数 |
||||||
|
error_msg.value = "用户名不存在或者密码错误!"; |
||||||
|
} |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
username, |
||||||
|
password, |
||||||
|
error_msg, |
||||||
|
login, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
button { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
.error-msg { |
||||||
|
font-weight: bold; |
||||||
|
color: red; |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
|
@ -0,0 +1,20 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase>404! Not Found!</ContentBase> |
||||||
|
|
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ContentBase from "@/components/ContentBase.vue"; |
||||||
|
export default { |
||||||
|
name:"NotFound", |
||||||
|
components:{ |
||||||
|
ContentBase, |
||||||
|
} |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
|
||||||
|
</style> |
||||||
|
|
||||||
|
|
@ -0,0 +1,117 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase> |
||||||
|
<div class="row justify-content-md-center"> |
||||||
|
<div class="col-3"> |
||||||
|
<form @submit.prevent="register"> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="username" class="form-label">用户名</label> |
||||||
|
<input |
||||||
|
v-model="username" |
||||||
|
type="text" |
||||||
|
class="form-control" |
||||||
|
id="username" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="mb-3"> |
||||||
|
<label for="password" class="form-label">密码</label> |
||||||
|
<input |
||||||
|
v-model="password" |
||||||
|
type="password" |
||||||
|
class="form-control" |
||||||
|
id="password" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="mb-3"> |
||||||
|
<label for="password_confirm" class="form-label">确认密码</label> |
||||||
|
<input |
||||||
|
v-model="password_confirm" |
||||||
|
type="password" |
||||||
|
class="form-control" |
||||||
|
id="password" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
<div class="error-msg">{{error_msg}}</div> |
||||||
|
<button type="submit" class="btn btn-primary">注册</button> |
||||||
|
</form> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</ContentBase> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ContentBase from "@/components/ContentBase.vue"; |
||||||
|
import { ref } from "vue"; |
||||||
|
import {useStore} from "vuex"; |
||||||
|
import router from "@/router/index"; |
||||||
|
import $ from "jquery"; |
||||||
|
export default { |
||||||
|
name: "RegisterView", |
||||||
|
components: { |
||||||
|
ContentBase, |
||||||
|
}, |
||||||
|
|
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let username = ref(""); |
||||||
|
let password = ref(""); |
||||||
|
let password_confirm = ref(""); |
||||||
|
let error_msg = ref(""); |
||||||
|
const register = ()=> { |
||||||
|
// 注册前先清空error_msg |
||||||
|
error_msg.value = ""; |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/user/", |
||||||
|
type: "POST", |
||||||
|
data: { |
||||||
|
username: username.value, |
||||||
|
password: password.value, |
||||||
|
password_confirm: password_confirm.value, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
// 注册成功 |
||||||
|
if(resp.result == "success"){ |
||||||
|
// 进入登录界面 |
||||||
|
store.dispatch("login",{ |
||||||
|
username: username.value, |
||||||
|
password: password.value, |
||||||
|
success() { |
||||||
|
router.push({name: "userlist"}); |
||||||
|
}, |
||||||
|
error () { |
||||||
|
error_msg.value = "系统异常,请稍后重试!"; |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
else { |
||||||
|
// 出现错误 |
||||||
|
error_msg.value = resp.result; |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
username, |
||||||
|
password, |
||||||
|
error_msg, |
||||||
|
password_confirm, |
||||||
|
register, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
button { |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
.error-msg { |
||||||
|
font-weight: bold; |
||||||
|
color: red; |
||||||
|
margin-bottom: 20px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
|
@ -0,0 +1,99 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase> |
||||||
|
<div class="card" v-for="user in users" :key="user.id" @click="open_user_profile(user.id)"> |
||||||
|
<div class="card-body"> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-1 img-field"> |
||||||
|
<!--将冒号后的字符串视为变量,而不是一个字符串--> |
||||||
|
<img class="img-fluid" :src="user.photo" alt="" /> |
||||||
|
</div> |
||||||
|
<div class="col-11"> |
||||||
|
<div class="username">{{ user.username }}</div> |
||||||
|
<div class="followcount">粉丝数: {{ user.followerCount }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</ContentBase> |
||||||
|
</template> |
||||||
|
|
||||||
|
<script> |
||||||
|
import ContentBase from "@/components/ContentBase.vue"; |
||||||
|
import $ from "jquery"; |
||||||
|
import { ref } from "vue"; |
||||||
|
import router from "@/router/index"; |
||||||
|
import { useStore } from "vuex"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "UserList", |
||||||
|
components: { |
||||||
|
ContentBase, |
||||||
|
}, |
||||||
|
|
||||||
|
setup() { |
||||||
|
const store = useStore(); |
||||||
|
let users = ref([]); |
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/userlist/", |
||||||
|
type: "get", |
||||||
|
success(resp) { |
||||||
|
users.value = resp; |
||||||
|
}, |
||||||
|
}); |
||||||
|
const open_user_profile = (userId) => { |
||||||
|
// 如果登录成功,跳转到用户动态 |
||||||
|
if (store.state.user.is_login) { |
||||||
|
router.push({ |
||||||
|
name: "userprofile", |
||||||
|
params: { |
||||||
|
userId: userId, |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
|
else { // 没有登录,跳转到登陆页面 |
||||||
|
router.push({name: "login"}); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
users, |
||||||
|
open_user_profile, |
||||||
|
}; |
||||||
|
}, |
||||||
|
}; |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
img { |
||||||
|
border-radius: 50%; |
||||||
|
} |
||||||
|
|
||||||
|
.username { |
||||||
|
font-weight: bold; |
||||||
|
height: 40%; |
||||||
|
} |
||||||
|
|
||||||
|
.followcount { |
||||||
|
font-size: 12px; |
||||||
|
color: gray; |
||||||
|
height: 60%; /*上下46开*/ |
||||||
|
} |
||||||
|
|
||||||
|
.card { |
||||||
|
margin-bottom: 20px; |
||||||
|
cursor: pointer; /*选中卡片变成鼠标变成小手形状*/ |
||||||
|
} |
||||||
|
|
||||||
|
.card:hover { |
||||||
|
box-shadow: 2px 2px 10px lightblue; |
||||||
|
transition: 500ms; |
||||||
|
} |
||||||
|
.img-field { |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
justify-content: center; |
||||||
|
|
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
|
@ -0,0 +1,146 @@ |
|||||||
|
<template> |
||||||
|
<ContentBase> |
||||||
|
<div class="row"> |
||||||
|
<div class="col-3"> |
||||||
|
<!--将user信息传递给子组件的userInfo--> |
||||||
|
<!--触发了follow123事件会调用follow函数--> |
||||||
|
<!--触发了follow1234事件会调用unfollow函数--> |
||||||
|
<UserProfileInfo @follow123="follow" @unfollow1234="unfollow" :userInfo="user" /> |
||||||
|
<!--父组件的send_msg事件会调用父组件send_msg函数(@某事件="某个函数",事件与函数绑定起来了)--> |
||||||
|
<UserProfileWrite v-if="is_me" @send_msg="send_msg" /> |
||||||
|
</div> |
||||||
|
<div class="col-9"> |
||||||
|
<div class="alert alert-primary" role="alert"> |
||||||
|
<div class="all-post"> 我的帖子 </div> |
||||||
|
</div> |
||||||
|
<UserProfilePost :user="user" :posts="posts" @delete_a_post="delete_a_post" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</ContentBase> |
||||||
|
|
||||||
|
</template> |
||||||
|
<!--父组件的信息发送给子组件,利用prop实现--> |
||||||
|
<!--子组件想更新父组件的内容,通过emit实现--> |
||||||
|
<script> |
||||||
|
import ContentBase from "@/components/ContentBase.vue"; |
||||||
|
import UserProfileInfo from "@/components/UserProfileInfo.vue"; |
||||||
|
import UserProfilePost from "@/components/UserProfilePost.vue"; |
||||||
|
import UserProfileWrite from "@/components/UserProfileWrite.vue"; |
||||||
|
import { reactive } from "vue"; |
||||||
|
import { useRoute } from "vue-router"; |
||||||
|
import $ from "jquery"; |
||||||
|
import {useStore} from "vuex"; |
||||||
|
import {computed} from "vue"; |
||||||
|
|
||||||
|
export default { |
||||||
|
name: "UserProfile", |
||||||
|
components: { |
||||||
|
ContentBase, |
||||||
|
UserProfileInfo, |
||||||
|
UserProfilePost, |
||||||
|
UserProfileWrite, |
||||||
|
}, |
||||||
|
|
||||||
|
setup() { |
||||||
|
const route = useRoute(); |
||||||
|
const store = useStore(); |
||||||
|
// 提取路径中的用户id |
||||||
|
const userId = parseInt(route.params.userId); |
||||||
|
// 下面的数据从云端拉取 |
||||||
|
const user = reactive({}); |
||||||
|
const posts = reactive({}); |
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/getinfo/", |
||||||
|
type: "GET", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
user_id : userId, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
user.id = resp.id; |
||||||
|
user.username = resp.username; |
||||||
|
user.photo = resp.photo; |
||||||
|
user.followerCount = resp.followerCount; |
||||||
|
user.is_followed = resp.is_followed; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
$.ajax({ |
||||||
|
url: "https://app165.acapp.acwing.com.cn/myspace/post/", |
||||||
|
type: "GET", |
||||||
|
headers: { |
||||||
|
"Authorization": "Bearer " + store.state.user.access, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
user_id : userId, |
||||||
|
}, |
||||||
|
success(resp) { |
||||||
|
posts.count = resp.count; |
||||||
|
posts.posts = resp; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// 判断当前页面是不是自己 |
||||||
|
const is_me = computed(() => userId === store.state.user.id); |
||||||
|
|
||||||
|
// 收到子组件的点击关注 |
||||||
|
const follow = () => { |
||||||
|
if (user.is_followed) return; // 不重复关注 |
||||||
|
user.is_followed = true; |
||||||
|
user.followerCount++; |
||||||
|
} |
||||||
|
|
||||||
|
// 收到子组件的取消关注 |
||||||
|
|
||||||
|
const unfollow = () => { |
||||||
|
if (!user.is_followed) return; // 不重复取消关注 |
||||||
|
user.is_followed = false; |
||||||
|
user.followerCount--; |
||||||
|
} |
||||||
|
|
||||||
|
// 收到子组件发来的帖子内容content |
||||||
|
const send_msg = (content) => { |
||||||
|
posts.count++; |
||||||
|
// 收到的帖子内容放在第一个位置 |
||||||
|
// 进行发帖操作 |
||||||
|
posts.posts.unshift({ |
||||||
|
id: posts.count, |
||||||
|
userId: 2, |
||||||
|
content: content, |
||||||
|
}) |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// 删除一个帖子,根据post_id依次判断所有帖子,和post_id相同删除掉 |
||||||
|
const delete_a_post = post_id => { |
||||||
|
posts.posts = posts.posts.filter(post=> post.id !== post_id); |
||||||
|
posts.count = posts.posts.length; |
||||||
|
} |
||||||
|
|
||||||
|
return { |
||||||
|
user: user, |
||||||
|
follow, |
||||||
|
unfollow, |
||||||
|
posts: posts, |
||||||
|
send_msg: send_msg, |
||||||
|
is_me, |
||||||
|
delete_a_post, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.all-post { |
||||||
|
font-weight: bold; |
||||||
|
font-size: 20px; |
||||||
|
color: red; |
||||||
|
} |
||||||
|
</style> |
||||||
|
|
||||||
|
|
@ -0,0 +1,4 @@ |
|||||||
|
const { defineConfig } = require('@vue/cli-service') |
||||||
|
module.exports = defineConfig({ |
||||||
|
transpileDependencies: true |
||||||
|
}) |
Loading…
Reference in new issue