1013 lines
28 KiB
Vue
1013 lines
28 KiB
Vue
<template>
|
||||
|
|
<view class="sort_warp" :class="platformType ? '' : 'iosplatformType'">
|
|||
|
|
<!-- #ifndef MP-WEIXIN -->
|
|||
|
|
<TopSafe bgColor="#F2EBFF"></TopSafe>
|
|||
|
|
<!-- #endif -->
|
|||
|
|
|
|||
|
|
<!-- 顶部固定区域:搜索 + 分类区域 -->
|
|||
|
|
<view class="top_fixed_header" :class="{ is_expanded: showAllCategories }" :style="mpHeaderStyle">
|
|||
|
|
<!-- 搜索框 -->
|
|||
|
|
<view class="search_bar" @click="jumpSearch">
|
|||
|
|
<u-icon name="search" size="18" color="#999"></u-icon>
|
|||
|
|
<text class="search_placeholder">搜索</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 1. 未展开时:顶部分类横向滚动条 -->
|
|||
|
|
<view class="top_category_row" v-if="!showAllCategories">
|
|||
|
|
<scroll-view class="top_category_scroll" scroll-x :show-scrollbar="false">
|
|||
|
|
<view class="top_cat_list">
|
|||
|
|
<view class="top_cat_item" v-for="(item, index) in categoryList" :key="item.id || index"
|
|||
|
|
@click="selectCategory(index)">
|
|||
|
|
<view class="top_cat_img_box" :class="{ active_img: currentCategoryIndex === index }">
|
|||
|
|
<image class="top_cat_img" :src="item.icon || defaultIcon" mode="aspectFill" />
|
|||
|
|
</view>
|
|||
|
|
<view class="top_cat_name_badge" :class="{ active_badge: currentCategoryIndex === index }">
|
|||
|
|
{{ item.name }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
|
|||
|
|
<!-- 右侧全部按钮 -->
|
|||
|
|
<view class="expand_all_btn" @click="toggleAllCategories">
|
|||
|
|
<text class="expand_text">全部</text>
|
|||
|
|
<u-icon name="arrow-down-fill" size="10" color="#666"></u-icon>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 2. 点开全部时:展开所有分类网格面板 -->
|
|||
|
|
<view class="all_categories_panel" v-else>
|
|||
|
|
<view class="panel_header">
|
|||
|
|
<text class="panel_title">所有分类</text>
|
|||
|
|
</view>
|
|||
|
|
<scroll-view class="panel_grid_scroll" scroll-y :show-scrollbar="false">
|
|||
|
|
<view class="panel_grid">
|
|||
|
|
<view class="top_cat_item panel_cat_item" v-for="(item, index) in categoryList" :key="item.id || index"
|
|||
|
|
@click="selectCategory(index, true)">
|
|||
|
|
<view class="top_cat_img_box" :class="{ active_img: currentCategoryIndex === index }">
|
|||
|
|
<image class="top_cat_img" :src="item.icon || defaultIcon" mode="aspectFill" />
|
|||
|
|
</view>
|
|||
|
|
<view class="top_cat_name_badge" :class="{ active_badge: currentCategoryIndex === index }">
|
|||
|
|
{{ item.name }}
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
<view class="collapse_btn" @click="toggleAllCategories">
|
|||
|
|
<text style="margin-right: 6rpx;">全部收起</text>
|
|||
|
|
<u-icon name="arrow-up" size="12" color="#666"></u-icon>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 主体区域:左侧边栏 + 右侧商品列表 -->
|
|||
|
|
<view class="main_container">
|
|||
|
|
<!-- 展开所有分类时的半透明遮罩 -->
|
|||
|
|
<view class="mask_overlay" v-if="showAllCategories" @click="showAllCategories = false"></view>
|
|||
|
|
|
|||
|
|
<!-- 左侧分类侧边栏 -->
|
|||
|
|
<scroll-view class="left_sidebar_scroll" scroll-y>
|
|||
|
|
<view class="sidebar_item" v-for="(item, index) in categoryList" :key="item.id || index"
|
|||
|
|
:class="{ active: currentCategoryIndex === index }" @click="selectCategory(index)">
|
|||
|
|
<text class="sidebar_name">{{ item.name }}</text>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
|
|||
|
|
<!-- 右侧商品列表区 -->
|
|||
|
|
<view class="right_content_area">
|
|||
|
|
<!-- 排序栏 -->
|
|||
|
|
<view class="sort_option_bar">
|
|||
|
|
<view class="cat_title_text">{{ currentCategory ? currentCategory.name : '' }}</view>
|
|||
|
|
<view class="sort_actions">
|
|||
|
|
<view class="sort_item" :class="{ active: sortBy === 'relevance' }" @click="setSort('relevance')">
|
|||
|
|
<text>综合</text>
|
|||
|
|
<view class="sort_icon_box">
|
|||
|
|
<u-icon name="arrow-up-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'relevance' && sortOrder === 'asc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_up"></u-icon>
|
|||
|
|
<u-icon name="arrow-down-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'relevance' && sortOrder === 'desc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_down"></u-icon>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="sort_item" :class="{ active: sortBy === 'sales' }" @click="setSort('sales')">
|
|||
|
|
<text>销量</text>
|
|||
|
|
<view class="sort_icon_box">
|
|||
|
|
<u-icon name="arrow-up-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'sales' && sortOrder === 'asc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_up"></u-icon>
|
|||
|
|
<u-icon name="arrow-down-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'sales' && sortOrder === 'desc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_down"></u-icon>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="sort_item" :class="{ active: sortBy === 'price' }" @click="setSort('price')">
|
|||
|
|
<text>价格</text>
|
|||
|
|
<view class="sort_icon_box">
|
|||
|
|
<u-icon name="arrow-up-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'price' && sortOrder === 'asc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_up"></u-icon>
|
|||
|
|
<u-icon name="arrow-down-fill" size="10rpx"
|
|||
|
|
:color="sortBy === 'price' && sortOrder === 'desc' ? '#7a35f6' : '#cccccc'"
|
|||
|
|
class="icon_down"></u-icon>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- 商品列表 -->
|
|||
|
|
<scroll-view class="product_list_scroll" scroll-y>
|
|||
|
|
<view class="product_list_box" v-if="displayProducts && displayProducts.length > 0">
|
|||
|
|
<view class="product_row_card" v-for="(item, index) in displayProducts" :key="item.id || index"
|
|||
|
|
@click="jumpInfo(item)">
|
|||
|
|
<view class="product_img_box">
|
|||
|
|
<image class="product_img" :src="item.image || item.icon || defaultProductImg" mode="aspectFill"
|
|||
|
|
lazy-load />
|
|||
|
|
</view>
|
|||
|
|
<view class="product_info_box">
|
|||
|
|
<view class="product_title">{{ item.name || item.title }}</view>
|
|||
|
|
|
|||
|
|
<view class="product_tags_row">
|
|||
|
|
<text class="tag_item tag_blue" v-if="item.tags && item.tags[0]">{{ item.tags[0] }}</text>
|
|||
|
|
<text class="tag_item tag_green" v-if="item.tags && item.tags[1]">{{ item.tags[1] }}</text>
|
|||
|
|
<text class="tag_item tag_blue" v-if="!item.tags">专业检测</text>
|
|||
|
|
<text class="tag_item tag_green" v-if="!item.tags">生态有机</text>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<view class="product_price_row">
|
|||
|
|
<view class="price_box">
|
|||
|
|
<text class="price_symbol">¥</text>
|
|||
|
|
<text class="price_val">{{ item.price }}</text>
|
|||
|
|
</view>
|
|||
|
|
<text class="sales_val">全网销量{{ limit999(item.sales) }}</text>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
<view class="empty_box" v-else>
|
|||
|
|
<image src="https://static.tbmall.xin/static/not_shopping.png" class="empty_img" />
|
|||
|
|
<text class="empty_text">暂无商品数据</text>
|
|||
|
|
</view>
|
|||
|
|
</scroll-view>
|
|||
|
|
</view>
|
|||
|
|
</view>
|
|||
|
|
|
|||
|
|
<!-- #ifndef H5 || MP-WEIXIN-->
|
|||
|
|
<CustomTabBar :tabIndex="1" />
|
|||
|
|
<!-- #endif -->
|
|||
|
|
</view>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
import { getCascadeCategory } from "@/api/common.js";
|
|||
|
|
import CustomTabBar from "@/components/custom-tab-bar.vue";
|
|||
|
|
import TopSafe from "@/components/common/top-safe.nvue";
|
|||
|
|
|
|||
|
|
// 假数据列表:当后端接口未就绪时自动提供高保真模拟数据
|
|||
|
|
const MOCK_CATEGORIES = [
|
|||
|
|
{
|
|||
|
|
id: 101,
|
|||
|
|
name: '有机食品',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/xinren.png',
|
|||
|
|
foods: [
|
|||
|
|
{
|
|||
|
|
id: 1001,
|
|||
|
|
name: '鲜8有机贝贝南瓜2500g粉糯板栗味自然生长不催熟',
|
|||
|
|
title: '鲜8有机贝贝南瓜2500g粉糯板栗味自然生长不催熟',
|
|||
|
|
price: '39.00',
|
|||
|
|
sales: 888,
|
|||
|
|
tags: ['专业检测', '生态有机'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/nangua.png'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 1002,
|
|||
|
|
name: '采缇国产车厘子5kg4j大连美早樱桃生鲜次日达樱桃',
|
|||
|
|
title: '采缇国产车厘子5kg4j大连美早樱桃生鲜次日达樱桃',
|
|||
|
|
price: '26.99',
|
|||
|
|
sales: 888,
|
|||
|
|
tags: ['专业检测', '生态有机'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/baokuan.png'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 1003,
|
|||
|
|
name: '江苏白凤水蜜桃 应季水果 &新鲜礼盒装',
|
|||
|
|
title: '江苏白凤水蜜桃 应季水果 &新鲜礼盒装',
|
|||
|
|
price: '34.88',
|
|||
|
|
sales: 888,
|
|||
|
|
tags: ['专业检测', '生态有机'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/cart.png'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 1004,
|
|||
|
|
name: '季新鲜 &进口山竹时令 &老树麻竹整箱送礼',
|
|||
|
|
title: '季新鲜 &进口山竹时令 &老树麻竹整箱送礼',
|
|||
|
|
price: '52.00',
|
|||
|
|
sales: 888,
|
|||
|
|
tags: ['专业检测', '生态有机'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/huiyuan.png'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 102,
|
|||
|
|
name: '生鲜食材',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/baokuan.png',
|
|||
|
|
foods: [
|
|||
|
|
{
|
|||
|
|
id: 1005,
|
|||
|
|
name: '精选新鲜三文鱼中段切片 200g刺身级',
|
|||
|
|
title: '精选新鲜三文鱼中段切片 200g刺身级',
|
|||
|
|
price: '59.90',
|
|||
|
|
sales: 1200,
|
|||
|
|
tags: ['冷链速达', '产地直供'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/baokuan.png'
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 1006,
|
|||
|
|
name: '澳洲进口谷饲安格斯原切雪花牛肉卷 500g',
|
|||
|
|
title: '澳洲进口谷饲安格斯原切雪花牛肉卷 500g',
|
|||
|
|
price: '89.00',
|
|||
|
|
sales: 650,
|
|||
|
|
tags: ['原切牛肉', '品质严选'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/cart.png'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 103,
|
|||
|
|
name: '米面粮油',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/cart.png',
|
|||
|
|
foods: [
|
|||
|
|
{
|
|||
|
|
id: 1007,
|
|||
|
|
name: '五常大米 优质长粒香米 5kg真空包装',
|
|||
|
|
title: '五常大米 优质长粒香米 5kg真空包装',
|
|||
|
|
price: '49.90',
|
|||
|
|
sales: 3400,
|
|||
|
|
tags: ['核心产区', '自然醇香'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/xinren.png'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 104,
|
|||
|
|
name: '零食饮料',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/huiyuan.png',
|
|||
|
|
foods: [
|
|||
|
|
{
|
|||
|
|
id: 1008,
|
|||
|
|
name: '花生造黑松露黑麦苏打饼干 590g*2盒',
|
|||
|
|
title: '花生造黑松露黑麦苏打饼干 590g*2盒',
|
|||
|
|
price: '29.90',
|
|||
|
|
sales: 2300,
|
|||
|
|
tags: ['零反式脂肪酸', '拒绝油炸'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/huiyuan.png'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 105,
|
|||
|
|
name: '滋补养生',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/xinren.png',
|
|||
|
|
foods: [
|
|||
|
|
{
|
|||
|
|
id: 1009,
|
|||
|
|
name: '青海野生黑枸杞干果 250g特级罐装',
|
|||
|
|
title: '青海野生黑枸杞干果 250g特级罐装',
|
|||
|
|
price: '78.00',
|
|||
|
|
sales: 510,
|
|||
|
|
tags: ['正宗原产', '花青素丰富'],
|
|||
|
|
image: 'https://static.tbmall.xin/static/new-home/nangua.png'
|
|||
|
|
}
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 106,
|
|||
|
|
name: '非遗传承',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/baokuan.png',
|
|||
|
|
foods: []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 107,
|
|||
|
|
name: '家居百货',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/cart.png',
|
|||
|
|
foods: []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 108,
|
|||
|
|
name: '美妆护肤',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/huiyuan.png',
|
|||
|
|
foods: []
|
|||
|
|
},
|
|||
|
|
{
|
|||
|
|
id: 109,
|
|||
|
|
name: '其他特色',
|
|||
|
|
icon: 'https://static.tbmall.xin/static/new-home/xinren.png',
|
|||
|
|
foods: []
|
|||
|
|
}
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
export default {
|
|||
|
|
components: { CustomTabBar, TopSafe },
|
|||
|
|
data() {
|
|||
|
|
return {
|
|||
|
|
platformType: true,
|
|||
|
|
currentCategoryIndex: 0,
|
|||
|
|
showAllCategories: false,
|
|||
|
|
sortBy: 'relevance', // 'relevance' | 'sales' | 'price'
|
|||
|
|
sortOrder: 'asc', // 'asc' | 'desc'
|
|||
|
|
categoryList: [], // 对应 sort copy.vue 的 tabbarList (顶层主分类)
|
|||
|
|
capsuleTop: 0,
|
|||
|
|
defaultIcon: 'https://static.tbmall.xin/static/new-home/xinren.png',
|
|||
|
|
defaultProductImg: 'https://static.tbmall.xin/static/new-home/nangua.png',
|
|||
|
|
isFromSortShop: false,
|
|||
|
|
loading: false
|
|||
|
|
};
|
|||
|
|
},
|
|||
|
|
computed: {
|
|||
|
|
mpHeaderStyle() {
|
|||
|
|
/*#ifdef MP-WEIXIN*/
|
|||
|
|
if (this.capsuleTop) {
|
|||
|
|
return { paddingTop: this.capsuleTop + 'px' };
|
|||
|
|
}
|
|||
|
|
return { paddingTop: '80rpx' };
|
|||
|
|
/*#endif*/
|
|||
|
|
return {};
|
|||
|
|
},
|
|||
|
|
currentCategory() {
|
|||
|
|
return this.categoryList[this.currentCategoryIndex] || null;
|
|||
|
|
},
|
|||
|
|
displayProducts() {
|
|||
|
|
if (!this.currentCategory) return [];
|
|||
|
|
let list = [];
|
|||
|
|
if (this.currentCategory.foods && this.currentCategory.foods.length > 0) {
|
|||
|
|
list = [...this.currentCategory.foods];
|
|||
|
|
} else if (this.currentCategory.productList && this.currentCategory.productList.length > 0) {
|
|||
|
|
list = [...this.currentCategory.productList];
|
|||
|
|
}
|
|||
|
|
if (!list || list.length === 0) return [];
|
|||
|
|
|
|||
|
|
if (this.sortBy === 'sales') {
|
|||
|
|
list.sort((a, b) => {
|
|||
|
|
let salesA = Number(a.sales) || 0;
|
|||
|
|
let salesB = Number(b.sales) || 0;
|
|||
|
|
return this.sortOrder === 'asc' ? salesA - salesB : salesB - salesA;
|
|||
|
|
});
|
|||
|
|
} else if (this.sortBy === 'price') {
|
|||
|
|
list.sort((a, b) => {
|
|||
|
|
let priceA = Number(a.price || a.adPrice) || 0;
|
|||
|
|
let priceB = Number(b.price || b.adPrice) || 0;
|
|||
|
|
return this.sortOrder === 'asc' ? priceA - priceB : priceB - priceA;
|
|||
|
|
});
|
|||
|
|
} else if (this.sortBy === 'relevance') {
|
|||
|
|
if (this.sortOrder === 'desc') {
|
|||
|
|
list.reverse();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return list;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
onShow() {
|
|||
|
|
/*#ifdef APP-PLUS*/
|
|||
|
|
uni.hideTabBar({ animation: false });
|
|||
|
|
const appUpdate = uni.getStorageSync("platform");
|
|||
|
|
this.platformType = appUpdate == "android" ? "ANDROID" : "IOS";
|
|||
|
|
/*#endif*/
|
|||
|
|
|
|||
|
|
/*#ifdef MP-WEIXIN*/
|
|||
|
|
try {
|
|||
|
|
const menu = uni.getMenuButtonBoundingClientRect();
|
|||
|
|
if (menu && menu.bottom) {
|
|||
|
|
this.capsuleTop = menu.bottom + 6;
|
|||
|
|
}
|
|||
|
|
} catch (e) {}
|
|||
|
|
/*#endif*/
|
|||
|
|
|
|||
|
|
const isBackFromSortShop = this.isFromSortShop;
|
|||
|
|
this.isFromSortShop = false;
|
|||
|
|
|
|||
|
|
this.fetchCategories(isBackFromSortShop);
|
|||
|
|
},
|
|||
|
|
methods: {
|
|||
|
|
// 异步查询分类接口
|
|||
|
|
async getSortApi(pId) {
|
|||
|
|
const params = {
|
|||
|
|
parentId: pId,
|
|||
|
|
sortBy: this.sortBy,
|
|||
|
|
sortOrder: this.sortOrder
|
|||
|
|
};
|
|||
|
|
try {
|
|||
|
|
const resp = await getCascadeCategory(params);
|
|||
|
|
if (resp && resp.bizcode === 100 && resp.data) {
|
|||
|
|
return resp.data;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error("getCascadeCategory error:", e);
|
|||
|
|
}
|
|||
|
|
return [];
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 1. 初始化/获取顶层主分类列表(对应 tabbarList)
|
|||
|
|
async fetchCategories(isBack = false) {
|
|||
|
|
try {
|
|||
|
|
const topList = await this.getSortApi(0);
|
|||
|
|
if (topList && topList.length > 0) {
|
|||
|
|
this.categoryList = topList;
|
|||
|
|
} else {
|
|||
|
|
this.categoryList = MOCK_CATEGORIES;
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.log("使用模拟分类数据", e);
|
|||
|
|
this.categoryList = MOCK_CATEGORIES;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let targetIndex = 0;
|
|||
|
|
if (isBack) {
|
|||
|
|
const sortId = uni.getStorageSync("sortId");
|
|||
|
|
if (sortId) {
|
|||
|
|
let idx = this.categoryList.findIndex(item => item.id === sortId);
|
|||
|
|
if (idx !== -1) targetIndex = idx;
|
|||
|
|
else if (this.currentCategoryIndex >= 0 && this.currentCategoryIndex < this.categoryList.length) {
|
|||
|
|
targetIndex = this.currentCategoryIndex;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
uni.removeStorageSync("sortId");
|
|||
|
|
targetIndex = 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.selectCategory(targetIndex);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
// 2. 选中某个顶层分类并加载右侧货品数据(对应 tabbarContentList 结构)
|
|||
|
|
async selectCategory(index, closePopup = false) {
|
|||
|
|
this.currentCategoryIndex = index;
|
|||
|
|
if (closePopup) {
|
|||
|
|
this.showAllCategories = false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const item = this.categoryList[index];
|
|||
|
|
if (!item) return;
|
|||
|
|
|
|||
|
|
if (item.id) {
|
|||
|
|
uni.setStorageSync("sortId", item.id);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果为 mock 数据且已包含 foods 列表,直接渲染
|
|||
|
|
if (!item.id || typeof item.id !== 'number' || (item.foods && item.foods.length > 0 && !item._fromApi)) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.loading = true;
|
|||
|
|
try {
|
|||
|
|
// 参考 sort copy.vue: 依次查询二级分类 (firstData) 及三级货品 (twoData)
|
|||
|
|
const firstData = await this.getSortApi(item.id);
|
|||
|
|
let allProducts = [];
|
|||
|
|
|
|||
|
|
if (firstData && firstData.length > 0) {
|
|||
|
|
for (let fItem of firstData) {
|
|||
|
|
const twoData = await this.getSortApi(fItem.id);
|
|||
|
|
fItem.foods = twoData || [];
|
|||
|
|
if (fItem.foods && fItem.foods.length > 0) {
|
|||
|
|
allProducts.push(...fItem.foods);
|
|||
|
|
} else {
|
|||
|
|
allProducts.push(fItem);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
this.$set(this.categoryList[index], 'tabbarContentList', firstData);
|
|||
|
|
this.$set(this.categoryList[index], 'foods', allProducts);
|
|||
|
|
this.$set(this.categoryList[index], '_fromApi', true);
|
|||
|
|
}
|
|||
|
|
} catch (err) {
|
|||
|
|
console.error("加载右侧货品数据失败:", err);
|
|||
|
|
} finally {
|
|||
|
|
this.loading = false;
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
toggleAllCategories() {
|
|||
|
|
this.showAllCategories = !this.showAllCategories;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
setSort(type) {
|
|||
|
|
if (type === 'relevance') {
|
|||
|
|
if (this.sortBy === 'relevance') {
|
|||
|
|
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
|||
|
|
} else {
|
|||
|
|
this.sortBy = 'relevance';
|
|||
|
|
this.sortOrder = 'asc';
|
|||
|
|
}
|
|||
|
|
} else if (type === 'sales') {
|
|||
|
|
if (this.sortBy === 'sales') {
|
|||
|
|
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
|||
|
|
} else {
|
|||
|
|
this.sortBy = 'sales';
|
|||
|
|
this.sortOrder = 'asc';
|
|||
|
|
}
|
|||
|
|
} else if (type === 'price') {
|
|||
|
|
if (this.sortBy === 'price') {
|
|||
|
|
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc';
|
|||
|
|
} else {
|
|||
|
|
this.sortBy = 'price';
|
|||
|
|
this.sortOrder = 'asc';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
if (this.currentCategory) {
|
|||
|
|
this.$set(this.categoryList[this.currentCategoryIndex], '_fromApi', false);
|
|||
|
|
}
|
|||
|
|
this.selectCategory(this.currentCategoryIndex);
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
limit999(n) {
|
|||
|
|
const num = Number(n);
|
|||
|
|
if (isNaN(num) || num < 10000) {
|
|||
|
|
return n != null && n !== '' ? String(n) : '0';
|
|||
|
|
}
|
|||
|
|
return `${Math.floor(num / 10000)}万+`;
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
jumpSearch() {
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: "/pages/rwa_package/insights/search",
|
|||
|
|
});
|
|||
|
|
},
|
|||
|
|
|
|||
|
|
jumpInfo(item) {
|
|||
|
|
this.isFromSortShop = true;
|
|||
|
|
uni.navigateTo({
|
|||
|
|
url: `/pages/rwa_package/shop/sort-shop?id=${item.id}&name=${encodeURIComponent(item.name || item.title || '')}`,
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<style lang="scss" scoped>
|
|||
|
|
page {
|
|||
|
|
height: 100%;
|
|||
|
|
overflow: hidden;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sort_warp {
|
|||
|
|
height: 100vh;
|
|||
|
|
/* #ifdef H5 */
|
|||
|
|
height: calc(100vh - var(--window-top, 0px) - var(--window-bottom, 0px));
|
|||
|
|
/* #endif */
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
overflow: hidden;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
background-color: #ffffff;
|
|||
|
|
position: relative;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 顶部固定区域 */
|
|||
|
|
.top_fixed_header {
|
|||
|
|
position: relative;
|
|||
|
|
z-index: 100;
|
|||
|
|
background: linear-gradient(180deg, #F2EBFF 0%, #FFFFFF 100%);
|
|||
|
|
border-bottom-left-radius: 36rpx;
|
|||
|
|
border-bottom-right-radius: 36rpx;
|
|||
|
|
overflow: hidden;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
box-shadow: none;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
|
|||
|
|
&.is_expanded {
|
|||
|
|
background: linear-gradient(180deg, #F2EBFF 0%, #FFFFFF 100%, #FFFFFF 100%) !important;
|
|||
|
|
border-bottom-left-radius: 36rpx !important;
|
|||
|
|
border-bottom-right-radius: 36rpx !important;
|
|||
|
|
box-shadow: 0 12rpx 32rpx rgba(122, 53, 246, 0.12) !important;
|
|||
|
|
overflow: hidden !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 搜索框 */
|
|||
|
|
.search_bar {
|
|||
|
|
margin: 16rpx 24rpx 12rpx;
|
|||
|
|
height: 72rpx;
|
|||
|
|
background-color: #ffffff;
|
|||
|
|
border-radius: 36rpx;
|
|||
|
|
padding: 0 28rpx;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
|
|||
|
|
.search_placeholder {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
color: #999999;
|
|||
|
|
margin-left: 12rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 顶部分类横划栏 */
|
|||
|
|
.top_category_row {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
padding: 10rpx 0 16rpx 20rpx;
|
|||
|
|
|
|||
|
|
.top_category_scroll {
|
|||
|
|
flex: 1;
|
|||
|
|
overflow-x: auto;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
min-width: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.top_cat_list {
|
|||
|
|
display: inline-flex;
|
|||
|
|
align-items: flex-start;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.expand_all_btn {
|
|||
|
|
width: 90rpx;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 22rpx;
|
|||
|
|
color: #666666;
|
|||
|
|
padding: 0 12rpx;
|
|||
|
|
|
|||
|
|
.expand_text {
|
|||
|
|
font-size: 22rpx;
|
|||
|
|
color: #666666;
|
|||
|
|
margin-bottom: 4rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 分类 Icon 与 Badge 通用样式(同时作用于横滑栏与展开网格) */
|
|||
|
|
.top_cat_item {
|
|||
|
|
display: inline-flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
margin-right: 32rpx;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
|
|||
|
|
&.panel_cat_item {
|
|||
|
|
width: 20% !important;
|
|||
|
|
max-width: 20% !important;
|
|||
|
|
margin-right: 0 !important;
|
|||
|
|
margin-bottom: 24rpx !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.top_cat_img_box {
|
|||
|
|
width: 96rpx !important;
|
|||
|
|
height: 96rpx !important;
|
|||
|
|
min-width: 96rpx !important;
|
|||
|
|
max-width: 96rpx !important;
|
|||
|
|
min-height: 96rpx !important;
|
|||
|
|
max-height: 96rpx !important;
|
|||
|
|
border-radius: 50% !important;
|
|||
|
|
overflow: hidden !important;
|
|||
|
|
background: #f7f7f7;
|
|||
|
|
display: flex !important;
|
|||
|
|
align-items: center !important;
|
|||
|
|
justify-content: center !important;
|
|||
|
|
box-sizing: border-box !important;
|
|||
|
|
border: 4rpx solid transparent;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
flex-shrink: 0 !important;
|
|||
|
|
|
|||
|
|
&.active_img {
|
|||
|
|
border-color: #7a35f6 !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.top_cat_img {
|
|||
|
|
width: 88rpx !important;
|
|||
|
|
height: 88rpx !important;
|
|||
|
|
min-width: 88rpx !important;
|
|||
|
|
max-width: 88rpx !important;
|
|||
|
|
min-height: 88rpx !important;
|
|||
|
|
max-height: 88rpx !important;
|
|||
|
|
border-radius: 50% !important;
|
|||
|
|
display: block !important;
|
|||
|
|
flex-shrink: 0 !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.top_cat_name_badge {
|
|||
|
|
font-size: 22rpx !important;
|
|||
|
|
color: #666666 !important;
|
|||
|
|
margin-top: 10rpx !important;
|
|||
|
|
padding: 2rpx 12rpx !important;
|
|||
|
|
border-radius: 16rpx !important;
|
|||
|
|
line-height: 32rpx !important;
|
|||
|
|
white-space: nowrap !important;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
|
|||
|
|
&.active_badge {
|
|||
|
|
background-color: #7a35f6 !important;
|
|||
|
|
color: #ffffff !important;
|
|||
|
|
font-weight: bold !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 点开全部时的网格面板 */
|
|||
|
|
.all_categories_panel {
|
|||
|
|
background: transparent;
|
|||
|
|
padding: 16rpx 24rpx 0;
|
|||
|
|
border-bottom-left-radius: 36rpx !important;
|
|||
|
|
border-bottom-right-radius: 36rpx !important;
|
|||
|
|
overflow: hidden !important;
|
|||
|
|
|
|||
|
|
.panel_header {
|
|||
|
|
margin-bottom: 20rpx;
|
|||
|
|
padding-left: 8rpx;
|
|||
|
|
|
|||
|
|
.panel_title {
|
|||
|
|
font-size: 30rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #1a1a1a;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.panel_grid_scroll {
|
|||
|
|
max-height: 55vh;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.panel_grid {
|
|||
|
|
display: flex !important;
|
|||
|
|
flex-wrap: wrap !important;
|
|||
|
|
width: 100% !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.collapse_btn {
|
|||
|
|
display: flex !important;
|
|||
|
|
align-items: center !important;
|
|||
|
|
justify-content: center !important;
|
|||
|
|
font-size: 24rpx !important;
|
|||
|
|
color: #666666 !important;
|
|||
|
|
height: 80rpx !important;
|
|||
|
|
background-color: #ffffff !important;
|
|||
|
|
border-top: 1rpx solid #eeeeee !important;
|
|||
|
|
margin-top: 16rpx !important;
|
|||
|
|
margin-left: -24rpx !important;
|
|||
|
|
margin-right: -24rpx !important;
|
|||
|
|
border-bottom-left-radius: 36rpx !important;
|
|||
|
|
border-bottom-right-radius: 36rpx !important;
|
|||
|
|
overflow: hidden !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 主内容区 */
|
|||
|
|
.main_container {
|
|||
|
|
position: relative;
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: row;
|
|||
|
|
min-height: 0;
|
|||
|
|
width: 100%;
|
|||
|
|
background-color: #f7f7f7;
|
|||
|
|
overflow: hidden;
|
|||
|
|
|
|||
|
|
/* 展开所有分类时的半透明遮罩 */
|
|||
|
|
.mask_overlay {
|
|||
|
|
position: fixed !important;
|
|||
|
|
top: 0 !important;
|
|||
|
|
left: 0 !important;
|
|||
|
|
right: 0 !important;
|
|||
|
|
bottom: 0 !important;
|
|||
|
|
width: 100vw !important;
|
|||
|
|
height: 100vh !important;
|
|||
|
|
background-color: rgba(0, 0, 0, 0.45) !important;
|
|||
|
|
z-index: 90 !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 左侧边栏 */
|
|||
|
|
.left_sidebar_scroll {
|
|||
|
|
width: 180rpx;
|
|||
|
|
height: 100%;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
background-color: #f7f7f7;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
|
|||
|
|
/* #ifdef APP-PLUS */
|
|||
|
|
padding-bottom: calc(110rpx + env(safe-area-inset-bottom));
|
|||
|
|
/* #endif */
|
|||
|
|
|
|||
|
|
.sidebar_item {
|
|||
|
|
height: 104rpx;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: #555555;
|
|||
|
|
padding: 0 12rpx;
|
|||
|
|
text-align: center;
|
|||
|
|
position: relative;
|
|||
|
|
box-sizing: border-box;
|
|||
|
|
|
|||
|
|
&.active {
|
|||
|
|
background-color: #ffffff;
|
|||
|
|
color: #7a35f6;
|
|||
|
|
font-weight: bold;
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
|
|||
|
|
&::before {
|
|||
|
|
content: "";
|
|||
|
|
position: absolute;
|
|||
|
|
left: 0;
|
|||
|
|
top: 34rpx;
|
|||
|
|
width: 6rpx;
|
|||
|
|
height: 36rpx;
|
|||
|
|
background-color: #7a35f6;
|
|||
|
|
border-radius: 0 4rpx 4rpx 0;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 右侧内容区域 */
|
|||
|
|
.right_content_area {
|
|||
|
|
flex: 1;
|
|||
|
|
height: 100%;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
background-color: #ffffff;
|
|||
|
|
min-width: 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 右侧排序栏 */
|
|||
|
|
.sort_option_bar {
|
|||
|
|
height: 80rpx;
|
|||
|
|
padding: 0 24rpx;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
border-bottom: none;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
|
|||
|
|
.cat_title_text {
|
|||
|
|
font-size: 30rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #7a35f6;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sort_actions {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
gap: 32rpx;
|
|||
|
|
|
|||
|
|
.sort_item {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: #999999;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
cursor: pointer;
|
|||
|
|
|
|||
|
|
&.active {
|
|||
|
|
color: #7a35f6;
|
|||
|
|
font-weight: bold;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sort_icon_box {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
margin-left: 6rpx;
|
|||
|
|
line-height: 1;
|
|||
|
|
|
|||
|
|
.icon_up {
|
|||
|
|
margin-bottom: 2rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* 商品列表 */
|
|||
|
|
.product_list_scroll {
|
|||
|
|
flex: 1;
|
|||
|
|
height: 100%;
|
|||
|
|
overflow-y: auto;
|
|||
|
|
|
|||
|
|
/* #ifdef APP-PLUS */
|
|||
|
|
padding-bottom: calc(110rpx + env(safe-area-inset-bottom));
|
|||
|
|
/* #endif */
|
|||
|
|
|
|||
|
|
.product_list_box {
|
|||
|
|
padding: 0 24rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.product_row_card {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: row;
|
|||
|
|
padding: 24rpx 0;
|
|||
|
|
border-bottom: 1rpx solid #f7f7f7;
|
|||
|
|
background: #ffffff;
|
|||
|
|
|
|||
|
|
.product_img_box {
|
|||
|
|
width: 200rpx;
|
|||
|
|
height: 200rpx;
|
|||
|
|
border-radius: 16rpx;
|
|||
|
|
overflow: hidden;
|
|||
|
|
flex-shrink: 0;
|
|||
|
|
margin-right: 20rpx;
|
|||
|
|
background: #f9f9f9;
|
|||
|
|
|
|||
|
|
.product_img {
|
|||
|
|
width: 100%;
|
|||
|
|
height: 100%;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.product_info_box {
|
|||
|
|
flex: 1;
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
min-width: 0;
|
|||
|
|
|
|||
|
|
.product_title {
|
|||
|
|
font-size: 28rpx;
|
|||
|
|
font-weight: bold;
|
|||
|
|
color: #1a1a1a;
|
|||
|
|
line-height: 38rpx;
|
|||
|
|
max-height: 76rpx;
|
|||
|
|
overflow: hidden;
|
|||
|
|
text-overflow: ellipsis;
|
|||
|
|
display: -webkit-box;
|
|||
|
|
-webkit-line-clamp: 2;
|
|||
|
|
-webkit-box-orient: vertical;
|
|||
|
|
word-break: break-all;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.product_tags_row {
|
|||
|
|
display: flex;
|
|||
|
|
gap: 12rpx;
|
|||
|
|
margin-top: 8rpx;
|
|||
|
|
|
|||
|
|
.tag_item {
|
|||
|
|
font-size: 20rpx;
|
|||
|
|
padding: 2rpx 10rpx;
|
|||
|
|
border-radius: 6rpx;
|
|||
|
|
white-space: nowrap;
|
|||
|
|
|
|||
|
|
&.tag_blue {
|
|||
|
|
color: #2979ff;
|
|||
|
|
border: 1rpx solid #2979ff;
|
|||
|
|
background-color: rgba(41, 121, 255, 0.05);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
&.tag_green {
|
|||
|
|
color: #19be6b;
|
|||
|
|
border: 1rpx solid #19be6b;
|
|||
|
|
background-color: rgba(25, 190, 107, 0.05);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.product_price_row {
|
|||
|
|
display: flex;
|
|||
|
|
justify-content: space-between;
|
|||
|
|
align-items: baseline;
|
|||
|
|
margin-top: 12rpx;
|
|||
|
|
|
|||
|
|
.price_box {
|
|||
|
|
color: #ff2a2a;
|
|||
|
|
font-weight: bold;
|
|||
|
|
|
|||
|
|
.price_symbol {
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.price_val {
|
|||
|
|
font-size: 38rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.sales_val {
|
|||
|
|
font-size: 24rpx;
|
|||
|
|
color: #999999;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty_box {
|
|||
|
|
display: flex;
|
|||
|
|
flex-direction: column;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
padding-top: 120rpx;
|
|||
|
|
|
|||
|
|
.empty_img {
|
|||
|
|
width: 160rpx;
|
|||
|
|
height: 160rpx;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.empty_text {
|
|||
|
|
font-size: 26rpx;
|
|||
|
|
color: #999999;
|
|||
|
|
margin-top: 20rpx;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
</style>
|