hookehuyr

feat(页面): 添加兑换券详情和我的兑换页面

- 新增兑换券详情页面,包含优惠券信息和核销功能
- 新增我的兑换页面,展示用户兑换券列表和状态筛选
- 修改个人中心页面,更新导航标题和菜单项
- 在应用配置中添加新页面路由
1 /* 1 /*
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-08-27 18:00:05 4 + * @LastEditTime: 2025-08-28 10:21:44
5 * @FilePath: /lls_program/src/app.config.js 5 * @FilePath: /lls_program/src/app.config.js
6 * @Description: 文件描述 6 * @Description: 文件描述
7 */ 7 */
...@@ -16,12 +16,14 @@ export default { ...@@ -16,12 +16,14 @@ export default {
16 'pages/Activities/index', 16 'pages/Activities/index',
17 'pages/RewardCategories/index', 17 'pages/RewardCategories/index',
18 'pages/Rewards/index', 18 'pages/Rewards/index',
19 + 'pages/MyRewards/index',
19 'pages/Profile/index', 20 'pages/Profile/index',
20 'pages/Feedback/index', 21 'pages/Feedback/index',
21 'pages/PointsDetail/index', 22 'pages/PointsDetail/index',
22 'pages/RewardDetail/index', 23 'pages/RewardDetail/index',
23 'pages/PrivacyPolicy/index', 24 'pages/PrivacyPolicy/index',
24 'pages/UserAgreement/index', 25 'pages/UserAgreement/index',
26 + 'pages/CouponDetail/index',
25 ], 27 ],
26 window: { 28 window: {
27 backgroundTextStyle: 'light', 29 backgroundTextStyle: 'light',
......
1 +/*
2 + * @Date: 2025-08-27 18:25:42
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 09:39:10
5 + * @FilePath: /lls_program/src/pages/RewardDetail/index.config.js
6 + * @Description: 文件描述
7 + */
8 +export default {
9 + navigationBarTitleText: '兑换券详情'
10 +}
1 +<template>
2 + <view class="min-h-screen bg-white pb-24">
3 + <!-- <AppHeader title="优惠券详情" :showBack="true" /> -->
4 + <!-- Top Image -->
5 + <view class="w-full h-48">
6 + <image :src="reward.image" class="w-full h-full object-cover" />
7 + </view>
8 +
9 + <!-- Main Content -->
10 + <view class="p-6">
11 + <!-- Points and Title -->
12 + <view class="text-center mb-8">
13 + <view class="text-4xl font-bold text-blue-500 mb-2">
14 + <text class="text-2xl">{{ reward.points }} 积分</text>
15 + </view>
16 + <h1 class="text-xl font-bold">{{ reward.title }}</h1>
17 + </view>
18 +
19 + <!-- Details Sections -->
20 + <view class="space-y-6">
21 + <!-- Applicable Stores -->
22 + <view>
23 + <h2 class="text-lg font-medium mb-3">可用门店</h2>
24 + <view class="space-y-2 text-gray-600">
25 + <view v-for="store in reward.stores" :key="store" class="flex text-sm">
26 + <span class="mr-2">·</span>
27 + <p>{{ store }}</p>
28 + </view>
29 + </view>
30 + </view>
31 +
32 + <!-- Redemption Rules -->
33 + <view>
34 + <h2 class="text-lg font-medium mb-3">有效期</h2>
35 + <view class="space-y-2 text-gray-600">
36 + <view v-for="rule in reward.redemption_rules" :key="rule" class="flex text-sm">
37 + <span class="mr-2">·</span>
38 + <p>{{ rule }}</p>
39 + </view>
40 + </view>
41 + </view>
42 +
43 + <!-- Usage Rules -->
44 + <view>
45 + <h2 class="text-lg font-medium mb-3">使用规则</h2>
46 + <view class="space-y-2 text-gray-600">
47 + <view v-for="rule in reward.usage_rules" :key="rule" class="flex text-sm">
48 + <span class="mr-2">·</span>
49 + <p>{{ rule }}</p>
50 + </view>
51 + </view>
52 + </view>
53 + </view>
54 + </view>
55 +
56 + <!-- Bottom Button -->
57 + <view class="fixed bottom-0 left-0 right-0 p-4 bg-white border-t border-gray-100">
58 + <nut-button type="primary" size="large" block color="#3B82F6" @click="handleRedeem">
59 + 立即核销
60 + </nut-button>
61 + </view>
62 + </view>
63 +</template>
64 +
65 +<script setup>
66 +import { ref } from 'vue';
67 +import Taro from '@tarojs/taro';
68 +import AppHeader from '../../components/AppHeader.vue';
69 +
70 +// Mock reward data based on the image
71 +const reward = ref({
72 + id: 1,
73 + title: '吴良材眼镜店85折券',
74 + points: 10,
75 + image: 'https://placehold.co/800x400/e2f3ff/0369a1?text=LFX&font=roboto', // Placeholder image
76 + stores: [
77 + '吴良材眼镜店 (南京东路店)',
78 + '吴良材眼镜店 (淮海中路店)',
79 + '吴良材眼镜店 (徐家汇店)'
80 + ],
81 + redemption_rules: [
82 + '2025-10-01~2025-12-31',
83 + ],
84 + usage_rules: [
85 + '仅限店内正价商品使用',
86 + '不可与其他优惠同时使用',
87 + '特价商品不可使用',
88 + '最终解释权归商家所有'
89 + ]
90 +});
91 +
92 +/**
93 + * @description Handles the redemption of the coupon.
94 + */
95 +const handleRedeem = () => {
96 + // Show a confirmation modal
97 + Taro.showModal({
98 + title: '确认兑换',
99 + content: `将消耗 ${reward.value.points} 积分兑换此优惠券,是否确认?`,
100 + success: (res) => {
101 + if (res.confirm) {
102 + // Simulate API call for redemption
103 + Taro.showLoading({ title: '兑换中...' });
104 + setTimeout(() => {
105 + Taro.hideLoading();
106 + Taro.showToast({
107 + title: '兑换成功',
108 + icon: 'success',
109 + duration: 2000
110 + });
111 + // After successful redemption, you might want to navigate the user to their coupons page
112 + // For now, we'll just navigate back.
113 + setTimeout(() => {
114 + Taro.navigateBack();
115 + }, 2000);
116 + }, 1500);
117 + }
118 + }
119 + });
120 +};
121 +</script>
1 +/*
2 + * @Date: 2025-08-28 09:52:28
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 09:52:48
5 + * @FilePath: /lls_program/src/pages/MyRewards/index.config.js
6 + * @Description: 我的兑换页面配置文件
7 + */
8 +export default {
9 + navigationBarTitleText: '我的兑换',
10 + usingComponents: {
11 + },
12 +}
1 +.red {
2 + color: red;
3 +}
1 +<template>
2 + <view class="min-h-screen bg-gray-100">
3 + <!-- <AppHeader title="我的券" :showBack="true" /> -->
4 +
5 + <!-- Tabs -->
6 + <view class="bg-white flex justify-around items-center border-b border-gray-200">
7 + <view
8 + v-for="tab in tabs"
9 + :key="tab.name"
10 + @click="activeTab = tab.name"
11 + class="py-3 text-center w-1/4 text-base font-medium cursor-pointer"
12 + :class="[
13 + activeTab === tab.name
14 + ? 'text-blue-500 border-b-2 border-blue-500'
15 + : 'text-gray-500'
16 + ]"
17 + >
18 + {{ tab.label }}
19 + </view>
20 + </view>
21 +
22 + <!-- Rewards List -->
23 + <view class="p-4">
24 + <view v-if="filteredRewards.length > 0" class="space-y-4">
25 + <view
26 + v-for="reward in filteredRewards"
27 + :key="reward.id"
28 + class="bg-white rounded-lg shadow-sm p-4 flex justify-between items-center"
29 + >
30 + <view class="flex-1">
31 + <h3 class="text-lg font-semibold text-gray-800">{{ reward.title }}</h3>
32 + <p class="text-sm text-gray-500 mt-1">有效期至 {{ reward.expiryDate }}</p>
33 + <p v-if="reward.status === 'used'" class="text-sm text-gray-400 mt-1">使用于 {{ reward.usedDate }}</p>
34 + </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>
47 + </view>
48 + <view v-else class="text-center py-20">
49 + <p class="text-gray-500">暂无相关兑换券</p>
50 + </view>
51 + </view>
52 + </view>
53 +</template>
54 +
55 +<script setup>
56 +import { ref, computed } from 'vue';
57 +import Taro from '@tarojs/taro';
58 +
59 +import AppHeader from '../../components/AppHeader.vue';
60 +
61 +const tabs = ref([
62 + { name: 'all', label: '全部' },
63 + { name: 'unused', label: '未使用' },
64 + { name: 'used', label: '已使用' },
65 + { name: 'expired', label: '已过期' },
66 +]);
67 +
68 +const activeTab = ref('all');
69 +
70 +const rewards = ref([
71 + {
72 + id: 1,
73 + title: '杏花楼集团 85折券',
74 + expiryDate: '2025-08-28',
75 + status: 'unused',
76 + usedDate: null,
77 + },
78 + {
79 + id: 2,
80 + title: '老凤祥银楼 20元抵用券',
81 + expiryDate: '2025-08-28',
82 + status: 'unused',
83 + usedDate: null,
84 + },
85 + {
86 + id: 3,
87 + title: '吴良材眼镜 5折券',
88 + expiryDate: '2024-05-20',
89 + status: 'used',
90 + usedDate: '2024-05-01',
91 + },
92 + {
93 + id: 4,
94 + title: '沈大成双酿团 免费券',
95 + expiryDate: '2024-03-15',
96 + status: 'expired',
97 + usedDate: null,
98 + },
99 +]);
100 +
101 +const filteredRewards = computed(() => {
102 + if (activeTab.value === 'all') {
103 + return rewards.value;
104 + }
105 + return rewards.value.filter(reward => reward.status === activeTab.value);
106 +});
107 +
108 +const getButtonText = (status) => {
109 + switch (status) {
110 + case 'unused':
111 + return '使用';
112 + case 'used':
113 + return '已使用';
114 + case 'expired':
115 + return '已过期';
116 + default:
117 + return '';
118 + }
119 +};
120 +
121 +const handleUseReward = (reward) => {
122 + if (reward.status === 'unused') {
123 + // Here you would typically navigate to a usage/QR code page
124 + // For now, we can just log it or update the status for demo purposes
125 + console.log(`Using reward: ${reward.title}`);
126 + // Example of updating status:
127 + // const item = rewards.value.find(r => r.id === reward.id);
128 + // if (item) {
129 + // item.status = 'used';
130 + // item.usedDate = new Date().toISOString().split('T')[0];
131 + // }
132 + // 跳转到卡券详情页
133 + Taro.navigateTo({
134 + url: '/pages/CouponDetail/index?id=' + reward.id
135 + })
136 + }
137 +};
138 +</script>
1 export default { 1 export default {
2 - navigationBarTitleText: '首页' 2 + navigationBarTitleText: '我的'
3 } 3 }
......
1 +<!--
2 + * @Date: 2025-08-27 17:47:46
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-08-28 09:57:23
5 + * @FilePath: /lls_program/src/pages/Profile/index.vue
6 + * @Description: 文件描述
7 +-->
1 <template> 8 <template>
2 <view class="min-h-screen flex flex-col bg-white"> 9 <view class="min-h-screen flex flex-col bg-white">
3 <!-- Blue header background --> 10 <!-- Blue header background -->
...@@ -7,14 +14,14 @@ ...@@ -7,14 +14,14 @@
7 <!-- User profile section --> 14 <!-- User profile section -->
8 <view class="px-4 pt-8 pb-6 flex items-center justify-between"> 15 <view class="px-4 pt-8 pb-6 flex items-center justify-between">
9 <view class="flex items-center"> 16 <view class="flex items-center">
10 - <image src="https://randomuser.me/api/portraits/men/32.jpg" alt="User avatar" class="w-16 h-16 rounded-full border-2 border-white" /> 17 + <image src="https://placehold.co/400x400/e2f3ff/0369a1?text=LFX&font=roboto" alt="User avatar" class="w-16 h-16 rounded-full border-2 border-white" />
11 <view class="ml-4"> 18 <view class="ml-4">
12 <h1 class="text-xl font-bold text-white">张爷爷</h1> 19 <h1 class="text-xl font-bold text-white">张爷爷</h1>
13 </view> 20 </view>
14 </view> 21 </view>
15 - <button class="text-white"> 22 + <view class="text-white">
16 <span>编辑</span> 23 <span>编辑</span>
17 - </button> 24 + </view>
18 </view> 25 </view>
19 <!-- Menu items --> 26 <!-- Menu items -->
20 <view class="bg-white rounded-t-3xl px-4 py-5"> 27 <view class="bg-white rounded-t-3xl px-4 py-5">
...@@ -37,25 +44,25 @@ ...@@ -37,25 +44,25 @@
37 </template> 44 </template>
38 45
39 <script setup> 46 <script setup>
40 -import { ref } from 'vue'; 47 +import { ref, shallowRef } from 'vue';
41 import Taro from '@tarojs/taro'; 48 import Taro from '@tarojs/taro';
42 import BottomNav from '../../components/BottomNav.vue'; 49 import BottomNav from '../../components/BottomNav.vue';
43 -import { Chart, Gift, Message, Document, Shield, Right } from '@nutui/icons-vue-taro'; 50 +import { Shop3, Cart, Message, Tips, Right } from '@nutui/icons-vue-taro';
44 51
45 -const menuItems = ref([ 52 +const menuItems = shallowRef([
46 { 53 {
47 id: 'points', 54 id: 'points',
48 - icon: Chart, 55 + icon: Shop3,
49 label: '积分明细', 56 label: '积分明细',
50 color: 'bg-blue-500', 57 color: 'bg-blue-500',
51 onClick: () => Taro.navigateTo({ url: '/pages/PointsDetail/index' }) 58 onClick: () => Taro.navigateTo({ url: '/pages/PointsDetail/index' })
52 }, 59 },
53 { 60 {
54 id: 'rewards', 61 id: 'rewards',
55 - icon: Gift, 62 + icon: Cart,
56 label: '我的兑换', 63 label: '我的兑换',
57 color: 'bg-blue-500', 64 color: 'bg-blue-500',
58 - onClick: () => Taro.navigateTo({ url: '/pages/Rewards/index' }) 65 + onClick: () => Taro.navigateTo({ url: '/pages/MyRewards/index' })
59 }, 66 },
60 { 67 {
61 id: 'feedback', 68 id: 'feedback',
...@@ -66,14 +73,14 @@ const menuItems = ref([ ...@@ -66,14 +73,14 @@ const menuItems = ref([
66 }, 73 },
67 { 74 {
68 id: 'agreement', 75 id: 'agreement',
69 - icon: Document, 76 + icon: Tips,
70 label: '用户协议', 77 label: '用户协议',
71 color: 'bg-blue-500', 78 color: 'bg-blue-500',
72 onClick: () => Taro.navigateTo({ url: '/pages/UserAgreement/index' }) 79 onClick: () => Taro.navigateTo({ url: '/pages/UserAgreement/index' })
73 }, 80 },
74 { 81 {
75 id: 'privacy', 82 id: 'privacy',
76 - icon: Shield, 83 + icon: Tips,
77 label: '隐私政策', 84 label: '隐私政策',
78 color: 'bg-blue-500', 85 color: 'bg-blue-500',
79 onClick: () => Taro.navigateTo({ url: '/pages/PrivacyPolicy/index' }) 86 onClick: () => Taro.navigateTo({ url: '/pages/PrivacyPolicy/index' })
......