Compare commits
5 Commits
7139865bff
...
00f1bcd0fa
| Author | SHA1 | Date | |
|---|---|---|---|
| 00f1bcd0fa | |||
| 1a3137bd12 | |||
| 4872f180dc | |||
| 605d463a00 | |||
| d88ef1756f |
@@ -0,0 +1,28 @@
|
||||
import { request } from './request'
|
||||
import { toPageQuery, toPagination } from './mappers'
|
||||
|
||||
const toCourseBrief = (backendCourse = {}) => ({
|
||||
id: backendCourse.id ?? null,
|
||||
title: backendCourse.title || '',
|
||||
cover_image: backendCourse.cover_image || '',
|
||||
updated_at: backendCourse.updated_at || ''
|
||||
})
|
||||
|
||||
const toCourseDetail = (backendCourse = {}) => ({
|
||||
...toCourseBrief(backendCourse),
|
||||
detail: backendCourse.detail || ''
|
||||
})
|
||||
|
||||
export const courseInfoApi = {
|
||||
getCourses(params = {}) {
|
||||
return request({
|
||||
url: '/api/courses',
|
||||
data: toPageQuery(params)
|
||||
}).then((data) => toPagination(data, toCourseBrief))
|
||||
},
|
||||
getCourseDetail(id) {
|
||||
return request({
|
||||
url: `/api/courses/${id}`
|
||||
}).then(toCourseDetail)
|
||||
}
|
||||
}
|
||||
@@ -86,10 +86,12 @@ export function request(options = {}) {
|
||||
}
|
||||
|
||||
const error = normalizeError(res, `请求错误 ${res.statusCode}`)
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none'
|
||||
})
|
||||
if (!options.silent) {
|
||||
uni.showToast({
|
||||
title: error.message,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
reject(error)
|
||||
},
|
||||
fail: (err) => {
|
||||
|
||||
@@ -39,7 +39,8 @@ const toTicketOrder = (backendData = {}) => ({
|
||||
export const ticketApi = {
|
||||
getTicketProduct() {
|
||||
return request({
|
||||
url: '/api/tickets/product'
|
||||
url: '/api/tickets/product',
|
||||
silent: true
|
||||
}).then(toTicketProduct)
|
||||
},
|
||||
getTicketAccount(params = {}) {
|
||||
|
||||
@@ -48,7 +48,8 @@
|
||||
{
|
||||
"path": "pages/profile/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "我的"
|
||||
"navigationBarTitleText": "我的",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -94,6 +95,19 @@
|
||||
"navigationBarTitleText": "课程详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/comprehensive/courses/list",
|
||||
"style": {
|
||||
"navigationBarTitleText": "全部课程",
|
||||
"enablePullDownRefresh": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/comprehensive/courses/info-detail",
|
||||
"style": {
|
||||
"navigationBarTitleText": "课程详情"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/comprehensive/courses/membership-detail",
|
||||
"style": {
|
||||
@@ -311,10 +325,10 @@
|
||||
"selectedIconPath": "static/comprehensive/tab/home-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/private/index",
|
||||
"text": "订场",
|
||||
"iconPath": "static/comprehensive/tab/private.png",
|
||||
"selectedIconPath": "static/comprehensive/tab/private-active.png"
|
||||
"pagePath": "pages/courses/index",
|
||||
"text": "约课",
|
||||
"iconPath": "static/comprehensive/tab/course.png",
|
||||
"selectedIconPath": "static/comprehensive/tab/course-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/mall/index",
|
||||
@@ -323,10 +337,10 @@
|
||||
"selectedIconPath": "static/comprehensive/tab/mall-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/courses/index",
|
||||
"text": "约课",
|
||||
"iconPath": "static/comprehensive/tab/course.png",
|
||||
"selectedIconPath": "static/comprehensive/tab/course-active.png"
|
||||
"pagePath": "pages/private/index",
|
||||
"text": "订场",
|
||||
"iconPath": "static/comprehensive/tab/private.png",
|
||||
"selectedIconPath": "static/comprehensive/tab/private-active.png"
|
||||
},
|
||||
{
|
||||
"pagePath": "pages/profile/index",
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
<view class="courses-page">
|
||||
<block>
|
||||
<view class="tab-bar">
|
||||
<view class="tab-item" :class="{ active: activeTab === 'membership' }" @tap="switchTab('membership')">会员卡</view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'tickets' }" @tap="switchTab('tickets')">门票</view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'membership' }" @tap="switchTab('membership')">课程</view>
|
||||
<view class="tab-item" :class="{ active: activeTab === 'tickets' }" @tap="switchTab('tickets')">篮球门票</view>
|
||||
</view>
|
||||
|
||||
<template v-if="activeTab === 'tickets'">
|
||||
@@ -12,7 +12,6 @@
|
||||
<view class="product-head">
|
||||
<view>
|
||||
<text class="product-title">篮球门票</text>
|
||||
<text class="product-subtitle">购买后进入会员权益账户,到馆由前台核销。</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price-block">
|
||||
@@ -53,6 +52,25 @@
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else-if="ticketPaused" class="ticket-section">
|
||||
<view class="card ticket-paused-card">
|
||||
<text class="ticket-paused-title">门票暂停购买</text>
|
||||
<text class="ticket-paused-desc">门票暂未开放购买,请稍后再来</text>
|
||||
</view>
|
||||
|
||||
<view v-if="ticketRecords.length" class="ticket-records-section">
|
||||
<view class="section-header">
|
||||
<text class="section-title">我的门票</text>
|
||||
</view>
|
||||
<view v-for="record in ticketRecords" :key="record.id" class="card ticket-record-card">
|
||||
<view class="ticket-record-top">
|
||||
<text class="ticket-record-no">订单号 {{ record.order_no }}</text>
|
||||
<text class="ticket-status-badge" :class="record.status">{{ record.status_label }}</text>
|
||||
</view>
|
||||
<text class="ticket-record-date">{{ formatDate(record.created_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="load-more-tip">加载中...</view>
|
||||
</template>
|
||||
|
||||
@@ -119,6 +137,7 @@ const isLoadingMore = ref(false)
|
||||
let loadRequestId = 0
|
||||
|
||||
const ticketProduct = ref(null)
|
||||
const ticketPaused = ref(false)
|
||||
const ticketQty = ref(1)
|
||||
const ticketSubmitting = ref(false)
|
||||
const ticketRecords = ref([])
|
||||
@@ -200,8 +219,11 @@ const loadTicketProduct = async () => {
|
||||
if (ticketProduct.value) return
|
||||
try {
|
||||
ticketProduct.value = await ticketApi.getTicketProduct()
|
||||
} catch {
|
||||
// ignore
|
||||
ticketPaused.value = false
|
||||
} catch (error) {
|
||||
if (error?.code === 40400 || error?.statusCode === 404) {
|
||||
ticketPaused.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,6 +285,7 @@ const goDetail = (item) => {
|
||||
const refresh = async () => {
|
||||
if (activeTab.value === 'tickets') {
|
||||
ticketProduct.value = null
|
||||
ticketPaused.value = false
|
||||
ticketRecords.value = []
|
||||
await Promise.all([loadTicketProduct(), loadTicketRecords()])
|
||||
} else {
|
||||
@@ -478,6 +501,25 @@ defineExpose({ refresh, loadMore })
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.ticket-paused-card {
|
||||
text-align: center;
|
||||
padding: $spacing-xl $spacing-md;
|
||||
}
|
||||
|
||||
.ticket-paused-title {
|
||||
display: block;
|
||||
color: $text-color-main;
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.ticket-paused-desc {
|
||||
display: block;
|
||||
margin-top: $spacing-xs;
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.product-head,
|
||||
.amount-row,
|
||||
.stepper-row {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<view class="article-detail container" v-if="course">
|
||||
<text class="title">{{ course.title }}</text>
|
||||
<text class="date">{{ formatDate(course.updated_at) }}</text>
|
||||
<image v-if="course.cover_image" :src="course.cover_image" mode="aspectFill" class="cover" />
|
||||
<view class="content-card card">
|
||||
<rich-text :nodes="course.detail"></rich-text>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="loading-state container">
|
||||
<text>加载中...</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { courseInfoApi } from '@/modules/comprehensive/api/courseInfo'
|
||||
import { formatDate } from '@/modules/comprehensive/utils/date'
|
||||
|
||||
const course = ref(null)
|
||||
|
||||
onLoad(async (query) => {
|
||||
course.value = await courseInfoApi.getCourseDetail(query.id)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title,
|
||||
.date {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $text-color-main;
|
||||
font-size: 42rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-top: $spacing-sm;
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
margin: $spacing-lg 0;
|
||||
border-radius: $border-radius-lg;
|
||||
background: $bg-color-hover;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
color: $text-color-regular;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.loading-state {
|
||||
padding: 200rpx 0;
|
||||
text-align: center;
|
||||
color: $text-color-light;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,126 @@
|
||||
<template>
|
||||
<view class="course-list-page container">
|
||||
<view class="page-title-block">
|
||||
<text class="page-title">全部课程</text>
|
||||
</view>
|
||||
|
||||
<view class="article-list">
|
||||
<view v-for="course in courses" :key="course.id" class="article-card card" @tap="goDetail(course.id)">
|
||||
<image :src="course.cover_image" mode="aspectFill" class="cover" />
|
||||
<view class="article-info">
|
||||
<text class="title">{{ course.title }}</text>
|
||||
<text class="date">{{ formatDate(course.updated_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="isLoading" class="load-more-tip">加载中...</view>
|
||||
<view v-else-if="courses.length === 0" class="empty-state">暂无课程</view>
|
||||
<view v-else-if="!hasMore" class="load-more-tip">已加载全部</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app'
|
||||
import { courseInfoApi } from '@/modules/comprehensive/api/courseInfo'
|
||||
import { formatDate } from '@/modules/comprehensive/utils/date'
|
||||
|
||||
const courses = ref([])
|
||||
const page = ref(1)
|
||||
const pageSize = 20
|
||||
const total = ref(0)
|
||||
const isLoading = ref(false)
|
||||
const isLoadingMore = ref(false)
|
||||
const hasMore = computed(() => courses.value.length < total.value)
|
||||
|
||||
onMounted(() => loadCourses(true))
|
||||
|
||||
onPullDownRefresh(() => {
|
||||
loadCourses(true).finally(() => uni.stopPullDownRefresh())
|
||||
})
|
||||
|
||||
onReachBottom(() => {
|
||||
if (!hasMore.value || isLoadingMore.value) return
|
||||
page.value += 1
|
||||
loadCourses(false)
|
||||
})
|
||||
|
||||
const loadCourses = async (reset) => {
|
||||
if (reset) {
|
||||
page.value = 1
|
||||
isLoading.value = true
|
||||
} else {
|
||||
isLoadingMore.value = true
|
||||
}
|
||||
try {
|
||||
const data = await courseInfoApi.getCourses({ page: page.value, pageSize })
|
||||
courses.value = reset ? data.items : [...courses.value, ...data.items]
|
||||
total.value = data.total
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
isLoadingMore.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const goDetail = (id) => {
|
||||
uni.navigateTo({ url: `/pages/comprehensive/courses/info-detail?id=${id}` })
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-title-block {
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.page-title,
|
||||
.title,
|
||||
.date {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
color: $text-color-main;
|
||||
font-size: 42rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.article-card {
|
||||
display: flex;
|
||||
gap: $spacing-md;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.cover {
|
||||
width: 190rpx;
|
||||
height: 136rpx;
|
||||
border-radius: $border-radius-base;
|
||||
background: $bg-color-hover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.article-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $text-color-main;
|
||||
font-size: 30rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.date {
|
||||
margin-top: $spacing-md;
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 160rpx 0;
|
||||
text-align: center;
|
||||
color: $text-color-light;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -38,6 +38,24 @@
|
||||
<view v-else class="empty-placeholder">暂无公告活动</view>
|
||||
</view>
|
||||
|
||||
<view class="section-container">
|
||||
<view class="section-header">
|
||||
<text class="section-title">课程</text>
|
||||
<text v-if="hasMoreCourses" class="section-more" @tap="go('/pages/comprehensive/courses/list')">查看全部</text>
|
||||
</view>
|
||||
|
||||
<view v-if="courses.length" class="activity-grid">
|
||||
<view v-for="course in courses" :key="course.id" class="activity-card" @tap="go(`/pages/comprehensive/courses/info-detail?id=${course.id}`)">
|
||||
<image :src="course.cover_image" mode="aspectFill" class="activity-cover" />
|
||||
<view class="activity-info">
|
||||
<text class="activity-title">{{ course.title }}</text>
|
||||
<text class="activity-date">{{ formatDate(course.updated_at) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-else class="empty-placeholder">暂无课程</view>
|
||||
</view>
|
||||
|
||||
<VenueSwitchHeader venue-name="综合场馆" />
|
||||
</view>
|
||||
</view>
|
||||
@@ -46,20 +64,28 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { contentApi } from '@/modules/comprehensive/api/content'
|
||||
import { courseInfoApi } from '@/modules/comprehensive/api/courseInfo'
|
||||
import { formatDate } from '@/modules/comprehensive/utils/date'
|
||||
import VenueSwitchHeader from '@/components/VenueSwitchHeader.vue'
|
||||
|
||||
const banners = ref([])
|
||||
const articles = ref([])
|
||||
const courses = ref([])
|
||||
const hasMoreCourses = ref(false)
|
||||
|
||||
onMounted(() => {
|
||||
loadPage()
|
||||
})
|
||||
|
||||
const loadPage = async () => {
|
||||
const home = await contentApi.getHomeContent({ banner_limit: 5, article_limit: 3 })
|
||||
const [home, courseData] = await Promise.all([
|
||||
contentApi.getHomeContent({ banner_limit: 5, article_limit: 3 }),
|
||||
courseInfoApi.getCourses({ page: 1, pageSize: 5 })
|
||||
])
|
||||
banners.value = home.banners || []
|
||||
articles.value = home.articles || []
|
||||
courses.value = courseData.items || []
|
||||
hasMoreCourses.value = courseData.total > courses.value.length
|
||||
}
|
||||
|
||||
const go = (url) => {
|
||||
@@ -131,6 +157,11 @@ defineExpose({ refresh: loadPage })
|
||||
background-color: $text-color-main;
|
||||
}
|
||||
|
||||
.section-more {
|
||||
color: $text-color-muted;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.activity-grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'stored_value' }"
|
||||
@tap="activeTab = 'stored_value'"
|
||||
@tap="switchTab('stored_value')"
|
||||
>
|
||||
储值卡
|
||||
会员卡
|
||||
</view>
|
||||
<view
|
||||
class="tab-item"
|
||||
:class="{ active: activeTab === 'membership' }"
|
||||
@tap="activeTab = 'membership'"
|
||||
@tap="switchTab('membership')"
|
||||
>
|
||||
会员卡
|
||||
课程套餐
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -80,7 +80,18 @@ const goDetail = (item) => {
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ refresh: loadPage })
|
||||
const switchTab = (tab) => {
|
||||
if (activeTab.value === tab) return
|
||||
activeTab.value = tab
|
||||
loadPage()
|
||||
}
|
||||
|
||||
const refresh = async () => {
|
||||
await session.init()
|
||||
await loadPage()
|
||||
}
|
||||
|
||||
defineExpose({ refresh })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -23,12 +23,24 @@
|
||||
|
||||
<view class="card method-card">
|
||||
<text class="card-title">支付方式</text>
|
||||
<view class="method-item active">
|
||||
<view
|
||||
v-if="!isWechatOnlyOrder"
|
||||
class="method-item"
|
||||
:class="{ active: payMethod === 'balance', disabled: !canUseBalance }"
|
||||
@tap="selectMethod('balance')"
|
||||
>
|
||||
<view>
|
||||
<text class="method-title">余额支付</text>
|
||||
<text class="method-sub">当前余额 ¥{{ balance.balance }}</text>
|
||||
</view>
|
||||
<text class="method-check">{{ payMethod === 'balance' ? '已选' : '' }}</text>
|
||||
</view>
|
||||
<view class="method-item" :class="{ active: payMethod === 'wechat' }" @tap="selectMethod('wechat')">
|
||||
<view>
|
||||
<text class="method-title">微信支付</text>
|
||||
<text class="method-sub">使用微信支付完成订单</text>
|
||||
</view>
|
||||
<text class="method-check">已选</text>
|
||||
<text class="method-check">{{ payMethod === 'wechat' ? '已选' : '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -49,19 +61,27 @@
|
||||
<script setup>
|
||||
import { computed, onUnmounted, ref } from 'vue'
|
||||
import { onLoad } from '@dcloudio/uni-app'
|
||||
import { memberApi } from '@/modules/comprehensive/api/member'
|
||||
import { orderApi } from '@/modules/comprehensive/api/order'
|
||||
import { formatDateTime } from '@/modules/comprehensive/utils/date'
|
||||
import { toAmountNumber } from '@/modules/comprehensive/utils/money'
|
||||
|
||||
const orderId = ref('')
|
||||
const order = ref(null)
|
||||
const balance = ref({ balance: '0.00' })
|
||||
const payMethod = ref('balance')
|
||||
const isSubmitting = ref(false)
|
||||
const nowTimestamp = ref(Date.now())
|
||||
let countdownTimer = null
|
||||
|
||||
const canPay = computed(() => order.value?.status === 'pending_pay')
|
||||
const isWechatOnlyOrder = computed(() => order.value?.business_type !== 'private_booking')
|
||||
const canUseBalance = computed(() => {
|
||||
return toAmountNumber(balance.value.balance) >= toAmountNumber(order.value?.amount)
|
||||
})
|
||||
const canPay = computed(() => order.value?.status === 'pending_pay' && (payMethod.value !== 'balance' || canUseBalance.value))
|
||||
const payButtonText = computed(() => {
|
||||
if (order.value?.status !== 'pending_pay') return '订单不可支付'
|
||||
return '微信支付'
|
||||
return payMethod.value === 'balance' ? '余额支付' : '微信支付'
|
||||
})
|
||||
const paymentRemainingSeconds = computed(() => {
|
||||
if (!order.value?.expire_at) return 0
|
||||
@@ -87,6 +107,12 @@ onUnmounted(() => {
|
||||
|
||||
const loadPage = async () => {
|
||||
order.value = await orderApi.getOrderDetail(orderId.value)
|
||||
if (isWechatOnlyOrder.value) {
|
||||
payMethod.value = 'wechat'
|
||||
} else {
|
||||
balance.value = await memberApi.getBalance()
|
||||
payMethod.value = canUseBalance.value ? 'balance' : 'wechat'
|
||||
}
|
||||
updateCountdownTimer()
|
||||
}
|
||||
|
||||
@@ -108,13 +134,26 @@ const stopCountdown = () => {
|
||||
|
||||
const padTime = (value) => String(value).padStart(2, '0')
|
||||
|
||||
const selectMethod = (method) => {
|
||||
if (method === 'balance' && !canUseBalance.value) {
|
||||
uni.showToast({ title: '余额不足', icon: 'none' })
|
||||
return
|
||||
}
|
||||
payMethod.value = method
|
||||
}
|
||||
|
||||
const pay = async () => {
|
||||
if (!canPay.value || isSubmitting.value) return
|
||||
isSubmitting.value = true
|
||||
try {
|
||||
const paymentData = await orderApi.payOrderByWechat(order.value.id)
|
||||
await requestWechatPayment(paymentData.wechat)
|
||||
uni.showToast({ title: '支付已提交', icon: 'success' })
|
||||
if (payMethod.value === 'balance') {
|
||||
await orderApi.payOrderByBalance(order.value.id)
|
||||
uni.showToast({ title: '支付成功', icon: 'success' })
|
||||
} else {
|
||||
const paymentData = await orderApi.payOrderByWechat(order.value.id)
|
||||
await requestWechatPayment(paymentData.wechat)
|
||||
uni.showToast({ title: '支付已提交', icon: 'success' })
|
||||
}
|
||||
await loadPage()
|
||||
setTimeout(() => {
|
||||
uni.redirectTo({ url: `/pages/comprehensive/orders/detail?id=${order.value.id}` })
|
||||
@@ -218,6 +257,10 @@ const requestWechatPayment = (wechatPayload) => new Promise((resolve, reject) =>
|
||||
color: $color-primary;
|
||||
}
|
||||
|
||||
.method-item.disabled {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.method-title,
|
||||
.method-sub {
|
||||
display: block;
|
||||
|
||||
@@ -119,7 +119,7 @@ const weekDays = ['周日', '周一', '周二', '周三', '周四', '周五', '
|
||||
const hasPositiveBalance = computed(() => toAmountNumber(session.member?.balance) > 0)
|
||||
const dateOptions = computed(() => {
|
||||
const today = dayjs()
|
||||
const dateCount = hasPositiveBalance.value ? 7 : 1
|
||||
const dateCount = hasPositiveBalance.value ? 7 : 3
|
||||
return Array.from({ length: dateCount }).map((_, i) => {
|
||||
const d = today.add(i, 'day')
|
||||
return {
|
||||
@@ -360,15 +360,20 @@ function normalizeClock(value) {
|
||||
|
||||
async function refresh() {
|
||||
await session.init()
|
||||
if (!hasPositiveBalance.value && selectedDate.value !== dayjs().format('YYYY-MM-DD')) {
|
||||
selectedDate.value = dayjs().format('YYYY-MM-DD')
|
||||
if (!hasPositiveBalance.value) {
|
||||
const todayStr = dayjs().format('YYYY-MM-DD')
|
||||
const maxDateStr = dayjs().add(2, 'day').format('YYYY-MM-DD')
|
||||
if (selectedDate.value < todayStr || selectedDate.value > maxDateStr) {
|
||||
selectedDate.value = todayStr
|
||||
}
|
||||
}
|
||||
now.value = dayjs()
|
||||
await loadGrid()
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
refresh
|
||||
refresh,
|
||||
loadGrid
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useComprehensiveSessionStore } from '@/modules/comprehensive/stores/session'
|
||||
import { memberApi } from '@/modules/comprehensive/api/member'
|
||||
import { formatAmount } from '@/modules/comprehensive/utils/money'
|
||||
@@ -88,21 +88,11 @@ const balance = ref('0.00')
|
||||
const balanceText = computed(() => formatAmount(balance.value))
|
||||
|
||||
const menuItems = [
|
||||
{ title: '我的会员卡', url: '/pages/comprehensive/membership/cards', theme: 'profile-theme', icon: 'profile' },
|
||||
{ title: '我的课程套餐', url: '/pages/comprehensive/membership/cards', theme: 'profile-theme', icon: 'profile' },
|
||||
{ title: '我的订单', url: '/pages/comprehensive/profile/orders', theme: 'order-theme', icon: 'order' },
|
||||
{ title: '余额流水', url: '/pages/comprehensive/profile/balance', theme: 'order-theme', icon: 'order' }
|
||||
]
|
||||
|
||||
onMounted(async () => {
|
||||
await session.init()
|
||||
try {
|
||||
const data = await memberApi.getBalance()
|
||||
balance.value = data.balance
|
||||
} catch {
|
||||
balance.value = '0.00'
|
||||
}
|
||||
})
|
||||
|
||||
const login = () => {
|
||||
uni.navigateTo({ url: '/pages/comprehensive/profile/login' })
|
||||
}
|
||||
@@ -116,7 +106,17 @@ const go = (url) => {
|
||||
uni.navigateTo({ url })
|
||||
}
|
||||
|
||||
defineExpose({ onPageShow: () => {} })
|
||||
const refresh = async () => {
|
||||
await session.init()
|
||||
try {
|
||||
const data = await memberApi.getBalance()
|
||||
balance.value = data.balance
|
||||
} catch {
|
||||
balance.value = '0.00'
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({ onPageShow: refresh, refresh })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
<script setup>
|
||||
defineExpose({
|
||||
refresh() {}
|
||||
refresh() {},
|
||||
loadGrid() {}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@
|
||||
<view class="svg-card-chip" />
|
||||
</view>
|
||||
</view>
|
||||
<text class="item-title">我的会员卡</text>
|
||||
<text class="item-title">我的课程套餐</text>
|
||||
</view>
|
||||
<view class="item-right">
|
||||
<view class="count-badge" v-if="member.cardCount > 0">
|
||||
@@ -497,7 +497,7 @@ const formatDate = (dateStr) => {
|
||||
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`
|
||||
}
|
||||
|
||||
defineExpose({ onPageShow: refreshSession })
|
||||
defineExpose({ onPageShow: refreshSession, refresh: refreshSession })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -7,13 +7,22 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { onPullDownRefresh, onShow } from '@dcloudio/uni-app'
|
||||
import { getStoredVenue } from '@/utils/venue'
|
||||
import ComprehensiveMall from '@/pages/comprehensive/mall/index.vue'
|
||||
import IceMall from '@/pages/ice/mall/index.vue'
|
||||
|
||||
const isComprehensive = getStoredVenue() === 'comprehensive'
|
||||
const contentRef = ref()
|
||||
let isFirstShow = true
|
||||
|
||||
onShow(() => {
|
||||
if (isFirstShow) {
|
||||
isFirstShow = false
|
||||
return
|
||||
}
|
||||
contentRef.value?.refresh?.()
|
||||
})
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
await contentRef.value?.refresh?.()
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { onShow, onHide } from '@dcloudio/uni-app'
|
||||
import { getStoredVenue } from '@/utils/venue'
|
||||
import ComprehensivePrivate from '@/pages/comprehensive/private/index.vue'
|
||||
import IcePrivate from '@/pages/ice/private/index.vue'
|
||||
@@ -15,13 +15,33 @@ import IcePrivate from '@/pages/ice/private/index.vue'
|
||||
const isComprehensive = getStoredVenue() === 'comprehensive'
|
||||
const contentRef = ref()
|
||||
let isFirstShow = true
|
||||
let autoRefreshTimer = null
|
||||
|
||||
const startAutoRefresh = () => {
|
||||
stopAutoRefresh()
|
||||
autoRefreshTimer = setInterval(() => {
|
||||
contentRef.value?.loadGrid?.()
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
const stopAutoRefresh = () => {
|
||||
if (autoRefreshTimer) {
|
||||
clearInterval(autoRefreshTimer)
|
||||
autoRefreshTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
if (isFirstShow) {
|
||||
isFirstShow = false
|
||||
return
|
||||
} else {
|
||||
contentRef.value?.refresh?.()
|
||||
}
|
||||
contentRef.value?.refresh?.()
|
||||
startAutoRefresh()
|
||||
})
|
||||
|
||||
onHide(() => {
|
||||
stopAutoRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { getStoredVenue } from '@/utils/venue'
|
||||
import ComprehensiveProfile from '@/pages/comprehensive/profile/index.vue'
|
||||
import IceProfile from '@/pages/ice/profile/index.vue'
|
||||
@@ -18,6 +18,11 @@ const contentRef = ref()
|
||||
onShow(() => {
|
||||
contentRef.value?.onPageShow?.()
|
||||
})
|
||||
|
||||
onPullDownRefresh(async () => {
|
||||
await contentRef.value?.refresh?.()
|
||||
uni.stopPullDownRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
Reference in New Issue
Block a user