init
This commit is contained in:
commit
560d986995
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
|
@ -0,0 +1,5 @@
|
|||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "doubao_community_frontend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"buefy": "^0.9.4",
|
||||
"core-js": "^3.6.5",
|
||||
"darkreader": "^4.9.27",
|
||||
"date-fns": "^2.17.0",
|
||||
"dayjs": "^1.10.4",
|
||||
"element-ui": "^2.15.0",
|
||||
"js-cookie": "^2.2.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"vditor": "^3.8.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-router": "^3.2.0",
|
||||
"vuex": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"vue-template-compiler": "^2.6.11"
|
||||
}
|
||||
}
|
Binary file not shown.
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,31 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="mb-5">
|
||||
<Header></Header>
|
||||
</div>
|
||||
|
||||
<div class="container context">
|
||||
<router-view :key="this.$route.fullPath"></router-view>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from "@/components/Layout/Header";
|
||||
import Footer from "@/components/Layout/Footer";
|
||||
|
||||
export default {
|
||||
name: "App",
|
||||
components: {Header, Footer},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.container {
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,32 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 注册
|
||||
export function userRegister(userDTO) {
|
||||
return request({
|
||||
url: '/ums/user/register',
|
||||
method: 'post',
|
||||
data: userDTO
|
||||
})
|
||||
}
|
||||
|
||||
// 前台用户登录
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: '/ums/user/login',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
// 登录后获取前台用户信息
|
||||
export function getUserInfo() {
|
||||
return request({
|
||||
url: '/ums/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
// 前台用户注销
|
||||
export function logout() {
|
||||
return request({
|
||||
url: '/ums/user/logout'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getBillboard() {
|
||||
return request({
|
||||
url: '/billboard/show',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function fetchCommentsByTopicId(topic_Id) {
|
||||
return request({
|
||||
url: '/comment/get_comments',
|
||||
method: 'get',
|
||||
params: {
|
||||
topicid: topic_Id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function pushComment(data) {
|
||||
return request({
|
||||
url: '/comment/add_comment',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 关注
|
||||
export function follow(id) {
|
||||
return request(({
|
||||
url: `/relationship/subscribe/${id}`,
|
||||
method: 'get'
|
||||
}))
|
||||
}
|
||||
|
||||
// 关注
|
||||
export function unFollow(id) {
|
||||
return request(({
|
||||
url: `/relationship/unsubscribe/${id}`,
|
||||
method: 'get'
|
||||
}))
|
||||
}
|
||||
|
||||
// 验证是否关注
|
||||
export function hasFollow(topicUserId) {
|
||||
return request(({
|
||||
url: `/relationship/validate/${topicUserId}`,
|
||||
method: 'get'
|
||||
}))
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 列表
|
||||
export function getList(pageNo, size, tab) {
|
||||
return request(({
|
||||
url: '/post/list',
|
||||
method: 'get',
|
||||
params: { pageNo: pageNo, size: size, tab: tab }
|
||||
}))
|
||||
}
|
||||
|
||||
// 发布
|
||||
export function post(topic) {
|
||||
return request({
|
||||
url: '/post/create',
|
||||
method: 'post',
|
||||
data: topic
|
||||
})
|
||||
}
|
||||
|
||||
// 浏览
|
||||
export function getTopic(id) {
|
||||
return request({
|
||||
url: `/post`,
|
||||
method: 'get',
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
})
|
||||
}
|
||||
// 获取详情页推荐
|
||||
export function getRecommendTopics(id) {
|
||||
return request({
|
||||
url: '/post/recommend',
|
||||
method: 'get',
|
||||
params: {
|
||||
topicId: id
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function update(topic) {
|
||||
return request({
|
||||
url: '/post/update',
|
||||
method: 'post',
|
||||
data: topic
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTopic(id) {
|
||||
return request({
|
||||
url: `/post/delete/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 获取推广
|
||||
export function getList() {
|
||||
return request(({
|
||||
url: '/promotion/all',
|
||||
method: 'get'
|
||||
}))
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 关键词检索
|
||||
export function searchByKeyword(query) {
|
||||
return request({
|
||||
url: `/search`,
|
||||
method: 'get',
|
||||
params: {
|
||||
keyword: query.keyword,
|
||||
pageNum: query.pageNum,
|
||||
pageSize: query.pageSize
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getTopicsByTag(paramMap) {
|
||||
return request({
|
||||
url: '/tag/' + paramMap.name,
|
||||
method: 'get',
|
||||
params: {
|
||||
page: paramMap.page,
|
||||
size: paramMap.size
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export function getTodayTip() {
|
||||
return request({
|
||||
url: '/tip/today',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 用户主页
|
||||
export function getInfoByName(username, page, size) {
|
||||
return request({
|
||||
url: '/ums/user/' + username,
|
||||
method: 'get',
|
||||
params: {
|
||||
pageNo: page,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
}
|
||||
// 用户主页
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: '/ums/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
// 更新
|
||||
export function update(user) {
|
||||
return request({
|
||||
url: '/ums/user/update',
|
||||
method: 'post',
|
||||
data: user
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
background-color: #f6f6f6;
|
||||
color: black;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.03em;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC,
|
||||
Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, WenQuanYi Micro Hei,
|
||||
sans-serif, Apple Color Emoji, Segoe UI Emoji, Noto Color Emoji,
|
||||
Segoe UI Symbol, Android Emoji, EmojiSymbols;
|
||||
}
|
||||
|
||||
/*背景图*/
|
||||
/*body {*/
|
||||
/* background-image: url('https://api.mz-moe.cn/img.php');*/
|
||||
/* background-repeat: round;*/
|
||||
/*}*/
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.container {
|
||||
width: 760px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.container {
|
||||
width: 980px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.container {
|
||||
width: 1080px;
|
||||
}
|
||||
}
|
||||
|
||||
/*滚动条*/
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
/**/
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgb(239, 239, 239);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #bfbfbf;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background: #179a16;
|
||||
}
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
z-index: 89;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
min-width: 1032px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 0px rgba(26, 26, 26, 0.1);
|
||||
height: 53px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #1d1d1d;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #f60;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.shadow-1 {
|
||||
box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1),
|
||||
0 0 0 1px rgba(10, 10, 10, 0.02);
|
||||
}
|
||||
|
||||
.navbar-dropdown {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
/*统一卡片样式*/
|
||||
.el-card {
|
||||
/*border-radius: 3px !important;*/
|
||||
margin-bottom: 16px;
|
||||
/*border: none;*/
|
||||
}
|
||||
|
||||
.my-card {
|
||||
cursor: pointer;
|
||||
transition: all 0.1s ease-in-out;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.my-card:hover {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
::selection {
|
||||
text-shadow: none;
|
||||
background: rgba(67, 135, 244, 0.56);
|
||||
}
|
||||
|
||||
/* 搜索框 */
|
||||
.search-bar input {
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*按钮居中*/
|
||||
.button-center {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: block;
|
||||
display: -webkit-box;
|
||||
margin: 0 auto;
|
||||
line-height: 1.4;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.is-ellipsis-1 {
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
|
||||
.is-ellipsis-2 {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.is-ellipsis-3 {
|
||||
-webkit-line-clamp: 3;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 434 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,28 @@
|
|||
<template>
|
||||
<el-backtop :bottom="60" :right="60">
|
||||
<div title="回到顶部"
|
||||
style="{
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: #f2f5f6;
|
||||
box-shadow: 0 1px 0 0;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
line-height: 40px;
|
||||
color: #167df0;
|
||||
}"
|
||||
>
|
||||
<i class="fa fa-arrow-up"></i>
|
||||
</div>
|
||||
</el-backtop>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "BackTop"
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<section class="box comments">
|
||||
<hr />
|
||||
<h3 class="title is-5">Comments</h3>
|
||||
<lv-comments-form :slug="slug" v-if="token" @loadComments="fetchComments"/>
|
||||
|
||||
<lv-comments-item
|
||||
v-for="comment in comments"
|
||||
:key="`comment-${comment.id}`"
|
||||
:comment="comment"
|
||||
/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { fetchCommentsByTopicId } from '@/api/comment'
|
||||
import LvCommentsForm from './CommentsForm'
|
||||
import LvCommentsItem from './CommentsItem'
|
||||
|
||||
export default {
|
||||
name: 'LvComments',
|
||||
components: {
|
||||
LvCommentsForm,
|
||||
LvCommentsItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
comments: []
|
||||
}
|
||||
},
|
||||
props: {
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'token'
|
||||
])
|
||||
},
|
||||
async mounted() {
|
||||
await this.fetchComments(this.slug)
|
||||
},
|
||||
methods: {
|
||||
// 初始化
|
||||
async fetchComments(topic_id) {
|
||||
console.log(topic_id)
|
||||
fetchCommentsByTopicId(topic_id).then(response => {
|
||||
const { data } = response
|
||||
this.comments = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<article class="media">
|
||||
<div class="media-content">
|
||||
<form @submit.prevent="onSubmit">
|
||||
<b-field>
|
||||
<b-input
|
||||
v-model.lazy="commentText"
|
||||
type="textarea"
|
||||
maxlength="400"
|
||||
placeholder="Add a comment..."
|
||||
:disabled="isLoading"
|
||||
></b-input>
|
||||
</b-field>
|
||||
<nav class="level">
|
||||
<div class="level-left">
|
||||
<b-button
|
||||
type="is-primary"
|
||||
native-type="submit"
|
||||
class="level-item"
|
||||
:disabled="isLoading"
|
||||
>
|
||||
Comment
|
||||
</b-button>
|
||||
</div>
|
||||
</nav>
|
||||
</form>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { pushComment } from '@/api/comment'
|
||||
|
||||
export default {
|
||||
name: 'LvCommentsForm',
|
||||
data() {
|
||||
return {
|
||||
commentText: '',
|
||||
isLoading: false
|
||||
}
|
||||
},
|
||||
props: {
|
||||
slug: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
this.isLoading = true
|
||||
try {
|
||||
let postData = {}
|
||||
console.log(this.commentText)
|
||||
postData['content'] = this.commentText
|
||||
postData['topic_id'] = this.slug
|
||||
await pushComment(postData)
|
||||
this.$emit('loadComments', this.slug)
|
||||
this.$message.success('留言成功')
|
||||
} catch (e) {
|
||||
this.$buefy.toast.open({
|
||||
message: `Cannot comment this story. ${e}`,
|
||||
type: 'is-danger'
|
||||
})
|
||||
} finally {
|
||||
this.isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<article class="media">
|
||||
<figure class="media-left image is-48x48">
|
||||
<img :src="`https://cn.gravatar.com/avatar/${comment.userId}?s=164&d=monsterid`">
|
||||
</figure>
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<p>
|
||||
<strong>{{ comment.username }}</strong>
|
||||
<small class="ml-2">{{ comment.createTime | date }}</small>
|
||||
<br />
|
||||
{{ comment.content }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'LvCommentsItem',
|
||||
props: {
|
||||
comment: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<footer class="footer has-text-grey-light has-background-grey-darker">
|
||||
<div class="container">
|
||||
<div class="">
|
||||
<span>简洁、实用、美观</span>
|
||||
|
||||
<span style="float: right">
|
||||
<a href="/?lang=zh_CN">中文</a> |
|
||||
<a href="/?lang=en_US">English</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span>{{ title }} ALL RIGHTS RESERVED</span>
|
||||
<div style="float: right">
|
||||
<template>
|
||||
<b-taglist attached>
|
||||
<b-tag size="is-normal" type="is-dark">Design</b-tag>
|
||||
<b-tag size="is-normal" type="is-info">{{ author }}</b-tag>
|
||||
</b-taglist>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<back-top></back-top>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BackTop from "@/components/Backtop/BackTop";
|
||||
|
||||
export default {
|
||||
name: "Footer",
|
||||
components: {
|
||||
BackTop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
title: "© " + new Date().getFullYear() + ' yovinchen',
|
||||
author: 'yovinchen',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
footer {
|
||||
margin-top: 120px;
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
</style>
|
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<header class="header has-background-white has-text-black">
|
||||
<b-navbar
|
||||
:fixed-top="true"
|
||||
class="container is-white"
|
||||
>
|
||||
<template slot="brand">
|
||||
<b-navbar-item
|
||||
:to="{ path: '/' }"
|
||||
tag="router-link">
|
||||
<img :src="doubaoImg" alt="logo">
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-item
|
||||
:to="{ path: '/' }"
|
||||
class="is-hidden-desktop"
|
||||
tag="router-link"
|
||||
>
|
||||
主页
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
<template slot="start">
|
||||
<b-navbar-item
|
||||
:to="{ path: '/' }"
|
||||
tag="router-link"
|
||||
>
|
||||
🌐 主页
|
||||
</b-navbar-item>
|
||||
</template>
|
||||
|
||||
<template slot="end">
|
||||
<b-navbar-item tag="div">
|
||||
<b-field position="is-centered">
|
||||
<b-input
|
||||
v-model="searchKey"
|
||||
class="s_input"
|
||||
clearable
|
||||
placeholder="搜索帖子、标签和用户"
|
||||
rounded
|
||||
width="80%"
|
||||
@keyup.enter.native="search()"
|
||||
/>
|
||||
|
||||
<p class="control">
|
||||
<b-button
|
||||
class="is-info"
|
||||
@click="search()"
|
||||
>检索
|
||||
</b-button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-item tag="div">
|
||||
<b-switch
|
||||
v-model="darkMode"
|
||||
passive-type="is-warning"
|
||||
type="is-dark"
|
||||
>
|
||||
{{ darkMode ? "夜" : "日" }}
|
||||
</b-switch>
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-item
|
||||
v-if="token == null || token === ''"
|
||||
tag="div"
|
||||
>
|
||||
<div class="buttons">
|
||||
<b-button
|
||||
:to="{ path: '/register' }"
|
||||
class="is-light"
|
||||
tag="router-link"
|
||||
>
|
||||
注册
|
||||
</b-button>
|
||||
<b-button
|
||||
:to="{ path: '/login' }"
|
||||
class="is-light"
|
||||
tag="router-link"
|
||||
>
|
||||
登录
|
||||
</b-button>
|
||||
</div>
|
||||
</b-navbar-item>
|
||||
|
||||
<b-navbar-dropdown
|
||||
v-else
|
||||
:label="user.alias"
|
||||
>
|
||||
<b-navbar-item
|
||||
:to="{ path: `/member/${user.username}/home` }"
|
||||
tag="router-link"
|
||||
>
|
||||
🧘 个人中心
|
||||
</b-navbar-item>
|
||||
<hr class="dropdown-divider">
|
||||
<b-navbar-item
|
||||
:to="{ path: `/member/${user.username}/setting` }"
|
||||
tag="router-link"
|
||||
>
|
||||
⚙ 设置中心
|
||||
</b-navbar-item>
|
||||
<hr class="dropdown-divider">
|
||||
<b-navbar-item
|
||||
tag="a"
|
||||
@click="logout"
|
||||
> 👋 退出登录
|
||||
</b-navbar-item>
|
||||
</b-navbar-dropdown>
|
||||
</template>
|
||||
</b-navbar>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {disable as disableDarkMode, enable as enableDarkMode} from 'darkreader'
|
||||
import {getDarkMode, setDarkMode} from '@/utils/auth'
|
||||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
data() {
|
||||
return {
|
||||
logoUrl: require('@/assets/logo.png'),
|
||||
doubaoImg: require('@/assets/image/doubao.png'),
|
||||
searchKey: '',
|
||||
darkMode: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['token', 'user'])
|
||||
},
|
||||
watch: {
|
||||
// 监听Theme模式
|
||||
darkMode(val) {
|
||||
if (val) {
|
||||
enableDarkMode({})
|
||||
} else {
|
||||
disableDarkMode()
|
||||
}
|
||||
setDarkMode(this.darkMode)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 获取cookie中的夜间还是白天模式
|
||||
this.darkMode = getDarkMode()
|
||||
if (this.darkMode) {
|
||||
enableDarkMode({})
|
||||
} else {
|
||||
disableDarkMode()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async logout() {
|
||||
this.$store.dispatch('user/logout').then(() => {
|
||||
this.$message.info('退出登录成功')
|
||||
setTimeout(() => {
|
||||
this.$router.push({path: this.redirect || '/'})
|
||||
}, 500)
|
||||
})
|
||||
},
|
||||
search() {
|
||||
console.log(this.token)
|
||||
if (this.searchKey.trim() === null || this.searchKey.trim() === '') {
|
||||
this.$message.info({
|
||||
showClose: true,
|
||||
message: '请输入关键字搜索!',
|
||||
type: 'warning'
|
||||
})
|
||||
return false
|
||||
}
|
||||
this.$router.push({path: '/search?key=' + this.searchKey})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
input {
|
||||
width: 80%;
|
||||
height: 86%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<div :class="{ hidden: hidden }" class="pagination-container">
|
||||
<el-pagination
|
||||
:background="background"
|
||||
:current-page.sync="currentPage"
|
||||
:page-size.sync="pageSize"
|
||||
:layout="layout"
|
||||
:page-sizes="pageSizes"
|
||||
:total="total"
|
||||
v-bind="$attrs"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {scrollTo} from "@/utils/scroll-to";
|
||||
|
||||
export default {
|
||||
name: "Pagination",
|
||||
props: {
|
||||
total: {
|
||||
required: true,
|
||||
type: Number,
|
||||
},
|
||||
page: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
pageSizes: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [5, 10, 20, 30, 50];
|
||||
},
|
||||
},
|
||||
layout: {
|
||||
type: String,
|
||||
default: "total, sizes, prev, pager, next, jumper",
|
||||
// default: 'sizes, prev, pager, next, jumper'
|
||||
},
|
||||
background: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
autoScroll: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hidden: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
currentPage: {
|
||||
get() {
|
||||
return this.page;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("update:page", val);
|
||||
},
|
||||
},
|
||||
pageSize: {
|
||||
get() {
|
||||
return this.limit;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit("update:limit", val);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleSizeChange(val) {
|
||||
this.$emit("pagination", { page: this.currentPage, limit: val });
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.$emit("pagination", { page: val, limit: this.pageSize });
|
||||
if (this.autoScroll) {
|
||||
scrollTo(0, 800);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pagination-container {
|
||||
/* background: #fff; */
|
||||
padding: 5px 0px;
|
||||
}
|
||||
|
||||
.pagination-container.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,42 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
// Buefy
|
||||
import Buefy from 'buefy'
|
||||
import 'buefy/dist/buefy.css'
|
||||
// ElementUI
|
||||
import ElementUI from 'element-ui';
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
import '@/assets/app.css'
|
||||
import './assets/plugins/font-awesome-4.7.0/css/font-awesome.min.css'
|
||||
import format from 'date-fns/format'
|
||||
import '@/permission'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||
|
||||
// 国际化
|
||||
import 'dayjs/locale/zh-cn'
|
||||
const dayjs = require('dayjs');
|
||||
|
||||
// 相对时间插件
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
dayjs.locale('zh-cn') // use locale globally
|
||||
dayjs().locale('zh-cn').format() // use locale in a specific instance
|
||||
|
||||
Vue.prototype.dayjs = dayjs;//可以全局使用dayjs
|
||||
|
||||
Vue.filter('date', (date) => {
|
||||
return format(new Date(date), 'yyyy-MM-dd')
|
||||
})
|
||||
|
||||
Vue.use(Buefy)
|
||||
Vue.use(ElementUI);
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
|
@ -0,0 +1,41 @@
|
|||
import router from './router'
|
||||
import store from './store'
|
||||
import getPageTitle from '@/utils/get-page-title'
|
||||
|
||||
import NProgress from 'nprogress' // progress bar
|
||||
import 'nprogress/nprogress.css'
|
||||
import {getToken} from "@/utils/auth"; // progress bar style
|
||||
|
||||
NProgress.configure({showSpinner: false}) // NProgress Configuration
|
||||
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
// start progress bar
|
||||
NProgress.start()
|
||||
// set page title
|
||||
document.title = getPageTitle(to.meta.title)
|
||||
// determine whether the user has logged in
|
||||
const hasToken = getToken();
|
||||
|
||||
if (hasToken) {
|
||||
if (to.path === '/login') {
|
||||
// 登录,跳转首页
|
||||
next({path: '/'})
|
||||
NProgress.done()
|
||||
} else {
|
||||
// 获取用户信息
|
||||
await store.dispatch('user/getInfo')
|
||||
next()
|
||||
}
|
||||
} else if (!to.meta.requireAuth)
|
||||
{
|
||||
next()
|
||||
}
|
||||
else {
|
||||
next('/login')
|
||||
}
|
||||
})
|
||||
|
||||
router.afterEach(() => {
|
||||
// finish progress bar
|
||||
NProgress.done()
|
||||
})
|
|
@ -0,0 +1,98 @@
|
|||
import Vue from "vue";
|
||||
import VueRouter from "vue-router";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: () => import("@/views/Home"),
|
||||
},
|
||||
{
|
||||
path: "/register",
|
||||
name: "register",
|
||||
component: () => import("@/views/auth/Register"),
|
||||
meta: { title: "注册" },
|
||||
},
|
||||
// 登录
|
||||
{
|
||||
name: "login",
|
||||
path: "/login",
|
||||
component: () => import("@/views/auth/Login"),
|
||||
meta: { title: "登录" },
|
||||
},
|
||||
// 发布
|
||||
{
|
||||
name: "post-create",
|
||||
path: "/post/create",
|
||||
component: () => import("@/views/post/Create"),
|
||||
meta: { title: "信息发布", requireAuth: true },
|
||||
},
|
||||
// 编辑
|
||||
{
|
||||
name: 'topic-edit',
|
||||
path: '/topic/edit/:id',
|
||||
component: () => import('@/views/post/Edit'),
|
||||
meta: {
|
||||
title: '编辑',
|
||||
requireAuth: true
|
||||
}
|
||||
},
|
||||
// 详情
|
||||
{
|
||||
name: "post-detail",
|
||||
path: "/post/:id",
|
||||
component: () => import("@/views/post/Detail"),
|
||||
meta: { title: "详情" },
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
path: '/tag/:name',
|
||||
component: () => import('@/views/tag/Tag'),
|
||||
meta: { title: '主题列表' }
|
||||
},
|
||||
// 检索
|
||||
{
|
||||
name: 'search',
|
||||
path: '/search',
|
||||
component: () => import('@/views/Search'),
|
||||
meta: { title: '检索' }
|
||||
},
|
||||
// 用户主页
|
||||
{
|
||||
name: 'user',
|
||||
path: '/member/:username/home',
|
||||
component: () => import('@/views/user/Profile'),
|
||||
meta: { title: '用户主页' }
|
||||
},
|
||||
// 用户设置
|
||||
{
|
||||
name: 'user-setting',
|
||||
path: '/member/:username/setting',
|
||||
component: () => import('@/views/user/Setting'),
|
||||
meta: { title: '设置', requireAuth: true }
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
name: "404",
|
||||
component: () => import("@/views/error/404"),
|
||||
meta: { title: "404-NotFound" },
|
||||
},
|
||||
{
|
||||
path: "*",
|
||||
redirect: "/404",
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
const originalPush = VueRouter.prototype.push;
|
||||
VueRouter.prototype.push = function push(location) {
|
||||
return originalPush.call(this, location).catch((err) => err);
|
||||
};
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,5 @@
|
|||
const getters = {
|
||||
token: state => state.user.token, // token
|
||||
user: state => state.user.user, // 用户对象
|
||||
}
|
||||
export default getters
|
|
@ -0,0 +1,15 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import getters from './getters'
|
||||
import user from './modules/user'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
|
@ -0,0 +1,80 @@
|
|||
import { getUserInfo, login, logout } from "@/api/auth/auth";
|
||||
import { getToken, setToken, removeToken } from "@/utils/auth";
|
||||
|
||||
const state = {
|
||||
token: getToken(), // token
|
||||
user: "", // 用户对象
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_TOKEN_STATE: (state, token) => {
|
||||
state.token = token;
|
||||
},
|
||||
SET_USER_STATE: (state, user) => {
|
||||
state.user = user;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
// 用户登录
|
||||
login({ commit }, userInfo) {
|
||||
console.log(userInfo);
|
||||
const { name, pass, rememberMe } = userInfo;
|
||||
return new Promise((resolve, reject) => {
|
||||
login({ username: name.trim(), password: pass, rememberMe: rememberMe })
|
||||
.then((response) => {
|
||||
const { data } = response;
|
||||
commit("SET_TOKEN_STATE", data.token);
|
||||
setToken(data.token);
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取用户信息
|
||||
getInfo({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
getUserInfo()
|
||||
.then((response) => {
|
||||
const { data } = response;
|
||||
if (!data) {
|
||||
commit("SET_TOKEN_STATE", "");
|
||||
commit("SET_USER_STATE", "");
|
||||
removeToken();
|
||||
resolve();
|
||||
reject("Verification failed, please Login again.");
|
||||
}
|
||||
commit("SET_USER_STATE", data);
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 注销
|
||||
logout({ commit, state }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
logout(state.token)
|
||||
.then((response) => {
|
||||
console.log(response);
|
||||
commit("SET_TOKEN_STATE", "");
|
||||
commit("SET_USER_STATE", "");
|
||||
removeToken();
|
||||
resolve();
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions,
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
// 用户主页
|
||||
export function getInfoByName(username, page, size) {
|
||||
return request({
|
||||
url: '/ums/user/' + username,
|
||||
method: 'get',
|
||||
params: {
|
||||
pageNo: page,
|
||||
size: size
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 用户主页
|
||||
export function getInfo() {
|
||||
return request({
|
||||
url: '/ums/user/info',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 更新
|
||||
export function update(user) {
|
||||
return request({
|
||||
url: '/ums/user/update',
|
||||
method: 'post',
|
||||
data: user
|
||||
})
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import Cookies from 'js-cookie'
|
||||
|
||||
const uToken = 'u_token'
|
||||
const darkMode = 'dark_mode';
|
||||
|
||||
// 获取Token
|
||||
export function getToken() {
|
||||
return Cookies.get(uToken);
|
||||
}
|
||||
|
||||
// 设置Token,1天,与后端同步
|
||||
export function setToken(token) {
|
||||
return Cookies.set(uToken, token, {expires: 1})
|
||||
}
|
||||
|
||||
// 删除Token
|
||||
export function removeToken() {
|
||||
return Cookies.remove(uToken)
|
||||
}
|
||||
|
||||
export function removeAll() {
|
||||
return Cookies.Cookies.removeAll()
|
||||
}
|
||||
|
||||
export function setDarkMode(mode) {
|
||||
return Cookies.set(darkMode, mode, {expires: 365})
|
||||
}
|
||||
|
||||
export function getDarkMode() {
|
||||
return !(undefined === Cookies.get(darkMode) || 'false' === Cookies.get(darkMode));
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
const title = '小而美的智慧社区系统'
|
||||
|
||||
export default function getPageTitle(pageTitle) {
|
||||
if (pageTitle) {
|
||||
return `${pageTitle} - ${title}`
|
||||
}
|
||||
return `${title}`
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
import axios from 'axios'
|
||||
import { Message, MessageBox } from 'element-ui'
|
||||
import store from '@/store'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
// 1.创建axios实例
|
||||
const service = axios.create({
|
||||
// 公共接口--这里注意后面会讲,url = base url + request url
|
||||
baseURL: process.env.VUE_APP_SERVER_URL,
|
||||
|
||||
// baseURL: 'https://api.example.com',
|
||||
// 超时时间 单位是ms,这里设置了5s的超时时间
|
||||
timeout: 5 * 1000
|
||||
})
|
||||
|
||||
// 2.请求拦截器request interceptor
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
// 发请求前做的一些处理,数据转化,配置请求头,设置token,设置loading等,根据需求去添加
|
||||
// 注意使用token的时候需要引入cookie方法或者用本地localStorage等方法,推荐js-cookie
|
||||
if (store.getters.token) {
|
||||
// config.params = {'token': token} // 如果要求携带在参数中
|
||||
// config.headers.token = token; // 如果要求携带在请求头中
|
||||
// bearer:w3c规范
|
||||
config.headers['Authorization'] = 'Bearer ' + getToken()
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
// do something with request error
|
||||
// console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// 设置cross跨域 并设置访问权限 允许跨域携带cookie信息,使用JWT可关闭
|
||||
service.defaults.withCredentials = false
|
||||
|
||||
service.interceptors.response.use(
|
||||
// 接收到响应数据并成功后的一些共有的处理,关闭loading等
|
||||
response => {
|
||||
const res = response.data
|
||||
// 如果自定义代码不是200,则将其判断为错误。
|
||||
if (res.code !== 200) {
|
||||
// 50008: 非法Token; 50012: 异地登录; 50014: Token失效;
|
||||
if (res.code === 401 || res.code === 50012 || res.code === 50014) {
|
||||
// 重新登录
|
||||
MessageBox.confirm('会话失效,您可以留在当前页面,或重新登录', '权限不足', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
center: true
|
||||
}).then(() => {
|
||||
window.location.href = '#/login'
|
||||
})
|
||||
} else { // 其他异常直接提示
|
||||
Message({
|
||||
showClose: true,
|
||||
message: '⚠' + res.message || 'Error',
|
||||
type: 'error',
|
||||
duration: 3 * 1000
|
||||
})
|
||||
}
|
||||
return Promise.reject(new Error(res.message || 'Error'))
|
||||
} else {
|
||||
return res
|
||||
}
|
||||
},
|
||||
error => {
|
||||
/** *** 接收到异常响应的处理开始 *****/
|
||||
// console.log('err' + error) // for debug
|
||||
Message({
|
||||
showClose: true,
|
||||
message: error.message,
|
||||
type: 'error',
|
||||
duration: 5 * 1000
|
||||
})
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
export default service
|
|
@ -0,0 +1,58 @@
|
|||
Math.easeInOutQuad = function(t, b, c, d) {
|
||||
t /= d / 2
|
||||
if (t < 1) {
|
||||
return c / 2 * t * t + b
|
||||
}
|
||||
t--
|
||||
return -c / 2 * (t * (t - 2) - 1) + b
|
||||
}
|
||||
|
||||
// requestAnimationFrame for Smart Animating http://goo.gl/sx5sts
|
||||
var requestAnimFrame = (function() {
|
||||
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function(callback) { window.setTimeout(callback, 1000 / 60) }
|
||||
})()
|
||||
|
||||
/**
|
||||
* Because it's so fucking difficult to detect the scrolling element, just move them all
|
||||
* @param {number} amount
|
||||
*/
|
||||
function move(amount) {
|
||||
document.documentElement.scrollTop = amount
|
||||
document.body.parentNode.scrollTop = amount
|
||||
document.body.scrollTop = amount
|
||||
}
|
||||
|
||||
function position() {
|
||||
return document.documentElement.scrollTop || document.body.parentNode.scrollTop || document.body.scrollTop
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} to
|
||||
* @param {number} duration
|
||||
* @param {Function} callback
|
||||
*/
|
||||
export function scrollTo(to, duration, callback) {
|
||||
const start = position()
|
||||
const change = to - start
|
||||
const increment = 20
|
||||
let currentTime = 0
|
||||
duration = (typeof (duration) === 'undefined') ? 500 : duration
|
||||
var animateScroll = function() {
|
||||
// increment the time
|
||||
currentTime += increment
|
||||
// find the value with the quadratic in-out easing function
|
||||
var val = Math.easeInOutQuad(currentTime, start, change, duration)
|
||||
// move the document.body
|
||||
move(val)
|
||||
// do the animation unless its over
|
||||
if (currentTime < duration) {
|
||||
requestAnimFrame(animateScroll)
|
||||
} else {
|
||||
if (callback && typeof (callback) === 'function') {
|
||||
// the animation is done so lets callback
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
animateScroll()
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="box">🔔 {{ billboard.content }}</div>
|
||||
<div class="columns">
|
||||
<div class="column is-three-quarters">
|
||||
<TopicList></TopicList>
|
||||
</div>
|
||||
<div class="column">
|
||||
<CardBar></CardBar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getBillboard } from "@/api/billboard";
|
||||
import CardBar from "@/views/card/CardBar"
|
||||
import PostList from '@/views/post/Index'
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {CardBar, TopicList: PostList},
|
||||
data() {
|
||||
return {
|
||||
billboard: {
|
||||
content: "",
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchBillboard();
|
||||
},
|
||||
methods: {
|
||||
async fetchBillboard() {
|
||||
getBillboard().then((value) => {
|
||||
const { data } = value;
|
||||
this.billboard = data;
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -0,0 +1,104 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
检索到 <code>{{ list.length }}</code>
|
||||
条关于 <code class="has-text-info">{{ query.keyword }}</code> 的记录
|
||||
</div>
|
||||
<div>
|
||||
<article v-for="(item, index) in list" :key="index" class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="`https://cn.gravatar.com/avatar/${item.userId}?s=164&d=monsterid`">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="">
|
||||
<p class="ellipsis is-ellipsis-1">
|
||||
<el-tooltip class="item" effect="dark" :content="item.title" placement="top">
|
||||
<router-link :to="{name:'post-detail',params:{id:item.id}}">
|
||||
<span class="is-size-6">{{ item.title }}</span>
|
||||
</router-link>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
<nav class="level has-text-grey is-mobile is-size-7 mt-2">
|
||||
<div class="level-left">
|
||||
<div class="level-left">
|
||||
<router-link class="level-item" :to="{ path: `/member/${item.username}/home` }">
|
||||
{{ item.alias }}
|
||||
</router-link>
|
||||
|
||||
<span class="mr-1">
|
||||
发布于:{{ dayjs(item.createTime).format("YYYY/MM/DD") }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-for="(tag, index) in item.tags"
|
||||
:key="index"
|
||||
class="tag is-hidden-mobile is-success is-light mr-1"
|
||||
>
|
||||
<router-link :to="{ name: 'tag', params: { name: tag.name } }">
|
||||
{{ "#" + tag.name }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
||||
<span class="is-hidden-mobile">浏览:{{ item.view }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="media-right" />
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!--分页-->
|
||||
<pagination
|
||||
v-show="query.total > 0"
|
||||
:total="query.total"
|
||||
:page.sync="query.pageNum"
|
||||
:limit.sync="query.pageSize"
|
||||
@pagination="fetchList"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { searchByKeyword } from '@/api/search'
|
||||
import Pagination from '@/components/Pagination'
|
||||
|
||||
export default {
|
||||
name: 'Search',
|
||||
components: { Pagination },
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
query: {
|
||||
keyword: this.$route.query.key,
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
searchByKeyword(this.query).then(value => {
|
||||
const { data } = value
|
||||
this.list = data.records
|
||||
this.query.total = data.total
|
||||
this.query.pageSize = data.size
|
||||
this.query.pageNum = data.current
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div class="columns py-6">
|
||||
<div class="column is-half is-offset-one-quarter">
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="has-text-centered has-text-weight-bold">
|
||||
用户登录
|
||||
</div>
|
||||
<div>
|
||||
<el-form
|
||||
v-loading="loading"
|
||||
:model="ruleForm"
|
||||
status-icon
|
||||
:rules="rules"
|
||||
ref="ruleForm"
|
||||
label-width="100px"
|
||||
class="demo-ruleForm"
|
||||
>
|
||||
<el-form-item label="账号" prop="name">
|
||||
<el-input v-model="ruleForm.name"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="pass">
|
||||
<el-input
|
||||
type="password"
|
||||
v-model="ruleForm.pass"
|
||||
autocomplete="off"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="记住" prop="delivery">
|
||||
<el-switch v-model="ruleForm.rememberMe"></el-switch>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('ruleForm')"
|
||||
>提交</el-button
|
||||
>
|
||||
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Login",
|
||||
data() {
|
||||
return {
|
||||
redirect: undefined,
|
||||
loading: false,
|
||||
ruleForm: {
|
||||
name: "",
|
||||
pass: "",
|
||||
rememberMe: true,
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: "请输入账号", trigger: "blur" },
|
||||
{
|
||||
min: 2,
|
||||
max: 15,
|
||||
message: "长度在 2 到 15 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
pass: [
|
||||
{ required: true, message: "请输入密码", trigger: "blur" },
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: "长度在 6 到 20 个字符",
|
||||
trigger: "blur",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true;
|
||||
this.$store
|
||||
.dispatch("user/login", this.ruleForm)
|
||||
.then(() => {
|
||||
this.$message({
|
||||
message: "恭喜你,登录成功",
|
||||
type: "success",
|
||||
duration: 2000,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
this.$router.push({ path: this.redirect || "/" });
|
||||
}, 0.1 * 1000);
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<div class="columns py-6">
|
||||
<div class="column is-half is-offset-one-quarter">
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="has-text-centered has-text-weight-bold">
|
||||
新用户入驻
|
||||
</div>
|
||||
<div>
|
||||
<el-form
|
||||
ref="ruleForm"
|
||||
v-loading="loading"
|
||||
:model="ruleForm"
|
||||
status-icon
|
||||
:rules="rules"
|
||||
label-width="100px"
|
||||
class="demo-ruleForm"
|
||||
>
|
||||
<el-form-item label="账号" prop="name">
|
||||
<el-input v-model="ruleForm.name" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="pass">
|
||||
<el-input
|
||||
v-model="ruleForm.pass"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="确认密码" prop="checkPass">
|
||||
<el-input
|
||||
v-model="ruleForm.checkPass"
|
||||
type="password"
|
||||
autocomplete="off"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="ruleForm.email" autocomplete="off" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitForm('ruleForm')"
|
||||
>立即注册</el-button>
|
||||
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { userRegister } from '@/api/auth/auth'
|
||||
|
||||
export default {
|
||||
name: 'Register',
|
||||
data() {
|
||||
const validatePass = (rule, value, callback) => {
|
||||
if (value === '') {
|
||||
callback(new Error('请再次输入密码'))
|
||||
} else if (value !== this.ruleForm.pass) {
|
||||
callback(new Error('两次输入密码不一致!'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
loading: false,
|
||||
ruleForm: {
|
||||
name: '',
|
||||
pass: '',
|
||||
checkPass: '',
|
||||
email: ''
|
||||
},
|
||||
rules: {
|
||||
name: [
|
||||
{ required: true, message: '请输入账号', trigger: 'blur' },
|
||||
{
|
||||
min: 2,
|
||||
max: 10,
|
||||
message: '长度在 2 到 10 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
pass: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' },
|
||||
{
|
||||
min: 6,
|
||||
max: 20,
|
||||
message: '长度在 6 到 20 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
checkPass: [
|
||||
{ required: true, message: '请再次输入密码', trigger: 'blur' },
|
||||
{ validator: validatePass, trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
|
||||
{
|
||||
type: 'email',
|
||||
message: '请输入正确的邮箱地址',
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
this.loading = true
|
||||
userRegister(this.ruleForm)
|
||||
.then((value) => {
|
||||
const { code, message } = value
|
||||
if (code === 200) {
|
||||
this.$message({
|
||||
message: '账号注册成功',
|
||||
type: 'success'
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.loading = false
|
||||
this.$router.push({ path: this.redirect || '/login' })
|
||||
}, 0.1 * 1000)
|
||||
} else {
|
||||
this.$message.error('注册失败,' + message)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<section>
|
||||
<!--是否登录-->
|
||||
<login-welcome />
|
||||
|
||||
<!--今日赠言-->
|
||||
<tip-card />
|
||||
|
||||
<!--资源推介-->
|
||||
<PromotionCard />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import TipCard from '@/views/card/Tip'
|
||||
import PromotionCard from '@/views/card/Promotion'
|
||||
import LoginWelcome from '@/views/card/LoginWelcome'
|
||||
|
||||
export default {
|
||||
name: 'CardBar',
|
||||
components: { LoginWelcome, PromotionCard, TipCard }
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header">
|
||||
<span>💐 发帖</span>
|
||||
</div>
|
||||
<div v-if="token != null && token !== ''" class="has-text-centered">
|
||||
<b-button type="is-danger" tag="router-link" :to="{path:'/post/create'}" outlined>✍ 发表想法</b-button>
|
||||
</div>
|
||||
|
||||
<div v-else class="has-text-centered">
|
||||
<b-button type="is-primary" tag="router-link" :to="{path:'/register'}" outlined>马上入驻</b-button>
|
||||
<b-button type="is-danger" tag="router-link" :to="{path:'/login'}" outlined class="ml-2"> 社区登入</b-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'LoginWelcome',
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'token'
|
||||
])
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,39 @@
|
|||
<template>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header">
|
||||
<span>🥂 推广</span>
|
||||
</div>
|
||||
<div>
|
||||
<p v-for="(item, index) in list" :key="index" class="block">
|
||||
<a :href="item.link" target="_blank">{{ item.title }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getList } from '@/api/promote'
|
||||
|
||||
export default {
|
||||
name: 'Promotion',
|
||||
data() {
|
||||
return {
|
||||
list: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList() {
|
||||
getList().then((response) => {
|
||||
const { data } = response
|
||||
this.list = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,43 @@
|
|||
<template>
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header">
|
||||
<span>🥳 每日一句</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="has-text-left block">
|
||||
{{ tip.content }}
|
||||
</div>
|
||||
<div class="has-text-right mt-5 block">
|
||||
——{{ tip.author }}
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getTodayTip} from '@/api/tip'
|
||||
|
||||
export default {
|
||||
name: 'Tip',
|
||||
data() {
|
||||
return {
|
||||
tip: {}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchTodayTip()
|
||||
},
|
||||
methods: {
|
||||
fetchTodayTip() {
|
||||
getTodayTip().then(response => {
|
||||
const { data } = response
|
||||
this.tip = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<div class="columns mt-6">
|
||||
<div class="column mt-6">
|
||||
<div class="mt-6">
|
||||
<p class="content">UH OH! 页面丢失</p>
|
||||
<p class="content subtitle mt-6">
|
||||
您所寻找的页面不存在, {{ times }} 秒后,将返回首页!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "404",
|
||||
data() {
|
||||
return {
|
||||
times: 10
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.goHome();
|
||||
},
|
||||
methods: {
|
||||
goHome: function () {
|
||||
this.timer = setInterval(() => {
|
||||
this.times--
|
||||
if (this.times === 0) {
|
||||
clearInterval(this.timer)
|
||||
this.$router.push({path: '/'});
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
|
@ -0,0 +1,103 @@
|
|||
<template>
|
||||
<section id="author">
|
||||
<el-card class="" shadow="never">
|
||||
<div slot="header">
|
||||
<span class="has-text-weight-bold">👨💻 关于作者</span>
|
||||
</div>
|
||||
<div class="has-text-centered">
|
||||
<p class="is-size-5 mb-5">
|
||||
<router-link :to="{ path: `/member/${user.username}/home` }">
|
||||
{{ user.alias }} <span class="is-size-7 has-text-grey">{{ '@' + user.username }}</span>
|
||||
</router-link>
|
||||
</p>
|
||||
<div class="columns is-mobile">
|
||||
<div class="column is-half">
|
||||
<code>{{ user.topicCount }}</code>
|
||||
<p>文章</p>
|
||||
</div>
|
||||
<div class="column is-half">
|
||||
<code>{{ user.followerCount }}</code>
|
||||
<p>粉丝</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
v-if="hasFollow"
|
||||
class="button is-success button-center is-fullwidth"
|
||||
@click="handleUnFollow(user.id)"
|
||||
>
|
||||
已关注
|
||||
</button>
|
||||
|
||||
<button v-else class="button is-link button-center is-fullwidth" @click="handleFollow(user.id)">
|
||||
关注
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { follow, hasFollow, unFollow } from '@/api/follow'
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'Author',
|
||||
props: {
|
||||
user: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasFollow: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchInfo()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'token'
|
||||
])
|
||||
},
|
||||
methods: {
|
||||
fetchInfo() {
|
||||
if(this.token != null && this.token !== '')
|
||||
{
|
||||
hasFollow(this.user.id).then(value => {
|
||||
const { data } = value
|
||||
this.hasFollow = data.hasFollow
|
||||
})
|
||||
}
|
||||
},
|
||||
handleFollow: function(id) {
|
||||
if(this.token != null && this.token !== '')
|
||||
{
|
||||
follow(id).then(response => {
|
||||
const { message } = response
|
||||
this.$message.success(message)
|
||||
this.hasFollow = !this.hasFollow
|
||||
this.user.followerCount = parseInt(this.user.followerCount) + 1
|
||||
})
|
||||
}
|
||||
else{
|
||||
this.$message.success('请先登录')
|
||||
}
|
||||
},
|
||||
handleUnFollow: function(id) {
|
||||
unFollow(id).then(response => {
|
||||
const { message } = response
|
||||
this.$message.success(message)
|
||||
this.hasFollow = !this.hasFollow
|
||||
this.user.followerCount = parseInt(this.user.followerCount) - 1
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<div class="columns">
|
||||
<div class="column is-full">
|
||||
<el-card
|
||||
class="box-card"
|
||||
shadow="never"
|
||||
>
|
||||
<div
|
||||
slot="header"
|
||||
class="clearfix"
|
||||
>
|
||||
<span><i class="fa fa fa-book"> 主题 / 发布主题</i></span>
|
||||
</div>
|
||||
<div>
|
||||
<el-form
|
||||
ref="ruleForm"
|
||||
:model="ruleForm"
|
||||
:rules="rules"
|
||||
class="demo-ruleForm"
|
||||
>
|
||||
<el-form-item prop="title">
|
||||
<el-input
|
||||
v-model="ruleForm.title"
|
||||
placeholder="输入主题名称"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<!--Markdown-->
|
||||
<div id="vditor" />
|
||||
|
||||
<b-taginput
|
||||
v-model="ruleForm.tags"
|
||||
class="my-3"
|
||||
maxlength="15"
|
||||
maxtags="3"
|
||||
ellipsis
|
||||
placeholder="请输入主题标签,限制为 15 个字符和 3 个标签"
|
||||
/>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
@click="submitForm('ruleForm')"
|
||||
>立即创建
|
||||
</el-button>
|
||||
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { post } from '@/api/post'
|
||||
import Vditor from 'vditor'
|
||||
import 'vditor/dist/index.css'
|
||||
|
||||
export default {
|
||||
name: 'TopicPost',
|
||||
|
||||
data() {
|
||||
return {
|
||||
contentEditor: {},
|
||||
ruleForm: {
|
||||
title: '', // 标题
|
||||
tags: [], // 标签
|
||||
content: '' // 内容
|
||||
},
|
||||
rules: {
|
||||
title: [
|
||||
{ required: true, message: '请输入话题名称', trigger: 'blur' },
|
||||
{
|
||||
min: 1,
|
||||
max: 25,
|
||||
message: '长度在 1 到 25 个字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.contentEditor = new Vditor('vditor', {
|
||||
height: 500,
|
||||
placeholder: '此处为话题内容……',
|
||||
theme: 'classic',
|
||||
counter: {
|
||||
enable: true,
|
||||
type: 'markdown'
|
||||
},
|
||||
preview: {
|
||||
delay: 0,
|
||||
hljs: {
|
||||
style: 'monokai',
|
||||
lineNumber: true
|
||||
}
|
||||
},
|
||||
tab: '\t',
|
||||
typewriterMode: true,
|
||||
toolbarConfig: {
|
||||
pin: true
|
||||
},
|
||||
cache: {
|
||||
enable: false
|
||||
},
|
||||
mode: 'sv'
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
submitForm(formName) {
|
||||
this.$refs[formName].validate((valid) => {
|
||||
if (valid) {
|
||||
if (
|
||||
this.contentEditor.getValue().length === 1 ||
|
||||
this.contentEditor.getValue() == null ||
|
||||
this.contentEditor.getValue() === ''
|
||||
) {
|
||||
alert('话题内容不可为空')
|
||||
return false
|
||||
}
|
||||
if (this.ruleForm.tags == null || this.ruleForm.tags.length === 0) {
|
||||
alert('标签不可以为空')
|
||||
return false
|
||||
}
|
||||
this.ruleForm.content = this.contentEditor.getValue()
|
||||
post(this.ruleForm).then((response) => {
|
||||
const { data } = response
|
||||
setTimeout(() => {
|
||||
this.$router.push({
|
||||
name: 'post-detail',
|
||||
params: { id: data.id }
|
||||
})
|
||||
}, 800)
|
||||
})
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
this.contentEditor.setValue('')
|
||||
this.ruleForm.tags = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -0,0 +1,153 @@
|
|||
<template>
|
||||
<div class="columns">
|
||||
<!--文章详情-->
|
||||
<div class="column is-three-quarters">
|
||||
<!--主题-->
|
||||
<el-card
|
||||
class="box-card"
|
||||
shadow="never"
|
||||
>
|
||||
<div
|
||||
slot="header"
|
||||
class="has-text-centered"
|
||||
>
|
||||
<p class="is-size-5 has-text-weight-bold">{{ topic.title }}</p>
|
||||
<div class="has-text-grey is-size-7 mt-3">
|
||||
<span>{{ dayjs(topic.createTime).format('YYYY/MM/DD HH:mm:ss') }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span>发布者:{{ topicUser.alias }}</span>
|
||||
<el-divider direction="vertical" />
|
||||
<span>查看:{{ topic.view }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Markdown-->
|
||||
<div id="preview" />
|
||||
|
||||
<!--标签-->
|
||||
<nav class="level has-text-grey is-size-7 mt-6">
|
||||
<div class="level-left">
|
||||
<p class="level-item">
|
||||
<b-taglist>
|
||||
<router-link
|
||||
v-for="(tag, index) in tags"
|
||||
:key="index"
|
||||
:to="{ name: 'tag', params: { name: tag.name } }"
|
||||
>
|
||||
<b-tag type="is-info is-light mr-1">
|
||||
{{ "#" + tag.name }}
|
||||
</b-tag>
|
||||
</router-link>
|
||||
</b-taglist>
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
v-if="token && user.id === topicUser.id"
|
||||
class="level-right"
|
||||
>
|
||||
<router-link
|
||||
class="level-item"
|
||||
:to="{name:'topic-edit',params: {id:topic.id}}"
|
||||
>
|
||||
<span class="tag">编辑</span>
|
||||
</router-link>
|
||||
<a class="level-item">
|
||||
<span
|
||||
class="tag"
|
||||
@click="handleDelete(topic.id)"
|
||||
>删除</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
</el-card>
|
||||
|
||||
<lv-comments :slug="topic.id" />
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<!--作者-->
|
||||
<Author
|
||||
v-if="flag"
|
||||
:user="topicUser"
|
||||
/>
|
||||
<!--推荐-->
|
||||
<recommend
|
||||
v-if="flag"
|
||||
:topic-id="topic.id"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { deleteTopic, getTopic } from '@/api/post'
|
||||
import { mapGetters } from 'vuex'
|
||||
import Author from '@/views/post/Author'
|
||||
import Recommend from '@/views/post/Recommend'
|
||||
import LvComments from '@/components/Comment/Comments'
|
||||
import Vditor from 'vditor'
|
||||
import 'vditor/dist/index.css'
|
||||
|
||||
export default {
|
||||
name: 'TopicDetail',
|
||||
components: { Author, Recommend, LvComments },
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'token','user'
|
||||
])
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
flag: false,
|
||||
topic: {
|
||||
content: '',
|
||||
id: this.$route.params.id
|
||||
},
|
||||
tags: [],
|
||||
topicUser: {}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.fetchTopic()
|
||||
},
|
||||
methods: {
|
||||
renderMarkdown(md) {
|
||||
Vditor.preview(document.getElementById('preview'), md, {
|
||||
hljs: { style: 'github' }
|
||||
})
|
||||
},
|
||||
// 初始化
|
||||
async fetchTopic() {
|
||||
getTopic(this.$route.params.id).then(response => {
|
||||
const { data } = response
|
||||
document.title = data.topic.title
|
||||
|
||||
this.topic = data.topic
|
||||
this.tags = data.tags
|
||||
this.topicUser = data.user
|
||||
// this.comments = data.comments
|
||||
this.renderMarkdown(this.topic.content)
|
||||
this.flag = true
|
||||
})
|
||||
},
|
||||
handleDelete(id) {
|
||||
deleteTopic(id).then(value => {
|
||||
const { code, message } = value
|
||||
alert(message)
|
||||
|
||||
if (code === 200) {
|
||||
setTimeout(() => {
|
||||
this.$router.push({ path: '/' })
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
#preview {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,108 @@
|
|||
<template>
|
||||
<section>
|
||||
<div class="columns">
|
||||
<div class="column is-full">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<span><i class="fa fa fa-book"> 主题 / 更新主题</i></span>
|
||||
</div>
|
||||
<div>
|
||||
<el-form :model="topic" ref="topic" class="demo-topic">
|
||||
<el-form-item prop="title">
|
||||
<el-input
|
||||
v-model="topic.title"
|
||||
placeholder="输入新的主题名称"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!--Markdown-->
|
||||
<div id="vditor"></div>
|
||||
|
||||
<b-taginput
|
||||
v-model="tags"
|
||||
class="my-3"
|
||||
maxlength="15"
|
||||
maxtags="3"
|
||||
ellipsis
|
||||
placeholder="请输入主题标签,限制为 15 个字符和 3 个标签"
|
||||
/>
|
||||
<el-form-item class="mt-3">
|
||||
<el-button type="primary" @click="handleUpdate()"
|
||||
>更新
|
||||
</el-button>
|
||||
<el-button @click="resetForm('topic')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTopic, update } from "@/api/post";
|
||||
import Vditor from "vditor";
|
||||
import "vditor/dist/index.css";
|
||||
|
||||
export default {
|
||||
name: "TopicEdit",
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
topic: {},
|
||||
tags: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.fetchTopic();
|
||||
},
|
||||
methods: {
|
||||
renderMarkdown(md) {
|
||||
this.contentEditor = new Vditor("vditor", {
|
||||
height: 460,
|
||||
placeholder: "输入要更新的内容",
|
||||
preview: {
|
||||
hljs: { style: "monokai" },
|
||||
},
|
||||
mode: "sv",
|
||||
after: () => {
|
||||
this.contentEditor.setValue(md);
|
||||
},
|
||||
});
|
||||
},
|
||||
fetchTopic() {
|
||||
getTopic(this.$route.params.id).then((value) => {
|
||||
this.topic = value.data.topic;
|
||||
this.tags = value.data.tags.map(tag => tag.name);
|
||||
this.renderMarkdown(this.topic.content);
|
||||
});
|
||||
},
|
||||
handleUpdate: function () {
|
||||
this.topic.content = this.contentEditor.getValue();
|
||||
update(this.topic).then((response) => {
|
||||
const { data } = response;
|
||||
console.log(data);
|
||||
setTimeout(() => {
|
||||
this.$router.push({
|
||||
name: "post-detail",
|
||||
params: { id: data.id },
|
||||
});
|
||||
}, 800);
|
||||
});
|
||||
},
|
||||
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields();
|
||||
this.contentEditor.setValue("");
|
||||
this.tags = "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.vditor-reset pre > code {
|
||||
font-size: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,157 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="clearfix">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="最新主题" name="latest">
|
||||
<article v-for="(item, index) in articleList" :key="index" class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="`https://cn.gravatar.com/avatar/${item.userId}?s=164&d=monsterid`"
|
||||
style="border-radius: 5px;">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="">
|
||||
<p class="ellipsis is-ellipsis-1">
|
||||
<el-tooltip :content="item.title" class="item" effect="dark" placement="top">
|
||||
<router-link :to="{name:'post-detail',params:{id:item.id}}">
|
||||
<span class="is-size-6">{{ item.title }}</span>
|
||||
</router-link>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
<nav class="level has-text-grey is-mobile is-size-7 mt-2">
|
||||
<div class="level-left">
|
||||
<div class="level-left">
|
||||
<router-link :to="{ path: `/member/${item.username}/home` }" class="level-item">
|
||||
{{ item.alias }}
|
||||
</router-link>
|
||||
|
||||
<span class="mr-1">
|
||||
发布于:{{ dayjs(item.createTime).format("YYYY/MM/DD") }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-for="(tag, index) in item.tags"
|
||||
:key="index"
|
||||
class="tag is-hidden-mobile is-success is-light mr-1"
|
||||
>
|
||||
<router-link :to="{ name: 'tag', params: { name: tag.name } }">
|
||||
{{ "#" + tag.name }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
||||
<span class="is-hidden-mobile">浏览:{{ item.view }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="media-right"/>
|
||||
</article>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="热门主题" name="hot">
|
||||
<article v-for="(item, index) in articleList" :key="index" class="media">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="`https://cn.gravatar.com/avatar/${item.userId}?s=164&d=monsterid`"
|
||||
style="border-radius: 5px;">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div class="">
|
||||
<p class="ellipsis is-ellipsis-1">
|
||||
<el-tooltip :content="item.title" class="item" effect="dark" placement="top">
|
||||
<router-link :to="{name:'post-detail',params:{id:item.id}}">
|
||||
<span class="is-size-6">{{ item.title }}</span>
|
||||
</router-link>
|
||||
</el-tooltip>
|
||||
</p>
|
||||
</div>
|
||||
<nav class="level has-text-grey is-mobile is-size-7 mt-2">
|
||||
<div class="level-left">
|
||||
<div class="level-left">
|
||||
<router-link :to="{ path: `/member/${item.username}/home` }" class="level-item">
|
||||
{{ item.alias }}
|
||||
</router-link>
|
||||
|
||||
<span class="mr-1">
|
||||
发布于:{{ dayjs(item.createTime).format("YYYY/MM/DD") }}
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-for="(tag, index) in item.tags"
|
||||
:key="index"
|
||||
class="tag is-hidden-mobile is-success is-light mr-1"
|
||||
>
|
||||
<router-link :to="{ name: 'tag', params: { name: tag.name } }">
|
||||
{{ "#" + tag.name }}
|
||||
</router-link>
|
||||
</span>
|
||||
|
||||
<span class="is-hidden-mobile">浏览:{{ item.view }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="media-right"/>
|
||||
</article>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
<!--分页-->
|
||||
<pagination
|
||||
v-show="page.total > 0"
|
||||
:limit.sync="page.size"
|
||||
:page.sync="page.current"
|
||||
:total="page.total"
|
||||
@pagination="init"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getList} from '@/api/post'
|
||||
import Pagination from '@/components/Pagination'
|
||||
|
||||
export default {
|
||||
name: 'TopicList',
|
||||
components: {Pagination},
|
||||
data() {
|
||||
return {
|
||||
activeName: 'latest',
|
||||
articleList: [],
|
||||
page: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0,
|
||||
tab: 'latest'
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init(this.tab)
|
||||
},
|
||||
methods: {
|
||||
init(tab) {
|
||||
getList(this.page.current, this.page.size, tab).then((response) => {
|
||||
const {data} = response
|
||||
this.page.current = data.current
|
||||
this.page.total = data.total
|
||||
this.page.size = data.size
|
||||
this.articleList = data.records
|
||||
})
|
||||
},
|
||||
handleClick(tab) {
|
||||
this.page.current = 1
|
||||
this.init(tab.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<el-card class="" shadow="never">
|
||||
<div slot="header">
|
||||
<span class="has-text-weight-bold">🧐 随便看看</span>
|
||||
</div>
|
||||
<div>
|
||||
<p v-for="(item,index) in recommend" :key="index" :title="item.title" class="block ellipsis is-ellipsis-1">
|
||||
<router-link :to="{name:'post-detail',params: { id: item.id }}">
|
||||
<span v-if="index<9" class="tag">
|
||||
0{{ parseInt(index) + 1 }}
|
||||
</span>
|
||||
<span v-else class="tag">
|
||||
{{ parseInt(index) + 1 }}
|
||||
</span>
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getRecommendTopics } from '@/api/post'
|
||||
|
||||
export default {
|
||||
name: 'Recommend',
|
||||
props: {
|
||||
topicId: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
recommend: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchRecommendTopics()
|
||||
},
|
||||
methods: {
|
||||
fetchRecommendTopics() {
|
||||
getRecommendTopics(this.topicId).then(value => {
|
||||
const { data } = value
|
||||
this.recommend = data
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<div id="tag" class="columns">
|
||||
<div class="column is-three-quarters">
|
||||
<el-card class="box-card" shadow="never">
|
||||
<div slot="header" class="">
|
||||
🔍 检索到 <span class="has-text-info">{{ topics.length }}</span> 篇有关
|
||||
<span class="has-text-info">{{ this.$route.params.name }}</span>
|
||||
的话题
|
||||
</div>
|
||||
|
||||
<div class="text item">
|
||||
<article v-for="(item, index) in topics" :key="index" class="media mt-3">
|
||||
<div class="media-content">
|
||||
<div class="content">
|
||||
<el-tooltip class="item" effect="dark" :content="item.title" placement="top">
|
||||
<router-link :to="{ name: 'post-detail',params:{id: item.id } }">
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
|
||||
<nav class="level has-text-grey is-size-7">
|
||||
<div class="level-left">
|
||||
<span>发布于:{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}</span>
|
||||
|
||||
<span class="mx-3">浏览:{{ item.view }}</span>
|
||||
|
||||
<span>评论:{{ item.comments }}</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<el-card class="box-card" shadow="hover">
|
||||
<div slot="header" class="clearfix">
|
||||
🤙 关于标签
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li class="content">标签由平台用户发布使用</li>
|
||||
<li class="content">系统每周会定时清理无用标签</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card shadow="hover">
|
||||
<div slot="header">
|
||||
🏷 热门标签
|
||||
</div>
|
||||
<div>
|
||||
<ul>
|
||||
<li v-for="(tag,index) in tags" :key="index" style="padding: 6px 0">
|
||||
<router-link :to="{name:'tag',params:{name:tag.name}}">
|
||||
<span v-if="index<9" class="tag">
|
||||
0{{ parseInt(index) + 1 }}
|
||||
</span>
|
||||
<span v-else class="tag">
|
||||
{{ parseInt(index) + 1 }}
|
||||
</span>
|
||||
{{ tag.name }}
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getTopicsByTag } from '@/api/tag'
|
||||
|
||||
export default {
|
||||
name: 'Tag',
|
||||
data() {
|
||||
return {
|
||||
topics: [],
|
||||
tags: [],
|
||||
paramMap: {
|
||||
name: this.$route.params.name,
|
||||
page: 1,
|
||||
size: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchList()
|
||||
},
|
||||
methods: {
|
||||
fetchList: function() {
|
||||
getTopicsByTag(this.paramMap).then(response => {
|
||||
console.log(response)
|
||||
this.topics = response.data.topics.records
|
||||
this.tags = response.data.hotTags.records
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#tag {
|
||||
min-height: 500px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,134 @@
|
|||
<template>
|
||||
<div class="member">
|
||||
<div class="columns">
|
||||
<div class="column is-one-quarter">
|
||||
<el-card shadow="never">
|
||||
<div slot="header" class="has-text-centered">
|
||||
<el-avatar :size="64" :src="`https://cn.gravatar.com/avatar/${topicUser.id}?s=164&d=monsterid`" />
|
||||
<p class="mt-3">{{ topicUser.alias || topicUser.username }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="content">积分:<code>{{ topicUser.score }}</code></p>
|
||||
<p class="content">入驻:{{ dayjs(topicUser.createTime).format("YYYY/MM/DD HH:MM:ss") }}</p>
|
||||
<p class="content">简介:{{ topicUser.bio }}</p>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<div class="column">
|
||||
<!--用户发布的话题-->
|
||||
<el-card class="box-card content" shadow="never">
|
||||
<div slot="header" class="has-text-weight-bold">
|
||||
<span>话题</span>
|
||||
</div>
|
||||
|
||||
<div v-if="topics.length===0">
|
||||
暂无话题
|
||||
</div>
|
||||
|
||||
<div v-else class="topicUser-info">
|
||||
<article v-for="(item, index) in topics" :key="index" class="media">
|
||||
<div class="media-content">
|
||||
<div class="content ellipsis is-ellipsis-1">
|
||||
<el-tooltip class="item" effect="dark" :content="item.title" placement="top">
|
||||
<router-link :to="{ name: 'post-detail', params: { id: item.id } }">
|
||||
{{ item.title }}
|
||||
</router-link>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<nav class="level has-text-grey is-size-7">
|
||||
<div class="level-left">
|
||||
<span class="mr-1">
|
||||
发布于:{{ dayjs(item.createTime).format("YYYY/MM/DD HH:mm:ss") }}
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
<div v-if="token" class="media-right">
|
||||
<div v-if="topicUser.username === user.username" class="level">
|
||||
<div class="level-item mr-1">
|
||||
<router-link :to="{name:'topic-edit',params: {id:item.id}}">
|
||||
<span class="tag is-warning">编辑</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="level-item">
|
||||
<a @click="handleDelete(item.id)">
|
||||
<span class="tag is-danger">删除</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!--分页-->
|
||||
<pagination
|
||||
v-show="page.total > 0"
|
||||
class="mt-5"
|
||||
:total="page.total"
|
||||
:page.sync="page.current"
|
||||
:limit.sync="page.size"
|
||||
@pagination="fetchUserById"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getInfoByName } from '@/api/user'
|
||||
import pagination from '@/components/Pagination/index'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { deleteTopic } from '@/api/post'
|
||||
|
||||
export default {
|
||||
name: 'Profile',
|
||||
components: { pagination },
|
||||
data() {
|
||||
return {
|
||||
topicUser: {},
|
||||
topics: {},
|
||||
page: {
|
||||
current: 1,
|
||||
size: 5,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['token', 'user'])
|
||||
},
|
||||
created() {
|
||||
this.fetchUserById()
|
||||
},
|
||||
methods: {
|
||||
fetchUserById() {
|
||||
getInfoByName(this.$route.params.username, this.page.current, this.page.size).then((res) => {
|
||||
const { data } = res
|
||||
this.topicUser = data.user
|
||||
this.page.current = data.topics.current
|
||||
this.page.size = data.topics.size
|
||||
this.page.total = data.topics.total
|
||||
this.topics = data.topics.records
|
||||
})
|
||||
},
|
||||
handleDelete(id) {
|
||||
deleteTopic(id).then(value => {
|
||||
const { code, message } = value
|
||||
alert(message)
|
||||
|
||||
if (code === 200) {
|
||||
setTimeout(() => {
|
||||
this.$router.push({ path: '/' })
|
||||
}, 500)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<section>
|
||||
<el-card shadow="never">
|
||||
<div slot="header">
|
||||
个人设置
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column is-full">
|
||||
<el-tabs v-model="activeName" @tab-click="handleClick">
|
||||
<el-tab-pane label="基础信息" name="first">
|
||||
<el-form :label-position="labelPosition" label-width="100px" :model="user">
|
||||
<el-form-item label="账号">
|
||||
<el-input v-model="user.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="昵称">
|
||||
<el-input v-model="user.alias" />
|
||||
</el-form-item>
|
||||
<el-form-item label="简介">
|
||||
<el-input v-model="user.bio" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
|
||||
<el-button @click="resetForm('ruleForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="头像" name="second">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="`https://cn.gravatar.com/avatar/${this.user.id}?s=164&d=monsterid`">
|
||||
</figure>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="电子邮箱" name="third">
|
||||
<el-form ref="dynamicValidateForm" :model="user" label-width="100px" class="demo-dynamic">
|
||||
<el-form-item
|
||||
prop="email"
|
||||
label="邮箱"
|
||||
:rules="[
|
||||
{ required: true, message: '请输入邮箱地址', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }
|
||||
]"
|
||||
>
|
||||
<el-input v-model="user.email" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('dynamicValidateForm')">提交</el-button>
|
||||
<el-button @click="resetForm('dynamicValidateForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="手机号" name="fourth">
|
||||
<el-form ref="dynamicValidateForm" :model="user" label-width="100px" class="demo-dynamic">
|
||||
<el-form-item>
|
||||
<el-input v-model="user.mobile" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="submitForm('dynamicValidateForm')">提交</el-button>
|
||||
<el-button @click="resetForm('dynamicValidateForm')">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getInfo, update} from '@/api/user'
|
||||
|
||||
export default {
|
||||
name: 'Setting',
|
||||
data() {
|
||||
return {
|
||||
activeName: 'first',
|
||||
labelPosition: 'right',
|
||||
user: {
|
||||
id: '',
|
||||
username: '',
|
||||
alias: '',
|
||||
bio: '',
|
||||
email: '',
|
||||
mobile: '',
|
||||
avatar: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchInfo()
|
||||
},
|
||||
methods: {
|
||||
fetchInfo() {
|
||||
getInfo(this.$route.params.username).then(res => {
|
||||
console.log(res)
|
||||
const { data } = res
|
||||
this.user = data
|
||||
})
|
||||
},
|
||||
handleClick(tab, event) {
|
||||
console.log(tab, event)
|
||||
},
|
||||
submitForm(formName) {
|
||||
console.log(this.user)
|
||||
update(this.user).then(res => {
|
||||
this.$message.success('信息修改成功')
|
||||
this.fetchInfo()
|
||||
})
|
||||
},
|
||||
resetForm(formName) {
|
||||
this.$refs[formName].resetFields()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue