328 lines
7.8 KiB
Vue
328 lines
7.8 KiB
Vue
<template>
|
||
<div class="article-view">
|
||
<Navbar />
|
||
<div class="container" v-if="article">
|
||
<h1 class="article-title">{{ isPreviewMode ? '草稿预览' : (article.title || '未知文章') }}</h1>
|
||
<div class="article-tags" style="margin-left: 3rem" v-if="!isPreviewMode">
|
||
<span v-if="article.official" class="article-official">官方</span>
|
||
<span v-if="article.highlight" class="article-highlight">优质</span>
|
||
<span v-if="article.top" class="article-top">置顶</span>
|
||
</div>
|
||
<div class="meta-wrapper" style="margin-left: 3rem">
|
||
<div class="article-meta">
|
||
<div class="article-author-info">
|
||
<div class="article-author">
|
||
<div v-if="article.co_authors && article.co_authors.length > 0" class="co-contributors-label">
|
||
🏅 {{ article.co_authors.length + 1 }} 位共同贡献者
|
||
</div>
|
||
<div v-else class="co-contributors-label">
|
||
🏅 贡献者
|
||
</div>
|
||
<UserMeta :id="article.user_id" />
|
||
<template v-if="article.co_authors && article.co_authors.length > 0">
|
||
<UserMeta v-for="authorId in article.co_authors" :key="authorId" :id="authorId" />
|
||
</template>
|
||
</div>
|
||
<span class="article-date">📅 最后更新于 {{ formatDate(article.updated_at) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<MarkdownArticle :content="article.content" />
|
||
|
||
<!-- 评论区组件,仅在非预览模式下显示 -->
|
||
<CommentSection v-if="!isPreviewMode" :articleId="$route.params.id" />
|
||
</div>
|
||
<div class="container" v-else-if="loading">
|
||
<Loading :visible="true" :text="'内容加载中,请稍后...'" />
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import Navbar from '@/components/NavBar.vue';
|
||
import MarkdownArticle from '@/components/MarkdownArticle.vue';
|
||
import Attachment from '@/components/Attachment.vue';
|
||
import Loading from '@/components/Loading.vue';
|
||
import UserMeta from '@/components/UserMeta.vue';
|
||
import CommentSection from '@/components/Comment.vue';
|
||
import login from '@/utils/login.js';
|
||
import Cookies from 'js-cookie';
|
||
|
||
export default {
|
||
name: 'ArticleView',
|
||
components: {
|
||
Navbar,
|
||
MarkdownArticle,
|
||
Attachment,
|
||
Loading,
|
||
UserMeta,
|
||
CommentSection
|
||
},
|
||
data() {
|
||
return {
|
||
article: null,
|
||
loading: true,
|
||
errorMessage: ''
|
||
};
|
||
},
|
||
computed: {
|
||
isPreviewMode() {
|
||
return this.$route.name === 'preview';
|
||
}
|
||
},
|
||
created() {
|
||
// 根据当前路由决定加载文章详情或草稿预览
|
||
if (this.isPreviewMode) {
|
||
this.fetchDraft();
|
||
} else {
|
||
this.fetchArticle();
|
||
}
|
||
},
|
||
methods: {
|
||
async fetchArticle() {
|
||
this.loading = true;
|
||
try {
|
||
// 从路由参数获取文章ID
|
||
const articleId = this.$route.params.id;
|
||
const token = Cookies.get('token');
|
||
const fetchArticleHeaders = token ? {
|
||
'Authorization': token
|
||
} : {};
|
||
|
||
// 使用fetch API代替axios
|
||
const response = await fetch(`https://newfront.xn--xhq44jb2fzpc.com/article?id=${articleId}`, {
|
||
headers: fetchArticleHeaders
|
||
});
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}));
|
||
throw new Error(errorData.error || `HTTP错误: ${response.status}`);
|
||
}
|
||
this.article = await response.json();
|
||
document.title = this.article?.title + ' - NEU小站';
|
||
} catch (error) {
|
||
console.error('获取文章失败:', error);
|
||
// 重定向到404页面
|
||
this.$router.push({ name: '404' });
|
||
return; // 提前返回,不再设置loading为false
|
||
} finally {
|
||
if (!this.$router.currentRoute.name === '404') {
|
||
this.loading = false;
|
||
}
|
||
}
|
||
},
|
||
|
||
async fetchDraft() {
|
||
this.loading = true;
|
||
try {
|
||
const token = Cookies.get('token');
|
||
|
||
if (!token) {
|
||
throw new Error('未登录,无法预览草稿');
|
||
}
|
||
|
||
const response = await fetch('https://userlogin.xn--xhq44jb2fzpc.com/submission/load-draft', {
|
||
method: 'GET',
|
||
headers: {
|
||
'Authorization': token
|
||
}
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorData = await response.json().catch(() => ({}));
|
||
throw new Error(errorData.message || `HTTP错误: ${response.status}`);
|
||
}
|
||
|
||
const draftData = await response.json();
|
||
|
||
// 创建一个包含必要字段的文章对象,包括 user_id
|
||
this.article = {
|
||
title: '草稿预览',
|
||
content: draftData.content,
|
||
user_id: draftData.user_id, // 使用接口返回的 user_id
|
||
updated_at: new Date() // 使用当前时间
|
||
};
|
||
|
||
document.title = '草稿预览 - NEU小站';
|
||
} catch (error) {
|
||
console.error('获取草稿失败:', error);
|
||
// 重定向到404页面
|
||
this.$router.push({ name: '404' });
|
||
return;
|
||
} finally {
|
||
if (!this.$router.currentRoute.name === '404') {
|
||
this.loading = false;
|
||
}
|
||
}
|
||
},
|
||
|
||
formatDate(dateString) {
|
||
if (!dateString) return '';
|
||
const date = new Date(dateString);
|
||
return date.toLocaleDateString('zh-CN', {
|
||
year: 'numeric',
|
||
month: '2-digit',
|
||
day: '2-digit'
|
||
});
|
||
}
|
||
}
|
||
};
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.article-view {
|
||
background-color: #fafafa;
|
||
min-height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding-top: 60px;
|
||
}
|
||
|
||
.container {
|
||
width: 98%;
|
||
margin: 0 auto;
|
||
padding: 2rem 1rem;
|
||
flex: 1;
|
||
}
|
||
|
||
.article-title {
|
||
font-size: 2.5rem;
|
||
font-weight: 700;
|
||
margin-bottom: 1rem;
|
||
color: #333;
|
||
}
|
||
|
||
.meta-wrapper {
|
||
display: flex;
|
||
}
|
||
|
||
.article-meta {
|
||
display: inline-flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 1.5rem;
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
background-color: #fff;
|
||
border-radius: 10px;
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
||
padding: 16px 20px;
|
||
position: relative;
|
||
width: auto;
|
||
max-width: 100%;
|
||
margin-bottom: 0;
|
||
border-left: 4px solid #3273dc;
|
||
}
|
||
|
||
.article-author-info {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.article-tags {
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.article-author {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
}
|
||
|
||
.co-contributors-label {
|
||
font-size: .9rem;
|
||
color: #555;
|
||
margin-bottom: 4px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.article-official, .article-highlight, .article-top {
|
||
padding: 3px 10px;
|
||
border-radius: 4px;
|
||
font-size: 0.8rem;
|
||
font-weight: 500;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.article-official {
|
||
background-color: #e6f7ff;
|
||
color: #1890ff;
|
||
}
|
||
|
||
.article-highlight {
|
||
background-color: #f6ffed;
|
||
color: #52c41a;
|
||
}
|
||
|
||
.article-top {
|
||
background-color: #fff7e6;
|
||
color: #fa8c16;
|
||
}
|
||
|
||
.loading-spinner {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
font-size: 1.1rem;
|
||
color: #666;
|
||
}
|
||
|
||
.error-message {
|
||
text-align: center;
|
||
padding: 2rem;
|
||
color: #f5222d;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
.article-date {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
color: #888;
|
||
}
|
||
|
||
.date-icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.container {
|
||
padding: 1rem;
|
||
padding-top: 1.5rem;
|
||
}
|
||
|
||
.article-title {
|
||
font-size: 2rem;
|
||
}
|
||
|
||
.meta-wrapper {
|
||
display: flex;
|
||
width: 100%;
|
||
}
|
||
|
||
.article-meta {
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 1rem;
|
||
padding: 14px 16px;
|
||
display: inline-flex;
|
||
width: auto;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.article-tags {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.article-author-info {
|
||
margin-left: 0;
|
||
width: 100%;
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 0.8rem;
|
||
}
|
||
}
|
||
</style> |