429 lines
11 KiB
Vue
429 lines
11 KiB
Vue
<template>
|
|
<div class="activity-detail-page">
|
|
<el-card class="activity-card" shadow="hover">
|
|
<!-- 活动基本信息 -->
|
|
<div class="header">
|
|
<h2 class="title">{{ detail.title }}</h2>
|
|
<span class="sub">{{ detail.clubName }} | {{ detail.deptName }}</span>
|
|
</div>
|
|
<div v-if="detail.coverImage" class="cover-wrapper">
|
|
<el-image :src="resolveImageUrl(detail.coverImage)" class="cover-image" fit="cover"/>
|
|
</div>
|
|
<el-descriptions :label-style="{ width: '120px' }" border column="2">
|
|
<el-descriptions-item label="开始时间">{{ formatDate(detail.startTime, false) }}</el-descriptions-item>
|
|
<el-descriptions-item label="结束时间">{{ formatDate(detail.endTime, false) }}</el-descriptions-item>
|
|
<el-descriptions-item label="活动地点">{{ detail.location }}</el-descriptions-item>
|
|
</el-descriptions>
|
|
|
|
<div class="section">
|
|
<h3>活动介绍</h3>
|
|
<div class="rich-text" v-html="detail.description"/>
|
|
</div>
|
|
|
|
<el-divider/>
|
|
|
|
<div class="section interactions">
|
|
<h3>互动</h3>
|
|
<div class="interaction-row">
|
|
<div class="reaction-buttons">
|
|
<el-button type="text" @click="handleReaction('like')">👍 {{ reactionStats.like }}</el-button>
|
|
<el-button type="text" @click="handleReaction('dislike')">👎 {{ reactionStats.dislike }}</el-button>
|
|
</div>
|
|
<div class="signup-buttons">
|
|
<el-button v-if="!hasRegistered" type="primary" @click="handleRegister">报名参加</el-button>
|
|
<el-button v-else-if="hasRegistered && !hasAttended" type="success" @click="handleSignIn">签到</el-button>
|
|
<el-button v-else disabled type="success">已签到</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<el-divider/>
|
|
|
|
<div class="section comments">
|
|
<h3>评论列表</h3>
|
|
<div v-for="comment in nestedComments" :key="comment.commentId" class="comment-block">
|
|
<div class="comment-item">
|
|
<span class="comment-user">{{ comment.userName }}</span>
|
|
<span class="comment-time">{{ formatDate(comment.createdAt) }}</span>
|
|
<div class="comment-content">{{ comment.content }}</div>
|
|
<div class="comment-actions">
|
|
<el-button size="mini" type="text" @click="replyTo(comment)">回复</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="comment.children.length" class="comment-children">
|
|
<div
|
|
v-for="child in comment.children"
|
|
:key="child.commentId"
|
|
class="comment-item child"
|
|
>
|
|
<span class="comment-user">{{ child.userName }}</span>
|
|
<span class="comment-time">{{ formatDate(child.createdAt) }}</span>
|
|
<div class="comment-content">{{ child.content }}</div>
|
|
<div class="comment-actions">
|
|
<el-button size="mini" type="text" @click="replyTo(child)">回复</el-button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 评论输入区 -->
|
|
<div class="add-comment">
|
|
<el-input
|
|
v-model="newCommentContent"
|
|
:rows="3"
|
|
placeholder="写下你的评论或回复..."
|
|
type="textarea"
|
|
/>
|
|
<el-button :disabled="!newCommentContent.trim()" size="small" type="primary" @click="submitComment">提交
|
|
</el-button>
|
|
<el-button v-if="replyTarget" size="small" @click="cancelReply">取消回复</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
</el-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import {getActivity} from '@/api/ams/activity'
|
|
import {addRegistration, listRegistration, updateRegistration} from '@/api/ams/registration'
|
|
import {addComment} from '@/api/ams/comment'
|
|
import {addReaction, delReaction, updateReaction} from '@/api/ams/reaction'
|
|
import {parseTime} from '@/utils/ruoyi'
|
|
import {listComment, listReaction} from '@/api/ui/stufront'
|
|
|
|
export default {
|
|
name: 'ActivityDetail',
|
|
data() {
|
|
return {
|
|
detail: {},
|
|
registration: null,
|
|
commentQuery: {pageNum: 1, pageSize: 5, actId: null},
|
|
commentList: [],
|
|
commentTotal: 0,
|
|
loadingComments: false,
|
|
newCommentContent: '',
|
|
replyTarget: null,
|
|
reactions: [],
|
|
reactionStats: {like: 0, dislike: 0},
|
|
reactionQuery: {pageNum: 1, pageSize: 5, actId: null},
|
|
reactionList: [],
|
|
reactionTotal: 0,
|
|
loadingReactions: false,
|
|
}
|
|
},
|
|
computed: {
|
|
userId() {
|
|
return this.$store.state.user.id
|
|
},
|
|
hasRegistered() {
|
|
return !!this.registration
|
|
},
|
|
hasAttended() {
|
|
return this.registration && this.registration.attendTime !== null
|
|
},
|
|
nestedComments() {
|
|
const map = {}
|
|
this.commentList.forEach(c => {
|
|
map[c.commentId] = { ...c, children: [] }
|
|
})
|
|
const tree = []
|
|
this.commentList.forEach(c => {
|
|
const parentId = c.parentCommentId
|
|
if (parentId && map[parentId]) {
|
|
map[parentId].children.push(map[c.commentId])
|
|
} else {
|
|
tree.push(map[c.commentId])
|
|
}
|
|
})
|
|
return tree
|
|
}
|
|
|
|
},
|
|
|
|
created() {
|
|
const actId = this.$route.params.id
|
|
this.commentQuery.actId = actId
|
|
this.reactionQuery.actId = actId
|
|
this.fetchData(actId)
|
|
this.fetchRegistration(actId)
|
|
this.getReactions()
|
|
this.fetchReactions()
|
|
this.getComments()
|
|
},
|
|
methods: {
|
|
fetchData(id) {
|
|
getActivity(id).then(r => (this.detail = r.data || {}))
|
|
},
|
|
fetchRegistration(actId) {
|
|
listRegistration({actId, userId: this.userId})
|
|
.then(r => (this.registration = r.rows[0] || null))
|
|
},
|
|
fetchReactions() {
|
|
listReaction({actId: this.detail.actId}).then(r => {
|
|
this.reactionStats = r.data || {like: 0, dislike: 0}
|
|
})
|
|
},
|
|
getComments() {
|
|
this.loadingComments = true
|
|
listComment(this.commentQuery).then(r => {
|
|
this.commentList = r.rows
|
|
this.commentTotal = r.total
|
|
}).finally(() => this.loadingComments = false)
|
|
},
|
|
submitComment() {
|
|
if (!this.newCommentContent.trim()) {
|
|
return this.$message.warning('评论不能为空')
|
|
}
|
|
addComment({
|
|
actId: this.detail.actId,
|
|
userId: this.userId,
|
|
content: this.newCommentContent,
|
|
parentCommentId: this.replyTarget ? this.replyTarget.commentId : null
|
|
}).then(() => {
|
|
this.newCommentContent = ''
|
|
this.replyTarget = null
|
|
this.getComments()
|
|
})
|
|
},
|
|
replyTo(comment) {
|
|
this.replyTarget = comment
|
|
this.newCommentContent = `@${comment.createBy || comment.userId} `
|
|
},
|
|
cancelReply() {
|
|
this.replyTarget = null
|
|
this.newCommentContent = ''
|
|
},
|
|
getReactions() {
|
|
this.loadingReactions = true
|
|
listReaction(this.reactionQuery).then(r => {
|
|
this.reactionList = r.rows
|
|
this.reactionTotal = r.total
|
|
}).finally(() => this.loadingReactions = false)
|
|
},
|
|
handleReaction(type) {
|
|
const exist = this.reactions.find(r => r.userId === this.userId)
|
|
if (!exist) {
|
|
addReaction({
|
|
actId: this.detail.actId,
|
|
userId: this.userId,
|
|
reactionType: type
|
|
}).then(() => this.fetchReactions())
|
|
} else if (exist.reactionType === type) {
|
|
delReaction(exist.reactionId).then(() => this.fetchReactions())
|
|
} else {
|
|
updateReaction({reactionId: exist.reactionId, reactionType: type}).then(() => this.fetchReactions())
|
|
}
|
|
},
|
|
handleRegister() {
|
|
addRegistration({
|
|
actId: this.detail.actId,
|
|
userId: this.userId,
|
|
registerTime: this.formatDateOnly(new Date())
|
|
}).then(() => this.fetchRegistration(this.detail.actId))
|
|
},
|
|
handleSignIn() {
|
|
updateRegistration({
|
|
regId: this.registration.regId,
|
|
actId: this.registration.actId,
|
|
userId: this.registration.userId,
|
|
attendTime: this.formatDateOnly(new Date()),
|
|
status: 'attended' // <-- 同步设置状态为已签到
|
|
}).then(() => {
|
|
this.$message.success('签到成功')
|
|
this.fetchRegistration(this.detail.actId)
|
|
})
|
|
},
|
|
formatDate(date, showTime = true) {
|
|
return parseTime(date, showTime ? '{y}-{m}-{d} {h}:{i}' : '{y}-{m}-{d}')
|
|
},
|
|
formatDateOnly(date) {
|
|
const y = date.getFullYear()
|
|
const m = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
const d = date.getDate().toString().padStart(2, '0')
|
|
return `${y}-${m}-${d}`
|
|
},
|
|
resolveImageUrl(url) {
|
|
if (!url) return require('@/assets/images/loading2.png').default
|
|
return url.startsWith('http') ? url : process.env.VUE_APP_BASE_API + url
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.activity-detail-page {
|
|
padding: 20px;
|
|
}
|
|
|
|
.activity-card {
|
|
max-width: 1000px;
|
|
margin: auto;
|
|
}
|
|
|
|
.header {
|
|
text-align: center;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.header .title {
|
|
font-size: 26px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.header .sub {
|
|
font-size: 14px;
|
|
color: #888;
|
|
}
|
|
|
|
.cover-wrapper {
|
|
text-align: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.cover-image {
|
|
width: 100%;
|
|
max-height: 400px;
|
|
border-radius: 8px;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.section {
|
|
margin-top: 30px;
|
|
}
|
|
|
|
.section h3 {
|
|
margin-bottom: 12px;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.rich-text {
|
|
background: #fafafa;
|
|
padding: 16px;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.interactions .interaction-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 24px;
|
|
}
|
|
|
|
.reaction-buttons {
|
|
font-size: 18px;
|
|
}
|
|
|
|
.signup-buttons el-button {
|
|
margin-left: 8px;
|
|
}
|
|
|
|
.comments .add-comment {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.comment-block {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.comment-item {
|
|
background: #f9f9f9;
|
|
padding: 10px;
|
|
border-radius: 6px;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.comment-user {
|
|
font-weight: bold;
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.comment-time {
|
|
color: #999;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.comment-content {
|
|
margin: 6px 0;
|
|
}
|
|
|
|
.comment-actions {
|
|
text-align: right;
|
|
}
|
|
|
|
.comment-children {
|
|
margin-left: 24px;
|
|
}
|
|
|
|
.child {
|
|
background: #f0f0f0;
|
|
}
|
|
|
|
.comment-block {
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.comment-item {
|
|
background: #fafafa;
|
|
padding: 6px 10px;
|
|
border-radius: 3px;
|
|
font-size: 13px;
|
|
line-height: 1.4;
|
|
margin-bottom: 4px;
|
|
border: 1px solid #eee;
|
|
position: relative;
|
|
}
|
|
|
|
.comment-user {
|
|
font-weight: 500;
|
|
margin-right: 6px;
|
|
font-size: 13px;
|
|
color: #333;
|
|
}
|
|
|
|
.comment-time {
|
|
color: #bbb;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.comment-content {
|
|
margin: 4px 0;
|
|
font-size: 13px;
|
|
}
|
|
|
|
.comment-actions {
|
|
position: absolute;
|
|
top: 6px;
|
|
right: 8px;
|
|
}
|
|
|
|
.comment-children {
|
|
margin-left: 14px;
|
|
}
|
|
|
|
.comment-item.child {
|
|
background: #f3f3f3;
|
|
border: 1px solid #e6e6e6;
|
|
}
|
|
|
|
.add-comment {
|
|
margin-top: 10px;
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
.add-comment .el-input__inner {
|
|
font-size: 13px;
|
|
padding: 6px 8px;
|
|
}
|
|
|
|
.add-comment .el-button {
|
|
padding: 6px 12px;
|
|
font-size: 13px;
|
|
}
|
|
|
|
|
|
</style>
|