feat(event): 添加酒局编辑删除功能并优化状态管理

- 移除 UniAppWebSocket 类实现,使用独立的 websocket 模块
- 添加 UpdateEvent 和 DeleteEvent API 接口
- 在 EventCard 组件中添加发起人管理操作按钮
- 实现酒局状态数字枚举到字符串的映射转换
- 在 circle 页面集成编辑删除事件处理
- 重构 event-create 页面支持编辑模式
- 在 event-detail 页面添加发起人操作区域
- 实现删除确认对话框和页面返回刷新逻辑
This commit is contained in:
cg
2026-08-05 22:58:26 +08:00
parent 9f23ccae46
commit 527b0e8a8d
5 changed files with 223 additions and 172 deletions
+27 -1
View File
@@ -109,6 +109,9 @@
v-for="evt in events"
:key="evt.id"
:event="evt"
:can-manage="evt.isOrganizer === true"
@edit="goEditEvent"
@delete="confirmDeleteEvent"
@item-click="goEventDetail"
/>
<EmptyState
@@ -134,7 +137,7 @@ import FeedCard from '../../components/FeedCard.vue'
import FriendItem from '../../components/FriendItem.vue'
import EventCard from '../../components/EventCard.vue'
import EmptyState from '../../components/EmptyState.vue'
import { GetCircleFeeds, LikeFeed, UnlikeFeed, DeleteFeed, GetFriends, GetFriendRequests, GetEvents, GetUnreadCount } from '../../common/api'
import { GetCircleFeeds, LikeFeed, UnlikeFeed, DeleteFeed, GetFriends, GetFriendRequests, GetEvents, DeleteEvent, GetUnreadCount } from '../../common/api'
import wsManager from '../../common/websocket'
import { getMyInviteCode } from '../../common/invite'
import themeMixin from '../../common/theme-mixin'
@@ -375,6 +378,29 @@ export default {
goEventDetail(evt) {
uni.navigateTo({ url: `/pages/event-detail/event-detail?id=${evt.id}` })
},
/** 编辑自己发起的酒局(复用发起页的编辑模式) */
goEditEvent(evt) {
uni.navigateTo({ url: `/pages/event-create/event-create?id=${evt.id}` })
},
/** 删除自己发起的酒局(二次确认) */
confirmDeleteEvent(evt) {
uni.showModal({
title: '删除酒局',
content: '确定要删除这场酒局吗?删除后不可恢复',
confirmText: '删除',
confirmColor: '#FF6B6B',
success: async (res) => {
if (!res.confirm) return
try {
await DeleteEvent({ eventId: evt.id })
this.events = this.events.filter(e => String(e.id) !== String(evt.id))
uni.showToast({ title: '已删除', icon: 'none' })
} catch (e) {
// httpRequest 已全局弹错误提示(如非本人酒局后端会拒绝)
}
}
})
},
goCreateEvent() {
uni.navigateTo({ url: '/pages/event-create/event-create' })
},
+64 -17
View File
@@ -6,7 +6,7 @@
<view class="create-back" @click="goBack">
<text class="create-back-icon"></text>
</view>
<text class="create-nav-title">发起酒局</text>
<text class="create-nav-title">{{ isEdit ? '编辑酒局' : '发起酒局' }}</text>
<view class="create-nav-ph"></view>
</view>
</view>
@@ -98,9 +98,9 @@
/>
</view>
<!-- 发布按钮 -->
<!-- 发布/保存按钮 -->
<button class="create-submit" :class="{ disabled: !canSubmit }" @click="handleSubmit">
发布酒局
{{ isEdit ? '保存修改' : '发布酒局' }}
</button>
</view>
</scroll-view>
@@ -108,7 +108,7 @@
</template>
<script>
import { CreateEvent } from '../../common/api'
import { CreateEvent, UpdateEvent, GetEventDetail } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
@@ -132,12 +132,24 @@ export default {
maxPeople: 6,
note: ''
},
submitting: false
submitting: false,
// 编辑模式:携带酒局ID时进入编辑态
editId: '',
loadingDetail: false
}
},
onLoad(options) {
if (options && options.id) {
this.editId = options.id
this.loadEventDetail()
}
},
computed: {
isEdit() {
return !!this.editId
},
canSubmit() {
return this.form.title.trim() && this.form.date && this.form.time && this.form.location.trim() && !this.submitting
return this.form.title.trim() && this.form.date && this.form.time && this.form.location.trim() && !this.submitting && !this.loadingDetail
},
locationMarkers() {
if (!this.form.latitude) return []
@@ -155,6 +167,35 @@ export default {
goBack() {
uni.navigateBack()
},
/** 编辑模式:拉取酒局详情回填表单 */
async loadEventDetail() {
this.loadingDetail = true
try {
const res = await GetEventDetail({ eventId: this.editId })
const evt = res.data || {}
this.form.title = evt.title || ''
// 后端 time 格式如 "2026-08-08 20:00:00",拆分为日期/时间两个选择器字段
const timeStr = String(evt.time || '')
const m = timeStr.match(/^(\d{4}-\d{2}-\d{2})[ T]?(\d{2}:\d{2})?/)
if (m) {
this.form.date = m[1]
this.form.time = m[2] || ''
}
this.form.location = evt.location || ''
this.form.address = evt.address || ''
// 兼容顶层字段与 geo 对象两种返回
const geo = evt.geo || {}
this.form.latitude = evt.latitude != null ? evt.latitude : (geo.latitude != null ? geo.latitude : null)
this.form.longitude = evt.longitude != null ? evt.longitude : (geo.longitude != null ? geo.longitude : null)
this.form.maxPeople = Number(evt.maxPeople) || this.form.maxPeople
this.form.note = evt.note || ''
} catch (e) {
// 加载失败时返回上一页,避免空表单误改
uni.showToast({ title: '加载酒局失败', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
}
this.loadingDetail = false
},
onDateChange(e) {
this.form.date = e.detail.value
},
@@ -201,18 +242,24 @@ export default {
async handleSubmit() {
if (!this.canSubmit) return
this.submitting = true
const payload = {
title: this.form.title,
time: `${this.form.date} ${this.form.time}:00`,
location: this.form.location,
address: this.form.address,
latitude: this.form.latitude,
longitude: this.form.longitude,
maxPeople: this.form.maxPeople,
note: this.form.note
}
try {
await CreateEvent({
title: this.form.title,
time: `${this.form.date} ${this.form.time}:00`,
location: this.form.location,
address: this.form.address,
latitude: this.form.latitude,
longitude: this.form.longitude,
maxPeople: this.form.maxPeople,
note: this.form.note
})
uni.showToast({ title: '酒局已发布 🎉', icon: 'none' })
if (this.isEdit) {
await UpdateEvent({ eventId: this.editId, ...payload })
uni.showToast({ title: '修改已保存 ✅', icon: 'none' })
} else {
await CreateEvent(payload)
uni.showToast({ title: '酒局已发布 🎉', icon: 'none' })
}
setTimeout(() => uni.navigateBack(), 800)
} catch (e) {
// 全局拦截器已弹出错误提示
+71 -12
View File
@@ -14,7 +14,7 @@
<scroll-view class="evt-scroll" scroll-y>
<view class="evt-body" v-if="event">
<!-- 状态 -->
<view class="evt-status" :class="'status-' + event.status">
<view class="evt-status" :class="'status-' + statusKey">
<text class="evt-status-text">{{ statusLabel }}</text>
</view>
@@ -94,42 +94,47 @@
</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"
v-if="canJoin && !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"
v-if="canJoin && event.isJoined && !event.isOrganizer"
class="evt-btn evt-btn-ghost"
@click="handleQuit"
>
取消报名
</button>
<button
v-if="event.status === 'ongoing' && event.isJoined"
v-if="statusKey === 'ongoing' && event.isJoined"
class="evt-btn evt-btn-primary"
@click="handleCheckIn"
>
到场签到
</button>
<view v-if="event.status === 'ended'" class="evt-ended-tip">
<view v-if="statusKey === 'ended' && !event.isOrganizer" 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 v-if="event.isOrganizer" class="evt-org-actions">
<button class="evt-btn evt-btn-ghost" @click="goEdit">
编辑酒局
</button>
<button class="evt-btn evt-btn-danger" @click="confirmDelete">
🗑 删除酒局
</button>
</view>
</view>
</view>
</template>
<script>
import { GetEventDetail, JoinEvent, QuitEvent, CheckInEvent } from '../../common/api'
import { GetEventDetail, JoinEvent, QuitEvent, CheckInEvent, DeleteEvent } from '../../common/api'
import themeMixin from '../../common/theme-mixin'
export default {
@@ -143,11 +148,20 @@ export default {
}
},
computed: {
statusLabel() {
/** 后端状态为数字枚举:1=报名中 2=待开始 3=进行中 4=已结束(兼容字符串返回) */
statusKey() {
if (!this.event) return ''
const val = String(this.event.status).toLowerCase()
const map = { 1: 'open', 2: 'upcoming', 3: 'ongoing', 4: 'ended' }
const raw = this.event.status
return map[Number(raw)] || String(raw).toLowerCase()
},
/** 报名中/待开始阶段可报名或取消 */
canJoin() {
return this.statusKey === 'open' || this.statusKey === 'upcoming'
},
statusLabel() {
const map = { open: '报名中', upcoming: '待开始', ongoing: '进行中', ended: '已结束' }
return map[val] || '报名中'
return map[this.statusKey] || '报名中'
},
eventMarkers() {
if (!this.event || !this.event.latitude) return []
@@ -165,6 +179,12 @@ export default {
this.eventId = options.id || ''
this.loadDetail()
},
onShow() {
// 从编辑页返回时刷新详情
if (this.eventId && this._hasLoaded) {
this.loadDetail()
}
},
methods: {
goBack() {
uni.navigateBack()
@@ -173,10 +193,34 @@ export default {
try {
const res = await GetEventDetail({ eventId: this.eventId })
this.event = res.data
this._hasLoaded = true
} catch (e) {
console.warn('加载酒局详情失败', e)
}
},
/** 发起人编辑酒局(复用发起页的编辑模式) */
goEdit() {
uni.navigateTo({ url: `/pages/event-create/event-create?id=${this.eventId}` })
},
/** 发起人删除酒局(二次确认,成功后返回上一页) */
confirmDelete() {
uni.showModal({
title: '删除酒局',
content: '确定要删除这场酒局吗?删除后不可恢复',
confirmText: '删除',
confirmColor: '#FF6B6B',
success: async (res) => {
if (!res.confirm) return
try {
await DeleteEvent({ eventId: this.eventId })
uni.showToast({ title: '已删除', icon: 'none' })
setTimeout(() => uni.navigateBack(), 800)
} catch (e) {
// httpRequest 已全局弹错误提示
}
}
})
},
async handleJoin() {
try {
await JoinEvent({ eventId: this.eventId })
@@ -525,4 +569,19 @@ export default {
font-size: $fs-base;
color: $text-tertiary;
}
.evt-org-actions {
display: flex;
gap: $sp-md;
.evt-btn {
flex: 1;
}
}
.evt-btn-danger {
background: rgba(255,107,107,0.1);
color: $coral;
border: 1rpx solid rgba(255,107,107,0.25);
}
</style>