|
|
@@ -76,6 +76,139 @@
|
|
|
</el-card>
|
|
|
</el-col>
|
|
|
</el-row>
|
|
|
+
|
|
|
+
|
|
|
+ <el-row class="mt-8px" :gutter="8" justify="space-between">
|
|
|
+ <el-col :xl="24" :lg="24" :md="24" :sm="24" :xs="24" class="mb-8px">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <div class="h-3 flex justify-between">
|
|
|
+ <span>【论坛】</span>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <el-skeleton :loading="loading" animated>
|
|
|
+ <el-row>
|
|
|
+ <el-col :span="24" class="mb-8px">
|
|
|
+ <!-- 发帖区域 -->
|
|
|
+ <div class="forum-post-area">
|
|
|
+ <el-input
|
|
|
+ v-model="newPostContent"
|
|
|
+ type="textarea"
|
|
|
+ :rows="3"
|
|
|
+ placeholder="分享你的想法..."
|
|
|
+ maxlength="500"
|
|
|
+ show-word-limit
|
|
|
+ />
|
|
|
+ <div class="flex justify-end mt-8px">
|
|
|
+ <el-button type="primary" :disabled="!newPostContent.trim()" @click="publishPost">
|
|
|
+ 发布
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <el-divider />
|
|
|
+
|
|
|
+ <!-- 帖子列表 -->
|
|
|
+ <div v-if="forumPosts.length === 0" class="text-center text-gray-400 py-20px">
|
|
|
+ 暂无帖子,快来发布第一条吧~
|
|
|
+ </div>
|
|
|
+ <div v-for="post in forumPosts" :key="post.id" class="forum-post-item">
|
|
|
+ <div class="flex items-start">
|
|
|
+ <el-avatar :size="40" class="mr-12px flex-shrink-0">
|
|
|
+ <img :src="post.avatar" alt="" />
|
|
|
+ </el-avatar>
|
|
|
+ <div class="flex-1 min-w-0">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div>
|
|
|
+ <span class="text-14px font-medium">{{ post.author }}</span>
|
|
|
+ <span class="text-12px text-gray-400 ml-8px">{{ post.time }}</span>
|
|
|
+ </div>
|
|
|
+ <el-popconfirm
|
|
|
+ v-if="post.author === username"
|
|
|
+ title="确定删除该帖子吗?"
|
|
|
+ confirm-button-text="确定"
|
|
|
+ cancel-button-text="取消"
|
|
|
+ @confirm="deletePost(post)"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <span class="text-12px text-gray-400 cursor-pointer hover:text-red-500">删除</span>
|
|
|
+ </template>
|
|
|
+ </el-popconfirm>
|
|
|
+ </div>
|
|
|
+ <div class="text-14px mt-8px text-gray-700" style="white-space: pre-wrap">{{ post.content }}</div>
|
|
|
+ <div class="flex items-center mt-10px text-12px text-gray-400">
|
|
|
+ <span class="cursor-pointer flex items-center mr-16px" @click="toggleLike(post)">
|
|
|
+ <Icon
|
|
|
+ :icon="post.liked ? 'ant-design:like-filled' : 'ant-design:like-outlined'"
|
|
|
+ :class="post.liked ? 'text-blue-500' : ''"
|
|
|
+ class="mr-4px"
|
|
|
+ />
|
|
|
+ {{ post.likes > 0 ? post.likes : '点赞' }}
|
|
|
+ </span>
|
|
|
+ <span class="cursor-pointer flex items-center" @click="post.showComments = !post.showComments">
|
|
|
+ <Icon icon="ant-design:message-outlined" class="mr-4px" />
|
|
|
+ {{ post.comments.length > 0 ? post.comments.length : '评论' }}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 评论区 -->
|
|
|
+ <div v-if="post.showComments" class="mt-12px bg-gray-50 rounded p-12px">
|
|
|
+ <div v-for="comment in post.comments" :key="comment.id" class="mb-8px">
|
|
|
+ <div class="flex items-start">
|
|
|
+ <el-avatar :size="28" class="mr-8px flex-shrink-0">
|
|
|
+ <img :src="comment.avatar" alt="" />
|
|
|
+ </el-avatar>
|
|
|
+ <div class="flex-1">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div>
|
|
|
+ <span class="font-medium">{{ comment.author }}</span>
|
|
|
+ <span class="text-gray-400 ml-6px">{{ comment.time }}</span>
|
|
|
+ </div>
|
|
|
+ <el-popconfirm
|
|
|
+ v-if="comment.author === username"
|
|
|
+ title="确定删除该评论吗?"
|
|
|
+ confirm-button-text="确定"
|
|
|
+ cancel-button-text="取消"
|
|
|
+ @confirm="deleteComment(post, comment)"
|
|
|
+ >
|
|
|
+ <template #reference>
|
|
|
+ <span class="text-12px text-gray-400 cursor-pointer hover:text-red-500">删除</span>
|
|
|
+ </template>
|
|
|
+ </el-popconfirm>
|
|
|
+ </div>
|
|
|
+ <div class="text-13px text-gray-600 mt-2px">{{ comment.content }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- 评论输入框 -->
|
|
|
+ <div class="flex items-center mt-8px">
|
|
|
+ <el-input
|
|
|
+ v-model="commentInputs[post.id]"
|
|
|
+ size="small"
|
|
|
+ placeholder="写下你的评论..."
|
|
|
+ @keyup.enter="addComment(post)"
|
|
|
+ />
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="primary"
|
|
|
+ :disabled="!commentInputs[post.id]?.trim()"
|
|
|
+ class="ml-8px"
|
|
|
+ @click="addComment(post)"
|
|
|
+ >
|
|
|
+ 发送
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <el-divider />
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-skeleton>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</template>
|
|
|
<script lang="ts" setup>
|
|
|
import { set } from 'lodash-es'
|
|
|
@@ -84,7 +217,7 @@ import { formatTime } from '@/utils'
|
|
|
|
|
|
import { useUserStore } from '@/store/modules/user'
|
|
|
// import { useWatermark } from '@/hooks/web/useWatermark'
|
|
|
-import type { WorkplaceTotal, Project, Notice, Shortcut } from './types'
|
|
|
+import type { WorkplaceTotal, Project, Notice, Shortcut, ForumPost, ForumComment } from './types'
|
|
|
import { pieOptions, barOptions } from './echarts-data'
|
|
|
import { useRouter } from 'vue-router'
|
|
|
|
|
|
@@ -259,6 +392,102 @@ const getWeeklyUserActivity = async () => {
|
|
|
])
|
|
|
}
|
|
|
|
|
|
+// ==================== 论坛功能 ====================
|
|
|
+const FORUM_CACHE_KEY = 'forum_posts'
|
|
|
+
|
|
|
+const newPostContent = ref('')
|
|
|
+const commentInputs = reactive<Record<string, string>>({})
|
|
|
+const forumPosts = reactive<ForumPost[]>([])
|
|
|
+
|
|
|
+const generateId = (): string => {
|
|
|
+ return Date.now().toString(36) + Math.random().toString(36).substring(2, 9)
|
|
|
+}
|
|
|
+
|
|
|
+const getCurrentTime = (): string => {
|
|
|
+ return formatTime(new Date(), 'yyyy-MM-dd HH:mm')
|
|
|
+}
|
|
|
+
|
|
|
+const loadForumPosts = () => {
|
|
|
+ try {
|
|
|
+ const cached = localStorage.getItem(FORUM_CACHE_KEY)
|
|
|
+ if (cached) {
|
|
|
+ const parsed = JSON.parse(cached) as ForumPost[]
|
|
|
+ forumPosts.splice(0, forumPosts.length, ...parsed)
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ localStorage.removeItem(FORUM_CACHE_KEY)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const saveForumPosts = () => {
|
|
|
+ localStorage.setItem(FORUM_CACHE_KEY, JSON.stringify(forumPosts))
|
|
|
+}
|
|
|
+
|
|
|
+const publishPost = () => {
|
|
|
+ const content = newPostContent.value.trim()
|
|
|
+ if (!content) return
|
|
|
+
|
|
|
+ const newPost: ForumPost = {
|
|
|
+ id: generateId(),
|
|
|
+ author: username,
|
|
|
+ avatar: avatar,
|
|
|
+ content,
|
|
|
+ time: getCurrentTime(),
|
|
|
+ likes: 0,
|
|
|
+ liked: false,
|
|
|
+ comments: [],
|
|
|
+ showComments: false
|
|
|
+ }
|
|
|
+
|
|
|
+ forumPosts.unshift(newPost)
|
|
|
+ newPostContent.value = ''
|
|
|
+ saveForumPosts()
|
|
|
+}
|
|
|
+
|
|
|
+const toggleLike = (post: ForumPost) => {
|
|
|
+ post.liked = !post.liked
|
|
|
+ post.likes += post.liked ? 1 : -1
|
|
|
+ saveForumPosts()
|
|
|
+}
|
|
|
+
|
|
|
+const addComment = (post: ForumPost) => {
|
|
|
+ const content = commentInputs[post.id]?.trim()
|
|
|
+ if (!content) return
|
|
|
+
|
|
|
+ const newComment: ForumComment = {
|
|
|
+ id: generateId(),
|
|
|
+ postId: post.id,
|
|
|
+ author: username,
|
|
|
+ avatar: avatar,
|
|
|
+ content,
|
|
|
+ time: getCurrentTime()
|
|
|
+ }
|
|
|
+
|
|
|
+ post.comments.push(newComment)
|
|
|
+ commentInputs[post.id] = ''
|
|
|
+ saveForumPosts()
|
|
|
+}
|
|
|
+
|
|
|
+const deletePost = (post: ForumPost) => {
|
|
|
+ if (post.author !== username) return
|
|
|
+ const index = forumPosts.findIndex(p => p.id === post.id)
|
|
|
+ if (index > -1) {
|
|
|
+ forumPosts.splice(index, 1)
|
|
|
+ saveForumPosts()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const deleteComment = (post: ForumPost, comment: ForumComment) => {
|
|
|
+ if (comment.author !== username) return
|
|
|
+ const index = post.comments.findIndex(c => c.id === comment.id)
|
|
|
+ if (index > -1) {
|
|
|
+ post.comments.splice(index, 1)
|
|
|
+ saveForumPosts()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+loadForumPosts()
|
|
|
+
|
|
|
const getAllApi = async () => {
|
|
|
await Promise.all([
|
|
|
getCount(),
|