hookehuyr

feat: 添加多个页面组件和功能实现

refactor: 重构PointsCollector组件样式和逻辑

style: 更新样式文件和组件布局

docs: 添加组件注释和文档说明

chore: 删除无用图片资源和更新配置文件
Showing 61 changed files with 2212 additions and 83 deletions
......@@ -7,13 +7,16 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
AppHeader: typeof import('./src/components/AppHeader.vue')['default']
BottomNav: typeof import('./src/components/BottomNav.vue')['default']
GlassCard: typeof import('./src/components/GlassCard.vue')['default']
NavBar: typeof import('./src/components/navBar.vue')['default']
NutButton: typeof import('@nutui/nutui-taro')['Button']
NutToast: typeof import('@nutui/nutui-taro')['Toast']
Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
PointsCollector: typeof import('./src/components/PointsCollector.vue')['default']
PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
PrimaryButton: typeof import('./src/components/PrimaryButton.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
TabBar: typeof import('./src/components/TabBar.vue')['default']
}
}
......
/*
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 13:19:11
* @LastEditTime: 2025-08-27 18:00:05
* @FilePath: /lls_program/src/app.config.js
* @Description: 文件描述
*/
export default {
pages: [
'pages/index/index',
'pages/auth/index',
],
subpackages: [ // 配置在tabBar中的页面不能分包写到subpackages中去
{
root: 'pages/demo',
pages: ['index'],
},
// 'pages/index/index',
// 'pages/auth/index',
'pages/Welcome/index',
'pages/CreateFamily/index',
'pages/JoinFamily/index',
'pages/Dashboard/index',
'pages/Activities/index',
'pages/RewardCategories/index',
'pages/Rewards/index',
'pages/Profile/index',
'pages/Feedback/index',
'pages/PointsDetail/index',
'pages/RewardDetail/index',
'pages/PrivacyPolicy/index',
'pages/UserAgreement/index',
],
window: {
backgroundTextStyle: 'light',
......
<!--
* @Date: 2025-08-27 17:45:12
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 18:05:42
* @FilePath: /lls_program/src/components/AppHeader.vue
* @Description: 文件描述
-->
<template>
<view class="relative px-4 py-5 flex items-center justify-center bg-white bg-opacity-70 backdrop-blur-md border-b border-gray-100">
<button
v-if="showBack && !isRootPath"
@click="goBack"
class="absolute left-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
aria-label="返回"
>
<Left size="24" />
</button>
<h1 class="text-xl font-medium">{{ title }}</h1>
<button
v-if="showHome && !isRootPath"
@click="goHome"
class="absolute right-4 p-2 rounded-full hover:bg-gray-100 transition-colors"
aria-label="首页"
>
<Home size="24" />
</button>
</view>
</template>
<script setup>
import { computed } from 'vue';
import Taro from '@tarojs/taro';
import { Left, Home } from '@nutui/icons-vue-taro';
defineProps({
title: {
type: String,
required: true
},
showBack: {
type: Boolean,
default: true
},
showHome: {
type: Boolean,
default: false
}
});
const currentPage = computed(() => {
const pages = Taro.getCurrentPages();
return pages.length > 0 ? '/' + pages[pages.length - 1].route : '';
});
const isRootPath = computed(() => {
return currentPage.value === '/pages/index/index' || currentPage.value === '/pages/Dashboard/index';
});
const goBack = () => {
Taro.navigateBack();
};
const goHome = () => {
Taro.switchTab({ url: '/pages/index/index' });
};
</script>
<template>
<view class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-100 flex justify-around py-2">
<view
v-for="item in navItems"
:key="item.path"
@click="navigate(item.path)"
:class="['flex flex-col items-center py-2 px-5', isActive(item.path) ? 'text-blue-600' : 'text-gray-500']"
>
<component :is="item.icon" size="24" />
<span class="text-xs mt-1">{{ item.label }}</span>
<view v-if="isActive(item.path)" class="absolute bottom-0 w-10 h-1 bg-blue-600 rounded-t-full"></view>
</view>
</view>
</template>
<script setup>
import { computed, shallowRef } from 'vue';
import Taro from '@tarojs/taro';
import { Home, Find, Shop, My } from '@nutui/icons-vue-taro';
const navItems = shallowRef([
{ path: '/pages/Dashboard/index', icon: Home, label: '首页' },
{ path: '/pages/Activities/index', icon: Find, label: '活动' },
{ path: '/pages/Rewards/index', icon: Shop, label: '兑换' },
{ path: '/pages/Profile/index', icon: My, label: '我的' },
]);
const currentPage = computed(() => {
const pages = Taro.getCurrentPages();
return pages.length > 0 ? '/' + pages[pages.length - 1].route : '';
});
const isActive = (path) => {
return currentPage.value === path;
};
const navigate = (path) => {
Taro.reLaunch({ url: path });
};
</script>
<template>
<div class="bg-white/30 backdrop-blur-lg rounded-xl shadow-lg p-6">
<slot></slot>
</div>
</template>
<script setup>
/**
* GlassCard 组件
* @description 一个具有玻璃拟态效果的卡片容器,用于包裹内容。
*/
</script>
......@@ -60,16 +60,16 @@ const isCollecting = ref(false) // 是否正在收集,防止重复触发
*/
const generateMockData = () => {
const mockItems = [
{ id: 1, type: 'steps', value: 500, steps: 5000 },
{ id: 1, type: 'steps', value: 5000, steps: 5000 },
{ id: 2, type: 'points', value: 500 },
{ id: 3, type: 'steps', value: 800, steps: 5000 },
{ id: 3, type: 'steps', value: 8000, steps: 5000 },
{ id: 4, type: 'points', value: 30 },
{ id: 5, type: 'steps', value: 1200, steps: 12000 },
{ id: 5, type: 'steps', value: 12000, steps: 12000 },
{ id: 6, type: 'points', value: 800 },
{ id: 7, type: 'points', value: 250 },
{ id: 8, type: 'steps', value: 600, steps: 6000 },
{ id: 8, type: 'steps', value: 6000, steps: 6000 },
{ id: 9, type: 'points', value: 1000 },
{ id: 10, type: 'steps', value: 1000, steps: 10000 },
{ id: 10, type: 'steps', value: 10000, steps: 10000 },
].map(item => ({ ...item, collecting: false })); // 初始化collecting状态
const maxValue = Math.max(...mockItems.map(i => i.value));
......@@ -287,12 +287,12 @@ onMounted(() => {
display: block;
font-size: 24rpx;
margin-top: 8rpx;
// opacity: 0.9;
opacity: 0.9;
}
.floating-item {
position: absolute;
background: rgb(62, 144, 239);
background: rgb(100, 166, 240);
border-radius: 50%;
display: flex;
align-items: center;
......@@ -309,7 +309,8 @@ onMounted(() => {
.item-value {
display: block;
font-size: 22rpx;
font-size: 20rpx;
// font-weight: bold;
line-height: 1;
}
......@@ -317,7 +318,6 @@ onMounted(() => {
display: block;
font-size: 23rpx;
margin-top: 4rpx;
font-weight: bold;
// opacity: 0.7;
}
......@@ -379,7 +379,7 @@ onMounted(() => {
}
.points-label {
font-size: 24rpx;
font-size: 20rpx;
}
}
</style>
......
<template>
<button
:class="[
'w-full bg-blue-500 text-white font-bold py-3 px-4 rounded-lg shadow-lg',
'hover:bg-blue-600 transition-colors duration-300',
'disabled:bg-gray-400 disabled:cursor-not-allowed'
]"
:disabled="disabled"
@click="$emit('click')"
>
<slot></slot>
</button>
</template>
<script setup>
/**
* PrimaryButton 组件
* @description 一个通用的主操作按钮,封装了统一样式和交互效果。
* @props {boolean} disabled - 是否禁用按钮。
* @emits {void} click - 点击事件。
*/
const props = defineProps({
disabled: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['click']);
</script>
<template>
<div class="fixed bottom-0 w-full max-w-md mx-auto z-50">
<div class="backdrop-blur-md bg-white/70 border-t border-gray-200 px-4 py-2">
<div class="flex justify-around items-center">
<!-- 首页 -->
<view
@click="navigateTo('/pages/index/index')"
:class="getTabClass('index')"
>
<Home size="20" :color="getIconColor('index')" />
<span class="text-xs mt-1">首页</span>
</view>
<!-- 分类 -->
<view
@click="navigateTo('/pages/post/index')"
:class="getTabClass('post')"
>
<Category size="20" :color="getIconColor('post')" />
<span class="text-xs mt-1">分类</span>
</view>
<!-- 我要卖车 -->
<div class="relative -mt-5">
<view
@click="navigateTo('/pages/sell/index')"
class="bg-orange-500 rounded-full p-3 text-white shadow-lg"
>
<span class="text-sm font-medium">我要卖车</span>
</view>
</div>
<!-- 消息 -->
<view
@click="navigateTo('/pages/messages/index')"
:class="getTabClass('messages')"
class="relative"
>
<Message size="20" :color="getIconColor('messages')" />
<!-- 未读消息红点 -->
<div
v-if="unreadCount > 0"
class="absolute top-0 right-0 bg-red-500 rounded-full w-2 h-2"
>
</div>
<span class="text-xs mt-1">通知</span>
</view>
<!-- 我的 -->
<view
@click="navigateTo('/pages/profile/index')"
:class="getTabClass('profile')"
>
<My size="20" :color="getIconColor('profile')" />
<span class="text-xs mt-1">我的</span>
</view>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import Taro from '@tarojs/taro'
import { Home, Category, Message, My } from '@nutui/icons-vue-taro'
import { useUserStore } from '@/stores/user'
import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
// 当前激活的tab
const activeTab = ref('')
// 用户状态管理
const userStore = useUserStore()
// 未读消息数量
const unreadCount = computed(() => userStore.userInfo.message_count)
/**
* 获取当前页面路径并设置激活状态
*/
const getCurrentPage = () => {
const pages = Taro.getCurrentPages()
if (pages.length > 0) {
const currentPage = pages[pages.length - 1]
const route = currentPage.route
if (route.includes('index/index')) {
activeTab.value = 'index'
} else if (route.includes('post/index')) {
activeTab.value = 'post'
} else if (route.includes('sell/index')) {
activeTab.value = 'sell'
} else if (route.includes('messages/index')) {
activeTab.value = 'messages'
} else if (route.includes('profile/index')) {
activeTab.value = 'profile'
}
}
}
/**
* 导航到指定页面
* @param {string} url - 页面路径
*/
const navigateTo = async (url) => {
// 定义需要权限验证的页面(移除个人中心和sell页面的权限检查)
// const permissionPages = {
// '/pages/messages/index': PERMISSION_TYPES.MESSAGE
// }
// 检查是否需要权限验证
// const permissionType = permissionPages[url]
// if (permissionType) {
// const hasPermission = await checkPermission(permissionType)
// if (!hasPermission) {
// return
// }
// }
Taro.reLaunch({
url
})
}
/**
* 获取tab按钮的样式类
* @param {string} tab - tab标识
* @returns {string} 样式类名
*/
const getTabClass = (tab) => {
const baseClass = 'flex flex-col items-center p-2'
const activeClass = activeTab.value === tab ? 'text-orange-500' : 'text-gray-500'
return `${baseClass} ${activeClass}`
}
/**
* 获取图标颜色
* @param {string} tab - tab标识
* @returns {string} 颜色值
*/
const getIconColor = (tab) => {
return activeTab.value === tab ? '#f97316' : '#6b7280'
}
onMounted(async () => {
getCurrentPage()
// 初始化用户信息
await userStore.fetchUserInfo()
})
</script>
<style lang="less" scoped>
/* 确保底部导航栏在最上层 */
.z-50 {
z-index: 50;
}
</style>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="城市漫步" :showBack="false" />
<!-- Map container -->
<view class="flex-1 relative">
<Map
class="w-full h-full"
:longitude="121.47"
:latitude="31.23"
:scale="15"
:markers="markers"
@markertap="onMarkerTap"
/>
</view>
<!-- Check-in point detail modal -->
<view v-if="showModal && selectedPoint" class="fixed inset-0 bg-black bg-opacity-50 flex items-end justify-center z-20">
<view class="bg-white rounded-t-2xl w-full max-h-[80%] overflow-y-auto">
<view class="relative h-64 overflow-hidden">
<image :src="selectedPoint.image" :alt="selectedPoint.name" class="w-full h-full object-cover" />
<button class="absolute top-4 right-4 bg-white rounded-full p-2" @click="showModal = false">
<Failure size="24" />
</button>
</view>
<view class="p-4">
<h2 class="text-2xl font-bold mb-2">{{ selectedPoint.name }}</h2>
<p class="text-gray-600 mb-6">{{ selectedPoint.description }}</p>
<button @click="handleCheckIn" :disabled="selectedPoint.isCheckedIn" :class="['w-full py-3 rounded-full text-white font-medium', selectedPoint.isCheckedIn ? 'bg-gray-400' : 'bg-blue-500']">
{{ selectedPoint.isCheckedIn ? '已打卡' : '打卡' }}
</button>
</view>
</view>
</view>
<!-- Check-in success message -->
<view v-if="showSuccess" class="fixed inset-0 flex items-center justify-center z-30 pointer-events-none">
<view class="bg-black bg-opacity-70 text-white px-6 py-4 rounded-lg flex items-center">
<Check size="24" class="text-green-400 mr-2" />
<span>打卡成功!</span>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
import { Failure, Check } from '@nutui/icons-vue-taro';
const selectedPoint = ref(null);
const showModal = ref(false);
const showSuccess = ref(false);
const checkInPoints = ref([
{
id: 'yuyuan',
name: '豫园',
description: '豫园始建于1559年,是明代著名的私家园林,现为国家重点文物保护单位。园内亭台楼阁布局精巧,景色优美,是上海著名的旅游景点。',
latitude: 31.2271,
longitude: 121.4875,
iconPath: '/static/images/activities/marker.png',
width: 30,
height: 30,
image: "/static/images/activities/citywalk2_1.png",
isCheckedIn: false
},
{
id: 'jiuquqiao',
name: '九曲桥',
description: '豫园九曲桥位于上海城隍庙豫园内,是上海的标志性建筑之一。桥因始建于明代嘉靖、万历年间,九曲桥与荷花池在那时就已存在,最初为木桥。清朝隆年间进行过修建,上世纪20年代因火灾改建为水泥桥,解放后又恢复了石桥形式。',
latitude: 31.2275,
longitude: 121.4895,
iconPath: '/static/images/activities/marker.png',
width: 30,
height: 30,
image: "/static/images/activities/citywalk2_1.png",
isCheckedIn: false
}
]);
const markers = computed(() => checkInPoints.value.map(p => ({
id: p.id,
latitude: p.latitude,
longitude: p.longitude,
iconPath: p.iconPath,
width: p.width,
height: p.height,
})));
const onMarkerTap = (e) => {
const point = checkInPoints.value.find(p => p.id === e.detail.markerId);
if (point) {
selectedPoint.value = point;
showModal.value = true;
}
};
const handleCheckIn = () => {
if (selectedPoint.value) {
const index = checkInPoints.value.findIndex(p => p.id === selectedPoint.value.id);
if (index !== -1) {
checkInPoints.value[index].isCheckedIn = true;
}
showModal.value = false;
showSuccess.value = true;
setTimeout(() => {
showSuccess.value = false;
}, 2000);
}
};
</script>
export default {
navigationBarTitleText: '首页'
}
<!--
* @Date: 2025-08-27 17:44:53
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 18:05:54
* @FilePath: /lls_program/src/pages/CreateFamily/index.vue
* @Description: 文件描述
-->
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="创建家庭" />
<view class="flex-1 px-4 py-6 overflow-auto">
<view class="mb-6">
<p class="text-gray-600 mb-6">
请填写家庭信息,创建您的专属家庭空间
</p>
<!-- Family Name -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">家庭名称</label>
<input
type="text"
v-model="familyName"
class="w-full text-gray-600 focus:outline-none"
placeholder="请输入家庭名称"
/>
</view>
</view>
<!-- Family Introduction -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">家庭介绍</label>
<textarea
v-model="familyIntro"
class="w-full text-gray-600 focus:outline-none resize-none"
placeholder="请输入您家庭的特色、成员特点等家庭标签"
:rows="2"
/>
</view>
</view>
<!-- Family Size -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-4">家庭规模</label>
<view class="flex gap-2">
<button
v-for="size in familySizes"
:key="size"
@click="familySize = size"
:class="[
'flex-1 py-3 rounded-lg border',
familySize === size
? 'border-blue-500 bg-blue-50 text-blue-500'
: 'border-gray-200 text-gray-700'
]"
>
{{ size }}
</button>
</view>
</view>
</view>
<!-- Family Motto -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="flex justify-between items-center mb-4">
<label class="block text-lg font-medium">家训口令</label>
<button
@click="generateRandomMotto"
class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm"
>
随机生成
</button>
</view>
<view class="flex gap-2 mb-4">
<view v-for="(char, index) in familyMotto" :key="index" class="flex-1">
<input
type="text"
v-model="familyMotto[index]"
maxlength="1"
class="w-full aspect-square flex items-center justify-center text-center text-xl bg-gray-100 rounded-lg"
/>
</view>
<view class="flex-1 flex items-center justify-center">
<button class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg text-blue-500">
<Edit size="20" />
</button>
</view>
</view>
<view class="flex items-center text-sm text-gray-600">
<Bulb size="16" class="text-yellow-500 mr-2" />
<p>设置有意义的家训口令,便于家人记忆和加入</p>
</view>
</view>
</view>
<!-- Family Avatar -->
<view class="mb-10">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">
家庭头像(选填)
</label>
<view
class="border border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center"
@click="chooseImage"
>
<view class="text-gray-400 mb-2">
<Image size="24" />
</view>
<p class="text-center text-gray-400">点击上传图片</p>
<p class="text-center text-gray-400 text-xs mt-1">
支持jpg、png格式,大小不超过2MB
</p>
</view>
</view>
</view>
</view>
<!-- Submit Button -->
<button
@click="handleCreateFamily"
class="w-full py-4 bg-blue-500 text-white text-lg font-medium rounded-lg"
>
创建家庭
</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import Taro from '@tarojs/taro';
import { Edit, Bulb, Image } from '@nutui/icons-vue-taro';
import AppHeader from '../../components/AppHeader.vue';
const familyName = ref('');
const familyIntro = ref('');
const familySize = ref('3-5人');
const familyMotto = ref(['孝', '敬', '和', '睦']);
const familySizes = ['2人', '3-5人', '6人+'];
const generateRandomMotto = () => {
// 在实际应用中,这里会生成随机家训
// 目前仅作为演示使用预设值
familyMotto.value = ['爱', '和', '勤', '俭'];
};
const chooseImage = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths;
// 在实际应用中,这里会上传图片到服务器
console.log('选择的图片路径:', tempFilePaths);
}
});
};
const handleCreateFamily = () => {
// 在实际应用中,这里会调用API创建家庭
// 目前仅作为演示跳转到仪表盘页面
Taro.navigateTo({
url: '/pages/Dashboard/index'
});
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white pb-16">
<!-- Hero section with family name and background image -->
<view class="relative h-48">
<image src="/static/images/home/dashboard-bg.png" alt="Family background" class="w-full h-full object-cover" />
<view class="absolute inset-0 bg-black bg-opacity-30 flex flex-col justify-end p-5">
<button class="absolute top-4 right-4 text-white" @click="goToProfile">
<Setting size="24" />
</button>
<h1 class="text-white text-2xl font-bold">张爷爷的家庭</h1>
<p class="text-white opacity-90">每日走万步,全家一起行</p>
</view>
</view>
<!-- Today's steps section -->
<view class="p-5">
<view class="flex justify-between items-center mb-1">
<span class="text-gray-500">今日</span>
</view>
<view class="flex justify-between items-center">
<view class="flex items-baseline">
<span class="text-4xl font-bold">
{{ todaySteps.toLocaleString() }}
</span>
<span class="ml-1 text-gray-500">步</span>
</view>
<button class="bg-blue-500 text-white px-4 py-2 rounded-full text-sm" @click="handleSyncSteps">
一键汇总
</button>
</view>
</view>
<!-- Points circles -->
<view class="flex justify-between px-5 my-4">
<view class="flex flex-col items-center">
<view class="w-16 h-16 bg-blue-500 rounded-full flex flex-col items-center justify-center text-white">
<span class="text-sm">热血</span>
<span class="text-sm font-bold">100分</span>
</view>
</view>
<view class="flex flex-col items-center">
<view class="w-20 h-20 bg-blue-500 rounded-full flex flex-col items-center justify-center text-white">
<span class="text-sm">恒心</span>
<span class="text-sm font-bold">900分</span>
</view>
</view>
<view class="flex flex-col items-center">
<view class="w-24 h-24 bg-blue-600 rounded-full flex flex-col items-center justify-center text-white cursor-pointer" @click="goToRewards">
<span class="text-lg font-bold">8,760分</span>
<span class="text-xs">去兑换 &gt;</span>
</view>
</view>
</view>
<!-- Family step ranking -->
<view class="px-5 mt-6">
<view class="flex justify-between items-center mb-4">
<h2 class="font-medium text-lg">今日家庭步数排行</h2>
<span class="text-sm text-gray-500">
总计 {{ totalSteps.toLocaleString() }} 步
</span>
</view>
<view class="grid grid-cols-4 gap-2">
<view v-for="member in familyMembers" :key="member.id" class="flex flex-col items-center">
<image :src="member.avatar" :alt="member.name" class="w-16 h-16 rounded-full mb-1" />
<span class="text-sm text-gray-700">
{{ member.steps.toLocaleString() }}步
</span>
</view>
</view>
</view>
<!-- Photo button -->
<view class="px-5 mt-6">
<button class="w-full bg-blue-500 text-white py-3 rounded-lg flex items-center justify-center" @click="goToActivities">
<Camera size="20" class="mr-2" />
拍照留念,奖励积分
</button>
</view>
<!-- Family album -->
<view class="px-5 mt-6">
<view class="flex justify-between items-center mb-2">
<h2 class="font-medium text-lg">家庭相册</h2>
<button class="text-blue-500 flex items-center" @click="goToActivities">
打开相册 <Right size="16" />
</button>
</view>
<p class="text-sm text-gray-500 mb-3">记录每一个家庭活动瞬间</p>
<view class="grid grid-cols-2 gap-3">
<view class="rounded-lg overflow-hidden">
<image src="/static/images/home/album-1.png" alt="家庭活动照片" class="w-full h-32 object-cover" />
</view>
<view class="rounded-lg overflow-hidden">
<image src="/static/images/home/album-2.png" alt="家庭活动照片" class="w-full h-32 object-cover" />
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import Taro from '@tarojs/taro';
import { Setting, Camera, Right } from '@nutui/icons-vue-taro';
import BottomNav from '../../components/BottomNav.vue';
const todaySteps = ref(2000);
// Mock data for family members
const familyMembers = ref([
{
id: 1,
name: '妈妈',
steps: 7000,
avatar: 'https://randomuser.me/api/portraits/women/44.jpg'
},
{
id: 2,
name: '爸爸',
steps: 6000,
avatar: 'https://randomuser.me/api/portraits/men/32.jpg'
},
{
id: 3,
name: '儿子',
steps: 5000,
avatar: 'https://randomuser.me/api/portraits/men/22.jpg'
},
{
id: 4,
name: '女儿',
steps: 4000,
avatar: 'https://randomuser.me/api/portraits/women/29.jpg'
}
]);
// Calculate total family steps
const totalSteps = computed(() =>
familyMembers.value.reduce((sum, member) => sum + member.steps, 0) + todaySteps.value
);
const handleSyncSteps = () => {
// In a real app, this would sync with a health API
// For demo purposes, we'll just log and do nothing
console.log('Syncing steps...');
};
const goToProfile = () => {
Taro.navigateTo({ url: '/pages/Profile/index' });
};
const goToRewards = () => {
Taro.navigateTo({ url: '/pages/RewardCategories/index' });
};
const goToActivities = () => {
Taro.switchTab({ url: '/pages/Activities/index' });
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="意见反馈" />
<view class="flex-1 px-4 py-6 pb-20">
<form @submit.prevent="handleSubmit" class="space-y-6">
<view>
<label for="feedback" class="block text-sm font-medium text-gray-700 mb-1">
您的反馈
</label>
<textarea
id="feedback"
rows="5"
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
placeholder="请描述您遇到的问题或建议..."
v-model="feedback"
required
/>
</view>
<view>
<label class="block text-sm font-medium text-gray-700 mb-1">
上传截图 (可选)
</label>
<view class="flex flex-wrap gap-2 mb-2">
<view v-for="(image, index) in images" :key="index" class="relative w-20 h-20">
<image :src="image" :alt="`上传图片 ${index + 1}`" class="w-full h-full object-cover rounded-lg" />
<button type="button" class="absolute -top-2 -right-2 bg-red-500 text-white rounded-full p-1" @click="removeImage(index)">
<Failure size="16" />
</button>
</view>
<view
class="w-20 h-20 flex flex-col items-center justify-center border-2 border-dashed border-gray-300 rounded-lg cursor-pointer hover:bg-gray-50"
@click="chooseImage"
>
<Upload size="24" class="text-gray-400" />
<span class="text-xs text-gray-500 mt-1">添加图片</span>
</view>
</view>
</view>
<view>
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">
您的姓名
</label>
<input
type="text"
id="name"
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入您的姓名"
v-model="name"
/>
</view>
<view>
<label for="contact" class="block text-sm font-medium text-gray-700 mb-1">
联系方式
</label>
<input
type="text"
id="contact"
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500"
placeholder="请输入您的手机号或微信号"
v-model="contact"
/>
</view>
<button type="submit" class="w-full py-3 bg-blue-500 text-white font-medium rounded-lg hover:bg-blue-600 transition-colors">
提交反馈
</button>
</form>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref } from 'vue';
import Taro from '@tarojs/taro';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
import { Upload, Failure } from '@nutui/icons-vue-taro';
const feedback = ref('');
const name = ref('');
const contact = ref('');
const images = ref([]);
const chooseImage = () => {
Taro.chooseImage({
count: 9 - images.value.length,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
images.value = [...images.value, ...res.tempFilePaths];
}
});
};
const removeImage = (index) => {
images.value = images.value.filter((_, i) => i !== index);
};
const handleSubmit = () => {
// In a real app, we would send this data to a server
Taro.showToast({
title: '感谢您的反馈!我们会尽快处理。',
icon: 'success',
duration: 2000
});
setTimeout(() => {
Taro.navigateBack();
}, 2000);
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="加入家庭" />
<view class="flex-1 px-4 py-6 flex flex-col">
<!-- Title -->
<h2 class="text-xl font-bold text-center mb-2 mt-4">
输入家训口令
</h2>
<!-- Description -->
<p class="text-gray-600 text-center text-sm mb-6">
请输入家人提供的家训口令,加入家庭一起参与健康挑战
</p>
<!-- Input boxes -->
<view class="flex justify-center gap-3 w-full mb-6">
<view v-for="(char, index) in mottoChars" :key="index" class="w-16 h-16">
<input
:ref="(el) => (inputRefs[index] = el)"
type="text"
v-model="mottoChars[index]"
@input="(e) => handleInputChange(index, e.target.value)"
@keydown="(e) => handleKeyDown(index, e)"
maxlength="1"
class="w-full h-full text-center text-xl border border-gray-300 rounded-md focus:border-blue-500 focus:outline-none"
/>
</view>
</view>
<!-- Help text -->
<p class="text-gray-500 text-center text-sm mb-8">
没有口令?请联系您的大家长获取
</p>
<!-- Role selection -->
<view class="mb-6">
<h3 class="text-base font-medium mb-4 border-t pt-4">
选择您的身份
</h3>
<view class="grid grid-cols-2 gap-3">
<button
v-for="role in familyRoles"
:key="role.id"
:class="[
'flex items-center justify-center p-3 border rounded-md',
selectedRole === role.id
? 'border-blue-500 bg-blue-50'
: 'border-gray-200'
]"
@click="selectedRole = role.id"
>
<view class="flex flex-col items-center">
<My
size="20"
:class="[
'mb-1',
selectedRole === role.id ? 'text-blue-500' : 'text-gray-400'
]"
/>
<span
:class="[
selectedRole === role.id ? 'text-blue-500' : 'text-gray-600'
]"
>
{{ role.label }}
</span>
</view>
</button>
</view>
</view>
<!-- Submit Button -->
<button
@click="handleJoinFamily"
:disabled="!isComplete"
:class="[
'w-full py-4 text-white text-lg font-medium rounded-lg mt-auto',
isComplete ? 'bg-blue-500' : 'bg-gray-300'
]"
>
加入家庭
</button>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import Taro from '@tarojs/taro';
import { My } from '@nutui/icons-vue-taro';
import AppHeader from '../../components/AppHeader.vue';
const mottoChars = ref(['', '', '', '']);
const selectedRole = ref('');
const inputRefs = ref([]);
const handleInputChange = (index, value) => {
if (value && !/^[\u4e00-\u9fa5]$/.test(value)) {
return;
}
mottoChars.value[index] = value;
if (value && index < 3) {
// Taro中无法直接操作DOM进行focus,这里仅保留逻辑
// 在小程序中,可以通过设置 `focus` 属性来控制
}
};
const handleKeyDown = (index, e) => {
if (e.key === 'Backspace' && !mottoChars.value[index] && index > 0) {
// 同样,在Taro中处理光标移动需要不同的方式
}
};
const familyRoles = [
{ id: 'husband', label: '丈夫' },
{ id: 'wife', label: '妻子' },
{ id: 'son', label: '儿子' },
{ id: 'daughter-in-law', label: '儿媳' },
{ id: 'son-in-law', label: '女婿' },
{ id: 'daughter', label: '女儿' },
{ id: 'grandson', label: '孙子' },
{ id: 'maternal-grandson', label: '外孙' },
{ id: 'granddaughter', label: '孙女' },
{ id: 'maternal-granddaughter', label: '外孙女' }
];
const isComplete = computed(() => {
return mottoChars.value.every((char) => char) && selectedRole.value;
});
const handleJoinFamily = () => {
if (isComplete.value) {
Taro.navigateTo({
url: '/pages/Dashboard/index'
});
}
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<!-- Blue header background -->
<view class="bg-blue-500 h-52 absolute w-full top-0 left-0 z-0"></view>
<AppHeader title="积分明细" />
<!-- Content -->
<view class="relative z-10 flex-1 pb-20">
<!-- Points display -->
<view class="pt-4 pb-6 flex flex-col items-center">
<h2 class="text-4xl font-bold text-white mb-1">2580</h2>
<p class="text-white text-opacity-80">当前可用积分</p>
</view>
<!-- Points strategy section -->
<view class="bg-white rounded-t-3xl px-4 pt-5">
<view class="flex justify-between items-center mb-4">
<h3 class="text-lg font-medium">积分攻略</h3>
<button class="text-blue-500 text-sm flex items-center">
查看全部
<Right size="16" />
</button>
</view>
<!-- Strategy cards -->
<view class="space-y-3 mb-6">
<view class="bg-blue-50 p-4 rounded-lg flex items-start">
<view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<My size="20" class="text-blue-500" />
</view>
<view>
<h4 class="font-medium">每日同步步数获得积分</h4>
<p class="text-sm text-gray-600">
每日前1000步可兑换1积分,每日上限100积分
</p>
</view>
</view>
<view class="bg-blue-50 p-4 rounded-lg flex items-start">
<view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<Trophy size="20" class="text-blue-500" />
</view>
<view>
<h4 class="font-medium">参与周末步数挑战</h4>
<p class="text-sm text-gray-600">
每周末参与可获得额外200积分奖励
</p>
</view>
</view>
<view class="bg-blue-50 p-4 rounded-lg flex items-start">
<view class="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center mr-3">
<People size="20" class="text-blue-500" />
</view>
<view>
<h4 class="font-medium">邀请家人加入家庭</h4>
<p class="text-sm text-gray-600">
每成功邀请1位家人,获得500积分奖励
</p>
</view>
</view>
</view>
<!-- Tabs -->
<view class="border-b border-gray-200">
<view class="flex space-x-8">
<button :class="['py-3 font-medium', activeTab === 'all' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'all'">
全部
</button>
<button :class="['py-3 font-medium', activeTab === 'earned' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'earned'">
已发放
</button>
<button :class="['py-3 font-medium', activeTab === 'spent' ? 'text-blue-500 border-b-2 border-blue-500' : 'text-gray-500']" @click="activeTab = 'spent'">
已消耗
</button>
</view>
</view>
<!-- Points history list -->
<view class="pt-4">
<view v-for="item in filteredPoints" :key="item.id" class="py-4 border-b border-gray-100 flex justify-between">
<view>
<h4 class="font-medium">{{ item.title }}</h4>
<p class="text-sm text-gray-500">{{ item.date }}</p>
</view>
<span :class="['font-medium', item.type === 'earned' ? 'text-green-500' : 'text-red-500']">
{{ item.type === 'earned' ? '+' : '-' }}
{{ item.points }}
</span>
</view>
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref, computed } from 'vue';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
import { Right, My, Trophy, People } from '@nutui/icons-vue-taro';
const activeTab = ref('all');
const pointsHistory = ref([
{
id: 1,
title: '每日步数奖励',
date: '2025-08-24 09:30',
points: 100,
type: 'earned'
},
{
id: 2,
title: '周末步数挑战完成',
date: '2025-08-24 08:15',
points: 200,
type: 'earned'
},
{
id: 3,
title: '兑换杏花楼85折券',
date: '2025-08-23 15:20',
points: 10,
type: 'spent'
},
{
id: 4,
title: '邀请家人奖励',
date: '2025-08-23 14:00',
points: 500,
type: 'earned'
},
{
id: 5,
title: '兑换老凤祥9折券',
date: '2025-08-22 16:45',
points: 1000,
type: 'spent'
}
]);
const filteredPoints = computed(() => {
if (activeTab.value === 'all') {
return pointsHistory.value;
}
return pointsHistory.value.filter(p => p.type === activeTab.value);
});
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="隐私政策" />
<view class="flex-1 px-4 py-6 pb-20">
<view class="prose prose-sm max-w-none">
<h2 class="text-xl font-bold mb-4">老来赛隐私政策</h2>
<p class="text-gray-700 mb-4">
本隐私政策旨在帮助您了解我们如何收集、使用、存储和共享您的个人信息,以及您如何访问、更新、控制和保护您的个人信息。请您在使用我们的服务前,仔细阅读并了解本隐私政策。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">一、我们收集的信息</h3>
<p class="text-gray-700 mb-4">
为了向您提供服务,我们可能会收集以下类型的信息:
</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>
<strong>您提供的信息:</strong>
当您注册账号、使用我们的服务或联系我们时,您可能会提供姓名、电话号码、电子邮件地址等个人信息。
</li>
<li>
<strong>设备信息:</strong>
我们可能会收集设备型号、操作系统版本、设备设置、唯一设备标识符等信息。
</li>
<li>
<strong>日志信息:</strong>
当您使用我们的服务时,我们的服务器会自动记录您的搜索查询、IP地址、浏览器类型、语言设置、访问日期和时间等信息。
</li>
<li>
<strong>位置信息:</strong>
经您同意,我们可能会收集和处理有关您实际位置的信息,以便为您提供相关服务。
</li>
<li>
<strong>微信步数信息:</strong>
经您授权,我们会从微信获取您的步数信息,用于计算积分和提供健康服务。
</li>
</ul>
<h3 class="text-lg font-semibold mt-6 mb-2">
二、我们如何使用收集的信息
</h3>
<p class="text-gray-700 mb-4">
我们可能将收集的信息用于以下目的:
</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>提供、维护和改进我们的服务</li>
<li>开发新的服务和功能</li>
<li>了解您如何使用我们的服务</li>
<li>个性化我们的服务,包括提供推荐、个性化内容和广告</li>
<li>进行身份验证、安全防护、存档和备份</li>
<li>发送服务通知和与您沟通</li>
<li>评估服务中的广告和其他促销及推广活动的效果</li>
<li>改进我们的广告投放和测量效果</li>
</ul>
<h3 class="text-lg font-semibold mt-6 mb-2">三、信息共享与披露</h3>
<p class="text-gray-700 mb-4">
除以下情况外,未经您同意,我们不会与任何第三方分享您的个人信息:
</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>
<strong>合作伙伴:</strong>
我们可能与合作伙伴共享您的某些信息,以便他们为您提供或改进我们的服务。
</li>
<li>
<strong>服务提供商:</strong>
我们可能会委托受信任的第三方代表我们提供服务,例如数据分析、发送电子邮件、提供客户服务等。
</li>
<li>
<strong>法律要求:</strong>
如果法律要求披露您的个人信息,或者我们有理由相信需要这样做以遵守法律程序、应对索赔或保护我们的用户的权利、财产或安全,我们将披露您的个人信息。
</li>
<li>
<strong>业务转让:</strong>
如果我们参与合并、收购或资产出售,您的个人信息可能作为此类交易的一部分被转让。
</li>
</ul>
<h3 class="text-lg font-semibold mt-6 mb-2">四、信息安全</h3>
<p class="text-gray-700 mb-4">
我们致力于保护您的个人信息安全。我们采取各种安全技术和程序,以防止信息的丢失、不当使用、未经授权的访问或披露。但请理解,由于技术的限制以及可能存在的各种恶意手段,在互联网行业,即便竭尽所能加强安全措施,也不可能始终保证信息百分之百的安全。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">五、您的权利</h3>
<p class="text-gray-700 mb-4">根据适用的法律法规,您可能有权:</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>访问您的个人信息</li>
<li>更正不准确的个人信息</li>
<li>删除您的个人信息</li>
<li>反对或限制对您个人信息的处理</li>
<li>数据可携带性</li>
<li>撤回您的同意</li>
</ul>
<p class="text-gray-700 mb-4">
如果您想行使这些权利,请通过本隐私政策中提供的联系方式与我们联系。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">六、儿童隐私</h3>
<p class="text-gray-700 mb-4">
我们的服务主要面向成年人。如果您是未满18岁的未成年人,您应当在父母或监护人的陪同下阅读本隐私政策,并在征得您的父母或监护人同意的前提下使用我们的服务和提供个人信息。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">七、隐私政策的更新</h3>
<p class="text-gray-700 mb-4">
我们可能会不时更新本隐私政策。当我们更新隐私政策时,我们会在应用内发布更新后的隐私政策,并更新"最后更新"日期。在某些情况下,我们还会通过其他方式通知您,例如在我们的应用程序中显示醒目通知或发送电子邮件通知。我们鼓励您定期查看本隐私政策,以了解我们如何保护您的信息。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">八、联系我们</h3>
<p class="text-gray-700 mb-4">
如果您对本隐私政策有任何疑问、意见或建议,请通过以下方式与我们联系:
</p>
<p class="text-gray-700 mb-4">
电子邮件:privacy@laolaisai.com
<br />
电话:400-888-8888
</p>
<p class="text-gray-700 mt-8">最后更新日期:2023年5月1日</p>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<!-- Blue header background -->
<view class="bg-blue-500 h-40 absolute w-full top-0 left-0 z-0"></view>
<!-- Content -->
<view class="relative z-10 flex-1 pb-20">
<!-- 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" />
<view class="ml-4">
<h1 class="text-xl font-bold text-white">张爷爷</h1>
</view>
</view>
<button class="text-white">
<span>编辑</span>
</button>
</view>
<!-- Menu items -->
<view class="bg-white rounded-t-3xl px-4 py-5">
<view class="space-y-px">
<template v-for="(item, index) in menuItems" :key="item.id">
<view class="flex items-center py-4 cursor-pointer" @click="item.onClick">
<view :class="['w-10 h-10', item.color, 'rounded-full flex items-center justify-center mr-4']">
<component :is="item.icon" size="20" class="text-white" />
</view>
<span class="flex-1 text-lg">{{ item.label }}</span>
<Right size="20" class="text-gray-400" />
</view>
<view v-if="index < menuItems.length - 1" class="h-px bg-gray-100" />
</template>
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref } 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';
const menuItems = ref([
{
id: 'points',
icon: Chart,
label: '积分明细',
color: 'bg-blue-500',
onClick: () => Taro.navigateTo({ url: '/pages/PointsDetail/index' })
},
{
id: 'rewards',
icon: Gift,
label: '我的兑换',
color: 'bg-blue-500',
onClick: () => Taro.navigateTo({ url: '/pages/Rewards/index' })
},
{
id: 'feedback',
icon: Message,
label: '意见反馈',
color: 'bg-blue-500',
onClick: () => Taro.navigateTo({ url: '/pages/Feedback/index' })
},
{
id: 'agreement',
icon: Document,
label: '用户协议',
color: 'bg-blue-500',
onClick: () => Taro.navigateTo({ url: '/pages/UserAgreement/index' })
},
{
id: 'privacy',
icon: Shield,
label: '隐私政策',
color: 'bg-blue-500',
onClick: () => Taro.navigateTo({ url: '/pages/PrivacyPolicy/index' })
}
]);
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="积分兑换" :showBack="false" />
<view class="flex-1 pb-20">
<view class="p-4 space-y-4">
<view v-for="category in categories" :key="category.id" class="rounded-lg overflow-hidden shadow-sm" @click="goToRewards">
<template v-if="category.id !== 'merchants'">
<view class="relative h-40">
<view class="absolute inset-0 bg-black bg-opacity-30 flex flex-col justify-end p-4 text-white" :style="{ background: category.bgColor || 'linear-gradient(rgba(0,0,0,0.3), rgba(0,0,0,0.6))' }">
<h3 class="text-xl font-bold mb-1">{{ category.title }}</h3>
<p class="text-sm text-white text-opacity-90">
{{ category.description }}
</p>
</view>
<image :src="category.image" :alt="category.title" class="w-full h-full object-cover" :style="{ objectPosition: category.bgPosition || 'center' }" />
<view v-if="category.iconUrl" class="absolute top-4 left-4 w-12 h-12 bg-white bg-opacity-90 rounded-full flex items-center justify-center">
<image :src="category.iconUrl" alt="" class="w-8 h-8" />
</view>
</view>
</template>
<template v-else>
<view class="bg-white border border-gray-100 rounded-lg p-4">
<view class="flex justify-between items-center mb-3">
<view>
<h3 class="text-lg font-bold">{{ category.title }}</h3>
<p class="text-sm text-gray-600">
{{ category.description }}
</p>
</view>
<Right size="20" class="text-gray-400" />
</view>
<view class="grid grid-cols-5 gap-2">
<view v-for="(merchant, index) in category.merchants.slice(0, 10)" :key="index" class="flex flex-col items-center">
<image :src="merchant.logo" :alt="merchant.name" class="w-12 h-12 object-contain rounded-md" />
<span class="text-xs text-gray-600 mt-1 text-center truncate w-full">
{{ merchant.name }}
</span>
</view>
</view>
</view>
</template>
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref } from 'vue';
import Taro from '@tarojs/taro';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
import { Right } from '@nutui/icons-vue-taro';
const categories = ref([
{
id: 'health',
title: '银龄健康特色兑换区',
description: '南京商圈线下实体店消费积分兑换',
image: "/static/images/rewards/reward-banner.png",
bgPosition: 'center'
},
{
id: 'online',
title: '民政领域网上商城"银龄购"',
description: '线上康复健康购物积分兑换',
image: 'https://images.unsplash.com/photo-1563013544-824ae1b704d3?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80',
bgPosition: 'center',
bgColor: 'linear-gradient(135deg, #0ea5e9, #2563eb)',
iconUrl: 'https://cdn-icons-png.flaticon.com/512/3144/3144456.png'
},
{
id: 'merchants',
title: '人驻商户多元场景广覆盖',
description: '丰富商户积分兑换',
image: 'https://images.unsplash.com/photo-1607082349566-187342175e2f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1740&q=80',
bgPosition: 'center',
merchants: [
{ name: '上海老饭店', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=上海老饭店&font=roboto' },
{ name: '功德林', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=功德林&font=roboto' },
{ name: '一茶一坐', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=一茶一坐&font=roboto' },
{ name: '杏花楼', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=杏花楼&font=roboto' },
{ name: '新雅', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=新雅&font=roboto' },
{ name: '五芳斋', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=五芳斋&font=roboto' },
{ name: '沈大成', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=沈大成&font=roboto' },
{ name: '老凤祥', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=老凤祥&font=roboto' },
{ name: '御宝轩', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=御宝轩&font=roboto' },
{ name: '恒源祥', logo: 'https://placehold.co/100x100/e2f3ff/0369a1?text=恒源祥&font=roboto' }
]
}
]);
const goToRewards = () => {
Taro.navigateTo({ url: '/pages/Rewards/index' });
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-gradient-to-br from-blue-50 to-purple-50">
<AppHeader title="优惠券详情" />
<view class="flex-1 p-4">
<template v-if="!redeemed">
<view class="bg-white rounded-2xl shadow-sm overflow-hidden mb-4">
<view class="h-40 bg-blue-600 flex items-center justify-center p-6">
<image :src="reward.image" :alt="reward.merchant" class="max-h-full max-w-full object-contain" />
</view>
<view class="p-4">
<h1 class="text-xl font-medium mb-1">{{ reward.title }}</h1>
<p class="text-gray-600">{{ reward.description }}</p>
<view class="flex items-center mt-4 text-blue-600">
<Ticket size="20" class="mr-2" />
<span class="font-medium">{{ reward.points }} 积分</span>
</view>
</view>
</view>
<GlassCard class="mb-4">
<h2 class="text-lg font-medium mb-4">使用须知</h2>
<view class="space-y-3">
<view class="flex">
<Calendar size="20" class="text-gray-500 mr-3 flex-shrink-0" />
<view>
<p class="font-medium">有效期</p>
<p class="text-gray-600">{{ reward.validUntil }}</p>
</view>
</view>
<view class="flex">
<Location2 size="20" class="text-gray-500 mr-3 flex-shrink-0" />
<view>
<p class="font-medium">适用门店</p>
<p class="text-gray-600">{{ reward.locations }}</p>
</view>
</view>
</view>
</GlassCard>
<GlassCard class="mb-6">
<h2 class="text-lg font-medium mb-4">使用条款</h2>
<view class="space-y-2">
<view v-for="(term, index) in reward.terms" :key="index" class="flex">
<Info size="16" class="text-gray-500 mr-2 flex-shrink-0 mt-0.5" />
<p class="text-gray-600">{{ term }}</p>
</view>
</view>
</GlassCard>
<PrimaryButton @click="handleRedeem" :disabled="redeeming">
<template v-if="redeeming">
<view class="animate-spin mr-2">
<Loading class="w-5 h-5" />
</view>
兑换中...
</template>
<template v-else>
立即兑换
</template>
</PrimaryButton>
</template>
<template v-else>
<view class="flex flex-col items-center justify-center h-full">
<GlassCard class="w-full max-w-md">
<view class="flex flex-col items-center">
<view class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mb-4">
<Check size="40" class="text-green-600" />
</view>
<h2 class="text-xl font-medium mb-2">兑换成功</h2>
<p class="text-gray-600 text-center mb-6">
您已成功兑换{{ reward.title }}
</p>
<view class="bg-gray-50 w-full rounded-lg p-4 mb-6 text-center">
<p class="text-gray-600 mb-2">兑换码</p>
<p class="text-2xl font-mono tracking-wider font-medium">
SB12345678
</p>
</view>
<image :src="reward.image" :alt="reward.merchant" class="w-24 h-24 object-contain mb-6" />
<view class="text-center mb-6">
<p class="font-medium">{{ reward.title }}</p>
<p class="text-gray-600">{{ reward.description }}</p>
<p class="text-gray-500 text-sm mt-2">
有效期至: {{ reward.validUntil }}
</p>
</view>
<PrimaryButton @click="goBack">
返回兑换页面
</PrimaryButton>
</view>
</GlassCard>
</view>
</template>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import Taro from '@tarojs/taro';
import AppHeader from '../../components/AppHeader.vue';
import GlassCard from '../../components/GlassCard.vue';
import PrimaryButton from '../../components/PrimaryButton.vue';
import { Ticket, Calendar, Location2, Info, Check, Loading } from '@nutui/icons-vue-taro';
const redeeming = ref(false);
const redeemed = ref(false);
// Mock reward data
const reward = {
id: 1,
title: '星巴克咖啡券',
description: '任意中杯咖啡一杯',
points: 500,
merchant: '星巴克',
image: 'https://upload.wikimedia.org/wikipedia/en/thumb/d/d3/Starbucks_Corporation_Logo_2011.svg/1200px-Starbucks_Corporation_Logo_2011.svg.png',
validUntil: '2023年12月31日',
locations: '全国各星巴克门店',
terms: ['每人限兑换1次', '不与其他优惠同享', '使用时请出示兑换码', '最终解释权归商家所有']
};
const handleRedeem = () => {
redeeming.value = true;
// Simulate API call
setTimeout(() => {
redeeming.value = false;
redeemed.value = true;
}, 1500);
};
const goBack = () => {
Taro.navigateTo({ url: '/pages/Rewards/index' });
};
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<!-- Blue header background -->
<view class="bg-blue-500 h-48 absolute w-full top-0 left-0 z-0"></view>
<AppHeader title="兑换中心" :showBack="false" />
<!-- Content -->
<view class="relative z-10 flex-1 pb-20">
<!-- Points display -->
<view class="pt-4 pb-8 flex flex-col items-center">
<h2 class="text-4xl font-bold text-white mb-1">2580分</h2>
<p class="text-white text-opacity-80">我的积分</p>
</view>
<!-- Main content -->
<view class="bg-white rounded-t-3xl px-4 pt-5">
<!-- Quick exchange options -->
<view class="flex gap-3 mb-6">
<button v-for="option in quickExchangeOptions" :key="option.points" class="flex-1 py-3 bg-white border border-gray-200 rounded-lg text-center text-gray-700">
{{ option.label }}
</button>
</view>
<h3 class="text-lg font-medium mb-4">可兑换列表</h3>
<!-- Rewards list -->
<view class="space-y-4">
<view v-for="reward in rewardItems" :key="reward.id" class="flex items-center border-b border-gray-100 pb-4">
<view class="w-12 h-12 mr-4 flex-shrink-0">
<image :src="reward.logo" :alt="reward.title" class="w-full h-full object-contain" />
</view>
<view class="flex-1">
<h4 class="font-medium">{{ reward.title }}</h4>
<p class="text-gray-500 text-sm">{{ reward.points }}积分</p>
</view>
<button class="px-4 py-1 bg-blue-500 text-white rounded-full text-sm">
兑换
</button>
</view>
</view>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import { ref } from 'vue';
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
const rewardItems = ref([
{
id: 1,
title: '星巴克中杯咖啡兑换券',
points: 1000,
logo: 'https://upload.wikimedia.org/wikipedia/en/thumb/d/d3/Starbucks_Corporation_Logo_2011.svg/1200px-Starbucks_Corporation_Logo_2011.svg.png'
},
{
id: 2,
title: '老凤祥20元抵用券',
points: 600,
logo: 'https://placehold.co/400x400/e2f3ff/0369a1?text=LFX&font=roboto'
},
{
id: 3,
title: '杏花楼集团8折券',
points: 500,
logo: 'https://placehold.co/400x400/e2f3ff/0369a1?text=XHL&font=roboto'
},
{
id: 4,
title: '肯德基汉堡套餐券',
points: 1000,
logo: 'https://upload.wikimedia.org/wikipedia/en/thumb/b/bf/KFC_logo.svg/1200px-KFC_logo.svg.png'
},
{
id: 5,
title: '沈大成双黄团3元抵用券',
points: 300,
logo: 'https://placehold.co/400x400/e2f3ff/0369a1?text=SDC&font=roboto'
}
]);
const quickExchangeOptions = ref([
{ points: 3000, label: '3000分可兑' },
{ points: 1000, label: '1000分可兑' },
{ points: 100, label: '100分可兑' }
]);
</script>
export default {
navigationBarTitleText: '首页'
}
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="用户协议" />
<view class="flex-1 px-4 py-6 pb-20">
<view class="prose prose-sm max-w-none">
<h2 class="text-xl font-bold mb-4">老来赛用户协议</h2>
<p class="text-gray-700 mb-4">
欢迎您使用老来赛应用服务。在使用老来赛应用服务之前,请您务必仔细阅读并透彻理解本用户协议。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">一、协议的范围</h3>
<p class="text-gray-700 mb-4">
本协议是您与老来赛应用之间关于您使用老来赛应用服务所订立的协议。"老来赛"是指老来赛应用及其开发者。"用户"是指注册、登录、使用本服务的个人或组织。本服务包括老来赛应用及相关网站。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">
二、账号注册与使用
</h3>
<p class="text-gray-700 mb-4">
1.
用户在使用本服务前需要注册一个老来赛账号。老来赛账号应当使用手机号码绑定注册,请用户使用尚未与老来赛账号绑定的手机号码,以及未被老来赛根据本协议封禁的手机号码注册老来赛账号。
</p>
<p class="text-gray-700 mb-4">
2.
用户应当对账号信息的真实性、合法性、有效性承担全部责任。用户不得冒充他人或利用他人的名义注册账号,不得利用多个手机号注册多个账号,不得使用可能侵犯他人合法权益的账号名称。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">三、用户行为规范</h3>
<p class="text-gray-700 mb-4">
1.
用户在使用老来赛服务时,必须遵守中华人民共和国相关法律法规的规定,不得利用本服务进行任何违法或不正当的活动,包括但不限于:
</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>发布、传送、传播、储存违反国家法律法规禁止的内容</li>
<li>
发布、传送、传播、储存侵害他人名誉权、肖像权、知识产权、商业秘密等合法权利的内容
</li>
<li>虚构事实、隐瞒真相以误导、欺骗他人</li>
<li>发布、传送、传播广告信息及垃圾信息</li>
</ul>
<h3 class="text-lg font-semibold mt-6 mb-2">
四、服务变更、中断或终止
</h3>
<p class="text-gray-700 mb-4">
1.
鉴于网络服务的特殊性,老来赛有权根据实际情况随时变更、中断或终止部分或全部的服务而无需对用户或第三方负责。
</p>
<p class="text-gray-700 mb-4">
2.
如发生下列任何一种情形,老来赛有权随时中断或终止向用户提供本协议项下的服务而无需对用户或任何第三方承担责任:
</p>
<ul class="list-disc pl-5 mb-4 text-gray-700">
<li>用户提供的个人资料不真实</li>
<li>用户违反本协议中规定的使用规则</li>
<li>用户在使用老来赛服务时有违法行为</li>
</ul>
<h3 class="text-lg font-semibold mt-6 mb-2">五、免责声明</h3>
<p class="text-gray-700 mb-4">
1.
用户明确同意其使用老来赛服务所存在的风险将完全由其自己承担;因其使用老来赛服务而产生的一切后果也由其自己承担,老来赛对用户不承担任何责任。
</p>
<p class="text-gray-700 mb-4">
2.
老来赛不保证服务一定能满足用户的要求,也不保证服务不会中断,对服务的及时性、安全性、准确性也都不作保证。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">六、协议修改</h3>
<p class="text-gray-700 mb-4">
1.
老来赛有权随时修改本协议的任何条款,一旦本协议的内容发生变动,老来赛将会在应用内更新并提示修改内容。
</p>
<p class="text-gray-700 mb-4">
2.
如果不同意老来赛对本协议相关条款所做的修改,用户有权停止使用老来赛服务。如果用户继续使用服务,则视为用户接受老来赛对本协议相关条款所做的修改。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">七、通知送达</h3>
<p class="text-gray-700 mb-4">
1.
本协议项下老来赛对于用户所有的通知均可通过网页公告、电子邮件、手机短信或常规的信件传送等方式进行。
</p>
<h3 class="text-lg font-semibold mt-6 mb-2">
八、法律适用与争议解决
</h3>
<p class="text-gray-700 mb-4">
1. 本协议的订立、执行和解释及争议的解决均应适用中华人民共和国法律。
</p>
<p class="text-gray-700 mb-4">
2.
如双方就本协议内容或其执行发生任何争议,双方应尽量友好协商解决;协商不成时,任何一方均可向老来赛所在地的人民法院提起诉讼。
</p>
<p class="text-gray-700 mt-8">本协议最终解释权归老来赛所有。</p>
</view>
</view>
<BottomNav />
</view>
</template>
<script setup>
import AppHeader from '../../components/AppHeader.vue';
import BottomNav from '../../components/BottomNav.vue';
</script>
export default {
navigationBarTitleText: '首页'
}
<!--
* @Date: 2025-08-27 17:43:45
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 18:21:32
* @FilePath: /lls_program/src/pages/Welcome/index.vue
* @Description: 文件描述
-->
<template>
<view class="min-h-screen flex flex-col bg-white">
<!-- Header -->
<header class="py-5 text-center">
<h1 class="text-xl font-bold">老来赛</h1>
</header>
<!-- Main content -->
<view class="flex-1 flex flex-col px-4 pb-20">
<!-- Hero Image -->
<view class="w-full mb-6">
<view class="w-full h-48 rounded-2xl overflow-hidden">
<image :src="welcomeHomeImg" alt="家庭在上海外滩散步" class="w-full h-full object-cover" />
</view>
</view>
<!-- Steps Section -->
<view class="mb-8">
<h2 class="text-xl font-bold mb-6">简单三步,开启健康生活</h2>
<view class="space-y-6">
<!-- Step 1 -->
<view class="flex items-center">
<view class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold text-lg mr-4 flex-shrink-0">
1
</view>
<view>
<h3 class="font-bold">创建家庭</h3>
<p class="text-gray-600 text-sm">
家长创建家庭,获取专属口令
</p>
</view>
</view>
<!-- Step 2 -->
<view class="flex items-center">
<view class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold text-lg mr-4 flex-shrink-0">
2
</view>
<view>
<h3 class="font-bold">邀请家人</h3>
<p class="text-gray-600 text-sm">分享口令,邀请家人加入</p>
</view>
</view>
<!-- Step 3 -->
<view class="flex items-center">
<view class="w-10 h-10 bg-blue-500 rounded-full flex items-center justify-center text-white font-bold text-lg mr-4 flex-shrink-0">
3
</view>
<view>
<h3 class="font-bold">同步步数,兑换好礼</h3>
<p class="text-gray-600 text-sm">
每日同步微信步数,兑换抵用券
</p>
</view>
</view>
</view>
</view>
<!-- Action Buttons -->
<view class="space-y-4 mt-auto">
<button @click="navigateTo('/pages/CreateFamily/index')" class="w-full py-3.5 bg-blue-500 text-white text-lg font-medium rounded-full">
创建家庭
</button>
<button @click="navigateTo('/pages/JoinFamily/index')" class="w-full py-3.5 bg-white text-gray-800 text-lg font-medium rounded-full border border-gray-300">
加入家庭
</button>
</view>
</view>
<!-- Bottom Navigation -->
<BottomNav />
</view>
</template>
<script setup>
import Taro from '@tarojs/taro';
import BottomNav from '../../components/BottomNav.vue'; // 假设BottomNav组件已转换
import welcomeHomeImg from '../../assets/images/welcome_home.png';
const navigateTo = (url) => {
Taro.navigateTo({ url });
};
</script>
<style lang="less">
.font-sans {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
</style>
<template>
<view class="min-h-screen flex flex-col bg-white">
<AppHeader title="创建家庭" />
<view class="flex-1 px-4 py-6 overflow-auto">
<view class="mb-6">
<p class="text-gray-600 mb-6">
请填写家庭信息,创建您的专属家庭空间
</p>
<!-- Family Name -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">家庭名称</label>
<input
type="text"
v-model="familyName"
class="w-full text-gray-600 focus:outline-none"
placeholder="请输入家庭名称"
/>
</view>
</view>
<!-- Family Introduction -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">家庭介绍</label>
<textarea
v-model="familyIntro"
class="w-full text-gray-600 focus:outline-none resize-none"
placeholder="请输入您家庭的特色、成员特点等家庭标签"
:rows="2"
/>
</view>
</view>
<!-- Family Size -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-4">家庭规模</label>
<view class="flex gap-2">
<button
v-for="size in familySizes"
:key="size"
@click="familySize = size"
:class="[
'flex-1 py-3 rounded-lg border',
familySize === size
? 'border-blue-500 bg-blue-50 text-blue-500'
: 'border-gray-200 text-gray-700'
]"
>
{{ size }}
</button>
</view>
</view>
</view>
<!-- Family Motto -->
<view class="mb-6">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<view class="flex justify-between items-center mb-4">
<label class="block text-lg font-medium">家训口令</label>
<button
@click="generateRandomMotto"
class="px-3 py-1 bg-blue-100 text-blue-600 rounded-full text-sm"
>
随机生成
</button>
</view>
<view class="flex gap-2 mb-4">
<view v-for="(char, index) in familyMotto" :key="index" class="flex-1">
<input
type="text"
v-model="familyMotto[index]"
maxlength="1"
class="w-full aspect-square flex items-center justify-center text-center text-xl bg-gray-100 rounded-lg"
/>
</view>
<view class="flex-1 flex items-center justify-center">
<button class="w-full aspect-square flex items-center justify-center bg-gray-100 rounded-lg text-blue-500">
<Edit size="20" />
</button>
</view>
</view>
<view class="flex items-center text-sm text-gray-600">
<Bulb size="16" class="text-yellow-500 mr-2" />
<p>设置有意义的家训口令,便于家人记忆和加入</p>
</view>
</view>
</view>
<!-- Family Avatar -->
<view class="mb-10">
<view class="bg-white rounded-lg border border-gray-200 p-4">
<label class="block text-lg font-medium mb-2">
家庭头像(选填)
</label>
<view
class="border border-dashed border-gray-300 rounded-lg p-6 flex flex-col items-center justify-center"
@click="chooseImage"
>
<view class="text-gray-400 mb-2">
<Image size="24" />
</view>
<p class="text-center text-gray-400">点击上传图片</p>
<p class="text-center text-gray-400 text-xs mt-1">
支持jpg、png格式,大小不超过2MB
</p>
</view>
</view>
</view>
</view>
<!-- Submit Button -->
<button
@click="handleCreateFamily"
class="w-full py-4 bg-blue-500 text-white text-lg font-medium rounded-lg"
>
创建家庭
</button>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue';
import Taro from '@tarojs/taro';
import { Edit, Bulb, Image } from '@nutui/icons-vue-taro';
import AppHeader from '../../components/AppHeader.vue';
const familyName = ref('');
const familyIntro = ref('');
const familySize = ref('3-5人');
const familyMotto = ref(['孝', '敬', '和', '睦']);
const familySizes = ['2人', '3-5人', '6人+'];
const generateRandomMotto = () => {
// 在实际应用中,这里会生成随机家训
// 目前仅作为演示使用预设值
familyMotto.value = ['爱', '和', '勤', '俭'];
};
const chooseImage = () => {
Taro.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempFilePaths = res.tempFilePaths;
// 在实际应用中,这里会上传图片到服务器
console.log('选择的图片路径:', tempFilePaths);
}
});
};
const handleCreateFamily = () => {
// 在实际应用中,这里会调用API创建家庭
// 目前仅作为演示跳转到仪表盘页面
Taro.navigateTo({
url: '/pages/demo/Dashboard'
});
};
</script>
export default {
navigationBarTitleText: '首页'
}
/**
* index页面样式
*/
.index {
// padding: 20px;
.nut-button {
margin-bottom: 20px;
}
}
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 17:13:11
* @FilePath: /lls_program/src/pages/index/index.vue
* @Description: 文件描述
-->
<template>
<view class="index">
<PointsCollector ref="pointsCollectorRef" height="30vh" />
<nut-button type="success" @click="handleCollectAll" style="margin-top: 20rpx;">一键收取</nut-button>
</view>
</template>
<script setup>
import Taro from '@tarojs/taro'
import '@tarojs/taro/html.css'
import { ref, onMounted } from 'vue'
import { useDidShow, useReady } from '@tarojs/taro'
import PointsCollector from '@/components/PointsCollector.vue'
import "./index.less";
const pointsCollectorRef = ref(null)
/**
* 触发积分收集组件的一键收取
*/
const handleCollectAll = () => {
if (pointsCollectorRef.value) {
pointsCollectorRef.value.collectAll()
}
}
// 生命周期钩子
useDidShow(() => {
console.warn('index onShow')
})
useReady(async () => {
console.warn('index onReady')
// 版本更新检查
if (!Taro.canIUse("getUpdateManager")) {
Taro.showModal({
title: "提示",
content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试",
showCancel: false,
});
return;
}
// https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html
const updateManager = Taro.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
// 请求完新版本信息的回调
if (res.hasUpdate) {
updateManager.onUpdateReady(function () {
Taro.showModal({
title: "更新提示",
content: "新版本已经准备好,是否重启应用?",
success: function (res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed(function () {
// 新版本下载失败
Taro.showModal({
title: "更新提示",
content: "新版本已上线,请删除当前小程序,重新搜索打开",
});
});
}
});
})
onMounted(() => {
console.warn('index mounted')
})
// 分享功能
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
</script>
<script>
import { getCurrentPageParam } from "@/utils/weapp";
export default {
name: "indexPage",
onHide () {
console.warn('index onHide')
},
onShareAppMessage() {
let params = getCurrentPageParam();
// 设置菜单中的转发按钮触发转发事件时的转发内容
var shareObj = {
title: "xxx", // 默认是小程序的名称(可以写slogan等)
path: `pages/detail/index?id=${params.id}&start_date=${params.start_date}&end_date=${params.end_date}&room_type=${params.room_type}`, // 默认是当前页面,必须是以'/'开头的完整路径
imageUrl: '', //自定义图片路径,可以是本地文件路径、代码包文件路径或者网络图片路径,支持PNG及JPG,不传入 imageUrl 则使用默认截图。显示图片长宽比是 5:4
success: function (res) {
// 转发成功之后的回调
if (res.errMsg == 'shareAppMessage:ok') {
//
}
},
fail: function () {
// 转发失败之后的回调
if (res.errMsg == 'shareAppMessage:fail cancel') {
// 用户取消转发
} else if (res.errMsg == 'shareAppMessage:fail') {
// 转发失败,其中 detail message 为详细失败信息
}
},
complete: function () {
// 转发结束之后的回调(转发成不成功都会执行)
}
}
// 来自页面内的按钮的转发
// if (options.from == 'button') {
// var eData = options.target.dataset;
// // 此处可以修改 shareObj 中的内容
// shareObj.path = '/pages/goods/goods?goodId=' + eData.id;
// }
// 返回shareObj
return shareObj;
}
};
</script>
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-08-27 17:13:11
* @LastEditTime: 2025-08-27 16:04:30
* @FilePath: /lls_program/src/pages/index/index.vue
* @Description: 文件描述
-->
......@@ -13,10 +13,7 @@
</template>
<script setup>
import Taro from '@tarojs/taro'
import '@tarojs/taro/html.css'
import { ref, onMounted } from 'vue'
import { useDidShow, useReady } from '@tarojs/taro'
import { ref } from 'vue'
import PointsCollector from '@/components/PointsCollector.vue'
import "./index.less";
......@@ -30,63 +27,6 @@ const handleCollectAll = () => {
pointsCollectorRef.value.collectAll()
}
}
// 生命周期钩子
useDidShow(() => {
console.warn('index onShow')
})
useReady(async () => {
console.warn('index onReady')
// 版本更新检查
if (!Taro.canIUse("getUpdateManager")) {
Taro.showModal({
title: "提示",
content: "当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试",
showCancel: false,
});
return;
}
// https://developers.weixin.qq.com/miniprogram/dev/api/base/update/UpdateManager.html
const updateManager = Taro.getUpdateManager();
updateManager.onCheckForUpdate((res) => {
// 请求完新版本信息的回调
if (res.hasUpdate) {
updateManager.onUpdateReady(function () {
Taro.showModal({
title: "更新提示",
content: "新版本已经准备好,是否重启应用?",
success: function (res) {
if (res.confirm) {
// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
updateManager.applyUpdate();
}
},
});
});
updateManager.onUpdateFailed(function () {
// 新版本下载失败
Taro.showModal({
title: "更新提示",
content: "新版本已上线,请删除当前小程序,重新搜索打开",
});
});
}
});
})
onMounted(() => {
console.warn('index mounted')
})
// 分享功能
wx.showShareMenu({
withShareTicket: true,
menus: ['shareAppMessage', 'shareTimeline']
})
</script>
<script>
......