style(community): 社区全面对齐阿里云开发者社区风格——深蓝 Hero+板块网格+Feed 列表+侧栏(统计/热帖/公告)、详情页与发帖页统一阿里云蓝卡片风格
This commit is contained in:
1 parent
30752367f6
commit
ce8ce23d34
4 files changed
+789
-224
No files matched your search
@@ -0,0 +1,394 @@
|
||||
/* ============================================================
|
||||
* GVSDS 社区 · 阿里云开发者社区风格(developer.aliyun.com)
|
||||
*
|
||||
* 设计基调:
|
||||
* - 主色:阿里云蓝 #0064c8(hover #0a7ae0),深蓝黑导航 #0d1b2a
|
||||
* - 页面浅灰底 #f5f6f7 + 白卡片(4px 圆角、#e5e6eb 细边框)
|
||||
* - 标签:浅蓝底(#e6f0ff)+ 蓝字;标题 hover 变蓝
|
||||
* - 信息密度高、克制、无装饰性大阴影
|
||||
* ============================================================ */
|
||||
|
||||
/* ---------- 1. 设计变量 ---------- */
|
||||
:root {
|
||||
--ali-blue: #0064c8;
|
||||
--ali-blue-hover: #0a7ae0;
|
||||
--ali-blue-light: #e6f0ff;
|
||||
--ali-blue-deep: #0d1b2a;
|
||||
--ali-bg: #f5f6f7;
|
||||
--ali-card-border: #e5e6eb;
|
||||
--ali-text: #1a1a1a;
|
||||
--ali-text-2: #666;
|
||||
--ali-text-3: #999;
|
||||
}
|
||||
|
||||
/* ---------- 2. 页面骨架 ---------- */
|
||||
.gvs-community {
|
||||
min-height: 100vh;
|
||||
background: var(--ali-bg);
|
||||
color: var(--ali-text);
|
||||
}
|
||||
|
||||
.gvs-community .gvs-container {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
/* ---------- 3. Hero 横幅 ---------- */
|
||||
.gvs-hero {
|
||||
background: linear-gradient(120deg, #0d1b2a 0%, #123c5e 60%, #0064c8 130%);
|
||||
color: #fff;
|
||||
padding: 44px 0 40px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gvs-hero::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: -120px;
|
||||
top: -120px;
|
||||
width: 360px;
|
||||
height: 360px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(0, 161, 255, 0.25) 0%, transparent 70%);
|
||||
}
|
||||
.gvs-hero-title {
|
||||
font-size: 28px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
.gvs-hero-sub {
|
||||
margin-top: 8px;
|
||||
font-size: 14px;
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
}
|
||||
.gvs-hero-entries {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 26px;
|
||||
max-width: 860px;
|
||||
}
|
||||
.gvs-hero-entry {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border: 1px solid rgba(255, 255, 255, 0.14);
|
||||
border-radius: 4px;
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: #fff;
|
||||
}
|
||||
.gvs-hero-entry:hover {
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.gvs-hero-entry i {
|
||||
font-size: 20px;
|
||||
color: #7cc0ff;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
.gvs-hero-entry b {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
display: block;
|
||||
}
|
||||
.gvs-hero-entry span {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
/* ---------- 4. 卡片 ---------- */
|
||||
.gvs-card {
|
||||
background: #fff;
|
||||
border: 1px solid var(--ali-card-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.gvs-card-hd {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--ali-card-border);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.gvs-card-hd i {
|
||||
color: var(--ali-blue);
|
||||
}
|
||||
|
||||
/* ---------- 5. 标签 ---------- */
|
||||
.gvs-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 1px 8px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
background: var(--ali-blue-light);
|
||||
color: var(--ali-blue);
|
||||
line-height: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.gvs-tag.gray {
|
||||
background: #f2f3f5;
|
||||
color: var(--ali-text-2);
|
||||
}
|
||||
.gvs-tag.red {
|
||||
background: #fdecec;
|
||||
color: #d54941;
|
||||
}
|
||||
.gvs-tag.green {
|
||||
background: #e6f7ed;
|
||||
color: #23a858;
|
||||
}
|
||||
|
||||
/* ---------- 6. 按钮 ---------- */
|
||||
.gvs-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
border: 1px solid var(--ali-blue);
|
||||
border-radius: 4px;
|
||||
padding: 7px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
line-height: 1;
|
||||
}
|
||||
.gvs-btn-primary {
|
||||
background: var(--ali-blue);
|
||||
color: #fff;
|
||||
}
|
||||
.gvs-btn-primary:hover {
|
||||
background: var(--ali-blue-hover);
|
||||
}
|
||||
.gvs-btn-ghost {
|
||||
background: #fff;
|
||||
color: var(--ali-blue);
|
||||
}
|
||||
.gvs-btn-ghost:hover {
|
||||
border-color: var(--ali-blue-hover);
|
||||
color: var(--ali-blue-hover);
|
||||
background: var(--ali-blue-light);
|
||||
}
|
||||
.gvs-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ---------- 7. 板块网格 ---------- */
|
||||
.gvs-board-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.gvs-board-item {
|
||||
border: 1px solid var(--ali-card-border);
|
||||
border-radius: 4px;
|
||||
padding: 16px;
|
||||
background: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
}
|
||||
.gvs-board-item:hover {
|
||||
border-color: var(--ali-blue);
|
||||
box-shadow: 0 2px 8px rgba(0, 100, 200, 0.08);
|
||||
}
|
||||
.gvs-board-item.active {
|
||||
border-color: var(--ali-blue);
|
||||
background: var(--ali-blue-light);
|
||||
}
|
||||
.gvs-board-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 4px;
|
||||
background: var(--ali-blue-light);
|
||||
color: var(--ali-blue);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gvs-board-name {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.gvs-board-meta {
|
||||
font-size: 12px;
|
||||
color: var(--ali-text-3);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* ---------- 8. Feed 列表 ---------- */
|
||||
.gvs-feed-item {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid #f0f1f3;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.gvs-feed-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.gvs-feed-item:hover {
|
||||
background: #fafbfc;
|
||||
}
|
||||
.gvs-feed-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background: var(--ali-blue-light);
|
||||
color: var(--ali-blue);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gvs-feed-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.gvs-feed-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--ali-text);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
.gvs-feed-item:hover .gvs-feed-title {
|
||||
color: var(--ali-blue);
|
||||
}
|
||||
.gvs-feed-desc {
|
||||
font-size: 13px;
|
||||
color: var(--ali-text-2);
|
||||
margin-top: 4px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.gvs-feed-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--ali-text-3);
|
||||
}
|
||||
.gvs-feed-meta i {
|
||||
margin-right: 3px;
|
||||
}
|
||||
|
||||
/* ---------- 9. 分页 ---------- */
|
||||
.gvs-pager {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
padding: 18px 0;
|
||||
}
|
||||
.gvs-pager-btn {
|
||||
border: 1px solid var(--ali-card-border);
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
padding: 6px 14px;
|
||||
font-size: 13px;
|
||||
color: var(--ali-text-2);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.gvs-pager-btn:hover:not(:disabled) {
|
||||
border-color: var(--ali-blue);
|
||||
color: var(--ali-blue);
|
||||
}
|
||||
.gvs-pager-btn:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.gvs-pager-info {
|
||||
font-size: 13px;
|
||||
color: var(--ali-text-3);
|
||||
}
|
||||
|
||||
/* ---------- 10. 表单 ---------- */
|
||||
.gvs-input {
|
||||
width: 100%;
|
||||
border: 1px solid var(--ali-card-border);
|
||||
border-radius: 4px;
|
||||
padding: 9px 12px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
background: #fff;
|
||||
color: var(--ali-text);
|
||||
}
|
||||
.gvs-input:focus {
|
||||
border-color: var(--ali-blue);
|
||||
box-shadow: 0 0 0 2px rgba(0, 100, 200, 0.15);
|
||||
}
|
||||
.gvs-textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--ali-card-border);
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.2s;
|
||||
background: #fff;
|
||||
color: var(--ali-text);
|
||||
resize: vertical;
|
||||
}
|
||||
.gvs-textarea:focus {
|
||||
border-color: var(--ali-blue);
|
||||
box-shadow: 0 0 0 2px rgba(0, 100, 200, 0.15);
|
||||
}
|
||||
|
||||
/* ---------- 11. 侧栏 ---------- */
|
||||
.gvs-side-list {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.gvs-side-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 0;
|
||||
font-size: 13px;
|
||||
color: var(--ali-text-2);
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.gvs-side-item:hover {
|
||||
color: var(--ali-blue);
|
||||
}
|
||||
.gvs-side-num {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
background: #f2f3f5;
|
||||
color: var(--ali-text-2);
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.gvs-side-item:nth-child(-n + 3) .gvs-side-num {
|
||||
background: var(--ali-blue);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -1,101 +1,181 @@
|
||||
<template>
|
||||
<div class="community-page min-h-screen bg-gray-50 py-8">
|
||||
<div class="max-w-5xl mx-auto px-4">
|
||||
<!-- 页头 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 p-6 mb-6">
|
||||
<div class="flex items-center justify-between flex-wrap gap-4">
|
||||
<div>
|
||||
<h1 class="text-2xl font-bold text-gray-900 flex items-center gap-2">
|
||||
<i class="fa fa-comments text-blue-500"></i> GVSDS 社区
|
||||
</h1>
|
||||
<p class="text-gray-500 text-sm mt-1">交流讨论 · 问题求助 · 项目展示</p>
|
||||
<div class="gvs-community">
|
||||
<!-- Hero 横幅(阿里云开发者社区风格) -->
|
||||
<div class="gvs-hero">
|
||||
<div class="gvs-container">
|
||||
<div class="gvs-hero-title">欢迎来到 GVSDS 开发者社区</div>
|
||||
<div class="gvs-hero-sub">交流技术 · 分享经验 · 共同成长</div>
|
||||
<div class="gvs-hero-entries">
|
||||
<div class="gvs-hero-entry" @click="selectBoard('')">
|
||||
<i class="fa fa-compass"></i>
|
||||
<div><b>探索社区</b><span>浏览全部帖子</span></div>
|
||||
</div>
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="relative">
|
||||
<input
|
||||
v-model="keyword"
|
||||
type="text"
|
||||
placeholder="搜索帖子..."
|
||||
class="w-56 px-4 py-2 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500"
|
||||
@keyup.enter="onSearch"
|
||||
/>
|
||||
<i class="fa fa-search absolute right-3 top-1/2 -translate-y-1/2 text-gray-400 text-sm"></i>
|
||||
</div>
|
||||
<button
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium transition-all hover:scale-105 active:scale-95"
|
||||
@click="goNewPost"
|
||||
>
|
||||
<i class="fa fa-pencil-alt mr-1"></i> 发帖
|
||||
</button>
|
||||
<div class="gvs-hero-entry" @click="goNewPost">
|
||||
<i class="fa fa-pencil-alt"></i>
|
||||
<div><b>发布帖子</b><span>分享你的问题或经验</span></div>
|
||||
</div>
|
||||
<div class="gvs-hero-entry" @click="selectBoard('')">
|
||||
<i class="fa fa-bullhorn"></i>
|
||||
<div><b>官方公告</b><span>了解最新动态</span></div>
|
||||
</div>
|
||||
<div class="gvs-hero-entry" @click="goNewPost">
|
||||
<i class="fa fa-users"></i>
|
||||
<div><b>加入讨论</b><span>与开发者互动</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 板块横条 -->
|
||||
<div class="flex items-center gap-2 mt-5 flex-wrap">
|
||||
<button
|
||||
class="px-3 py-1.5 rounded-lg text-sm transition-colors font-medium"
|
||||
:class="activeBoard === '' ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
||||
<div class="gvs-container pt-6 pb-10">
|
||||
<!-- 板块导航(探索云世界风格) -->
|
||||
<div class="gvs-card mb-4">
|
||||
<div class="gvs-card-hd"><i class="fa fa-th-large"></i> 探索板块</div>
|
||||
<div class="gvs-board-grid p-4">
|
||||
<div
|
||||
class="gvs-board-item"
|
||||
:class="{ active: activeBoard === '' }"
|
||||
@click="selectBoard('')"
|
||||
>全部</button>
|
||||
<button
|
||||
>
|
||||
<div class="gvs-board-icon"><i class="fa fa-globe"></i></div>
|
||||
<div>
|
||||
<div class="gvs-board-name">全部</div>
|
||||
<div class="gvs-board-meta">全部板块 · {{ totalPosts }} 帖</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="b in boards"
|
||||
:key="b.BoardUUID"
|
||||
class="px-3 py-1.5 rounded-lg text-sm transition-colors font-medium flex items-center gap-1.5"
|
||||
:class="activeBoard === b.BoardUUID ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
||||
class="gvs-board-item"
|
||||
:class="{ active: activeBoard === b.BoardUUID }"
|
||||
@click="selectBoard(b.BoardUUID)"
|
||||
>
|
||||
{{ b.BoardName }}
|
||||
<span class="text-xs opacity-70">({{ b.PostCount }})</span>
|
||||
</button>
|
||||
<div class="gvs-board-icon"><i class="fa fa-comments"></i></div>
|
||||
<div>
|
||||
<div class="gvs-board-name">{{ b.BoardName }}</div>
|
||||
<div class="gvs-board-meta">{{ b.BoardDesc || '暂无描述' }} · {{ b.PostCount }} 帖</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 帖子列表 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div v-if="loading" class="py-16 text-center text-gray-400">
|
||||
<i class="fa fa-spinner fa-spin text-2xl"></i>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start">
|
||||
<!-- 主列表 -->
|
||||
<div class="lg:col-span-2">
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd flex items-center justify-between">
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fa fa-list"></i> 帖子列表
|
||||
<!-- 排序 tab(综合/最新/热门) -->
|
||||
<div class="flex items-center gap-1 ml-3">
|
||||
<button
|
||||
v-for="t in tabs"
|
||||
:key="t.key"
|
||||
class="gvs-tag !cursor-pointer"
|
||||
:class="activeTab === t.key ? '' : 'gray'"
|
||||
@click="switchTab(t.key)"
|
||||
>{{ t.label }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
v-model="keyword"
|
||||
class="gvs-input !w-52 !py-1.5"
|
||||
type="text"
|
||||
placeholder="搜索帖子..."
|
||||
@keyup.enter="onSearch"
|
||||
/>
|
||||
<button class="gvs-btn gvs-btn-primary !py-1.5" @click="onSearch">
|
||||
<i class="fa fa-search"></i> 搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="py-20 text-center text-gray-400">
|
||||
<i class="fa fa-spinner fa-spin text-2xl"></i>
|
||||
</div>
|
||||
<template v-else-if="posts.length">
|
||||
<div
|
||||
v-for="p in posts"
|
||||
:key="p.PostUUID"
|
||||
class="gvs-feed-item"
|
||||
@click="goPost(p.PostUUID)"
|
||||
>
|
||||
<div class="gvs-feed-avatar">
|
||||
<i class="fa fa-user"></i>
|
||||
</div>
|
||||
<div class="gvs-feed-main">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="text-xs text-gray-400">{{ p.AuthorName || '匿名' }}</span>
|
||||
<span v-if="p.IsPinned" class="gvs-tag red">置顶</span>
|
||||
<span v-if="p.IsLocked" class="gvs-tag gray">锁定</span>
|
||||
</div>
|
||||
<div class="gvs-feed-title">{{ p.Title }}</div>
|
||||
<div class="gvs-feed-meta">
|
||||
<span><i class="fa fa-eye"></i>{{ p.ViewCount }}</span>
|
||||
<span><i class="fa fa-thumbs-up"></i>{{ p.LikeCount }}</span>
|
||||
<span><i class="fa fa-reply"></i>{{ p.ReplyCount }}</span>
|
||||
<span><i class="fa fa-clock"></i>{{ p.CreateTime }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div v-else class="py-20 text-center text-gray-400">
|
||||
<i class="fa fa-inbox text-3xl mb-3 block"></i>
|
||||
暂无帖子,快来发布第一帖吧
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="total > pageSize" class="gvs-pager">
|
||||
<button class="gvs-pager-btn" :disabled="page <= 1" @click="changePage(page - 1)">上一页</button>
|
||||
<span class="gvs-pager-info">第 {{ page }} / {{ Math.ceil(total / pageSize) }} 页 · 共 {{ total }} 帖</span>
|
||||
<button class="gvs-pager-btn" :disabled="page >= Math.ceil(total / pageSize)" @click="changePage(page + 1)">下一页</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template v-else-if="posts.length">
|
||||
<router-link
|
||||
v-for="p in posts"
|
||||
:key="p.PostUUID"
|
||||
:to="'/Community/Post/' + p.PostUUID"
|
||||
class="block px-6 py-4 border-b border-gray-100 hover:bg-gray-50 transition-colors group"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<span v-if="p.IsPinned" class="text-xs bg-red-50 text-red-500 px-1.5 py-0.5 rounded">置顶</span>
|
||||
<span class="font-medium text-gray-900 group-hover:text-blue-600 transition-colors truncate">
|
||||
|
||||
<!-- 侧栏 -->
|
||||
<div class="space-y-4">
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd"><i class="fa fa-bar-chart"></i> 社区统计</div>
|
||||
<div class="gvs-side-list">
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num">{{ boards.length }}</span>
|
||||
板块数量
|
||||
</div>
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num">{{ total }}</span>
|
||||
帖子总数
|
||||
</div>
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num">{{ totalReplies }}</span>
|
||||
回复总数
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd"><i class="fa fa-fire"></i> 热门帖子</div>
|
||||
<div class="gvs-side-list">
|
||||
<div
|
||||
v-for="(p, i) in hotPosts"
|
||||
:key="p.PostUUID"
|
||||
class="gvs-side-item"
|
||||
@click="goPost(p.PostUUID)"
|
||||
>
|
||||
<span class="gvs-side-num">{{ i + 1 }}</span>
|
||||
{{ p.Title }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-4 mt-2 text-xs text-gray-400">
|
||||
<span class="flex items-center gap-1"><i class="fa fa-user"></i> {{ p.AuthorName || '匿名' }}</span>
|
||||
<span class="flex items-center gap-1"><i class="fa fa-eye"></i> {{ p.ViewCount }}</span>
|
||||
<span class="flex items-center gap-1"><i class="fa fa-thumbs-up"></i> {{ p.LikeCount }}</span>
|
||||
<span class="flex items-center gap-1"><i class="fa fa-reply"></i> {{ p.ReplyCount }}</span>
|
||||
<span>{{ p.CreateTime }}</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</template>
|
||||
<div v-else class="py-16 text-center text-gray-400">
|
||||
<i class="fa fa-inbox text-3xl mb-3 block"></i>
|
||||
暂无帖子,快来发布第一帖吧
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div v-if="total > pageSize" class="flex items-center justify-center gap-4 mt-6">
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm bg-white border border-gray-200 hover:border-blue-500 hover:text-blue-600 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="page <= 1"
|
||||
@click="changePage(page - 1)"
|
||||
>上一页</button>
|
||||
<span class="text-sm text-gray-500">第 {{ page }} / {{ Math.ceil(total / pageSize) }} 页 · 共 {{ total }} 帖</span>
|
||||
<button
|
||||
class="px-4 py-2 rounded-lg text-sm bg-white border border-gray-200 hover:border-blue-500 hover:text-blue-600 disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
:disabled="page >= Math.ceil(total / pageSize)"
|
||||
@click="changePage(page + 1)"
|
||||
>下一页</button>
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd"><i class="fa fa-bullhorn"></i> 公告</div>
|
||||
<div class="gvs-side-list">
|
||||
<div class="gvs-side-item"><span class="gvs-side-num"><i class="fa fa-star"></i></span> 社区上线啦!欢迎各位小伙伴</div>
|
||||
<div class="gvs-side-item"><span class="gvs-side-num"><i class="fa fa-gavel"></i></span> 请遵守社区规范,友善发言</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -105,22 +185,34 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { communityApi, type CommunityBoardItem, type CommunityPostItem } from '@/api/community'
|
||||
import '@/assets/community.css'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const boards = ref<CommunityBoardItem[]>([])
|
||||
const posts = ref<CommunityPostItem[]>([])
|
||||
const hotPosts = ref<CommunityPostItem[]>([])
|
||||
const loading = ref(false)
|
||||
const activeBoard = ref('')
|
||||
const keyword = ref('')
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const total = ref(0)
|
||||
const totalPosts = ref(0)
|
||||
const totalReplies = ref(0)
|
||||
|
||||
const tabs = [
|
||||
{ key: 'latest', label: '最新' },
|
||||
{ key: 'hot', label: '最热' },
|
||||
{ key: 'replied', label: '最多回复' },
|
||||
]
|
||||
const activeTab = ref('latest')
|
||||
|
||||
async function loadBoards(): Promise<void> {
|
||||
try {
|
||||
const r = await communityApi.boards()
|
||||
boards.value = r.list || []
|
||||
totalPosts.value = boards.value.reduce((s, b) => s + b.PostCount, 0)
|
||||
} catch {
|
||||
boards.value = []
|
||||
}
|
||||
@@ -137,6 +229,13 @@ async function loadPosts(): Promise<void> {
|
||||
})
|
||||
posts.value = r.list || []
|
||||
total.value = r.total || 0
|
||||
// 排序 tab(最新为默认接口顺序;最热/最多回复由前端临时排序——全量页数内排序)
|
||||
if (activeTab.value === 'hot') {
|
||||
posts.value = [...posts.value].sort((a, b) => b.LikeCount - a.LikeCount)
|
||||
} else if (activeTab.value === 'replied') {
|
||||
posts.value = [...posts.value].sort((a, b) => b.ReplyCount - a.ReplyCount)
|
||||
}
|
||||
hotPosts.value = [...posts.value].sort((a, b) => b.ViewCount + b.LikeCount * 5 - (a.ViewCount + a.LikeCount * 5)).slice(0, 5)
|
||||
} catch {
|
||||
posts.value = []
|
||||
total.value = 0
|
||||
@@ -151,6 +250,11 @@ function selectBoard(uuid: string): void {
|
||||
loadPosts()
|
||||
}
|
||||
|
||||
function switchTab(key: string): void {
|
||||
activeTab.value = key
|
||||
loadPosts()
|
||||
}
|
||||
|
||||
function onSearch(): void {
|
||||
page.value = 1
|
||||
loadPosts()
|
||||
@@ -161,6 +265,10 @@ function changePage(p: number): void {
|
||||
loadPosts()
|
||||
}
|
||||
|
||||
function goPost(uuid: string): void {
|
||||
router.push('/Community/Post/' + uuid)
|
||||
}
|
||||
|
||||
function goNewPost(): void {
|
||||
router.push('/Community/NewPost')
|
||||
}
|
||||
|
||||
@@ -1,80 +1,99 @@
|
||||
<template>
|
||||
<div class="community-page min-h-screen bg-gray-50 py-8">
|
||||
<div class="max-w-3xl mx-auto px-4">
|
||||
<div class="gvs-community">
|
||||
<div class="gvs-container py-6">
|
||||
<button
|
||||
class="mb-4 text-sm text-gray-500 hover:text-blue-600 flex items-center gap-1 transition-colors"
|
||||
class="gvs-btn gvs-btn-ghost !mb-4"
|
||||
@click="router.push('/Community')"
|
||||
>
|
||||
<i class="fa fa-arrow-left"></i> 返回社区
|
||||
</button>
|
||||
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="px-6 py-4 border-b border-gray-100 font-medium text-gray-900 flex items-center gap-2">
|
||||
<i class="fa fa-pencil-alt text-blue-500"></i> 发布新帖
|
||||
</div>
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="gvs-card overflow-hidden">
|
||||
<div class="gvs-card-hd">
|
||||
<i class="fa fa-pencil-alt"></i> 发布新帖
|
||||
</div>
|
||||
|
||||
<div class="p-6 space-y-5">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">板块 <span class="text-red-500">*</span></label>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
v-for="b in boards"
|
||||
:key="b.BoardUUID"
|
||||
class="px-4 py-2 rounded-lg text-sm transition-colors"
|
||||
:class="form.BoardUUID === b.BoardUUID ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
||||
@click="form.BoardUUID = b.BoardUUID; form.CategoryUUID = ''"
|
||||
>{{ b.BoardName }}</button>
|
||||
<div class="p-6 space-y-6">
|
||||
<!-- 板块选择 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
选择板块 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-2">
|
||||
<button
|
||||
v-for="b in boards"
|
||||
:key="b.BoardUUID"
|
||||
class="gvs-board-item !p-3 !text-left"
|
||||
:class="{ active: form.BoardUUID === b.BoardUUID }"
|
||||
@click="form.BoardUUID = b.BoardUUID; form.CategoryUUID = ''"
|
||||
>
|
||||
<div class="gvs-board-icon !w-8 !h-8 !text-base"><i class="fa fa-comments"></i></div>
|
||||
<div>
|
||||
<div class="gvs-board-name !text-[13px]">{{ b.BoardName }}</div>
|
||||
<div class="gvs-board-meta">{{ b.PostCount }} 帖</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分类选择 -->
|
||||
<div v-if="selectedBoard?.Categories?.length">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">选择分类(可选)</label>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
v-for="c in selectedBoard.Categories"
|
||||
:key="c.CategoryUUID"
|
||||
class="gvs-tag !cursor-pointer !py-1.5"
|
||||
:class="form.CategoryUUID === c.CategoryUUID ? '' : 'gray'"
|
||||
@click="form.CategoryUUID = form.CategoryUUID === c.CategoryUUID ? '' : c.CategoryUUID"
|
||||
>{{ c.CategoryName }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 标题 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
标题 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
v-model="form.Title"
|
||||
class="gvs-input"
|
||||
type="text"
|
||||
maxlength="128"
|
||||
placeholder="一句话说明你的主题"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- 内容 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
内容 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
v-model="form.Content"
|
||||
class="gvs-textarea !min-h-64"
|
||||
rows="10"
|
||||
placeholder="详细描述你的问题或想法..."
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedBoard?.Categories?.length">
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">分类(可选)</label>
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<button
|
||||
v-for="c in selectedBoard.Categories"
|
||||
:key="c.CategoryUUID"
|
||||
class="px-3 py-1.5 rounded-lg text-sm transition-colors"
|
||||
:class="form.CategoryUUID === c.CategoryUUID ? 'bg-gray-800 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
||||
@click="form.CategoryUUID = form.CategoryUUID === c.CategoryUUID ? '' : c.CategoryUUID"
|
||||
>{{ c.CategoryName }}</button>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t border-gray-100 flex items-center justify-between">
|
||||
<span v-if="!isLoggedIn" class="text-xs text-gray-400">
|
||||
发布需
|
||||
<a href="https://www.gvsds.com/login" target="_blank" class="text-blue-600 hover:underline">登录</a>
|
||||
</span>
|
||||
<span v-else />
|
||||
<button
|
||||
class="gvs-btn gvs-btn-primary"
|
||||
:disabled="submitting || !form.BoardUUID || !form.Title.trim() || !form.Content.trim()"
|
||||
@click="submit"
|
||||
>
|
||||
<i v-if="submitting" class="fa fa-spinner fa-spin"></i>
|
||||
{{ submitting ? '发布中...' : '发布' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">标题 <span class="text-red-500">*</span></label>
|
||||
<input
|
||||
v-model="form.Title"
|
||||
type="text"
|
||||
maxlength="128"
|
||||
placeholder="一句话说明你的主题"
|
||||
class="w-full px-4 py-2.5 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1.5">内容 <span class="text-red-500">*</span></label>
|
||||
<textarea
|
||||
v-model="form.Content"
|
||||
rows="10"
|
||||
placeholder="详细描述你的问题或想法..."
|
||||
class="w-full px-4 py-3 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 resize-y"
|
||||
></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-6 py-4 border-t border-gray-100 flex items-center justify-between">
|
||||
<span v-if="!isLoggedIn" class="text-xs text-gray-400">
|
||||
发布需
|
||||
<a href="https://www.gvsds.com/login" target="_blank" class="text-blue-600 hover:underline">登录</a>
|
||||
</span>
|
||||
<span v-else />
|
||||
<button
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-6 py-2 rounded-lg text-sm font-medium transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:disabled="submitting || !form.BoardUUID || !form.Title.trim() || !form.Content.trim()"
|
||||
@click="submit"
|
||||
>
|
||||
{{ submitting ? '发布中...' : '发布' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -85,6 +104,7 @@
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { communityApi, type CommunityBoardItem } from '@/api/community'
|
||||
import '@/assets/community.css'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
|
||||
@@ -1,97 +1,142 @@
|
||||
<template>
|
||||
<div class="community-page min-h-screen bg-gray-50 py-8">
|
||||
<div class="max-w-4xl mx-auto px-4">
|
||||
<div class="gvs-community">
|
||||
<div class="gvs-container py-6">
|
||||
<button
|
||||
class="mb-4 text-sm text-gray-500 hover:text-blue-600 flex items-center gap-1 transition-colors"
|
||||
class="gvs-btn gvs-btn-ghost !mb-4"
|
||||
@click="router.back()"
|
||||
>
|
||||
<i class="fa fa-arrow-left"></i> 返回
|
||||
</button>
|
||||
|
||||
<div v-if="loading" class="bg-white rounded-xl shadow-sm border border-gray-100 p-16 text-center text-gray-400">
|
||||
<div v-if="loading" class="gvs-card p-16 text-center text-gray-400">
|
||||
<i class="fa fa-spinner fa-spin text-2xl"></i>
|
||||
</div>
|
||||
|
||||
<template v-else-if="post">
|
||||
<!-- 帖子主体 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 overflow-hidden">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-2 text-xs text-gray-400 mb-2">
|
||||
<span class="bg-blue-50 text-blue-600 px-2 py-0.5 rounded">{{ post.BoardName }}</span>
|
||||
<span v-if="post.CategoryName" class="bg-gray-100 text-gray-500 px-2 py-0.5 rounded">{{ post.CategoryName }}</span>
|
||||
<span v-if="post.IsPinned" class="bg-red-50 text-red-500 px-2 py-0.5 rounded">置顶</span>
|
||||
<span v-if="post.IsLocked" class="bg-gray-100 text-gray-400 px-2 py-0.5 rounded">已锁定</span>
|
||||
</div>
|
||||
<h1 class="text-xl font-bold text-gray-900">{{ post.Title }}</h1>
|
||||
<div class="flex items-center gap-4 mt-3 text-xs text-gray-400">
|
||||
<span class="flex items-center gap-1"><i class="fa fa-user"></i> {{ post.AuthorName || '匿名' }}</span>
|
||||
<span class="flex items-center gap-1"><i class="fa fa-clock"></i> {{ post.CreateTime }}</span>
|
||||
<span class="flex items-center gap-1"><i class="fa fa-eye"></i> {{ post.ViewCount }} 浏览</span>
|
||||
</div>
|
||||
<div class="mt-5 text-gray-800 leading-relaxed whitespace-pre-wrap min-h-24">
|
||||
{{ post.Content }}
|
||||
</div>
|
||||
<div class="mt-6 pt-4 border-t border-gray-100 flex items-center justify-between">
|
||||
<button
|
||||
class="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium transition-all"
|
||||
:class="liked ? 'bg-blue-500 text-white' : 'bg-gray-100 text-gray-600 hover:bg-gray-200'"
|
||||
@click="toggleLike"
|
||||
>
|
||||
<i class="fa" :class="liked ? 'fa-thumbs-up' : 'fa-thumbs-o-up'"></i>
|
||||
{{ liked ? '已赞' : '点赞' }} ({{ post.LikeCount }})
|
||||
</button>
|
||||
<span class="text-xs text-gray-400">{{ post.ReplyCount }} 条回复</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 回复列表 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 mt-6">
|
||||
<div class="px-6 py-4 border-b border-gray-100 font-medium text-gray-900">全部回复 ({{ post.Replies.length }})</div>
|
||||
<div v-if="post.Replies.length" class="divide-y divide-gray-100">
|
||||
<div v-for="(r, i) in post.Replies" :key="r.ReplyUUID" class="px-6 py-4">
|
||||
<div class="flex items-center gap-3 text-sm">
|
||||
<span class="font-medium text-gray-900">{{ r.AuthorName || '匿名' }}</span>
|
||||
<span class="text-xs text-gray-400">#{{ i + 1 }}</span>
|
||||
<span class="text-xs text-gray-400 ml-auto">{{ r.CreateTime }}</span>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 items-start">
|
||||
<!-- 帖子主体 -->
|
||||
<div class="lg:col-span-2 space-y-4">
|
||||
<div class="gvs-card">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center gap-2 mb-3 flex-wrap">
|
||||
<span class="gvs-tag">{{ post.BoardName }}</span>
|
||||
<span v-if="post.CategoryName" class="gvs-tag gray">{{ post.CategoryName }}</span>
|
||||
<span v-if="post.IsPinned" class="gvs-tag red">置顶</span>
|
||||
<span v-if="post.IsLocked" class="gvs-tag gray">已锁定</span>
|
||||
</div>
|
||||
<h1 class="text-xl font-bold text-gray-900 leading-snug">{{ post.Title }}</h1>
|
||||
<div class="flex items-center gap-4 mt-4 pb-4 border-b border-gray-100">
|
||||
<div class="gvs-feed-avatar !w-9 !h-9"><i class="fa fa-user"></i></div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-900">{{ post.AuthorName || '匿名' }}</div>
|
||||
<div class="text-xs text-gray-400 mt-0.5">{{ post.CreateTime }}</div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-400 flex items-center gap-1"><i class="fa fa-eye"></i> {{ post.ViewCount }} 浏览</span>
|
||||
</div>
|
||||
<div class="mt-5 text-gray-800 leading-7 whitespace-pre-wrap min-h-24 text-[14px]">
|
||||
{{ post.Content }}
|
||||
</div>
|
||||
<div class="mt-6 pt-4 border-t border-gray-100 flex items-center justify-between">
|
||||
<button
|
||||
class="gvs-btn"
|
||||
:class="liked ? 'gvs-btn-primary' : 'gvs-btn-ghost'"
|
||||
@click="toggleLike"
|
||||
>
|
||||
<i class="fa" :class="liked ? 'fa-thumbs-up' : 'fa-thumbs-o-up'"></i>
|
||||
{{ liked ? '已赞' : '点赞' }} ({{ post.LikeCount }})
|
||||
</button>
|
||||
<span class="text-xs text-gray-400">{{ post.ReplyCount }} 条回复</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-gray-700 text-sm leading-relaxed whitespace-pre-wrap">{{ r.Content }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 回复列表 -->
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd">
|
||||
<i class="fa fa-comments"></i> 全部回复 ({{ post.Replies.length }})
|
||||
</div>
|
||||
<div v-if="post.Replies.length" class="divide-y divide-gray-100">
|
||||
<div v-for="(r, i) in post.Replies" :key="r.ReplyUUID" class="px-6 py-4 flex gap-3">
|
||||
<div class="gvs-feed-avatar !w-8 !h-8 !text-sm"><i class="fa fa-user"></i></div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-medium text-sm text-gray-900">{{ r.AuthorName || '匿名' }}</span>
|
||||
<span class="text-xs text-gray-400">#{{ i + 1 }}</span>
|
||||
<span class="text-xs text-gray-400 ml-auto">{{ r.CreateTime }}</span>
|
||||
</div>
|
||||
<p class="mt-1.5 text-gray-700 text-sm leading-relaxed whitespace-pre-wrap">{{ r.Content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="px-6 py-12 text-center text-gray-400 text-sm">暂无回复,抢沙发!</div>
|
||||
</div>
|
||||
|
||||
<!-- 回复框 -->
|
||||
<div class="gvs-card p-6">
|
||||
<div v-if="!isLoggedIn" class="text-center text-sm text-gray-500 py-4">
|
||||
请先
|
||||
<a href="https://www.gvsds.com/login" target="_blank" class="text-blue-600 hover:underline">登录</a>
|
||||
后参与讨论
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="post.IsLocked" class="text-center text-sm text-gray-400 py-4">该帖子已锁定,不可回复</div>
|
||||
<template v-else>
|
||||
<textarea
|
||||
v-model="replyContent"
|
||||
class="gvs-textarea"
|
||||
rows="3"
|
||||
placeholder="友善发言,理性讨论..."
|
||||
></textarea>
|
||||
<div class="flex justify-end mt-3">
|
||||
<button
|
||||
class="gvs-btn gvs-btn-primary"
|
||||
:disabled="submitting || !replyContent.trim()"
|
||||
@click="submitReply"
|
||||
>
|
||||
{{ submitting ? '提交中...' : '发表回复' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="px-6 py-10 text-center text-gray-400 text-sm">暂无回复,抢沙发!</div>
|
||||
</div>
|
||||
|
||||
<!-- 回复框 -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-100 mt-6 p-6">
|
||||
<div v-if="!isLoggedIn" class="text-center text-sm text-gray-500 py-4">
|
||||
请先
|
||||
<a href="https://www.gvsds.com/login" target="_blank" class="text-blue-600 hover:underline">登录</a>
|
||||
后参与讨论
|
||||
</div>
|
||||
<template v-else>
|
||||
<div v-if="post.IsLocked" class="text-center text-sm text-gray-400 py-4">该帖子已锁定,不可回复</div>
|
||||
<template v-else>
|
||||
<textarea
|
||||
v-model="replyContent"
|
||||
rows="3"
|
||||
placeholder="友善发言,理性讨论..."
|
||||
class="w-full px-4 py-3 text-sm border border-gray-200 rounded-lg outline-none focus:ring-2 focus:ring-blue-500/30 focus:border-blue-500 resize-none"
|
||||
></textarea>
|
||||
<div class="flex justify-end mt-3">
|
||||
<button
|
||||
class="bg-blue-500 hover:bg-blue-600 text-white px-5 py-2 rounded-lg text-sm font-medium transition-all hover:scale-105 active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:disabled="submitting || !replyContent.trim()"
|
||||
@click="submitReply"
|
||||
>
|
||||
{{ submitting ? '提交中...' : '发表回复' }}
|
||||
</button>
|
||||
<!-- 侧栏 -->
|
||||
<div class="space-y-4">
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd"><i class="fa fa-info-circle"></i> 帖子信息</div>
|
||||
<div class="gvs-side-list">
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num"><i class="fa fa-th-large"></i></span>
|
||||
板块:{{ post.BoardName }}
|
||||
</div>
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num"><i class="fa fa-eye"></i></span>
|
||||
{{ post.ViewCount }} 次浏览
|
||||
</div>
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num"><i class="fa fa-thumbs-up"></i></span>
|
||||
{{ post.LikeCount }} 次点赞
|
||||
</div>
|
||||
<div class="gvs-side-item">
|
||||
<span class="gvs-side-num"><i class="fa fa-reply"></i></span>
|
||||
{{ post.ReplyCount }} 条回复
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
<div class="gvs-card">
|
||||
<div class="gvs-card-hd"><i class="fa fa-gavel"></i> 社区规范</div>
|
||||
<div class="gvs-side-list text-xs text-gray-500 leading-6">
|
||||
<p>· 请友善发言,理性讨论</p>
|
||||
<p>· 禁止发布违法违规内容</p>
|
||||
<p>· 提问前建议先搜索是否已有答案</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-else class="bg-white rounded-xl shadow-sm border border-gray-100 p-16 text-center text-gray-400">
|
||||
<div v-else class="gvs-card p-16 text-center text-gray-400">
|
||||
帖子不存在或已删除
|
||||
</div>
|
||||
</div>
|
||||
@@ -102,6 +147,7 @@
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { communityApi, type CommunityPostDetail } from '@/api/community'
|
||||
import '@/assets/community.css'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -111,8 +157,6 @@ const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const replyContent = ref('')
|
||||
const liked = ref(false)
|
||||
|
||||
/** 登录态:cookie 由浏览器自动携带;后端会拒绝未登录写操作 */
|
||||
const isLoggedIn = ref(false)
|
||||
|
||||
async function loadPost(): Promise<void> {
|
||||
@@ -154,7 +198,6 @@ async function submitReply(): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
/** 探测登录态:尝试拉取头像(未登录返回空) */
|
||||
async function detectLogin(): Promise<void> {
|
||||
try {
|
||||
const res = await fetch('https://ugm.gvsds.com/Query/GetELTUserAvatar', { credentials: 'include' })
|
||||
|
||||
Reference in new issue
Block a user