hookehuyr

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

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