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