Files
wagoo-douy3/src/pages/client/shop/index.vue
2026-04-10 19:00:01 +08:00

677 lines
17 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="flex-column-start shop-container">
<!-- 自定义导航栏 -->
<view class="custom-navbar">
<view class="status-bar" style="height: 60px"></view>
<view class="navbar-content">
<!-- <view class="back-btn" @click="handleBack">
<view class="back-icon"></view>
</view> -->
<view
class="search-input-wrapper"
style="width: 80%"
@click="jumpToSearch"
>
<input
class="search-input"
type="text"
placeholder="搜索商品"
v-model="searchKeyword"
@confirm="handleSearch"
@focus="handleSearchFocus"
/>
</view>
</view>
</view>
<view class="flex-row-start category-content">
<!-- 左侧一级分类列表 -->
<view v-if="categoryList.length" class="category-list-left">
<view
class="flex-center category-item-left"
:class="{
'category-item-left-active': item.id === selectCategoryId,
}"
v-for="(item, index) in categoryList"
:key="index"
@click="changeCategory(item)"
>
<text
class="fs-28 app-fc-main app-text-ellipse category-item-left-name"
>
{{ item.type_name }}
</text>
<view v-if="selectCategoryId === item.id" class="border-line"></view>
</view>
</view>
<!-- 右侧内容区域 -->
<view class="category-right-wrapper">
<!-- 右侧下方商品列表 -->
<scroll-view
class="category-right"
scroll-y
:refresher-enabled="true"
:refresher-triggered="refreshTriggered"
@refresherrefresh="onRefresh"
@scrolltolower="onLoadMore"
>
<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"
@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"
@addToCar="addToCar"
/>
</view>
</view>
<view v-if="isLoading" class="loading-wrapper flex-center">
<uni-load-more status="loading" :show-text="false" />
</view>
</scroll-view>
</view>
</view>
<!-- <view class="add-car-view">
<image class="add-car-icon" :src="`${imgPrefix}mall-shopCar.png`" @click="jumpToCart" />
<view v-if="cartShowCount" class="fs-20 app-fc-white flex-center cart-count"
:class="{ 'cart-count-bounce': cartCountBounce }">
{{ cartShowCount }}
</view>
</view> -->
<!-- 可拖动联系客服组件 -->
<DraggableContact ref="draggableContact" :onClick="handleContactClick" />
<add-goods-modal
v-if="showModal"
:data="addGoodInfo"
optText="加入购物车"
@change="(val) => (showModal = val)"
@optAction="optAction"
/>
<contact-modal v-if="showContactModal" @close="showContactModal = false" />
<WeChatCopyModal ref="wechatCopyModal" />
</view>
</template>
<script>
import { imgPrefix } from "@/utils/common";
import GoodItem from "../shop/components/GoodItem.vue";
import AddGoodsModal from "@/components/goods/AddGoodsModal.vue";
import ContactModal from "@/components/ContactModal.vue";
import WeChatCopyModal from "@/components/WeChatCopyModal.vue";
import DraggableContact from "@/components/DraggableContact.vue";
import {
getGoodsClassify,
getGoodsListData,
addCart,
getCartList,
} from "@/api/shop";
import { jumpToWeChat } from "@/utils/common";
export default {
components: {
GoodItem,
AddGoodsModal,
ContactModal,
WeChatCopyModal,
DraggableContact,
},
data() {
const systemInfo = uni.getSystemInfoSync();
// 计算搜索输入框宽度,避免被胶囊按钮遮挡
let searchInputWidth = 500; // 默认宽度
try {
const menuButtonInfo = uni.getMenuButtonBoundingClientRect();
if (menuButtonInfo && menuButtonInfo.left) {
// 屏幕宽度转换为 rpx (750rpx 对应 375px)
const screenWidthRpx = 750;
const leftPadding = 32; // navbar-content 左侧 padding
const backBtnWidth = 60; // 返回按钮宽度
const backBtnMargin = 20; // 返回按钮右边距
// 右侧需要留出的空间:屏幕宽度 (px) - 胶囊按钮左边距 (px) + 额外间距 (px),然后转换为 rpx
const rightSpacePx = systemInfo.windowWidth - menuButtonInfo.left + 20;
const rightSpaceRpx =
(rightSpacePx / systemInfo.windowWidth) * screenWidthRpx;
searchInputWidth =
screenWidthRpx -
leftPadding -
backBtnWidth -
backBtnMargin -
rightSpaceRpx;
}
} catch (e) {
console.log("获取胶囊按钮信息失败", e);
}
return {
statusBarHeight: systemInfo.statusBarHeight || 0,
searchInputWidth: searchInputWidth,
imgPrefix,
searchKeyword: "",
activeCategoryIndex: 0,
categoryList: [],
selectCategoryId: "",
refreshTriggered: false,
isLoading: false,
goodsList: [],
total: 0,
page: 1,
size: 10,
nextCursor: '',
hasMore: true,
showModal: false,
addGoodInfo: null,
showContactModal: false,
cartCount: 0,
cartCountBounce: false,
petOrderId: "",
petOrderAddressId: "",
};
},
computed: {
selectCategory() {
return (
this.categoryList.find((item) => item.id === this.selectCategoryId) ||
{}
);
},
cartShowCount() {
return this.cartCount > 9 ? "9+" : this.cartCount;
},
leftColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 0);
},
rightColumnGoods() {
return this.goodsList.filter((v, i) => i % 2 === 1);
},
},
mounted() {
this.initData();
},
onShow() {
this.getCartListData();
},
watch: {
selectCategoryId(val) {
if (val) {
this.getGoodsList();
}
},
},
methods: {
handleBack() {
uni.navigateBack();
},
handleSearch() {
// 处理搜索逻辑
this.page = 1;
this.getGoodsList();
},
handleSearchFocus() {
// 可以跳转到搜索页面
},
jumpToSearch() {
// 跳转到搜索页面
},
// 跳转微信客服 - 使用自定义弹窗(与首页在线客服功能一致)
handleContactClick() {
// console.log('[shop.vue] handleContactClick called')
// console.log('[shop.vue] $refs.wechatCopyModal:', this.$refs.wechatCopyModal)
if (this.$refs.wechatCopyModal && this.$refs.wechatCopyModal.show) {
this.$refs.wechatCopyModal.show({
title: "请添加客服号",
weChatCode: "Wagoo2025",
});
// console.log('[shop.vue] wechatCopyModal.show called')
} else {
// console.error('[shop.vue] wechatCopyModal not available, using fallback')
// 降级方案:直接显示系统弹窗
uni.showModal({
title: "提示",
content: "请添加客服号Wagoo2025",
confirmText: "复制微信号",
showCancel: true,
cancelText: "取消",
success: (res) => {
if (res.confirm) {
uni.setClipboardData({
data: "Wagoo2025",
success: () => {
uni.showToast({
title: "已复制微信号",
icon: "success",
});
},
});
}
},
});
}
},
jumpToWeChat,
initData() {
this.getCategoryList();
this.page = 1;
this.nextCursor = '';
this.hasMore = true;
this.goodsList = [];
this.getGoodsList();
this.getCartListData();
},
// 分类列表
getCategoryList() {
getGoodsClassify().then((res) => {
this.categoryList = res?.data || [];
if (!this.selectCategoryId) {
this.selectCategoryId = this.categoryList[0]?.id || "";
}
});
},
changeCategory(data) {
this.selectCategoryId = data.id;
},
// 商品列表
getGoodsList() {
if (this.isLoading) return;
if (!this.hasMore && this.page > 1) return;
this.isLoading = true;
getGoodsListData({
type: this.selectCategoryId ? this.selectCategoryId : 0,
cursor: this.nextCursor,
})
.then((res) => {
const list = res?.data.data.products || [];
const hasMore = res?.data.data.has_more;
const nextCursor = res?.data.data.next_cursor || '';
if (this.page === 1) {
this.goodsList = list;
} else {
this.goodsList = [...this.goodsList, ...list];
}
this.hasMore = hasMore;
this.nextCursor = nextCursor;
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
});
},
onRefresh() {
this.refreshTriggered = true;
this.page = 1;
this.size = 10;
this.total = 0;
this.nextCursor = '';
this.hasMore = true;
this.getGoodsList();
},
onLoadMore() {
if (!this.isLoading && this.hasMore) {
this.page++;
this.getGoodsList();
}
},
// 加入购物车
addCartAction(data) {
addCart(data)
.then(() => {
uni.showToast({
title: "已加入购物车!",
icon: "none",
});
this.getCartListData();
// 触发购物车数量徽章动画
this.triggerCartCountAnimation();
})
.catch((err) => {
uni.showToast({
title: err || "加入购物车失败!",
icon: "none",
});
});
},
optAction(data) {
const { product_id, type, prices, product_name, product_pic, number } =
data;
this.addCartAction({
item_id: product_id,
item_type: type,
price_id: prices?.[0]?.price_id,
price_desc: prices?.[0]?.price_desc,
item_name: product_name,
item_price: prices?.[0]?.actual_price,
number,
item_pic: product_pic,
is_select: 1,
});
this.showModal = false;
// 触发对应商品项的动画
this.triggerGoodItemAnimation(product_id);
},
// 触发商品项的添加购物车动画
triggerGoodItemAnimation(goodsId) {
this.$nextTick(() => {
const ref = this.$refs[`goodItem_${goodsId}`];
if (ref && ref[0]) {
ref[0].triggerAddCartAnimation();
}
});
},
// 触发购物车数量徽章动画
triggerCartCountAnimation() {
this.cartCountBounce = true;
setTimeout(() => {
this.cartCountBounce = false;
}, 600);
},
addToCar(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?product_id=${goodsData.product.product_id}`,
// success: (res) => {
// // 通过eventChannel向被打开页面传送数据
// res.eventChannel.emit("createOrder", {
// goodList: [{
// goods_id:goodsData.product.out_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
// }, ],
// });
// },
});
},
// 购物车列表
getCartListData() {
getCartList({
p: 1,
num: 999,
}).then((res) => {
this.cartCount = (res?.data.list || []).reduce(
(total, prev) => total + prev.number,
0
);
});
},
jumpToCart() {
uni.navigateTo({
url: `/pages/client/cart/index`,
});
},
// jumpToDetail(details) {
// uni.navigateTo({
// url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
// });
// },
// 立即购买
handleBuyNow(good) {
uni.navigateTo({
url: `/pages/client/order/create?product_id=${good.product.product_id}`,
// success: (res) => {
// // 通过 eventChannel 向被打开页面传送数据
// res.eventChannel.emit("createOrder", {
// goodList: [
// {
// goods_id: good.product_id,
// item_type: good.type,
// price_id: good.prices?.[0]?.price_id,
// price_desc: good.prices?.[0]?.price_desc,
// item_name: good.product_name,
// item_price: good.prices?.[0]?.actual_price,
// item_pic: good.product_pic,
// number: 1,
// product_id: good.product_id,
// product_name: good.product_name,
// product_pic: good.product_pic,
// goods_price: good.prices?.[0]?.actual_price / 100,
// product_price: good.prices?.[0]?.actual_price / 100,
// original_price: good.prices?.[0]?.original_price,
// yunfei: good.yunfei || 0,
// },
// ],
// });
// },
});
},
},
};
</script>
<style lang="scss" scoped>
.shop-container {
height: 100%;
align-items: stretch;
position: relative;
padding-top: calc(var(--status-bar-height, 0px) + 88rpx + 20rpx);
background: #fff;
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background: #ff19a0;
border-radius: 0px 0px 16px 16px;
.status-bar {
background: #ff19a0;
}
.navbar-content {
display: flex;
align-items: center;
justify-content: center;
padding: 0 32rpx;
height: 80rpx;
box-sizing: border-box;
margin-bottom: 20px;
.back-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 20rpx;
flex-shrink: 0;
.back-icon {
width: 20rpx;
height: 20rpx;
border-left: 3rpx solid #fff;
border-bottom: 3rpx solid #fff;
transform: rotate(45deg);
}
}
.search-input-wrapper {
height: 64rpx;
background: #fff;
border-radius: 32rpx;
padding: 0 24rpx;
display: flex;
align-items: center;
box-sizing: border-box;
.search-input {
width: 100%;
height: 100%;
font-size: 28rpx;
color: #333;
}
}
}
}
.category-content {
flex: 1;
overflow: hidden;
padding-top: 90rpx;
// 左侧:一级分类列表
.category-list-left {
width: 180rpx;
height: 100%;
overflow: auto;
background: #f7f8fa;
.category-item-left {
text-align: center;
width: 100%;
height: 110rpx;
position: relative;
background: #f7f8fa;
.category-item-left-name {
color: #3d3d3d;
}
&.category-item-left-active {
background-color: #fff;
.category-item-left-name {
color: #ff19a0;
}
.border-line {
position: absolute;
top: 28rpx;
bottom: 28rpx;
left: 0;
width: 10rpx;
border-radius: 28rpx;
background: #ff19a0;
}
}
}
}
// 右侧内容区域
.category-right-wrapper {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
// 商品列表区域
.category-right {
flex: 1;
height: 100%;
}
}
}
}
.add-car-view {
position: fixed;
bottom: 35vh;
right: 20rpx;
background-color: #fff;
border-radius: 50%;
padding: 20rpx;
box-shadow: 0px 4px 10px 0px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
.add-car-icon {
width: 48rpx;
height: 48rpx;
}
.cart-count {
position: absolute;
top: 8rpx;
right: 8rpx;
width: 30rpx;
height: 30rpx;
border-radius: 30rpx;
background: #ff19a0;
z-index: 10;
border: 1px solid #ffffff;
transition: transform 0.3s ease;
&.cart-count-bounce {
animation: cartCountBounce 0.6s ease;
}
}
}
@keyframes cartCountBounce {
0% {
transform: scale(1);
}
25% {
transform: scale(1.3);
}
50% {
transform: scale(1.1);
}
75% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
.loading-wrapper {
padding: 10rpx 0;
}
.goods-list {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: flex-start;
padding: 0 20rpx;
.goods-list-item {
flex: 1;
min-width: 0;
&.left {
margin-right: 20rpx;
}
}
}
</style>