From a350bcc4edb2d1979a82035ae0b4f47d4d4afa78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=82=B9=E6=96=B9=E6=88=90?= Date: Mon, 22 Dec 2025 21:06:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=A7=AF=E5=88=86?= =?UTF-8?q?=E5=85=91=E6=8D=A2=E5=95=86=E5=93=81=E5=8A=9F=E8=83=BD=E5=8F=8A?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=AE=A2=E5=8D=95=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在request.js中添加积分兑换商品API - 在shop页面实现积分兑换功能及UI优化 - 在orders页面优化订单显示逻辑,支持优惠券和道具卡标签 - 在mine页面调整订单导航逻辑,支持跳转至cabinet指定tab - 优化道具卡和优惠券的显示及状态处理 --- pages/activity/duiduipeng/index.vue | 17 +- pages/cabinet/index.vue | 9 + pages/mine/index.vue | 266 ++++++++++++++++++++++++---- pages/orders/detail.vue | 60 +++++-- pages/orders/index.vue | 32 +++- pages/shop/detail.vue | 119 +++++++++++-- pages/shop/index.vue | 88 +++++---- utils/request.js | 8 + 8 files changed, 505 insertions(+), 94 deletions(-) diff --git a/pages/activity/duiduipeng/index.vue b/pages/activity/duiduipeng/index.vue index a825a1e..564caf1 100644 --- a/pages/activity/duiduipeng/index.vue +++ b/pages/activity/duiduipeng/index.vue @@ -1133,15 +1133,22 @@ async function fetchPropCards() { const user_id = uni.getStorageSync('user_id') if (!user_id) return try { - const res = await getItemCards(user_id, 0) + // Status 1 = Unused + const res = await getItemCards(user_id, 1) let list = [] if (Array.isArray(res)) list = res else if (res && Array.isArray(res.list)) list = res.list else if (res && Array.isArray(res.data)) list = res.data - propCards.value = list.map((i, idx) => ({ - id: i.id ?? i.card_id ?? i.item_card_id ?? String(idx), - name: i.name ?? i.title ?? i.card_name ?? '道具卡' - })) + propCards.value = list.map((i, idx) => { + const count = i.count ?? i.remaining ?? 1 + const name = i.name ?? i.title ?? i.card_name ?? '道具卡' + return { + id: i.id ?? i.card_id ?? i.item_card_id ?? String(idx), + name: `${name} (×${count})`, + rawName: name, + count: count + } + }) } catch (e) { propCards.value = [] } diff --git a/pages/cabinet/index.vue b/pages/cabinet/index.vue index 335dd06..a61a966 100644 --- a/pages/cabinet/index.vue +++ b/pages/cabinet/index.vue @@ -159,6 +159,15 @@ const isAllSelected = computed(() => { }) onShow(() => { + // Check for external tab switch request + try { + const targetTab = uni.getStorageSync('cabinet_target_tab') + if (targetTab !== '' && targetTab !== null && targetTab !== undefined) { + currentTab.value = Number(targetTab) + uni.removeStorageSync('cabinet_target_tab') + } + } catch (e) {} + const token = uni.getStorageSync('token') const phoneBound = !!uni.getStorageSync('phone_bound') console.log('cabinet onShow token:', token, 'isLogin:', !!token, 'phoneBound:', phoneBound) diff --git a/pages/mine/index.vue b/pages/mine/index.vue index b2cb6bc..5714cb5 100644 --- a/pages/mine/index.vue +++ b/pages/mine/index.vue @@ -93,30 +93,34 @@ 全部订单 › - - - - - 盒柜 - + 待付款 - + + 待发货 - + + 已发货 + + + + + + 全部订单 + @@ -254,7 +258,12 @@ - {{ formatCouponExpiry(item) }} + + {{ formatCouponExpiry(item) }} + + 使用时间:{{ formatDateTime(item.used_at) }} + + 去使用 @@ -262,9 +271,6 @@ {{ couponsTab === 2 ? '已使用' : '已过期' }} - - - 使用时间:{{ formatDateTime(item.used_at) }} @@ -598,6 +604,10 @@ export default { toOrders(status) { uni.navigateTo({ url: `/pages/orders/index?status=${status}` }) }, + toCabinetTab(tabIndex) { + uni.setStorageSync('cabinet_target_tab', tabIndex) + uni.switchTab({ url: '/pages/cabinet/index' }) + }, toAddresses() { uni.navigateTo({ url: '/pages/address/index' }) }, @@ -790,23 +800,27 @@ export default { this.itemCardsLoading = true this.itemCardsList = [] try { - // Mocking used status via local filter if API doesn't support - // Or assume API supports status param. - const res = await getItemCards(this.userId) - // Assuming res is list of cards with counts. - // For "used", we might need a different API or field. - // For now, just show all for unused, and empty for used as mock. - let list = Array.isArray(res) ? res : (res.list || []) + // Pass status: 0(tab)=>1(unused), 1(tab)=>2(used) + const status = this.itemCardsTab === 0 ? 1 : 2 + const res = await getItemCards(this.userId, status) + // Robustly get the list (support res.list or res.data) + let list = Array.isArray(res) ? res : (res.list || res.data || []) + + this.itemCardsList = list.map(item => { + return { + ...item, + // Ensure count exists, default to 1 if undefined + count: item.count ?? item.remaining ?? 1 + } + }) + + // For unused tab, filter out items with 0 count if any if (this.itemCardsTab === 0) { - // Show cards with count > 0 - this.itemCardsList = list.filter(i => (i.count || i.remaining) > 0) - } else { - // Mock used history or filter - this.itemCardsList = [] + this.itemCardsList = this.itemCardsList.filter(i => i.count > 0) } } catch (e) { - console.error(e) + console.error('loadItemCards error:', e) } finally { this.itemCardsLoading = false } @@ -882,8 +896,24 @@ export default { }, formatDateTime(ts) { if (!ts) return '' - const d = new Date(ts * 1000) - return `${this.formatDate(ts)} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}` + // Convert string timestamp (e.g. "2023-01-01T...") to timestamp if needed + let d + if (typeof ts === 'string') { + // iOS compatibility for ISO strings + const safeTs = ts.replace(/-/g, '/') + d = new Date(safeTs) + if (isNaN(d.getTime())) { + // Fallback for non-ISO strings or just numbers as strings + const n = Number(ts) + if (!isNaN(n)) d = new Date(n * 1000) + } + } else { + d = new Date(ts * 1000) + } + + if (!d || isNaN(d.getTime())) return '' + + return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}` } } } @@ -1166,11 +1196,67 @@ export default { line-height: 1; padding: 10rpx; } + .status-text, .empty-state { padding: 60rpx; text-align: center; color: $text-sub; font-size: 26rpx; } .empty-icon { font-size: 80rpx; display: block; margin-bottom: 20rpx; } +.no-more { + text-align: center; + font-size: 24rpx; + color: $text-tertiary; + padding: 30rpx 0; + display: flex; + align-items: center; + justify-content: center; + + .text { + margin: 0 16rpx; + } + + .divider { + width: 60rpx; + height: 1px; + background: #e0e0e0; + } +} + +/* 弹窗 Tab 栏 */ +.popup-tabs { + display: flex; + justify-content: space-around; + padding: 0 20rpx; + background: #fff; + border-bottom: 1rpx solid $border-color-light; + margin-bottom: 20rpx; +} +.popup-tab { + flex: 1; + text-align: center; + font-size: 28rpx; + color: $text-sub; + padding: 24rpx 0; + position: relative; + font-weight: 500; + transition: all 0.2s; +} +.popup-tab.active { + color: $text-main; + font-weight: 700; +} +.popup-tab.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 50%; + transform: translateX(-50%); + width: 40rpx; + height: 6rpx; + background: $brand-primary; + border-radius: 6rpx; +} + /* 列表滚动区 */ .points-list, .coupon-scroll, .item-cards-scroll, .task-list-scroll, .invite-list-scroll { flex: 1; min-height: 400rpx; background: $bg-grey; padding: 20rpx; @@ -1197,20 +1283,103 @@ export default { .coupon-left-v2 { width: 180rpx; background: linear-gradient(135deg, #FFF5E6, #fff); display: flex; flex-direction: column; align-items: center; justify-content: center; - padding: 20rpx; border-right: 2rpx dashed #E0E0E0; + padding: 20rpx; position: relative; } .coupon-remaining { color: $brand-primary; font-weight: 900; } .coupon-symbol { font-size: 24rpx; } .coupon-amount-num { font-size: 56rpx; line-height: 1; } .coupon-label { font-size: 20rpx; color: $brand-primary; margin-top: 8rpx; border: 1px solid $brand-primary; padding: 2rpx 8rpx; border-radius: 6rpx; } -.coupon-right-v2 { flex: 1; padding: 24rpx; display: flex; flex-direction: column; justify-content: space-between; } -.coupon-name-v2 { font-size: $font-md; font-weight: 700; color: $text-main; margin-bottom: 8rpx; } + +/* 优惠券分割线 */ +.coupon-divider-v2 { + width: 30rpx; + position: relative; + background: #fff; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + overflow: hidden; +} +.divider-notch { + width: 24rpx; + height: 24rpx; + background: $bg-grey; /* Match scroll container bg */ + border-radius: 50%; + position: absolute; + left: 50%; + transform: translateX(-50%); + z-index: 2; +} +.divider-notch.top { top: -12rpx; } +.divider-notch.bottom { bottom: -12rpx; } +.divider-dash { + width: 0; + height: 80%; + border-left: 2rpx dashed #eee; +} + +.coupon-right-v2 { + flex: 1; + padding: 24rpx; + display: flex; + flex-direction: column; + justify-content: space-between; + overflow: hidden; /* Ensure content stays inside */ +} +.coupon-name-v2 { + font-size: $font-md; + font-weight: 700; + color: $text-main; + margin-bottom: 8rpx; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.coupon-original { font-size: 20rpx; color: $text-tertiary; text-decoration: line-through; margin-left: 8rpx; display: inline-block; } .coupon-rules { font-size: $font-xs; color: $text-sub; margin-bottom: 16rpx; } -.coupon-footer-row { display: flex; justify-content: space-between; align-items: center; margin-top: auto; } + +/* 优惠券进度条 */ +.coupon-progress-wrap { + margin-bottom: 12rpx; +} +.coupon-progress-bar { + height: 6rpx; + background: $bg-secondary; + border-radius: 100rpx; + overflow: hidden; + margin-bottom: 4rpx; +} +.coupon-progress-fill { + height: 100%; + background: $brand-primary; + border-radius: 100rpx; +} +.coupon-progress-text { + font-size: 18rpx; + color: $text-tertiary; +} + +.coupon-footer-row { display: flex; justify-content: space-between; align-items: flex-end; margin-top: auto; } +.coupon-footer-left { display: flex; flex-direction: column; } .coupon-expire-v2 { font-size: 20rpx; color: $text-tertiary; } .use-btn-v2 { background: $brand-primary; color: #fff; font-size: 22rpx; padding: 8rpx 24rpx; border-radius: 100rpx; } .status-tag { font-size: 22rpx; color: $text-tertiary; background: #F5F5F5; padding: 4rpx 12rpx; border-radius: 6rpx; } +.coupon-used-time { font-size: 18rpx; color: $text-tertiary; margin-top: 4rpx; text-align: left; } + +/* 过期/已使用状态 */ +.coupon-used .coupon-left-v2, .coupon-expired .coupon-left-v2 { + background: #f9f9f9; +} +.coupon-used .coupon-remaining, .coupon-expired .coupon-remaining, +.coupon-used .coupon-label, .coupon-expired .coupon-label { + color: $text-tertiary; + border-color: $text-tertiary; +} +.coupon-used .coupon-name-v2, .coupon-expired .coupon-name-v2 { + color: $text-sub; +} /* 道具卡 */ .item-cards-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx; } @@ -1227,6 +1396,41 @@ export default { .card-count-badge { position: absolute; top: 20rpx; right: 20rpx; background: rgba(0,0,0,0.05); padding: 4rpx 12rpx; border-radius: 100rpx; } .count-num { font-size: 22rpx; font-weight: 700; color: $text-main; } +/* 道具卡已使用状态 */ +.item-card.used { + background: #fafafa; +} +.item-card.used .card-name { + color: $text-sub; +} +.item-card.used .card-desc { + color: $text-tertiary; +} +.item-card.used .card-icon-wrap { + background: #f0f0f0; + opacity: 0.6; +} +.card-used-badge { + position: absolute; + top: 20rpx; + right: 20rpx; + background: #eee; + padding: 4rpx 12rpx; + border-radius: 100rpx; +} +.used-text { + font-size: 22rpx; + color: $text-tertiary; +} +.card-use-time { + font-size: 18rpx; + color: $text-tertiary; + margin-top: 12rpx; + display: block; + border-top: 1rpx dashed #eee; + padding-top: 8rpx; +} + /* 任务中心弹窗 */ .task-center-popup { background: #F8F9FA; } .overall-progress { diff --git a/pages/orders/detail.vue b/pages/orders/detail.vue index 081e827..a564a6a 100644 --- a/pages/orders/detail.vue +++ b/pages/orders/detail.vue @@ -63,8 +63,8 @@ - ¥ - {{ formatPrice(item.price) }} + ¥ + {{ item.price > 0 ? formatPrice(item.price) : '奖品' }} x{{ item.quantity }} @@ -87,8 +87,8 @@ - ¥ - {{ formatPrice(order.actual_amount) }} + ¥ + {{ order.actual_amount > 0 ? formatPrice(order.actual_amount) : '奖品' }} x1 @@ -130,19 +130,39 @@ 商品总额 ¥{{ formatPrice(order.total_amount) }} - - 优惠金额 + + + + 优惠券 + + {{ order.coupon_info.name }} + -¥{{ formatPrice(order.coupon_info.value) }} + + + + + + 道具卡 + + {{ order.item_card_info.name }} + 双倍奖励 + 已使用 + + + + + 其他优惠 -¥{{ formatPrice(order.discount_amount) }} - 实付款 - - ¥ - {{ formatPrice(order.actual_amount) }} - + {{ order.actual_amount > 0 ? '实付款' : '状态' }} + + ¥ + {{ order.actual_amount > 0 ? formatPrice(order.actual_amount) : '无需支付' }} + @@ -656,6 +676,24 @@ function showProofHelp() { background: $bg-secondary; } } + + .tag-small { + font-size: 20rpx; + padding: 2rpx 8rpx; + border-radius: 6rpx; + + &.coupon { + color: #FF6B6B; + background: rgba(255, 107, 107, 0.1); + border: 1rpx solid rgba(255, 107, 107, 0.2); + } + + &.card { + color: #6C5CE7; + background: rgba(108, 92, 231, 0.1); + border: 1rpx solid rgba(108, 92, 231, 0.2); + } + } } .divider { diff --git a/pages/orders/index.vue b/pages/orders/index.vue index 1370336..d513e82 100644 --- a/pages/orders/index.vue +++ b/pages/orders/index.vue @@ -82,6 +82,8 @@ {{ item.activity_name }} 第{{ item.issue_number }}期 + 券: {{ item.coupon_info.name }} + 卡: {{ item.item_card_info.name }} {{ formatTime(item.created_at) }} @@ -94,9 +96,9 @@ {{ item.order_no }} - 实付 - {{ formatAmount(item.actual_amount || item.total_amount) }} - + 实付 + {{ getAmountText(item) }} + @@ -167,6 +169,22 @@ function formatAmount(a) { return `¥${yuan.toFixed(2)}` } +function shouldShowAmountLabel(item) { + const amount = item.actual_amount || item.total_amount + return amount > 0 +} + +function getAmountText(item) { + const amount = item.actual_amount || item.total_amount + if (amount > 0) return formatAmount(amount) + + // 金额为0的情况 + if (item.source_type === 3 || item.source_type === 2) { + return '奖品' + } + return '免费' +} + function getOrderTitle(item) { // 1. 优先使用 items 中的商品名称(通常是实物购买或中奖) if (item.items && item.items.length > 0 && item.items[0].title) { @@ -724,6 +742,14 @@ onReachBottom(() => { background: $bg-secondary; padding: 4rpx 12rpx; border-radius: $radius-sm; + .coupon-tag { + color: #FF6B6B; + background: rgba(255, 107, 107, 0.1); + } + .card-tag { + color: #6C5CE7; + background: rgba(108, 92, 231, 0.1); + } } .order-time { font-size: $font-xs; diff --git a/pages/shop/detail.vue b/pages/shop/detail.vue index 84ad784..a27967b 100644 --- a/pages/shop/detail.vue +++ b/pages/shop/detail.vue @@ -7,14 +7,25 @@ {{ detail.title || detail.name || '-' }} - ¥{{ formatPrice(detail.price_sale || detail.price) }} - {{ detail.points_required }}积分 + + {{ detail.points_required }} + 积分 + + ¥{{ formatPrice(detail.price_sale || detail.price) }} 库存:{{ detail.stock }} {{ detail.description }} + 商品不存在 + + + + + 立即兑换 + 立即购买 + @@ -22,6 +33,7 @@ import { ref } from 'vue' import { onLoad } from '@dcloudio/uni-app' import { getProductDetail } from '../../api/appUser' +import { redeemProductByPoints } from '../../utils/request.js' const detail = ref({}) const loading = ref(false) @@ -43,6 +55,52 @@ async function fetchDetail(id) { } } +function onBuy() { + uni.showToast({ title: '暂未开放购买', icon: 'none' }) +} + +async function onRedeem() { + const p = detail.value + if (!p || !p.id) return + + const token = uni.getStorageSync('token') + if (!token) { + uni.showModal({ + title: '提示', + content: '请先登录', + confirmText: '去登录', + success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) } + }) + return + } + + uni.showModal({ + title: '确认兑换', + content: `是否消耗 ${p.points_required} 积分兑换 ${p.title}?`, + success: async (res) => { + if (res.confirm) { + uni.showLoading({ title: '兑换中...' }) + try { + const userId = uni.getStorageSync('user_id') + if (!userId) throw new Error('用户ID不存在') + + await redeemProductByPoints(userId, p.id, 1) + uni.showToast({ title: '兑换成功', icon: 'success' }) + + // Refresh detail + setTimeout(() => { + fetchDetail(p.id) + }, 1500) + } catch (e) { + uni.showToast({ title: e.message || '兑换失败', icon: 'none' }) + } finally { + uni.hideLoading() + } + } + } + }) +} + onLoad((opts) => { const id = opts && opts.id if (id) fetchDetail(id) @@ -81,15 +139,14 @@ onLoad((opts) => { } .info-card { - margin: $spacing-lg; - margin-top: -60rpx; - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(20rpx); - border-radius: $radius-xl; + background: #fff; + border-radius: $radius-xl $radius-xl 0 0; padding: $spacing-xl; - box-shadow: $shadow-lg; + box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.05); position: relative; z-index: 2; + margin-top: -40rpx; /* Slight overlap */ + min-height: 50vh; } .title { @@ -120,12 +177,19 @@ onLoad((opts) => { } } -.points { - font-size: $font-sm; - color: $brand-primary; - padding: 6rpx $spacing-md; - background: rgba($brand-primary, 0.1); - border-radius: 100rpx; +.points-wrap { + display: flex; align-items: baseline; +} +.points-val { + font-size: 48rpx; + font-weight: 900; + color: #FF9800; + font-family: 'DIN Alternate', sans-serif; +} +.points-unit { + font-size: 24rpx; + color: #FF9800; + margin-left: 6rpx; font-weight: 600; } @@ -156,6 +220,33 @@ onLoad((opts) => { } } +.action-bar-placeholder { height: 120rpx; } +.action-bar { + position: fixed; + bottom: 0; left: 0; right: 0; + padding: 20rpx 40rpx; + padding-bottom: calc(20rpx + env(safe-area-inset-bottom)); + background: rgba(255,255,255,0.9); + backdrop-filter: blur(10px); + box-shadow: 0 -4rpx 20rpx rgba(0,0,0,0.05); + display: flex; + justify-content: flex-end; + z-index: 10; +} +.action-btn { + width: 100%; + height: 88rpx; + border-radius: 44rpx; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 700; + color: #fff; +} +.action-btn.redeem { background: linear-gradient(135deg, #FFB74D, #FF9800); box-shadow: 0 8rpx 20rpx rgba(255, 152, 0, 0.3); } +.action-btn.buy { background: linear-gradient(135deg, #FF6B6B, #FF3B30); box-shadow: 0 8rpx 20rpx rgba(255, 59, 48, 0.3); } + @keyframes fadeInUp { from { opacity: 0; transform: translateY(40rpx); } to { opacity: 1; transform: translateY(0); } diff --git a/pages/shop/index.vue b/pages/shop/index.vue index a09bf39..a48f0e7 100644 --- a/pages/shop/index.vue +++ b/pages/shop/index.vue @@ -37,14 +37,15 @@ {{ p.title }} - - - {{ p.price }} - - + {{ p.points }} 积分 + + + {{ p.price }} + + 兑换 @@ -108,6 +109,46 @@ function onProductTap(p) { } } +async function onRedeemTap(p) { + const token = uni.getStorageSync('token') + if (!token) { + uni.showModal({ + title: '提示', + content: '请先登录', + confirmText: '去登录', + success: (res) => { if (res.confirm) uni.navigateTo({ url: '/pages/login/index' }) } + }) + return + } + + uni.showModal({ + title: '确认兑换', + content: `是否消耗 ${p.points} 积分兑换 ${p.title}?`, + success: async (res) => { + if (res.confirm) { + uni.showLoading({ title: '兑换中...' }) + try { + // Get user_id from storage + const userId = uni.getStorageSync('user_id') + if (!userId) throw new Error('用户ID不存在') + + await redeemProductByPoints(userId, p.id, 1) + uni.showToast({ title: '兑换成功', icon: 'success' }) + + // Refresh products to update stock/points if needed + setTimeout(() => { + loadProducts() + }, 1500) + } catch (e) { + uni.showToast({ title: e.message || '兑换失败', icon: 'none' }) + } finally { + uni.hideLoading() + } + } + } + }) +} + // Filter logic const allProducts = ref([]) // Store all fetched products for client-side filtering @@ -473,33 +514,20 @@ onShareTimeline(() => { gap: 8rpx; } .price-row { - color: $accent-red; - font-weight: 700; - display: flex; - align-items: baseline; + display: flex; align-items: baseline; } -.price-symbol { +.points-val { font-size: 36rpx; font-weight: 700; color: #FF9800; } +.points-unit { font-size: 22rpx; color: #FF9800; margin-left: 4rpx; } +.price-symbol { font-size: 24rpx; color: #FF3B30; } +.price-val { font-size: 36rpx; font-weight: 700; color: #FF3B30; } + +.redeem-btn { + background: #FF9800; + color: #fff; font-size: 24rpx; -} -.price-val { - font-size: 34rpx; -} -.points-badge { - background: rgba($brand-primary, 0.1); - color: $brand-primary; - border: 1px solid rgba($brand-primary, 0.2); - border-radius: 8rpx; - padding: 2rpx 10rpx; - display: flex; - align-items: center; - gap: 4rpx; -} -.points-val { - font-size: 24rpx; - font-weight: 700; -} -.points-unit { - font-size: 20rpx; + padding: 8rpx 20rpx; + border-radius: 30rpx; + margin-left: auto; } /* Loading & Empty */ diff --git a/utils/request.js b/utils/request.js index ddcc632..4c67358 100644 --- a/utils/request.js +++ b/utils/request.js @@ -66,6 +66,14 @@ export function authRequest(options) { return request({ ...options, header }) } +export function redeemProductByPoints(user_id, product_id, count) { + return authRequest({ + url: '/api/app/users/points/redeem-product', + method: 'POST', + data: { user_id, product_id, count } + }) +} + function getLanguage() { try { return (uni.getSystemInfoSync().language || 'zh-CN') } catch (_) { return 'zh-CN' } }