Files
wagoo-douy3/src/pages/client/home/index.vue
2026-04-10 15:47:09 +08:00

852 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="home-page">
<!-- 微信复制弹窗 -->
<WeChatCopyModal ref="wechatCopyModal" />
<!-- 可拖动联系客服组件 -->
<DraggableContact ref="draggableContact" :onClick="handleContactClick" />
<scroll-view class="homeContainer" scroll-y :show-scrollbar="false" :enhanced="true">
<view class="swiperWrapper">
<swiper indicator-dots="true" autoplay="true" interval="3000" duration="500" circular="true"
class="swiper">
<swiper-item v-for="(item, index) in swiperDataList" :key="index">
<image class="swiper-img" :src="item.image || `${imgPrefix}home-ground.png`" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="userInfoWrapper">
<view class="userInfo" @click="handleUserInfo">
<view class="userContent">
<image
:src=" userInfo.avatar ? userInfo.avatar : `${imgPrefix}home-head.png`"
class="userAvatar" mode="aspectFill" />
<view class="user-info-wrapper">
<view class="userName">
{{ userInfo.username ? userInfo.username : "嗨,你好呀" }}
</view>
<view class="userTips" v-if="!userInfo.userID">
登陆享受更多精彩内容
</view>
<!-- <view v-else class="user-membership-row">
<view
v-if="userInfo.membershipTier && userInfo.membershipTier > 0"
class="membership-tier-badge"
:class="{
'membership-tier-1': userInfo.membershipTier === 1,
'membership-tier-2': userInfo.membershipTier === 2,
'membership-tier-3': userInfo.membershipTier === 3
}"
>
<text class="membership-tier-text">{{ getMembershipTierText(userInfo.membershipTier) }}</text>
</view>
</view> -->
</view>
</view>
</view>
<view class="loginBtn" @click="toLogin" v-if="!userInfo.userID">
注册/登陆
</view>
<view class="logoutBtn" @click="logout" v-if="userInfo.userID">
退出登录
</view>
</view>
<view class="shadowBackground" />
</view>
<view class="recommand-goods-wrapper">
<!-- <view class="fs-36 app-font-bold app-fc-main recommand-title">
推荐商品
</view> -->
<view class="goods-list">
<view class="goods-list-item left">
<good-item v-for="(good, i) in leftColumnGoods" :index="2 * i" :key="2 * i" :data="good" :isHome="true"
@addToCar="addToCar" />
</view>
<view class="goods-list-item right">
<good-item v-for="(good, i) in rightColumnGoods" :index="2 * i + 1" :key="2 * i + 1" :data="good" :isHome="true"
@addToCar="addToCar" />
</view>
</view>
<view v-if="isLoadingGoods" class="loading-wrapper flex-center">
<uni-load-more status="loading" :show-text="false" />
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
imgPrefix,
showLoginConfirmModal
} from "@/utils/common";
import {
userWllet
} from "../../../api/login";
import {
loginOut,
} from "../../../api/user";
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
import GoodItem from "../shop/components/GoodItem.vue";
import DraggableContact from "@/components/DraggableContact.vue";
import {
getGoodsClassify,
getGoodsListData
} from "@/api/shop";
import { getHomeGoodsWithCache } from "@/utils/goodsCache";
export default {
name: "HomePage",
components: {
GoodItem,
WeChatCopyModal,
DraggableContact
},
data() {
return {
imgPrefix,
couponCount: 1,
goodsList: [], // 商品列表
goodsTotal: 0, // 商品总数
goodPage: 1, // 当前页码
isLoadingGoods: false, // 是否正在加载商品
refreshTriggered: false, // 刷新是否已触发
cartCount: 0, // 购物车数量
swiperDataList: [
{
image: `${imgPrefix}banner1.png`
},
{
image: `${imgPrefix}bannerShare.png`
}
],
secondMenuItemList: [{
title: "邀请有礼",
tips: "邀请好友得好礼",
img: `${imgPrefix}home-invite .png`,
naviUrl: "/pages/client/recharge/membership-code",
params: {
yaoqing_code: (userInfo) => userInfo?.userID || '',
member_id: (userInfo) => userInfo?.userID || ''
}
},
{
title: "领券中心",
tips: "领取更多优惠券",
img: `${imgPrefix}home-getCoupon.png`,
naviUrl: "/pages/client/coupon/get-list",
},
{
title: "充值有礼",
tips: "最高返赠1650元",
img: `${imgPrefix}home-recharge.png`,
naviUrl: '/pages/client/recharge/index',
params: {
user_id: (userInfo) => userInfo?.userID || '',
}
},
{
title: "开通会员",
tips: "服务享8折",
img: `${imgPrefix}home-openVip.png`,
naviUrl: "/pages/richText/member-interests"
},
],
walletInfo: {}, // 钱包信息
secondMenuIndicatorIndex: 0, // 第二排菜单指示器当前页 0=第一页 1=第二页
};
},
computed: {
couponCountText() {
return `${this.couponCount}张优惠券`;
},
userInfo() {
return this.$store.state?.user?.userInfo || {};
},
leftColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 0);
},
rightColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 1);
},
cartShowCount() {
return this.cartCount > 9 ? "9+" : this.cartCount;
}
},
created() {
this.getGoodsList()
},
onShow() {
// 页面显示时检查是否需要刷新商品数据
this.getGoodsList(false);
},
methods: {
getGoodsList(forceRefresh = false) {
if (this.isLoadingGoods) return;
this.isLoadingGoods = true;
const params = {
type: 0
}
getHomeGoodsWithCache(params, forceRefresh)
.then((res) => {
const list = res?.data.data.products || [];
// 只有当数据有变化时才更新商品列表,避免不必要的刷新
if (res.hasChanged !== false || this.goodsList.length === 0) {
this.goodsList = list;
}
this.goodsTotal = res?.count || list.length || 0;
})
.catch((err) => {
console.error('获取商品列表失败', err);
})
.finally(() => {
this.isLoadingGoods = false;
this.refreshTriggered = false;
});
},
toLogin() {
uni.navigateTo({
url: "/pages/client/auth/index",
});
},
logout() {
loginOut();
// 清除Vuex中的用户状态
this.$store.dispatch('user/deleteToken');
this.$store.dispatch('user/clearUserInfo');
// 清除本地缓存
uni.clearStorageSync();
uni.reLaunch({
url: "/pages/client/auth/index",
});
},
// 统一的 token 检查方法,未登录时弹窗让用户自主选择
async checkTokenAndExecute(callback) {
const token = uni.getStorageSync('token');
if (!token) {
const willLogin = await showLoginConfirmModal();
if (!willLogin) return;
// 用户选择去登录,跳转后不执行 callback
return;
}
if (typeof callback === 'function') {
callback();
}
},
async handleUserInfo() {
const token = uni.getStorageSync('token');
if (token) {
uni.navigateTo({
url: "/pages/client/mine/userInfo",
});
} else {
await showLoginConfirmModal();
}
},
toReservation() {
this.checkTokenAndExecute(() => {
// 直接调用父组件的 handleTabChange 方法来切换 TabBar
// 这是最可靠的方式,因为 home 组件是 index 组件的子组件
if (this.$parent && typeof this.$parent.handleTabChange === 'function') {
this.$parent.handleTabChange(["reservationPage"]);
}
});
},
toDogTraining() {
uni.showToast({
title: '暂未开放',
icon: 'none',
duration: 2000
});
return;
// uni.navigateTo({
// url: '/pageHome/service/index'
// });
},
onSecondMenuScroll(e) {
const scrollLeft = e.detail.scrollLeft || 0;
const sysInfo = uni.getSystemInfoSync();
const windowWidth = sysInfo.windowWidth || 375;
// 5项一屏4个可滚动范围=1项宽≈windowWidth/4过半即第二页
const maxScroll = windowWidth / 4;
this.secondMenuIndicatorIndex = scrollLeft >= maxScroll * 0.5 ? 1 : 0;
},
getMembershipTierText(tier) {
const tierMap = {
1: '黄金会员',
2: '白金会员',
3: '黑金会员'
};
return tierMap[tier] || '';
},
handleNav(item) {
this.checkTokenAndExecute(() => {
// 健康顾问暂未开放
if (item.title === '健康顾问') {
uni.showToast({
title: '暂未开放',
icon: 'none',
duration: 2000
});
return;
}
if (item.naviUrl) {
let url = item.naviUrl;
// 如果有 params 参数,进行拼接
if (item.params && typeof item.params === 'object') {
const params = [];
for (const key in item.params) {
const value = item.params[key];
// 支持函数形式,动态获取值(可接收 userInfo 和 walletInfo
const paramValue = typeof value === 'function'
? value(this.userInfo, this.walletInfo)
: value;
if (paramValue !== undefined && paramValue !== null) {
params.push(`${key}=${encodeURIComponent(paramValue)}`);
}
}
if (params.length > 0) {
url += (url.includes('?') ? '&' : '?') + params.join('&');
}
}
uni.navigateTo({
url: url,
});
}
});
},
toCouponList() { // 前往优惠券
uni.navigateTo({
url: "/pages/client/coupon/list",
});
},
toPublicBenefit() {
this.checkTokenAndExecute(() => {
uni.navigateTo({
url: '/pageHome/welfare/index'
});
});
},
toJoin() {
this.checkTokenAndExecute(() => {
uni.navigateTo({
url: '/pageHome/franchise/index'
});
});
},
// 立即购买
addToCar(goodsData) {
console.log(goodsData,'--=')
// Parse image_list and get the first image URL
let firstImageUrl = '';
const imageList = goodsData.product.attr_key_value_map.image_list;
if (imageList) {
try {
// Try to parse as JSON if it's a string
const parsedList = typeof imageList === 'string' ? JSON.parse(imageList) : imageList;
if (Array.isArray(parsedList) && parsedList.length > 0) {
firstImageUrl = parsedList[0].url || parsedList[0] || '';
}
} catch (e) {
console.error('Error parsing image_list:', e);
}
}
uni.navigateTo({
url: `/pages/client/order/create`,
success: (res) => {
// 通过eventChannel向被打开页面传送数据
res.eventChannel.emit("createOrder", {
goodList: [{
...this.goodsData,
goods_id:goodsData.product.out_id,
// price_id:goodsData.prices[0].price_id,
product_pic: firstImageUrl,
number:1,
goods_name: goodsData.product.product_name,
price_name: goodsData?.product.product_name,
goods_price: goodsData.sku.actual_amount / 100
}, ],
});
},
});
},
// 跳转微信客服 - 使用自定义弹窗
jumpToWeChat() {
if (this.$refs.wechatCopyModal) {
this.$refs.wechatCopyModal.show({
title: '请添加客服号',
weChatCode: 'Wagoo2025'
});
}
},
// 可拖动联系客服组件点击回调
handleContactClick() {
if (this.$refs.wechatCopyModal) {
this.$refs.wechatCopyModal.show({
title: '请添加客服号',
weChatCode: 'Wagoo2025'
});
}
},
},
};
</script>
<style lang="scss" scoped>
.home-page {
height: 100vh;
background-color: #ffecf3;
padding-top: 0;
}
.recommand-goods-wrapper {
.recommand-title {
margin-bottom: 10rpx;
}
.goods-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
margin-top: 50rpx;
padding: 0 20rpx;
.goods-list-item {
flex: 1;
min-width: 0;
&.left {
margin-right: 20rpx;
}
}
}
.loading-wrapper {
padding: 10rpx 0;
}
}
.homeContainer {
height: calc(100vh - #{$app_tabbar_height + 36});
background-color: #ffecf3;
overflow: scroll;
.menu-img-lg {
width: 160rpx;
height: 160rpx;
}
.menu-img-sm {
width: 120rpx;
height: 164rpx;
}
.second-icon,
.third-icon,
.service-icon {
width: 96rpx;
height: 120rpx;
}
.service-icon {
width: 66rpx;
height: 66rpx;
}
.swiperWrapper {
position: relative;
margin-top: -88rpx;
.swiper {
height: 552rpx;
width: 100%;
padding-top: 88rpx;
box-sizing: content-box;
.swiper-img {
width: 100%;
height: 100%;
}
}
.userInfoWrapper {
display: flex;
justify-content: space-between;
align-items: center;
background-color: #fff;
position: absolute;
bottom: -5%;
left: 50%;
transform: translateX(-50%);
width: calc(100% - 40rpx);
padding: 32rpx 28rpx;
box-sizing: border-box;
border-radius: 16rpx;
z-index: 10;
.userInfo {
display: flex;
align-items: center;
.userContent {
display: flex;
align-items: center;
gap: 16rpx;
.userAvatar {
width: 92rpx;
height: 92rpx;
border-radius: 50%;
flex-shrink: 0;
}
.user-info-wrapper {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.userName {
font-weight: 500;
font-size: 28rpx;
}
.userTips {
font-size: 20rpx;
color: #808080;
margin-top: 8rpx;
}
.user-membership-row {
display: flex;
align-items: center;
gap: 16rpx;
margin-top: 16rpx;
flex-wrap: wrap;
}
.vipWrapper {
display: inline-flex;
align-items: center;
background-color: #ff19a0;
padding: 4rpx 8rpx;
border-radius: 50px;
font-size: 20rpx;
color: #fff;
text-align: center;
.lableImg {
width: 24rpx;
height: 20rpx;
margin-right: 4rpx;
}
}
.membership-tier-badge {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 4rpx 8rpx;
border-radius: 50px;
font-size: 20rpx;
font-weight: 500;
}
.membership-tier-1 {
background-color: #EDCA69;
}
.membership-tier-1 .membership-tier-text {
color: #754410;
font-size: 20rpx;
}
.membership-tier-2 {
background-color: #CECECE;
}
.membership-tier-2 .membership-tier-text {
color: #3D3D3D;
font-size: 20rpx;
}
.membership-tier-3 {
background-color: #321500;
}
.membership-tier-3 .membership-tier-text {
color: #CBAD78;
font-size: 20rpx;
}
}
}
.loginBtn {
background-color: #ff19a0;
border-radius: 218px;
color: #fff;
font-size: 23rpx;
padding: 16rpx 24rpx;
display: flex;
align-items: center;
.couponText {
color: #fff;
font-size: 24rpx;
}
.discountCoupon {
width: 11rpx;
height: 18rpx;
margin-left: 8rpx;
}
}
.logoutBtn {
background: linear-gradient(270deg, #FF19A0 0%, #FF6BB3 100%);
border-radius: 218px;
color: #fff;
font-size: 23rpx;
padding: 16rpx 24rpx;
display: flex;
align-items: center;
}
}
.shadowBackground {
position: absolute;
left: 0;
bottom: -20rpx;
z-index: 8;
width: 100%;
height: 72rpx;
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #ffecf3 64%);
}
}
.menuBody {
margin-top: 32rpx;
padding: 20rpx;
.firstMenu {
display: flex;
background-color: #fff;
border-radius: 16rpx;
justify-content: space-between;
align-items: center;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.itemWrapper {
flex: 1;
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
}
.line {
width: 2rpx;
background-color: #e8e8e8;
height: 280rpx;
}
.titlWrapper {
background-color: #ff19a0;
color: #fff;
font-size: 20rpx;
padding: 8rpx 16rpx;
border-radius: 0px 0px 12rpx 12rpx;
display: inline-block;
}
.content {
font-size: 36rpx;
font-weight: 500;
margin-top: 24rpx;
}
.tips {
font-size: 20rpx;
margin-top: 16rpx;
color: #808080;
}
.itemImg {
margin: auto;
margin-top: 48rpx;
margin-bottom: 32rpx;
}
}
.secondMenu {
margin-top: 20rpx;
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.scrollWrapper {
width: 100%;
height: auto; /* 由内容撑开;小程序 scroll-view 横向滚动时默认可能不随内容计算高度 */
min-height: 220rpx; /* 兜底:约等于 padding-top + 图标 + 标题 + 副标题 */
display: flex;
align-items: flex-start; /* 不拉伸子项,高度由内容决定 */
white-space: nowrap;
padding-top: 24rpx;
padding-bottom: 24rpx;
box-sizing: border-box;
.secondMenuInner {
display: flex;
flex-wrap: nowrap;
flex-shrink: 0;
width: 937.5rpx; /* 5 * 187.5 一屏4个共5项 */
&.no-scroll {
width: 100%; /* 四项时一屏排满,不滚动 */
.itemWrapper {
flex: 1;
width: 0; /* 均分宽度,避免被 menuBody padding 裁切 */
min-width: 0;
}
}
}
.itemWrapper {
width: 187.5rpx; /* 750/4 一屏固定4个 */
flex-shrink: 0;
text-align: center;
.imgWrapper {
display: inline-block;
margin: auto;
}
.itemTitle {
font-size: 24rpx;
font-weight: 500;
margin-top: 8rpx;
}
.itemTips {
font-size: 20rpx;
color: #808080;
margin-top: 8rpx;
}
}
}
.custom-indicator {
display: flex;
margin-top: 12rpx;
justify-content: center;
margin-bottom: 20rpx;
gap: 12rpx;
.itemLine {
background-color: #ededed;
width: 20rpx;
height: 6rpx;
border-radius: 170px;
&.active {
background-color: #ff19a0;
}
}
}
}
.thirdMenu {
display: flex;
gap: 16rpx;
margin-top: 20rpx;
.itemWrapper {
flex: 1;
background-color: #fff;
border-radius: 16rpx;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 24rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.title {
font-size: 24rpx;
font-weight: 500;
}
.tips {
font-size: 20rpx;
color: #808080;
margin-top: 16rpx;
}
}
}
.serviceMenu {
display: flex;
background-color: #fff;
margin-top: 20rpx;
padding: 20rpx 24rpx;
align-items: center;
justify-content: space-between;
border-radius: 16rpx;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.04);
.left {
display: flex;
align-items: center;
}
.rightArrow {
width: 11rpx;
height: 18rpx;
}
.content {
margin-left: 16rpx;
.title {
font-size: 28rpx;
font-weight: 500;
}
.tips {
font-size: 22rpx;
margin-top: 8rpx;
color: #808080;
}
}
}
}
}
.flexClass {
display: flex;
align-items: center;
justify-content: center;
}
</style>