Files
hejiu/pages/event-detail/event-detail.vue
T
cg c7c023975d feat(circle): 集成HaveADrink SDK实现酒友圈功能
- 集成HaveADrink SDK版本从1.0.4升级至1.0.8
- 实现酒友圈API接口,替换原有的mock数据
- 添加地理位置权限配置(requiredPrivateInfos)
- 重构API调用方式,统一使用client实例调用真实接口
- 更新WebSocket协议与SDK保持一致,支持聊天、输入状态、已读回执等功能
- 实现游标分页获取动态信息流
- 添加文件上传API支持图片上传
- 优化API错误处理,支持业务级错误提示
- 调整酒局状态显示,新增upcoming待开始状态
- 优化自我感觉标签映射逻辑,支持更多状态类型
- 更新websocket连接认证方式,使用Authorization header传递token
- 添加服务端连接确认和被踢下线事件处理
2026-07-22 23:03:40 +08:00

529 lines
12 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-map-section" v-if="event.latitude">
<map
class="evt-map"
:latitude="event.latitude"
:longitude="event.longitude"
:markers="eventMarkers"
:scale="15"
:show-location="true"
></map>
<view class="evt-map-nav-btn" @click="openNavigation">
<text class="evt-map-nav-icon">🧭</text>
<text class="evt-map-nav-text">导航到这里</text>
</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.status === 'upcoming') && !event.isJoined && !event.isOrganizer"
class="evt-btn evt-btn-primary"
@click="handleJoin"
>
报名参加
</button>
<button
v-if="(event.status === 'open' || event.status === 'upcoming') && 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 val = String(this.event.status).toLowerCase()
const map = { open: '报名中', upcoming: '待开始', ongoing: '进行中', ended: '已结束' }
return map[val] || '报名中'
},
eventMarkers() {
if (!this.event || !this.event.latitude) return []
return [{
id: 1,
latitude: this.event.latitude,
longitude: this.event.longitude,
title: this.event.location,
width: 28,
height: 38
}]
}
},
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' })
}
},
openNavigation() {
if (!this.event || !this.event.latitude) return
uni.openLocation({
latitude: this.event.latitude,
longitude: this.event.longitude,
name: this.event.location,
address: this.event.address || this.event.location,
fail: () => {
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-upcoming { background: rgba(139,133,184,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-upcoming & { color: $lavender; }
.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-map-section {
margin-bottom: $sp-xl;
border-radius: $radius-xl;
overflow: hidden;
border: 1rpx solid var(--border-faint, rgba(255,255,255,0.08));
}
.evt-map {
width: 100%;
height: 320rpx;
}
.evt-map-nav-btn {
display: flex;
align-items: center;
justify-content: center;
gap: $sp-sm;
padding: $sp-md;
background: $bg-card;
&:active {
background: $bg-card-alt;
}
}
.evt-map-nav-icon {
font-size: $fs-base;
}
.evt-map-nav-text {
font-size: $fs-base;
color: $amber;
font-weight: $fw-bold;
}
/* 发起人 & 参与者 */
.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>