feat: 优化微信订阅消息体系并新增约课取消通知与开课前1小时提醒
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -90,6 +90,9 @@
|
||||
</view>
|
||||
|
||||
<ClassReviewForm v-if="booking && booking.userId === userStore.user?.id && booking.status === BookingStatus.COMPLETED" :key="booking.id" :booking-id="booking.id" />
|
||||
<view v-if="canSubscribeClassReminder" class="panel">
|
||||
<button class="review-subscribe" @tap="subscribeClassReminder">🔔 订阅开课前 1 小时微信提醒</button>
|
||||
</view>
|
||||
<view v-if="canSubscribeReview" class="panel">
|
||||
<button class="review-subscribe" @tap="subscribeReview">{{ booking?.status === BookingStatus.COMPLETED ? '如果现在不评,24 小时后提醒我' : '订阅课后评价提醒' }}</button>
|
||||
</view>
|
||||
@@ -222,10 +225,35 @@ import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||
import ClassReviewForm from '../../components/ClassReviewForm.vue'
|
||||
import BookingConfirmPopup from '../../components/BookingConfirmPopup.vue'
|
||||
|
||||
import { requestSubscriptionMessage } from '../../utils/wechat-subscription'
|
||||
import {
|
||||
requestSubscriptionMessage,
|
||||
requestBookingCancelSubscriptionMessage,
|
||||
requestClassReminderSubscriptionMessage,
|
||||
cacheSubscriptionMessageTemplateConfig,
|
||||
} from '../../utils/wechat-subscription'
|
||||
import { get } from '../../utils/request'
|
||||
import { cacheSubscriptionMessageTemplateConfig } from '../../utils/wechat-subscription'
|
||||
import type { SubscriptionMessageTemplateConfig } from '@mp-pilates/shared'
|
||||
|
||||
const canSubscribeClassReminder = computed(() => {
|
||||
if (!booking.value || booking.value.status !== BookingStatus.CONFIRMED) return false
|
||||
if (booking.value.userId !== userStore.user?.id) return false
|
||||
const slot = booking.value.timeSlot
|
||||
if (!slot) return false
|
||||
return !isSlotPast(slot.date, slot.startTime)
|
||||
})
|
||||
|
||||
async function subscribeClassReminder() {
|
||||
try {
|
||||
const results = await requestClassReminderSubscriptionMessage()
|
||||
uni.showToast({
|
||||
title: results.some((r) => r.result === 'accept') ? '上课提醒已开启' : '暂未开启提醒',
|
||||
icon: 'none',
|
||||
})
|
||||
} catch {
|
||||
uni.showToast({ title: '订阅失败,请稍后重试', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
async function subscribeReview() {
|
||||
try { const results = await requestSubscriptionMessage(SubscriptionMessageScene.CLASS_REVIEW); uni.showToast({ title: results.some(r => r.result === 'accept') ? '提醒已开启' : '暂未开启提醒', icon: 'none' }) } catch { uni.showToast({ title: '订阅失败,请稍后重试', icon: 'none' }) }
|
||||
}
|
||||
@@ -606,6 +634,12 @@ async function handleNoShow() {
|
||||
}
|
||||
|
||||
async function handleCancel() {
|
||||
try {
|
||||
await requestBookingCancelSubscriptionMessage()
|
||||
} catch (err: unknown) {
|
||||
console.warn('[subscribe] cancel pre-subscribe failed', err)
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '取消预约',
|
||||
content: '确定要取消该预约?',
|
||||
|
||||
@@ -98,6 +98,7 @@ import DateSelector from '../../components/DateSelector.vue'
|
||||
import TimePeriodFilter from '../../components/TimePeriodFilter.vue'
|
||||
import SlotCard from '../../components/SlotCard.vue'
|
||||
import BookingConfirmPopup from '../../components/BookingConfirmPopup.vue'
|
||||
import { requestBookingCancelSubscriptionMessage } from '../../utils/wechat-subscription'
|
||||
|
||||
type PeriodKey = keyof typeof TIME_PERIODS | null
|
||||
|
||||
@@ -368,6 +369,12 @@ async function onConfirmBooking(payload: { timeSlotId: string; membershipId: str
|
||||
async function onCancelTap(slot: TimeSlotWithBookingStatus) {
|
||||
if (!slot.myBookingId) return
|
||||
|
||||
try {
|
||||
await requestBookingCancelSubscriptionMessage()
|
||||
} catch (err: unknown) {
|
||||
console.warn('[subscribe] cancel pre-subscribe failed', err)
|
||||
}
|
||||
|
||||
uni.showModal({
|
||||
title: '取消预约',
|
||||
content: '确定要取消这个预约吗?',
|
||||
|
||||
@@ -154,6 +154,7 @@ import {
|
||||
} from '../../utils/booking-helpers'
|
||||
import CustomNavBar from '../../components/CustomNavBar.vue'
|
||||
import LessonSupplementList from '../../components/LessonSupplementList.vue'
|
||||
import { requestBookingCancelSubscriptionMessage } from '../../utils/wechat-subscription'
|
||||
|
||||
type TabKey = 'upcoming' | 'history'
|
||||
|
||||
@@ -327,6 +328,12 @@ function goDetail(booking: BookingWithDetails) {
|
||||
}
|
||||
|
||||
async function handleCancel(booking: BookingWithDetails) {
|
||||
try {
|
||||
await requestBookingCancelSubscriptionMessage()
|
||||
} catch (err: unknown) {
|
||||
console.warn('[subscribe] cancel pre-subscribe failed', err)
|
||||
}
|
||||
|
||||
const dateLabel = formatDateDisplay(booking.timeSlot.date)
|
||||
const timeLabel = startTime(booking)
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
:require-auth="loggedIn"
|
||||
@clear-cache="handleClearCache"
|
||||
@require-login="handleLogin"
|
||||
@open-notifications="showNotificationsModal = true"
|
||||
>
|
||||
<PracticeActivityCard v-if="loggedIn" :key="userStore.token" :refresh-key="activityRefreshKey" />
|
||||
</ProfileMenu>
|
||||
@@ -25,6 +26,9 @@
|
||||
<view v-if="loggedIn" class="profile-page__logout-wrap">
|
||||
<button class="profile-page__logout-btn" @tap="handleLogout">退出登录</button>
|
||||
</view>
|
||||
|
||||
<!-- Notification Settings Modal -->
|
||||
<SubscriptionSettingsModal v-model:visible="showNotificationsModal" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@@ -40,11 +44,13 @@ import { getErrorMessage } from '../../utils/auth'
|
||||
import PracticeActivityCard from '../../components/PracticeActivityCard.vue'
|
||||
import UserCard from '../../components/UserCard.vue'
|
||||
import ProfileMenu from '../../components/ProfileMenu.vue'
|
||||
import SubscriptionSettingsModal from '../../components/SubscriptionSettingsModal.vue'
|
||||
|
||||
const invite = useInviteStore()
|
||||
const userStore = useUserStore()
|
||||
const { loggedIn, hasProfile, user, memberships, membershipsLoading, membershipsLoaded, membershipsError, isAdmin } = storeToRefs(userStore)
|
||||
|
||||
const showNotificationsModal = ref(false)
|
||||
const activityRefreshKey = ref(0)
|
||||
const membershipNow = ref(Date.now())
|
||||
const loginLoading = ref(false)
|
||||
|
||||
Reference in New Issue
Block a user