Files
wagoo-douy3/src/pages/client/category/index.vue
2026-03-12 13:32:10 +08:00

653 lines
15 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 category-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%;">
<input class="search-input" type="text" placeholder="搜索商品" v-model="searchKeyword"
@confirm="handleSearch" @focus="handleSearchFocus" />
</view>
</view>
</view>
<!-- <view class="flex-row-start category-list">
<view
class="flex-center category-item"
:class="{ active: selectCategoryId === item.id }"
v-for="item in categoryList.slice(0, 6)"
:key="item.id"
@click="changeCategory(item)"
>
<image
class="category-item-icon"
:src="selectCategoryId === item.id ? item.select_type_pic : item.type_pic"
mode="aspectFit"
/>
<text class="flex-center fs-20 app-text-ellipse category-item-name">{{
item.name
}}</text>
</view>
<view class="flex-center open-cell" @click="showAllCategory = true">
<text class="fs-24 app-fc-main app-font-bold"></text>
<text class="fs-24 app-fc-main app-font-bold"></text>
<image class="open-icon" src="@/static/images/open_icon.png" />
</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="changeCateg(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">
<!-- 右侧上方二级分类标签 -->
<!-- <view v-if="subCategoryList.length" class="sub-category-tabs">
<scroll-view class="sub-category-tabs-scroll" scroll-x>
<view class="sub-category-tab-item" :class="{
'sub-category-tab-item-active': !selectSubCategoryId || selectSubCategoryId === '',
}" @click="changeSubCategory({ id: '', name: '全部' })">
全部
</view>
<view class="sub-category-tab-item" :class="{
'sub-category-tab-item-active': item.id === selectSubCategoryId,
}" v-for="(item, index) in subCategoryList" :key="index" @click="changeSubCategory(item)">
{{ item.name }}
</view>
</scroll-view>
</view> -->
<!-- 右侧下方商品列表 -->
<scroll-view class="category-right" scroll-y :refresher-enabled="true"
:refresher-triggered="refreshTriggered" @refresherrefresh="onRefresh" @scrolltolower="onLoadMore">
<view class="goods-list">
<good-item v-for="good in goodsList" :key="good.product_id" :ref="`goodItem_${good.product_id}`"
:data="good" @addToCar="addToCar" @clickCard="jumpToDetail" />
</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>
<add-goods-modal v-if="showModal" :data="addGoodInfo" optText="加入购物车" @change="(val) => (showModal = val)"
@optAction="optAction" />
<category-modal v-if="showAllCategory" :list="categoryList" :selectCategoryId="selectCategoryId"
@close="showAllCategory = false" @change="changeCategory" />
</view>
</template>
<script>
import {
getGoodsClassify,
addCart,
getCartList
} from "@/api/shop";
import {
imgPrefix
} from "@/utils/common";
import GoodItem from "./components/GoodItem.vue";
import AddGoodsModal from "@/components/goods/AddGoodsModal.vue";
import CategoryModal from "./components/CategoryModal.vue";
import {
getGoodsListData
} from "../../../api/shop";
export default {
components: {
GoodItem,
AddGoodsModal,
CategoryModal,
},
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,
imgPrefix,
searchInputWidth: searchInputWidth,
searchKeyword: '',
id: "",
categoryList: [],
selectCategoryId: "",
selectSubCategoryId: "",
showModal: false,
addGoodInfo: null,
showAllCategory: false,
goodsList: [],
total: 0,
page: 1,
size: 10,
refreshTriggered: false,
isLoading: false,
petOrderId: '',
petOrderAddressId: '',
cartCount: 0,
cartCountBounce: false,
};
},
computed: {
cartShowCount() {
return this.cartCount > 9 ? "9+" : this.cartCount;
},
selectCategory() {
return (
this.categoryList.find((item) => item.id === this.selectCategoryId) || {}
);
},
subCategoryList() {
return this.selectCategory?.children || [];
},
selectSubCategory() {
return (
this.subCategoryList.find(
(item) => item.id === this.selectSubCategoryId
) || {}
);
},
},
mounted() {
this.getCategoryList();
},
onLoad(options) {
const {
id,
petOrderId = '',
addressId = ''
} = options;
this.selectCategoryId = +id;
this.petOrderId = petOrderId
this.petOrderAddressId = addressId
},
onShow() {
this.getCartListData();
},
watch: {
selectCategoryId(val) {
if (val && !this.selectSubCategoryId) {
this.getShopList();
}
},
subCategoryList(list) {
// 当二级分类列表变化时,默认选择"全部"(空字符串)
this.selectSubCategoryId = "";
},
selectSubCategoryId(id) {
this.getShopList();
},
},
methods: {
// 分类列表
getCategoryList(parent_id = -1) {
getGoodsClassify({
parent_id,
p: 1,
num: 999
}).then((res) => {
this.categoryList = res?.data || [];
if (!this.selectCategoryId) {
this.selectCategoryId = this.categoryList[0]?.id || "";
}
});
},
changeCateg(item) {
this.changeId = item.id
this.selectCategoryId = item.id;
this.showAllCategory = false;
// console.log(item,'--')
},
// 商品列表
getShopList() {
getGoodsListData({
type: this.changeId,
p: this.page,
num: this.size,
keyword: "",
is_tui: 0,
})
.then((res) => {
const list = res?.data || [];
this.goodsList =
this.page === 1 ? list : [...this.goodsList, ...list];
this.total = res?.count || 0;
})
.finally(() => {
this.isLoading = false;
this.refreshTriggered = false;
});
},
changeCategory(data) {
console.log(data, '--=')
this.selectCategoryId = data.id;
this.showAllCategory = false;
},
changeSubCategory(data) {
this.selectSubCategoryId = data.id;
},
// 加入购物车
addCartAction(data) {
addCart(data)
.then(() => {
uni.showToast({
title: "已加入购物车!",
icon: "none"
});
this.getCartListData();
// 触发购物车数量徽章动画
this.triggerCartCountAnimation();
})
.catch((err) => {
uni.showToast({
title: err || "加入购物车失败!",
icon: "none"
});
});
},
optAction(data) {
console.log(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(good) {
this.showModal = true;
this.addGoodInfo = {
...good
};
},
// 购物车列表
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) {
console.log(details, '??')
uni.navigateTo({
url: `/pages/client/shop/details?product_id=${details.product_id}&petOrderId=${this.petOrderId}&petOrderAddressId=${this.petOrderAddressId}`,
});
},
onRefresh() {
this.refreshTriggered = true;
this.page = 1;
this.size = 10;
this.total = 0;
this.getShopList();
},
onLoadMore() {
if (!this.isLoading && this.total > this.goodsList.length) {
this.page++;
this.getShopList();
}
},
handleBack() {
uni.navigateBack();
},
handleSearch() {
// 处理搜索逻辑
this.page = 1;
this.getShopList();
},
handleSearchFocus() {
// 可以跳转到搜索页面
// uni.navigateTo({
// url: '/pages/client/search/index'
// });
},
},
};
</script>
<style lang="scss" scoped>
.category-container {
height: 100%;
align-items: stretch;
position: relative;
padding-top: calc(var(--status-bar-height, 0px) + 88rpx + 20rpx);
.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;
padding: 0 32rpx;
height: 88rpx;
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-list {
position: relative;
overflow: hidden;
padding: 20rpx 24rpx;
box-sizing: border-box;
background: #fff;
min-height: 176rpx;
margin-bottom: 20rpx;
.category-item {
.category-item-icon {
width: 90rpx;
height: 90rpx;
background: #fee9f3;
border-radius: 90rpx;
border: 4rpx solid #fee9f3;
margin-bottom: 12rpx;
}
.category-item-name {
padding: 0 22rpx;
box-sizing: border-box;
max-width: 140rpx;
height: 36rpx;
border-radius: 36rpx;
color: $app_fc_main;
line-height: 36rpx;
}
&.active {
.category-item-icon {
border: 4rpx solid $app_color_main;
}
.category-item-name {
background: $app_color_main;
color: #fff;
}
}
}
.open-cell {
position: absolute;
top: 0;
bottom: 0;
right: 0;
width: 72rpx;
background: #fff;
.open-icon {
width: 27rpx;
height: 23rpx;
margin-top: 10rpx;
}
}
}
.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: $app_fc_mark;
}
.border-line {
position: absolute;
top: 28rpx;
bottom: 28rpx;
left: 0;
width: 10rpx;
border-radius: 28rpx;
background: $app_color_main;
}
}
}
}
// 右侧内容区域
.category-right-wrapper {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
background: #fff;
// 二级分类标签
.sub-category-tabs {
flex-shrink: 0;
background: #fff;
padding: 28rpx 0;
.sub-category-tabs-scroll {
white-space: nowrap;
display: flex;
align-items: center;
padding: 0 32rpx;
.sub-category-tab-item {
display: inline-block;
padding: 12rpx 20rpx;
margin-right: 16rpx;
font-size: 24rpx;
color: #3D3D3D;
white-space: nowrap;
background: #F5F5F5;
border-radius: 32rpx;
flex-shrink: 0;
&.sub-category-tab-item-active {
color: #FF19A0;
background: #FFECF3;
font-weight: normal;
}
}
}
}
// 商品列表区域
.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);
}
}
</style>