hookehuyr

feat(优惠券): 重构优惠券列表和详情页面,添加加载状态和API集成

重构优惠券列表页面,添加分页加载和状态筛选功能
优化优惠券详情页面,完善状态显示和核销逻辑
新增蓝色主题相关样式类
更新API接口路径和返回字段
...@@ -19,6 +19,7 @@ declare module 'vue' { ...@@ -19,6 +19,7 @@ declare module 'vue' {
19 NutDialog: typeof import('@nutui/nutui-taro')['Dialog'] 19 NutDialog: typeof import('@nutui/nutui-taro')['Dialog']
20 NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview'] 20 NutImagePreview: typeof import('@nutui/nutui-taro')['ImagePreview']
21 NutInput: typeof import('@nutui/nutui-taro')['Input'] 21 NutInput: typeof import('@nutui/nutui-taro')['Input']
22 + NutLoading: typeof import('@nutui/nutui-taro')['Loading']
22 NutPicker: typeof import('@nutui/nutui-taro')['Picker'] 23 NutPicker: typeof import('@nutui/nutui-taro')['Picker']
23 NutPopup: typeof import('@nutui/nutui-taro')['Popup'] 24 NutPopup: typeof import('@nutui/nutui-taro')['Popup']
24 NutRow: typeof import('@nutui/nutui-taro')['Row'] 25 NutRow: typeof import('@nutui/nutui-taro')['Row']
......
1 /* 1 /*
2 * @Date: 2024-01-01 00:00:00 2 * @Date: 2024-01-01 00:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-09-08 13:15:39 4 + * @LastEditTime: 2025-09-08 16:23:23
5 * @FilePath: /lls_program/src/api/coupon.js 5 * @FilePath: /lls_program/src/api/coupon.js
6 * @Description: 优惠券相关接口 6 * @Description: 优惠券相关接口
7 */ 7 */
...@@ -15,7 +15,7 @@ const Api = { ...@@ -15,7 +15,7 @@ const Api = {
15 REDEEM_COUPON: '/srv/?a=coupon&t=redeem', 15 REDEEM_COUPON: '/srv/?a=coupon&t=redeem',
16 MY_COUPON_LIST: '/srv/?a=redemption&t=list', 16 MY_COUPON_LIST: '/srv/?a=redemption&t=list',
17 MY_COUPON_DETAIL: '/srv/?a=redemption&t=detail', 17 MY_COUPON_DETAIL: '/srv/?a=redemption&t=detail',
18 - USE_COUPON: '/srv/?a=coupon&t=use', 18 + USE_COUPON: '/srv/?a=redemption&t=use',
19 } 19 }
20 20
21 /** 21 /**
...@@ -111,6 +111,7 @@ export const redeemCouponAPI = (params) => fn(fetch.post(Api.REDEEM_COUPON, para ...@@ -111,6 +111,7 @@ export const redeemCouponAPI = (params) => fn(fetch.post(Api.REDEEM_COUPON, para
111 * @returns {string} response.data.thumbnail - 优惠券缩略图 111 * @returns {string} response.data.thumbnail - 优惠券缩略图
112 * @returns {string} response.data.status - 优惠券状态, UNUSED=未使用, USED=已使用, EXPIRED=已过期 112 * @returns {string} response.data.status - 优惠券状态, UNUSED=未使用, USED=已使用, EXPIRED=已过期
113 * @returns {string} response.data.expire_time - 有效期截止时间 113 * @returns {string} response.data.expire_time - 有效期截止时间
114 + * @returns {string} response.data.used_time - 核销时间
114 */ 115 */
115 export const getMyCouponListAPI = (params = {}) => fn(fetch.get(Api.MY_COUPON_LIST, params)); 116 export const getMyCouponListAPI = (params = {}) => fn(fetch.get(Api.MY_COUPON_LIST, params));
116 117
...@@ -127,6 +128,7 @@ export const getMyCouponListAPI = (params = {}) => fn(fetch.get(Api.MY_COUPON_LI ...@@ -127,6 +128,7 @@ export const getMyCouponListAPI = (params = {}) => fn(fetch.get(Api.MY_COUPON_LI
127 * @returns {string} response.data.banner - 优惠券banner图 128 * @returns {string} response.data.banner - 优惠券banner图
128 * @returns {string} response.data.status - 优惠券状态, UNUSED=未使用, USED=已使用, EXPIRED=已过期 129 * @returns {string} response.data.status - 优惠券状态, UNUSED=未使用, USED=已使用, EXPIRED=已过期
129 * @returns {string} response.data.expire_time - 有效期截止时间 130 * @returns {string} response.data.expire_time - 有效期截止时间
131 + * @returns {string} response.data.expiration_rules[string] - 有效期
130 * @returns {Array} response.data.applicable_stores[string] - 可用门店 132 * @returns {Array} response.data.applicable_stores[string] - 可用门店
131 * @returns {Array} response.data.usage_rules[string] - 使用规则 133 * @returns {Array} response.data.usage_rules[string] - 使用规则
132 */ 134 */
......
...@@ -5,3 +5,11 @@ ...@@ -5,3 +5,11 @@
5 .bg-blue-500 { 5 .bg-blue-500 {
6 background-color: #4A90E2 !important; 6 background-color: #4A90E2 !important;
7 } 7 }
8 +
9 +.text-blue-500 {
10 + color: #4A90E2 !important;
11 +}
12 +
13 +.border-blue-500 {
14 + border-color: #4A90E2 !important;
15 +}
......
1 <template> 1 <template>
2 <view class="min-h-screen bg-white pb-24"> 2 <view class="min-h-screen bg-white pb-24">
3 - <!-- <AppHeader title="优惠券详情" :showBack="true" /> --> 3 + <!-- Loading State -->
4 - <!-- Top Image --> 4 + <view v-if="loading" class="flex justify-center items-center h-screen">
5 - <view class="w-full h-48"> 5 + <view class="text-gray-500">加载中...</view>
6 - <image :src="reward.image" class="w-full h-full object-cover" />
7 </view> 6 </view>
8 7
9 - <!-- Main Content --> 8 + <!-- Content -->
10 - <view class="p-6"> 9 + <view v-else>
11 - <!-- Points and Title --> 10 + <!-- Top Image -->
12 - <view class="text-center mb-8"> 11 + <view class="w-full h-48">
13 - <view class="text-4xl font-bold text-blue-500 mb-2"> 12 + <image :src="reward.banner || 'https://placehold.co/800x400/e2f3ff/0369a1?text=优惠券&font=roboto'" class="w-full h-full object-cover" />
14 - <text class="text-2xl">{{ reward.title }}</text>
15 - </view>
16 </view> 13 </view>
17 14
18 - <!-- Details Sections --> 15 + <!-- Main Content -->
19 - <view class="space-y-6"> 16 + <view class="p-6">
20 - <!-- Applicable Stores --> 17 + <!-- Points and Title -->
21 - <view> 18 + <view class="text-center mb-8">
22 - <h2 class="text-lg font-medium mb-3">可用门店</h2> 19 + <view class="text-4xl font-bold text-blue-500 mb-2">
23 - <view class="space-y-2 text-gray-600"> 20 + <text class="text-2xl">{{ reward.title }}</text>
24 - <view v-for="store in reward.stores" :key="store" class="flex text-sm">
25 - <span class="mr-2">·</span>
26 - <p>{{ store }}</p>
27 - </view>
28 </view> 21 </view>
22 + <!-- Status Badge -->
23 + <!-- <view class="mt-2">
24 + <text :class="getStatusClass(reward.status)">{{ getStatusText(reward.status) }}</text>
25 + </view> -->
29 </view> 26 </view>
30 27
31 - <!-- Redemption Rules --> 28 + <!-- Details Sections -->
32 - <view> 29 + <view class="space-y-6">
33 - <h2 class="text-lg font-medium mb-3">有效期</h2> 30 + <!-- Applicable Stores -->
34 - <view class="space-y-2 text-gray-600"> 31 + <view v-if="reward.applicable_stores && reward.applicable_stores.length > 0">
35 - <view v-for="rule in reward.redemption_rules" :key="rule" class="flex text-sm"> 32 + <h2 class="text-lg font-medium mb-3">可用门店</h2>
36 - <span class="mr-2">·</span> 33 + <view class="space-y-2 text-gray-600">
37 - <p>{{ rule }}</p> 34 + <view v-for="store in reward.applicable_stores" :key="store" class="flex text-sm">
35 + <span class="mr-2">·</span>
36 + <p>{{ store }}</p>
37 + </view>
38 + </view>
39 + </view>
40 +
41 + <!-- Expiration Rules -->
42 + <view v-if="reward.expiration_rules && reward.expiration_rules.length > 0">
43 + <h2 class="text-lg font-medium mb-3">有效期</h2>
44 + <view class="space-y-2 text-gray-600">
45 + <view v-for="rule in reward.expiration_rules" :key="rule" class="flex text-sm">
46 + <span class="mr-2">·</span>
47 + <p>{{ rule }}</p>
48 + </view>
38 </view> 49 </view>
39 </view> 50 </view>
40 - </view>
41 51
42 - <!-- Usage Rules --> 52 + <!-- Expire Time -->
43 - <view> 53 + <view v-if="reward.expire_time">
44 - <h2 class="text-lg font-medium mb-3">使用规则</h2> 54 + <h2 class="text-lg font-medium mb-3">到期时间</h2>
45 - <view class="space-y-2 text-gray-600"> 55 + <view class="text-gray-600 text-sm">
46 - <view v-for="rule in reward.usage_rules" :key="rule" class="flex text-sm"> 56 + <p>{{ reward.expire_time }}</p>
47 - <span class="mr-2">·</span> 57 + </view>
48 - <p>{{ rule }}</p> 58 + </view>
59 +
60 + <!-- Usage Rules -->
61 + <view v-if="reward.usage_rules && reward.usage_rules.length > 0">
62 + <h2 class="text-lg font-medium mb-3">使用规则</h2>
63 + <view class="space-y-2 text-gray-600">
64 + <view v-for="rule in reward.usage_rules" :key="rule" class="flex text-sm">
65 + <span class="mr-2">·</span>
66 + <p>{{ rule }}</p>
67 + </view>
49 </view> 68 </view>
50 </view> 69 </view>
51 </view> 70 </view>
52 </view> 71 </view>
53 - </view>
54 72
55 - <!-- Bottom Button --> 73 + <!-- Bottom Button -->
56 - <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100"> 74 + <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
57 - <nut-button type="primary" size="large" block :color="THEME_COLORS.PRIMARY" @click="handleRedeem"> 75 + <nut-button
58 - 立即核销 76 + type="primary"
59 - </nut-button> 77 + size="large"
78 + block
79 + :color="reward.status === 'UNUSED' ? THEME_COLORS.PRIMARY : '#D1D5DB'"
80 + :disabled="reward.status !== 'UNUSED' || useLoading"
81 + :loading="useLoading"
82 + @click="handleRedeem"
83 + >
84 + {{ getButtonText(reward.status) }}
85 + </nut-button>
86 + </view>
60 </view> 87 </view>
61 </view> 88 </view>
62 </template> 89 </template>
63 90
64 <script setup> 91 <script setup>
65 -import { ref } from 'vue'; 92 +import { ref, onMounted } from 'vue';
66 -import Taro from '@tarojs/taro'; 93 +import Taro, { useLoad } from '@tarojs/taro';
67 -import AppHeader from '../../components/AppHeader.vue';
68 // 导入主题颜色 94 // 导入主题颜色
69 import { THEME_COLORS } from '@/utils/config'; 95 import { THEME_COLORS } from '@/utils/config';
96 +// 导入接口
97 +import { getMyCouponDetailAPI, useCouponAPI } from '@/api/coupon';
70 98
71 -// Mock reward data based on the image 99 +// 页面参数
100 +const couponId = ref('');
101 +const loading = ref(false);
102 +const useLoading = ref(false);
103 +
104 +// 优惠券详情数据
72 const reward = ref({ 105 const reward = ref({
73 - id: 1, 106 + id: '',
74 - title: '吴良材眼镜店85折券', 107 + title: '',
75 - points: 10, 108 + banner: '',
76 - image: 'https://placehold.co/800x400/e2f3ff/0369a1?text=LFX&font=roboto', // Placeholder image 109 + status: 'UNUSED', // UNUSED=未使用, USED=已使用, EXPIRED=已过期
77 - stores: [ 110 + expire_time: '',
78 - '吴良材眼镜店 (南京东路店)', 111 + expiration_rules: [],
79 - '吴良材眼镜店 (淮海中路店)', 112 + applicable_stores: [],
80 - '吴良材眼镜店 (徐家汇店)' 113 + usage_rules: []
81 - ],
82 - redemption_rules: [
83 - '2025-10-01~2025-12-31',
84 - ],
85 - usage_rules: [
86 - '仅限店内正价商品使用',
87 - '不可与其他优惠同时使用',
88 - '特价商品不可使用',
89 - '最终解释权归商家所有'
90 - ]
91 }); 114 });
92 115
93 /** 116 /**
94 - * @description Handles the redemption of the coupon. 117 + * 获取我的优惠券详情
95 */ 118 */
96 -const handleRedeem = () => { 119 +const fetchCouponDetail = async () => {
97 - // Show a confirmation modal 120 + if (!couponId.value) {
98 - Taro.showModal({ 121 + console.error('缺少优惠券ID');
99 - title: '温馨提示', 122 + return;
100 - content: `将核销此优惠券,是否确认?`, 123 + }
101 - success: (res) => { 124 +
102 - if (res.confirm) { 125 + loading.value = true;
103 - // Simulate API call for redemption 126 + try {
104 - Taro.showLoading({ title: '核销中...' }); 127 + const response = await getMyCouponDetailAPI({ id: couponId.value });
128 +
129 + if (response && response.data) {
130 + reward.value = {
131 + id: response.data.id || '',
132 + title: response.data.title || '',
133 + banner: response.data.banner || '',
134 + status: response.data.status || 'UNUSED',
135 + expire_time: response.data.expire_time || '',
136 + expiration_rules: response.data.expiration_rules || [],
137 + applicable_stores: response.data.applicable_stores || [],
138 + usage_rules: response.data.usage_rules || []
139 + };
140 + }
141 + } catch (error) {
142 + console.error('获取优惠券详情失败:', error);
143 + Taro.showToast({
144 + title: '获取详情失败',
145 + icon: 'error'
146 + });
147 + } finally {
148 + loading.value = false;
149 + }
150 +};
151 +
152 +/**
153 + * 获取状态显示文本
154 + */
155 +const getStatusText = (status) => {
156 + switch (status) {
157 + case 'UNUSED':
158 + return '未使用';
159 + case 'USED':
160 + return '已使用';
161 + case 'EXPIRED':
162 + return '已过期';
163 + default:
164 + return '';
165 + }
166 +};
167 +
168 +/**
169 + * 获取状态样式类
170 + */
171 +const getStatusClass = (status) => {
172 + switch (status) {
173 + case 'UNUSED':
174 + return 'px-2 py-1 bg-green-100 text-green-600 rounded text-sm';
175 + case 'USED':
176 + return 'px-2 py-1 bg-gray-100 text-gray-600 rounded text-sm';
177 + case 'EXPIRED':
178 + return 'px-2 py-1 bg-red-100 text-red-600 rounded text-sm';
179 + default:
180 + return 'px-2 py-1 bg-gray-100 text-gray-600 rounded text-sm';
181 + }
182 +};
183 +
184 +/**
185 + * 获取按钮文本
186 + */
187 +const getButtonText = (status) => {
188 + switch (status) {
189 + case 'UNUSED':
190 + return '立即核销';
191 + case 'USED':
192 + return '已核销';
193 + case 'EXPIRED':
194 + return '已过期';
195 + default:
196 + return '不可用';
197 + }
198 +};
199 +
200 +/**
201 + * 处理优惠券核销
202 + */
203 +const handleRedeem = async () => {
204 + if (reward.value.status !== 'UNUSED') {
205 + Taro.showToast({
206 + title: '优惠券不可用',
207 + icon: 'none'
208 + });
209 + return;
210 + }
211 +
212 + try {
213 + // 显示确认弹窗
214 + const result = await Taro.showModal({
215 + title: '温馨提示',
216 + content: '将核销此优惠券,是否确认?',
217 + confirmText: '确认核销',
218 + cancelText: '取消'
219 + });
220 +
221 + if (result.confirm) {
222 + useLoading.value = true;
223 +
224 + // 调用核销API
225 + const response = await useCouponAPI({ id: couponId.value });
226 +
227 + if (response) {
228 + // 核销成功
229 + reward.value.status = 'USED';
230 +
231 + Taro.showToast({
232 + title: '核销成功',
233 + icon: 'success',
234 + duration: 2000
235 + });
236 +
237 + // 延迟返回上一页
105 setTimeout(() => { 238 setTimeout(() => {
106 - Taro.hideLoading(); 239 + Taro.navigateBack();
107 - Taro.showToast({ 240 + }, 2000);
108 - title: '核销成功',
109 - icon: 'success',
110 - duration: 2000
111 - });
112 - // After successful redemption, you might want to navigate the user to their coupons page
113 - // For now, we'll just navigate back.
114 - setTimeout(() => {
115 - Taro.navigateBack();
116 - }, 2000);
117 - }, 1500);
118 } 241 }
119 } 242 }
120 - }); 243 + } catch (error) {
244 + console.error('核销失败:', error);
245 + Taro.showToast({
246 + title: error.message || '核销失败,请重试',
247 + icon: 'error'
248 + });
249 + } finally {
250 + useLoading.value = false;
251 + }
252 +};
253 +
254 +/**
255 + * 初始化页面数据
256 + */
257 +const initPageData = async (options = {}) => {
258 + // 获取URL参数中的优惠券ID
259 + if (options.id) {
260 + couponId.value = options.id;
261 + await fetchCouponDetail();
262 + } else {
263 + console.error('缺少优惠券ID参数');
264 + Taro.showToast({
265 + title: '参数错误',
266 + icon: 'error'
267 + });
268 + }
121 }; 269 };
270 +
271 +// 页面加载时初始化数据
272 +useLoad((options) => {
273 + initPageData(options);
274 +});
122 </script> 275 </script>
......
1 <template> 1 <template>
2 - <view class="min-h-screen bg-gray-100"> 2 + <view class="min-h-screen bg-gray-50">
3 - <!-- <AppHeader title="我的券" :showBack="true" /> -->
4 -
5 <!-- Tabs --> 3 <!-- Tabs -->
6 - <view class="bg-white flex justify-around items-center border-b border-gray-200"> 4 + <view class="bg-white px-4 py-2 border-b border-gray-200">
7 - <view 5 + <view class="flex space-x-6">
8 - v-for="tab in tabs" 6 + <view
9 - :key="tab.name" 7 + v-for="tab in tabs"
10 - @click="activeTab = tab.name" 8 + :key="tab.name"
11 - class="py-3 text-center w-1/4 text-base font-medium cursor-pointer" 9 + @click="handleTabChange(tab.name)"
12 - :class="[ 10 + :class="{
13 - activeTab === tab.name 11 + 'text-blue-500 border-b-2 border-blue-500': activeTab === tab.name,
14 - ? 'text-blue-500 border-b-2 border-blue-500' 12 + 'text-gray-500': activeTab !== tab.name
15 - : 'text-gray-500' 13 + }"
16 - ]" 14 + class="pb-2 px-1 text-sm font-medium transition-colors"
17 - > 15 + >
18 - {{ tab.label }} 16 + {{ tab.label }}
17 + </view>
19 </view> 18 </view>
20 </view> 19 </view>
21 20
22 - <!-- Rewards List --> 21 + <!-- Loading State -->
23 - <view class="p-4"> 22 + <view v-if="loading" class="flex justify-center items-center py-20">
23 + <view class="text-gray-500">加载中...</view>
24 + </view>
25 +
26 + <!-- Content -->
27 + <view v-else class="p-4">
24 <view v-if="filteredRewards.length > 0" class="space-y-4"> 28 <view v-if="filteredRewards.length > 0" class="space-y-4">
25 <view 29 <view
26 v-for="reward in filteredRewards" 30 v-for="reward in filteredRewards"
27 :key="reward.id" 31 :key="reward.id"
28 - class="bg-white rounded-lg shadow-sm p-4 flex justify-between items-center" 32 + class="bg-white rounded-xl p-4 flex items-center shadow-[0_2px_8px_rgba(0,0,0,0.08)]"
29 > 33 >
30 - <view class="flex-1"> 34 + <image :src="reward.thumbnail || 'https://placehold.co/120x120/e2f3ff/0369a1?text=券&font=roboto'" class="w-16 h-16 rounded-lg mr-4 flex-shrink-0" mode="aspectFill" />
31 - <h3 class="text-lg font-semibold text-gray-800">{{ reward.title }}</h3> 35 + <view class="flex-1 min-w-0">
32 - <p class="text-sm text-gray-500 mt-1">有效期至 {{ reward.expiryDate }}</p> 36 + <view class="font-medium text-base mb-1">{{ reward.title }}</view>
33 - <p v-if="reward.status === 'used'" class="text-sm text-gray-400 mt-1">使用于 {{ reward.usedDate }}</p> 37 + <view v-if="reward.status === 'UNUSED'" class="text-xs text-gray-400">
38 + 有效期至:{{ formatDate(reward.expire_time) }}
39 + </view>
40 + <view v-if="reward.status === 'USED'" class="text-xs text-red-500">
41 + 使用日期:{{ formatDate(reward.used_time) }}
42 + </view>
43 + </view>
44 + <view class="ml-4 flex flex-col items-end">
45 + <button
46 + @click="handleUseReward(reward)"
47 + :disabled="reward.status !== 'UNUSED'"
48 + :class="{
49 + 'bg-blue-500 text-white': reward.status === 'UNUSED',
50 + 'bg-gray-300 text-gray-500': reward.status !== 'UNUSED'
51 + }"
52 + class="px-4 py-2 rounded-lg text-sm font-medium transition-colors flex-shrink-0"
53 + >
54 + {{ getButtonText(reward.status) }}
55 + </button>
34 </view> 56 </view>
35 - <button
36 - @click="handleUseReward(reward)"
37 - :disabled="reward.status !== 'unused'"
38 - class="px-6 py-2 rounded-full text-white font-medium text-sm transition-colors"
39 - :class="{
40 - 'bg-blue-500 hover:bg-blue-600': reward.status === 'unused',
41 - 'bg-gray-300 cursor-not-allowed': reward.status !== 'unused'
42 - }"
43 - >
44 - {{ getButtonText(reward.status) }}
45 - </button>
46 </view> 57 </view>
47 </view> 58 </view>
59 +
60 + <!-- Empty State -->
48 <view v-else class="text-center py-20"> 61 <view v-else class="text-center py-20">
49 - <p class="text-gray-500">暂无相关兑换券</p> 62 + <view class="text-gray-400 text-lg mb-2">暂无券</view>
63 + <view class="text-gray-500 text-sm">您还没有任何券</view>
64 + </view>
65 +
66 + <!-- Load More -->
67 + <view v-if="hasMore && filteredRewards.length > 0" class="text-center mt-6">
68 + <view
69 + @click="loadMore"
70 + class="text-blue-500 py-4"
71 + >
72 + {{ loading ? '加载中...' : '加载更多' }}
73 + </view>
74 + </view>
75 +
76 + <!-- No More Data -->
77 + <view v-if="!hasMore && filteredRewards.length > 0" class="text-center py-4 text-gray-500">
78 + 没有更多数据了
50 </view> 79 </view>
51 </view> 80 </view>
52 </view> 81 </view>
...@@ -56,104 +85,150 @@ ...@@ -56,104 +85,150 @@
56 import { ref, computed } from 'vue'; 85 import { ref, computed } from 'vue';
57 import Taro from '@tarojs/taro'; 86 import Taro from '@tarojs/taro';
58 import { useDidShow } from '@tarojs/taro'; 87 import { useDidShow } from '@tarojs/taro';
88 +// 导入接口
89 +import { getMyCouponListAPI } from '@/api/coupon';
59 90
60 const tabs = ref([ 91 const tabs = ref([
61 - { name: 'all', label: '全部' }, 92 + { name: 'all', label: '全部', status: '' },
62 - { name: 'unused', label: '未使用' }, 93 + { name: 'unused', label: '未使用', status: 'UNUSED' },
63 - { name: 'used', label: '已使用' }, 94 + { name: 'used', label: '已使用', status: 'USED' },
64 - { name: 'expired', label: '已过期' }, 95 + { name: 'expired', label: '已过期', status: 'EXPIRED' },
65 ]); 96 ]);
66 97
67 const activeTab = ref('all'); 98 const activeTab = ref('all');
68 - 99 +const loading = ref(false);
69 const rewards = ref([]); 100 const rewards = ref([]);
101 +const currentPage = ref(0);
102 +const hasMore = ref(true);
70 103
71 const filteredRewards = computed(() => { 104 const filteredRewards = computed(() => {
72 - if (activeTab.value === 'all') { 105 + // 由于API已经根据status参数进行了筛选,这里直接返回rewards
73 - return rewards.value; 106 + return rewards.value;
74 - }
75 - return rewards.value.filter(reward => reward.status === activeTab.value);
76 }); 107 });
77 108
109 +/**
110 + * 获取按钮文本
111 + */
78 const getButtonText = (status) => { 112 const getButtonText = (status) => {
79 switch (status) { 113 switch (status) {
80 - case 'unused': 114 + case 'UNUSED':
81 return '使用'; 115 return '使用';
82 - case 'used': 116 + case 'USED':
83 return '已使用'; 117 return '已使用';
84 - case 'expired': 118 + case 'EXPIRED':
85 return '已过期'; 119 return '已过期';
86 default: 120 default:
87 return ''; 121 return '';
88 } 122 }
89 }; 123 };
90 124
125 +/**
126 + * 获取状态文本
127 + */
128 +// const getStatusText = (status) => {
129 +// switch (status) {
130 +// case 'UNUSED':
131 +// return '未使用';
132 +// case 'USED':
133 +// return '已使用';
134 +// case 'EXPIRED':
135 +// return '已过期';
136 +// default:
137 +// return '';
138 +// }
139 +// };
140 +
141 +/**
142 + * 格式化日期
143 + */
144 +const formatDate = (dateString) => {
145 + if (!dateString) return '';
146 + const date = new Date(dateString);
147 + return date.toLocaleDateString('zh-CN');
148 +};
149 +
150 +/**
151 + * 处理优惠券使用
152 + */
91 const handleUseReward = (reward) => { 153 const handleUseReward = (reward) => {
92 - if (reward.status === 'unused') { 154 + if (reward.status === 'UNUSED') {
93 - // Here you would typically navigate to a usage/QR code page
94 - // For now, we can just log it or update the status for demo purposes
95 - console.log(`Using reward: ${reward.title}`);
96 - // Example of updating status:
97 - // const item = rewards.value.find(r => r.id === reward.id);
98 - // if (item) {
99 - // item.status = 'used';
100 - // item.usedDate = new Date().toISOString().split('T')[0];
101 - // }
102 // 跳转到卡券详情页 155 // 跳转到卡券详情页
103 Taro.navigateTo({ 156 Taro.navigateTo({
104 url: '/pages/CouponDetail/index?id=' + reward.id 157 url: '/pages/CouponDetail/index?id=' + reward.id
105 - }) 158 + });
159 + }
160 +};
161 +
162 +/**
163 + * 获取我的优惠券列表
164 + */
165 +const fetchMyCouponList = async (reset = false) => {
166 + if (loading.value || (!hasMore.value && !reset)) return;
167 +
168 + loading.value = true;
169 + try {
170 + const currentTab = tabs.value.find(tab => tab.name === activeTab.value);
171 + const params = {
172 + status: currentTab?.status || '',
173 + page: reset ? 0 : currentPage.value,
174 + limit: 10
175 + };
176 +
177 + const response = await getMyCouponListAPI(params);
178 +
179 + if (response && response.data) {
180 + const coupons = Array.isArray(response.data) ? response.data : [];
181 +
182 + if (reset) {
183 + rewards.value = coupons;
184 + currentPage.value = 0;
185 + } else {
186 + rewards.value = [...rewards.value, ...coupons];
187 + }
188 +
189 + // 如果返回的数据少于limit,说明没有更多数据了
190 + hasMore.value = coupons.length >= 10;
191 + currentPage.value += 1;
192 + }
193 + } catch (error) {
194 + console.error('获取我的优惠券列表失败:', error);
195 + Taro.showToast({
196 + title: '获取列表失败',
197 + icon: 'error'
198 + });
199 + } finally {
200 + loading.value = false;
106 } 201 }
107 }; 202 };
108 203
204 +/**
205 + * 处理标签页切换
206 + */
207 +const handleTabChange = (tabName) => {
208 + if (activeTab.value === tabName) return;
209 +
210 + activeTab.value = tabName;
211 + currentPage.value = 0;
212 + hasMore.value = true;
213 + fetchMyCouponList(true); // 重置并重新加载数据
214 +};
215 +
216 +/**
217 + * 加载更多
218 + */
219 +const loadMore = () => {
220 + fetchMyCouponList(false);
221 +};
222 +
223 +/**
224 + * 初始化页面数据
225 + */
226 +const initPageData = () => {
227 + fetchMyCouponList(true);
228 +};
229 +
230 +// 页面显示时刷新数据
109 useDidShow(() => { 231 useDidShow(() => {
110 initPageData(); 232 initPageData();
111 }); 233 });
112 -
113 -const initPageData = () => {
114 - rewards.value = [
115 - {
116 - id: 1,
117 - title: '杏花楼集团 85折券',
118 - expiryDate: '2025-08-28',
119 - status: 'unused',
120 - usedDate: null,
121 - },
122 - {
123 - id: 2,
124 - title: '老凤祥银楼 20元抵用券',
125 - expiryDate: '2025-08-28',
126 - status: 'unused',
127 - usedDate: null,
128 - },
129 - {
130 - id: 3,
131 - title: '吴良材眼镜 5折券',
132 - expiryDate: '2024-05-20',
133 - status: 'used',
134 - usedDate: '2024-05-01',
135 - },
136 - {
137 - id: 4,
138 - title: '沈大成双酿团 免费券',
139 - expiryDate: '2024-03-15',
140 - status: 'expired',
141 - usedDate: null,
142 - },
143 - {
144 - id: 5,
145 - title: '沈大成双酿团 免费券',
146 - expiryDate: '2024-03-15',
147 - status: 'expired',
148 - usedDate: null,
149 - },
150 - {
151 - id: 6,
152 - title: '沈大成双酿团 免费券',
153 - expiryDate: '2024-03-15',
154 - status: 'expired',
155 - usedDate: null,
156 - },
157 - ];
158 -}
159 </script> 234 </script>
......