Files
hejiu/pages/event-detail/event-detail.vue
T
cg fabbb167cf feat(circle): 添加酒友圈社交模块
- 新增酒友圈、圈子详情、发布动态、好友列表、活动创建等页面配置
- 添加酒友圈相关API接口,包括动态、评论、好友、活动等功能
- 实现模拟数据用于前端开发和测试
- 创建评论项、空状态、活动卡片、动态卡片、好友项等组件
- 编写酒友圈模块详细的接口文档,定义数据结构和业务规则
2026-07-20 22:53:04 +08:00

451 lines
10 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>
<view :class="themeClass" class="page-container evt-detail-page">
<!-- 顶部导航 -->
<view class="evt-nav" :style="{ paddingTop: navPaddingTop }">
<view class="evt-nav-inner">
<view class="evt-back" @click="goBack">
<text class="evt-back-icon"></text>
</view>
<text class="evt-nav-title">酒局详情</text>
<view class="evt-nav-ph"></view>
</view>
</view>
<scroll-view class="evt-scroll" scroll-y>
<view class="evt-body" v-if="event">
<!-- 状态 -->
<view class="evt-status" :class="'status-' + event.status">
<text class="evt-status-text">{{ statusLabel }}</text>
</view>
<!-- 标题 -->
<text class="evt-title">{{ event.title }}</text>
<!-- 信息卡片 -->
<view class="evt-info-card">
<view class="evt-info-row">
<text class="evt-info-icon">🕐</text>
<view class="evt-info-content">
<text class="evt-info-label">时间</text>
<text class="evt-info-value">{{ event.time }}</text>
</view>
</view>
<view class="evt-info-row">
<text class="evt-info-icon">📍</text>
<view class="evt-info-content">
<text class="evt-info-label">地点</text>
<text class="evt-info-value">{{ event.location }}</text>
</view>
</view>
<view class="evt-info-row">
<text class="evt-info-icon">👥</text>
<view class="evt-info-content">
<text class="evt-info-label">人数</text>
<text class="evt-info-value">{{ event.joined }}/{{ event.maxPeople }} 人已报名</text>
</view>
</view>
<view class="evt-info-row" v-if="event.note">
<text class="evt-info-icon">📝</text>
<view class="evt-info-content">
<text class="evt-info-label">备注</text>
<text class="evt-info-value">{{ event.note }}</text>
</view>
</view>
</view>
<!-- 发起人 -->
<view class="evt-organizer">
<text class="evt-section-label">发起人</text>
<view class="evt-org-row">
<view class="evt-org-avatar">
<text class="evt-org-avatar-text">{{ event.organizer ? event.organizer.nickname[0] : '酒' }}</text>
</view>
<text class="evt-org-name">{{ event.organizer ? event.organizer.nickname : '' }}</text>
</view>
</view>
<!-- 参与者 -->
<view class="evt-participants" v-if="event.participants && event.participants.length">
<text class="evt-section-label">已报名 ({{ event.participants.length }})</text>
<view class="evt-people-grid">
<view class="evt-person" v-for="p in event.participants" :key="p.id">
<view class="evt-person-avatar">
<text class="evt-person-text">{{ p.nickname[0] }}</text>
</view>
<text class="evt-person-name">{{ p.nickname }}</text>
</view>
</view>
</view>
</view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="evt-action-bar" v-if="event">
<button
v-if="event.status === 'open' && !event.isJoined && !event.isOrganizer"
class="evt-btn evt-btn-primary"
@click="handleJoin"
>
报名参加
</button>
<button
v-if="event.status === 'open' && event.isJoined && !event.isOrganizer"
class="evt-btn evt-btn-ghost"
@click="handleQuit"
>
取消报名
</button>
<button
v-if="event.status === 'ongoing' && event.isJoined"
class="evt-btn evt-btn-primary"
@click="handleCheckIn"
>
到场签到
</button>
<view v-if="event.status === 'ended'" class="evt-ended-tip">
<text class="evt-ended-text">酒局已结束</text>
</view>
<view v-if="event.isOrganizer" class="evt-ended-tip">
<text class="evt-ended-text">你是发起人</text>
</view>
</view>
</view>
</template>
<script>
import { GetEventDetail, JoinEvent, QuitEvent, CheckInEvent } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
mixins: [themeMixin],
data() {
const menuBtn = uni.getMenuButtonBoundingClientRect()
return {
navPaddingTop: menuBtn.top + 'px',
eventId: '',
event: null
}
},
computed: {
statusLabel() {
if (!this.event) return ''
const map = { open: '报名中', ongoing: '进行中', ended: '已结束' }
return map[this.event.status] || '报名中'
}
},
onLoad(options) {
this.eventId = options.id || ''
this.loadDetail()
},
methods: {
goBack() {
uni.navigateBack()
},
async loadDetail() {
try {
const res = await GetEventDetail({ eventId: this.eventId })
this.event = res.data
} catch (e) {
console.warn('加载酒局详情失败', e)
}
},
async handleJoin() {
try {
await JoinEvent({ eventId: this.eventId })
this.event.isJoined = true
this.event.joined++
uni.showToast({ title: '报名成功 🎉', icon: 'none' })
} catch (e) {
uni.showToast({ title: '报名失败', icon: 'none' })
}
},
async handleQuit() {
uni.showModal({
title: '取消报名',
content: '确定要退出这个酒局吗?',
confirmText: '退出',
confirmColor: '#FF6B6B',
success: async (res) => {
if (res.confirm) {
try {
await QuitEvent({ eventId: this.eventId })
this.event.isJoined = false
this.event.joined--
uni.showToast({ title: '已取消报名', icon: 'none' })
} catch (e) {
uni.showToast({ title: '操作失败', icon: 'none' })
}
}
}
})
},
async handleCheckIn() {
try {
await CheckInEvent({ eventId: this.eventId })
uni.showToast({ title: '签到成功 🍻', icon: 'none' })
} catch (e) {
uni.showToast({ title: '签到失败', icon: 'none' })
}
}
}
}
</script>
<style lang="scss" scoped>
.evt-detail-page {
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
}
.evt-nav {
background: $bg-base;
padding-left: $sp-lg;
padding-right: $sp-lg;
}
.evt-nav-inner {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
}
.evt-back {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: $bg-card;
&:active { background: $bg-card-alt; }
}
.evt-back-icon {
font-size: $fs-xl;
color: $text-primary;
font-weight: $fw-bold;
}
.evt-nav-title {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.evt-nav-ph {
width: 64rpx;
}
.evt-scroll {
flex: 1;
height: 0;
}
.evt-body {
padding: $sp-lg;
padding-bottom: $sp-xl;
}
.evt-status {
display: inline-flex;
padding: 8rpx 20rpx;
border-radius: $radius-full;
margin-bottom: $sp-lg;
&.status-open { background: rgba(94,198,160,0.12); }
&.status-ongoing { background: rgba(232,168,56,0.12); }
&.status-ended { background: $bg-elevated; }
}
.evt-status-text {
font-size: $fs-sm;
font-weight: $fw-medium;
.status-open & { color: $mint; }
.status-ongoing & { color: $amber; }
.status-ended & { color: $text-tertiary; }
}
.evt-title {
display: block;
font-size: $fs-2xl;
font-weight: $fw-black;
color: $text-primary;
margin-bottom: $sp-xl;
line-height: $lh-tight;
}
/* 信息卡片 */
.evt-info-card {
background: $bg-card;
border-radius: $radius-xl;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
padding: $sp-xl;
margin-bottom: $sp-xl;
}
.evt-info-row {
display: flex;
align-items: flex-start;
gap: $sp-md;
padding: $sp-md 0;
&:not(:last-child) {
border-bottom: 1rpx solid var(--divider-color, rgba(255,255,255,0.08));
}
}
.evt-info-icon {
font-size: $fs-base;
width: 40rpx;
text-align: center;
flex-shrink: 0;
margin-top: 4rpx;
}
.evt-info-content {
display: flex;
flex-direction: column;
gap: 4rpx;
}
.evt-info-label {
font-size: $fs-xs;
color: $text-tertiary;
}
.evt-info-value {
font-size: $fs-base;
color: $text-primary;
font-weight: $fw-medium;
}
/* 发起人 & 参与者 */
.evt-section-label {
display: block;
font-size: $fs-sm;
color: $text-tertiary;
font-weight: $fw-medium;
margin-bottom: $sp-md;
}
.evt-organizer {
margin-bottom: $sp-xl;
}
.evt-org-row {
display: flex;
align-items: center;
gap: $sp-md;
}
.evt-org-avatar {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: linear-gradient(135deg, rgba(232,168,56,0.15), rgba(139,133,184,0.15));
display: flex;
align-items: center;
justify-content: center;
}
.evt-org-avatar-text {
font-size: $fs-base;
font-weight: $fw-bold;
color: $amber;
}
.evt-org-name {
font-size: $fs-base;
font-weight: $fw-bold;
color: $text-primary;
}
.evt-people-grid {
display: flex;
flex-wrap: wrap;
gap: $sp-lg;
}
.evt-person {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
width: 100rpx;
}
.evt-person-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: $bg-elevated;
display: flex;
align-items: center;
justify-content: center;
}
.evt-person-text {
font-size: $fs-sm;
font-weight: $fw-bold;
color: $lavender;
}
.evt-person-name {
font-size: $fs-xs;
color: $text-secondary;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 100rpx;
text-align: center;
}
/* 底部操作栏 */
.evt-action-bar {
padding: $sp-md $sp-lg;
padding-bottom: calc(env(safe-area-inset-bottom) + #{$sp-md});
background: $bg-card;
border-top: 1rpx solid var(--divider-color, rgba(255,255,255,0.08));
}
.evt-btn {
width: 100%;
height: 96rpx;
line-height: 96rpx;
text-align: center;
border-radius: $radius-full;
font-size: $fs-lg;
font-weight: $fw-bold;
border: none;
&::after { border: none; }
&:active { transform: scale(0.98); }
}
.evt-btn-primary {
background: linear-gradient(135deg, $amber, $amber-deep);
color: $text-on-amber;
}
.evt-btn-ghost {
background: $bg-elevated;
color: $coral;
border: 1rpx solid rgba(255,107,107,0.2);
}
.evt-ended-tip {
text-align: center;
padding: $sp-md 0;
}
.evt-ended-text {
font-size: $fs-base;
color: $text-tertiary;
}
</style>