2025-04-06 21:59:05 +08:00

978 lines
23 KiB
Vue

<template>
<div class="page-header-wrapper-grid-content-main custom-scrollbar">
<!-- 数据统计卡片 -->
<div class="data-stats animate-fade-in-up">
<div class="stat-card glow-border">
<div class="stat-number">{{ articleCount }}</div>
<div class="stat-title">发布文章</div>
</div>
<div class="stat-card glow-border">
<div class="stat-number">{{ viewCount }}</div>
<div class="stat-title">文章浏览</div>
</div>
<div class="stat-card glow-border">
<div class="stat-number">{{ likeCount }}</div>
<div class="stat-title">获得点赞</div>
</div>
<div class="stat-card glow-border">
<div class="stat-number">{{ matchs.length }}</div>
<div class="stat-title">参与比赛</div>
</div>
</div>
<a-row :gutter="24">
<a-col :md="24" :lg="8">
<a-card
:bordered="false"
class="hover-card animate-fade-in-up"
style="animation-delay: 0.1s"
>
<div class="account-center-avatarHolder">
<div class="avatar">
<img
src="https://onlinephoto.oss-cn-chengdu.aliyuncs.com/hangtian/touxiang.jpg"
data-src="//www.cmstui.com/wp-content/themes/zibll/img/avatar-default.png"
class="lazyload avatar avatar-id-1"
alt="用户头像"
/>
</div>
<div class="username">{{ $store.state.user.userName }}</div>
<div class="bio">{{ shenfen }}</div>
</div>
<a-divider />
<div class="account-center-tags">
<div class="tagsTitle">个人标签</div>
<div>
<template v-for="(tag, index) in tags">
<a-tooltip v-if="tag.length > 20" :key="tag" :title="tag">
<a-tag
:key="tag"
:closable="index !== 0"
@close="() => handleTagClose(tag)"
>{{ `${tag.slice(0, 20)}...` }}</a-tag
>
</a-tooltip>
<a-tag
v-else
:key="tag"
:closable="index !== 0"
@close="() => handleTagClose(tag)"
>{{ tag }}</a-tag
>
</template>
<a-input
v-if="tagInputVisible"
ref="tagInput"
type="text"
size="small"
:style="{ width: '78px' }"
:value="tagInputValue"
@change="handleInputChange"
@blur="handleTagInputConfirm"
@keyup.enter="handleTagInputConfirm"
/>
<a-tag
v-else
@click="showTagInput"
style="background: #fff; borderstyle: dashed"
>
<a-icon type="plus" />添加标签
</a-tag>
</div>
</div>
<a-divider :dashed="true" />
<div class="account-center-team">
<div class="teamTitle">我的比赛</div>
<a-empty v-if="matchs.length === 0" description="暂无参赛记录" />
<a-spin :spinning="teamSpinning">
<div class="members" v-for="(item, index) in matchs" :key="index">
<a-card
class="match-card"
:bordered="false"
:bodyStyle="{ padding: '12px' }"
>
<div class="match-title">
<a-icon
type="trophy"
:style="{ color: colors[index % colors.length] }"
/>
<span>{{ item.competitionName }}</span>
</div>
<div class="match-info">
<a-tag :color="colors[index % colors.length]">
{{ item.competitionType }}
</a-tag>
<span class="match-date">{{
formatDate(item.registrationTime)
}}</span>
</div>
</a-card>
</div>
</a-spin>
</div>
</a-card>
</a-col>
<a-col :md="24" :lg="16">
<a-card
class="animate-fade-in-up"
style="animation-delay: 0.2s; width: 100%"
:bordered="false"
:tabList="tabListNoTitle"
:activeTabKey="noTitleKey"
@tabChange="(key) => handleTabChange(key, 'noTitleKey')"
>
<div v-if="noTitleKey === 'article'">
<!-- 文章筛选和排序 -->
<div class="article-filter">
<a-radio-group
v-model="articleFilter"
buttonStyle="solid"
@change="filterArticles"
>
<a-radio-button value="all">全部文章</a-radio-button>
<a-radio-button value="hot">热门文章</a-radio-button>
<a-radio-button value="recent">最近发布</a-radio-button>
</a-radio-group>
<a-button type="primary" icon="plus" @click="createNewArticle">
发布文章
</a-button>
</div>
<!-- 文章列表 -->
<div class="article-list">
<a-empty v-if="articles.length === 0" description="暂无文章" />
<div v-else>
<div
class="article-item"
v-for="(article, index) in articles"
:key="index"
@click="viewArticle(article.id)"
>
<div class="article-cover">
<img :src="getArticleImage(article)" :alt="article.title" />
</div>
<div class="article-content">
<div class="article-title">{{ article.articleTitle }}</div>
<div
class="article-description"
v-html="getArticleExcerpt(article.articleContent)"
></div>
<div class="article-meta">
<div class="article-date">
<a-icon type="calendar" />
{{ formatDate(article.publishTime) }}
</div>
<div class="article-stats">
<span
><a-icon type="eye" />
{{ article.viewCount || 0 }}</span
>
<span style="margin-left: 16px"
><a-icon type="like" />
{{ article.likeCount || 0 }}</span
>
<span style="margin-left: 16px"
><a-icon type="message" />
{{ article.commentCount || 0 }}</span
>
</div>
</div>
</div>
</div>
</div>
<!-- 分页 -->
<div class="pagination-container" v-if="articles.length > 0">
<a-pagination
:current="current"
:pageSize="pageSize"
:total="total"
@change="handlePageChange"
showQuickJumper
:showSizeChanger="true"
:pageSizeOptions="['6', '12', '24', '36']"
@showSizeChange="onShowSizeChange"
/>
</div>
</div>
</div>
<!-- 其他标签页内容 -->
<div v-else-if="noTitleKey === 'app'">
<a-empty description="功能开发中..." />
</div>
<div v-else-if="noTitleKey === 'project'">
<a-empty description="功能开发中..." />
</div>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script>
import PageView from "../../../layouts/PageView.vue";
import RouteView from "../../../layouts/RouteView.vue";
import { AppPage, ArticlePage, ProjectPage } from "./page";
import { mapGetters } from "vuex";
export default {
components: {
RouteView,
PageView,
AppPage,
ArticlePage,
ProjectPage,
},
data() {
return {
userimg: this.$store.state.avatar,
tags: ["技术爱好者", "前端开发", "生活记录"],
tagInputVisible: false,
tagInputValue: "",
teamSpinning: true,
shenfen: "",
tabListNoTitle: [
{
key: "article",
tab: "我的文章",
},
{
key: "app",
tab: "我的收藏",
},
{
key: "project",
tab: "我的评论",
},
],
noTitleKey: "article",
matchs: [],
colors: ["#f50", "#2db7f5", "#87d068", "#108ee9", "#722ed1"],
// 文章相关数据
articles: [],
current: 1,
pageSize: 6,
total: 0,
articleFilter: "all",
// 统计数据
articleCount: 0,
viewCount: 0,
likeCount: 0,
};
},
computed: {
...mapGetters(["nickname", "avatar"]),
},
mounted() {
// 设置用户身份
if (this.$store.state.user.userPrivileges == 0) {
this.shenfen = "管理员";
} else if (this.$store.state.user.userPrivileges == 1) {
this.shenfen = "教师";
} else {
this.shenfen = "学生";
}
// 获取比赛信息
this.fetchCompetitions();
// 获取文章列表
this.fetchArticles();
// 获取统计数据
this.fetchStats();
},
methods: {
// 获取比赛信息
fetchCompetitions() {
this.teamSpinning = true;
this.$api
.RegistrationAll()
.then((res) => {
this.matchs = res.data.filter((match) => {
return match.studentId == this.$store.state.user.userId;
});
this.teamSpinning = false;
})
.catch((err) => {
console.error("获取比赛信息失败:", err);
this.teamSpinning = false;
});
},
// 获取文章列表
fetchArticles() {
this.$api
.AllArticle()
.then((res) => {
// 过滤出当前用户的文章
const userArticles = res.data.filter((article) => {
return article.userId === this.$store.state.user.userId;
});
// 根据筛选条件排序
this.filterArticlesByCondition(userArticles);
// 设置总数
this.total = userArticles.length;
this.articleCount = userArticles.length;
// 计算总浏览量和点赞数
this.calculateStats(userArticles);
})
.catch((err) => {
console.error("获取文章列表失败:", err);
});
},
// 根据筛选条件过滤文章
filterArticlesByCondition(articles) {
let filteredArticles = [...articles];
// 应用筛选条件
switch (this.articleFilter) {
case "hot":
// 按热度(浏览量)排序
filteredArticles.sort(
(a, b) => (b.viewCount || 0) - (a.viewCount || 0)
);
break;
case "recent":
// 按发布时间排序
filteredArticles.sort(
(a, b) => new Date(b.publishTime) - new Date(a.publishTime)
);
break;
default:
// 默认按发布时间排序
filteredArticles.sort(
(a, b) => new Date(b.publishTime) - new Date(a.publishTime)
);
}
// 分页处理
const startIndex = (this.current - 1) * this.pageSize;
const endIndex = startIndex + this.pageSize;
this.articles = filteredArticles.slice(startIndex, endIndex);
},
// 计算统计数据
calculateStats(articles) {
this.viewCount = articles.reduce(
(sum, article) => sum + (article.viewCount || 0),
0
);
this.likeCount = articles.reduce(
(sum, article) => sum + (article.likeCount || 0),
0
);
},
// 获取统计数据
fetchStats() {
// 如果有专门的API可以在这里调用
},
// 筛选文章
filterArticles() {
this.current = 1; // 重置页码
this.fetchArticles();
},
// 分页处理
handlePageChange(page) {
this.current = page;
this.fetchArticles();
},
// 每页显示数量变化
onShowSizeChange(current, size) {
this.current = 1;
this.pageSize = size;
this.fetchArticles();
},
// 获取文章封面图
getArticleImage(article) {
if (!article.articleContent) return require("@/assets/bg.svg");
// 从文章内容中提取图片
const imageRegex = /(http[s]?:\/\/[^(\s|")]+\.(png|jpg|jpeg|gif|webp))/gi;
const matches = article.articleContent.match(imageRegex);
// 如果找到图片,返回第一张
if (matches && matches.length > 0) {
return matches[0];
}
const randomSeed = Math.floor(Math.random() * 10000);
// 如果没有找到匹配的图片,可以使用随机图片
return "https://tu.ltyuanfang.cn/api/fengjing.php?" + randomSeed;
},
// 获取文章摘要
getArticleExcerpt(content) {
if (!content) return "";
// 移除HTML标签
const plainText = content.replace(/<[^>]+>/g, "");
// 截取前100个字符作为摘要
return (
plainText.substring(0, 100) + (plainText.length > 100 ? "..." : "")
);
},
// 格式化日期
formatDate(dateString) {
if (!dateString) return "";
const date = new Date(dateString);
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(
2,
"0"
)}-${String(date.getDate()).padStart(2, "0")}`;
},
// 查看文章详情
viewArticle(id) {
this.$router.push(`/community/pages?id=${id}`);
},
// 创建新文章
createNewArticle() {
this.$router.push("/write");
},
// 标签相关方法
handleTabChange(key, type) {
this[type] = key;
},
handleTagClose(removeTag) {
const tags = this.tags.filter((tag) => tag !== removeTag);
this.tags = tags;
},
showTagInput() {
this.tagInputVisible = true;
this.$nextTick(() => {
this.$refs.tagInput.focus();
});
},
handleInputChange(e) {
this.tagInputValue = e.target.value;
},
handleTagInputConfirm() {
const inputValue = this.tagInputValue;
let tags = this.tags;
if (inputValue && !tags.includes(inputValue)) {
tags = [...tags, inputValue];
}
Object.assign(this, {
tags,
tagInputVisible: false,
tagInputValue: "",
});
},
},
};
</script>
<style lang="less" scoped>
/* 论坛个人中心美化样式 */
/* 整体页面背景 */
.page-header-wrapper-grid-content-main {
width: 100%;
min-height: 100vh;
background-color: #f6f8fa;
background-image: linear-gradient(
to bottom,
rgba(240, 244, 248, 0.8),
rgba(255, 255, 255, 0.8)
);
padding: 24px;
}
/* 卡片通用样式 */
.ant-card {
border-radius: 16px;
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
overflow: hidden;
transition: all 0.4s ease;
margin-bottom: 24px;
border: none;
}
.ant-card:hover {
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transform: translateY(-5px);
}
/* 左侧个人信息卡片 */
.account-center-avatarHolder {
display: flex;
flex-direction: column;
align-items: center;
padding: 32px 0 16px;
position: relative;
}
/* 背景装饰 */
.account-center-avatarHolder::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 120px;
background: linear-gradient(135deg, #3690cf, #2a78b8);
z-index: 0;
border-radius: 16px 16px 50% 50% / 16px 16px 25% 25%;
}
/* 头像样式 */
.account-center-avatarHolder .avatar {
width: 120px;
height: 120px;
border-radius: 50%;
margin-bottom: 24px;
border: 5px solid #fff;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
overflow: hidden;
position: relative;
z-index: 1;
background: #fff;
transition: all 0.3s ease;
}
.account-center-avatarHolder .avatar:hover {
transform: scale(1.05);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
}
.account-center-avatarHolder .avatar img {
width: 100%;
height: 100%;
object-fit: cover;
transition: all 0.5s ease;
}
.account-center-avatarHolder .avatar:hover img {
transform: scale(1.1);
}
/* 用户名样式 */
.account-center-avatarHolder .username {
color: #2c3e50;
font-size: 24px;
font-weight: 600;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
/* 身份标签 */
.account-center-avatarHolder .bio {
display: inline-block;
padding: 4px 12px;
background: rgba(54, 144, 207, 0.1);
color: #3690cf;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
position: relative;
z-index: 1;
}
/* 分割线美化 */
.ant-divider {
margin: 24px 0;
border-color: rgba(0, 0, 0, 0.06);
}
.ant-divider-dashed {
border-style: dashed;
border-color: rgba(0, 0, 0, 0.09);
}
/* 标签区域 */
.account-center-tags {
padding: 0 16px;
}
.account-center-tags .tagsTitle {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 16px;
position: relative;
padding-left: 12px;
}
.account-center-tags .tagsTitle::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 4px;
background: #3690cf;
border-radius: 2px;
}
/* 标签样式 */
.ant-tag {
margin: 0 8px 8px 0;
padding: 4px 12px;
border-radius: 16px;
font-size: 13px;
border: none;
background: #f0f2f5;
color: #555;
transition: all 0.3s;
}
.ant-tag:hover {
background: #e6f7ff;
color: #3690cf;
}
/* 新增标签按钮 */
.ant-tag-dashed {
border: 1px dashed #d9d9d9;
background: transparent;
transition: all 0.3s;
}
.ant-tag-dashed:hover {
border-color: #3690cf;
color: #3690cf;
}
/* 比赛区域 */
.account-center-team {
padding: 0 16px;
}
.account-center-team .teamTitle {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
margin-bottom: 16px;
position: relative;
padding-left: 12px;
}
.account-center-team .teamTitle::before {
content: "";
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 4px;
background: #3690cf;
border-radius: 2px;
}
/* 比赛卡片样式 */
.members {
margin-bottom: 16px;
transition: all 0.3s;
}
.members .ant-tag {
display: inline-block;
margin: 5px;
padding: 6px 12px;
border-radius: 8px;
font-size: 14px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s;
}
.members .ant-tag:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
}
/* 右侧选项卡样式 */
.ant-tabs-bar {
border-bottom: none;
margin-bottom: 24px;
}
.ant-tabs-nav {
margin: 0 auto 16px;
width: fit-content;
background: #f5f5f5;
border-radius: 50px;
padding: 4px;
}
.ant-tabs-tab {
margin: 0 !important;
padding: 8px 24px !important;
border-radius: 50px !important;
transition: all 0.3s;
font-weight: 500;
}
.ant-tabs-tab-active {
background: #3690cf !important;
color: white !important;
}
/* 文章列表样式 */
.article-list {
padding: 16px;
}
.article-item {
display: flex;
margin-bottom: 20px;
background: white;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
}
.article-item:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}
.article-cover {
width: 200px;
height: 134px;
overflow: hidden;
}
.article-cover img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s;
}
.article-item:hover .article-cover img {
transform: scale(1.1);
}
.article-content {
flex: 1;
padding: 16px;
position: relative;
}
.article-title {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: #333;
}
.article-description {
font-size: 14px;
color: #666;
margin-bottom: 16px;
line-height: 1.6;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.article-meta {
position: absolute;
bottom: 16px;
left: 16px;
right: 16px;
display: flex;
justify-content: space-between;
font-size: 12px;
color: #999;
}
/* 数据统计卡片 */
.data-stats {
display: flex;
margin-bottom: 24px;
}
.stat-card {
flex: 1;
background: white;
border-radius: 12px;
padding: 16px;
text-align: center;
margin: 0 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
transition: all 0.3s;
}
.stat-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}
.stat-number {
font-size: 32px;
font-weight: 600;
color: #3690cf;
margin-bottom: 8px;
}
.stat-title {
font-size: 14px;
color: #666;
}
/* 响应式调整 */
@media (max-width: 768px) {
.article-cover {
width: 120px;
}
.stat-card {
padding: 12px;
}
.stat-number {
font-size: 24px;
}
}
@media (max-width: 480px) {
.article-item {
flex-direction: column;
}
.article-cover {
width: 100%;
height: 180px;
}
.data-stats {
flex-wrap: wrap;
}
.stat-card {
flex: 0 0 calc(50% - 16px);
margin-bottom: 16px;
}
}
/* 悬浮效果 */
.hover-card {
position: relative;
overflow: hidden;
}
.hover-card::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.1);
transform: translateX(-100%) rotate(45deg);
transition: transform 0.6s;
}
.hover-card:hover::after {
transform: translateX(100%) rotate(45deg);
}
/* 动画效果 */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-up {
animation: fadeInUp 0.6s ease forwards;
}
/* 更多自定义样式 */
.custom-scrollbar::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.custom-scrollbar::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 10px;
}
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
background: #3690cf;
}
/* 闪光边框效果 */
.glow-border {
position: relative;
}
.glow-border::before {
content: "";
position: absolute;
top: -2px;
left: -2px;
right: -2px;
bottom: -2px;
background: linear-gradient(45deg, #3690cf, #6dc6ff, #3690cf);
background-size: 200% 200%;
z-index: -1;
border-radius: 18px;
animation: glowingBorder 3s ease-in-out infinite;
opacity: 0;
transition: opacity 0.3s ease;
}
.glow-border:hover::before {
opacity: 1;
}
@keyframes glowingBorder {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
</style>