newfront/src/views/ArticleView.vue
2025-12-02 20:45:25 +08:00

328 lines
7.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<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>