Files
frontend-app/pages/sort/sort.vue
T

858 lines
23 KiB
Vue
Raw Normal View History

2026-08-06 18:50:25 +08:00
<template>
<view class="sort_warp" :class="platformType ? '' : 'iosplatformType'">
<!-- #ifndef MP-WEIXIN -->
<TopSafe></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: sortType === 'default' }" @click="setSort('default')">
综合 <text class="sort_arrow">⇅</text>
</view>
<view class="sort_item" :class="{ active: sortType === 'sales' }" @click="setSort('sales')">
销量 <text class="sort_arrow">⇅</text>
</view>
<view class="sort_item" :class="{ active: sortType === 'price' }" @click="setSort('price')">
价格 <text class="sort_arrow">⇅</text>
</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,
sortType: 'default', // default | sales | price
sortAsc: true,
categoryList: [],
capsuleTop: 0,
defaultIcon: 'https://static.tbmall.xin/static/new-home/xinren.png',
defaultProductImg: 'https://static.tbmall.xin/static/new-home/nangua.png'
};
},
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 = this.currentCategory.foods || this.currentCategory.productList || [];
if (!list || list.length === 0) return [];
list = [...list];
if (this.sortType === 'sales') {
list.sort((a, b) => (Number(b.sales) || 0) - (Number(a.sales) || 0));
} else if (this.sortType === 'price') {
if (this.sortAsc) {
list.sort((a, b) => (Number(a.price) || 0) - (Number(b.price) || 0));
} else {
list.sort((a, b) => (Number(b.price) || 0) - (Number(a.price) || 0));
}
}
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*/
this.fetchCategories();
},
methods: {
async fetchCategories() {
try {
const resp = await getCascadeCategory({ parentId: 0 });
if (resp && resp.bizcode === 100 && resp.data && resp.data.length > 0) {
this.categoryList = resp.data;
} else {
this.categoryList = MOCK_CATEGORIES;
}
} catch (e) {
console.log("使用模拟分类数据", e);
this.categoryList = MOCK_CATEGORIES;
}
},
selectCategory(index, closePopup = false) {
this.currentCategoryIndex = index;
if (closePopup) {
this.showAllCategories = false;
}
},
toggleAllCategories() {
this.showAllCategories = !this.showAllCategories;
},
setSort(type) {
if (this.sortType === type && type === 'price') {
this.sortAsc = !this.sortAsc;
} else {
this.sortType = type;
this.sortAsc = true;
}
},
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) {
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: #ffffff;
flex-shrink: 0;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.03);
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: 1rpx solid #f5f5f5;
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;
&.active {
color: #7a35f6;
font-weight: bold;
}
.sort_arrow {
font-size: 20rpx;
margin-left: 4rpx;
}
}
}
}
/* 商品列表 */
.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>