Files
lucky_shop/components-diy/diy-category-item.vue

2925 lines
87 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 data-component-name="diy-category-item" class="item-wrap" :class="type">
<block v-if="type == 'category' && category.child_list && category.child_list.length">
<view class="category-adv" v-if="category.image_adv" @tap.stop="diyRedirectTo(category.link_url)">
<image :src="$util.img(category.image_adv)" mode="widthFix" show-menu-by-longpress />
</view>
<block v-if="value.template == 99">
<scroll-view scroll-x="true" class="second-category-btns" :scroll-with-animation="true">
<view class="second-category-btn"
v-for="(one, oneIndex) in category.child_list"
:key="oneIndex"
:class="{ selected: selectedSecondCategory == one.category_id }"
@tap="selectSecondCategory(one)">
{{ one.category_name }}
</view>
</scroll-view>
<view class="category-goods-list" v-if="selectedSecondCategory">
<view class="goods-list show" :class="{ 'double-column': !isList, 'single-column': isList }" :data-template="value.template" v-if="secondCategoryGoodsList.length">
<view class="goods-item" v-for="(item, index) in secondCategoryGoodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="item.isinformation == 0 && showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap">
<view class="consult-now-btn-wrap" v-if="item.isinformation == 1">
<view class="consult-now-btn" @tap.stop="sendToAIChat(item)">
{{ $lang('components-diy.diy-category-item.consultNow') }}
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<view class="category-goods-empty" v-if="selectedSecondCategory && !secondCategoryGoodsList.length && !loading">
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
<view class="tips">{{ $lang('components-diy.diy-category.noCategory') }}</view>
</view>
</block>
<!-- 模板1: 后台配置三级分类 + 有子分类时, 按二级分类分别显示各自的商品(图 f325d5a779 样式)
只渲染 filteredGroupedChildren(过滤掉无商品的二级分类), 没商品时整块(标题+网格)都不显示 -->
<block v-else-if="isGroupedDisplay">
<block v-for="(one, oneIndex) in filteredGroupedChildren" :key="one.category_id">
<view :class="['category-title', 'category-title-with-lines']">{{ one.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list show" :class="{ 'double-column': !isList, 'single-column': isList }" v-if="template1ChildGoodsList[one.category_id] && template1ChildGoodsList[one.category_id].length">
<view class="goods-item" v-for="(item, index) in template1ChildGoodsList[one.category_id]" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="template1ImgError(one.category_id, index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="item.isinformation == 0 && showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap footer-wrap-template1">
<view class="detail-btn" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.consultNow') }}</view>
</view>
</view>
</view>
</view>
<!-- filteredGroupedChildren 已保证加载完成且 length > 0, 此处无需再渲染空状态 -->
</view>
</block>
</block>
<!-- 模板1: 后台配置二级分类 + 有子分类时, 在一级分类标题下以双列网格显示所有商品(图 c2df0f3d29 样式)
模板5 不走这里: 它有专属分支 value.template == 5(后台"展示分类"决定渲染商品网格或二级分类网格) -->
<view v-else-if="value.template == 1">
<view :class="['category-title', 'category-title-with-lines']">{{ category.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list show" :class="{ 'double-column': !isList, 'single-column': isList }" v-if="template1GoodsList.length">
<view class="goods-item" v-for="(item, index) in template1GoodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="template1ImgError(index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="item.isinformation == 0 && showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap footer-wrap-template1">
<view class="detail-btn" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.consultNow') }}</view>
</view>
</view>
</view>
</view>
<view class="category-goods-empty" v-else-if="!template1Loading">
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
<view class="tips">暂无商品</view>
</view>
</view>
</view>
<!-- 模板5 专属: 后台"展示分类"决定右侧渲染内容(对应图2/图3)
二级分类(value.level == 2) → 一级分类标题 + 商品网格(图2)
三级分类(value.level == 3) → 一级分类标题 + 二级分类网格(图3) -->
<block v-else-if="value.template == 5">
<block v-if="value.level == 2">
<view :class="['category-title', '']">{{ category.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list show" :class="{ 'double-column': !isList, 'single-column': isList }" v-if="template1GoodsList.length">
<view class="goods-item" v-for="(item, index) in template1GoodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="template1ImgError(index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="item.isinformation == 0 && showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap footer-wrap-template1">
<view class="detail-btn" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.consultNow') }}</view>
</view>
</view>
</view>
</view>
<view class="category-goods-empty" v-else-if="!template1Loading">
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
<view class="tips">暂无商品</view>
</view>
</view>
</block>
<block v-else>
<!-- 三级分类(及更深层级): 一级分类标题 + 二级分类网格(图3) -->
<view :class="['category-title', '']">{{ category.category_name }}</view>
<view class="category-list">
<view class="category-item" v-for="(one, oneIndex) in category.child_list" :key="oneIndex"
@tap.stop="$util.redirectTo('/pages_goods/list', { category_id: one.category_id })">
<view class="img-box">
<image :src="categoryImg(one.image)" mode="aspectFit" @error="categoryImgError(2, oneIndex, -1)" show-menu-by-longpress />
</view>
<view class="name">{{ one.category_name }}</view>
</view>
</view>
</block>
</block>
</block>
<!-- 模板1: 无子分类时(外层块不进入), 在一级分类标题下以双列网格显示所有商品(图 c2df0f3d29 样式) -->
<view v-else-if="(value.template == 1 || value.template == 5) && type == 'category'">
<view :class="['category-title', 'category-title-with-lines']">{{ category.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list show" :class="{ 'double-column': !isList, 'single-column': isList }" v-if="template1GoodsList.length">
<view class="goods-item" v-for="(item, index) in template1GoodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="template1ImgError(index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<view class="info-wrap">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{ parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{ parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="item.isinformation == 0 && showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap footer-wrap-template1">
<view class="detail-btn" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.consultNow') }}</view>
</view>
</view>
</view>
</view>
<view class="category-goods-empty" v-else-if="!template1Loading">
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
<view class="tips">暂无商品</view>
</view>
</view>
</view>
<block v-if="type == 'goods'">
<view class="categoty-goods-wrap" v-if="loadType == 'part'"
:style="'padding-top:' + (value.search ? 0 : '20rpx')">
<!-- 分类筛选 -->
<block v-if="(value.template == 4 ? (currentOneCategory && currentOneCategory.child_list) : category.child_list) && value.goodsLevel == 2">
<view class="screen-category-wrap">
<scroll-view scroll-x="true" class="screen-category"
:class="{ 'screen-category-4': value.template == 4 }" :scroll-with-animation="true"
:scroll-into-view="scrollIntoView">
<view class="item" id="category-2--1" :class="{ selected: categoryId == -1 }"
@tap.stop="selectCategory(-1)">{{ $lang('components-diy.diy-category-item.all') }}</view>
<view class="item" :id="'category-2-' + oneIndex"
:class="{ selected: categoryId == oneIndex }" @tap.stop="selectCategory(oneIndex)"
v-for="(one, oneIndex) in (value.template == 4 ? currentOneCategory.child_list : category.child_list)" :key="oneIndex">
{{ one.category_name }}
</view>
</scroll-view>
<view class="iconfont icon-unfold" @tap.stop="$refs.screenCategoryPopup.open()"></view>
</view>
<uni-popup type="top" ref="screenCategoryPopup">
<view class="screen-category-popup" @tap.stop="$refs.screenCategoryPopup.close()">
<scroll-view scroll-y="true" class="screen-category"
:class="{ 'screen-category-4': value.template == 4 }">
<view class="title">{{ $lang('components-diy.diy-category-item.all') }}</view>
<view class="item" :class="{ selected: categoryId == oneIndex }"
@tap.stop="selectCategory(oneIndex)" v-for="(one, oneIndex) in (value.template == 4 ? currentOneCategory.child_list : category.child_list)"
:key="oneIndex">
{{ one.category_name }}
</view>
</scroll-view>
</view>
</uni-popup>
</block>
<!---->
<scroll-view scroll-y="true" class="scroll-goods-view" lower-threshold="300" :scroll-top="scrollTop"
@scrolltolower="scrolltolower" @touchstart="touchstart" @touchend="touchend" @scroll="listenScroll">
<!--一级分类展示商品显示-->
<view class="category-title" v-if="value.template != 3 && value.template != 4" :style="{ fontSize: (themeStyle && themeStyle.diy_category_item_goods_title_size) || '28rpx', paddingBottom: 0 }">
{{ category.category_name }}
</view>
<!--一级-->
<view class="goods-list"
:class="{ 'double-column': !isList, 'single-column': isList, 'show': true }"
:data-template="value.template">
<block v-if="goodsList.length">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}
</view>
</view>
<!-- 模板3: 卡片式布局 - 上方名称, 下方价格+按钮 -->
<view class="info-wrap" v-if="value.template == 3">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="info-bottom">
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="buy-now-btn color-base-bg" @tap.stop="toDetail(item)">
{{ $lang('components-diy.diy-category-item.buyNow') }}
</view>
</view>
</view>
<!-- 其他模板: 保留原结构 -->
<view class="info-wrap" v-else>
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item) && value.template != 4">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" show-menu-by-longpress />
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1 && value.template != 4">
<image :src="$util.img('public/uniapp/index/discount.png')"
mode="widthFix" show-menu-by-longpress />
</view>
<!-- 模板2/4: 删除线价格(放在现价下方, 样式见 price-wrap 的 delete-price 规则) -->
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item) && (value.template == 2 || value.template == 4)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="footer-wrap">
<view class="pro-info">
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item) && value.template != 4 && value.template != 2">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="consult-now-btn-wrap" v-if="item.isinformation >= 1">
<view class="consult-now-btn" @tap.stop="sendToAIChat(item)">
{{ $lang('components-diy.diy-category-item.consultNow') }}
</view>
</view>
<!-- 风格2/模板4: 右侧+按钮/择规格按钮 -->
<view class="right-wrap" v-if="(value.template == 2 || value.template == 4) && showAddCart && item.isinformation == 0">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.buyNow') }}</view>
</block>
<block v-else>
<view v-if="item.goods_spec_format" class="color-base-bg select-sku"
@tap.stop="selectSku(item)">
<text>{{ $lang('components-diy.diy-category-item.selectSpecs') }}</text>
<text class="num-tag" v-if="item.num">{{ item.num }}</text>
</view>
<block v-else>
<block
v-if="cartList['goods_' + item.goods_id] && cartList['goods_' + item.goods_id]['sku_' + item.sku_id]">
<view class="num-action reduce" @tap.stop="reduce(item)">
<text class="iconfont icon-jian"></text>
</view>
<view class="num">{{ cartList['goods_' + item.goods_id]['sku_' +
item.sku_id].num }}</view>
<view class="num-action" :id="'cart-num-' + index"
@tap.stop="increase($event, item)">
<text class="iconfont icon-jia"></text>
<view class="click-event"></view>
</view>
</block>
<view class="num-action" v-else :id="'cart-num-' + index"
@tap.stop="increase($event, item, 0)">
<text class="iconfont icon-jia"></text>
<view class="click-event"></view>
</view>
</block>
</block>
</view>
</view>
</view>
</view>
</block>
<view class="category-empty" v-else>
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress />
</view>
<!-- <view class="end-tips" ref="endTips" v-if="last && (categoryId == -1 || !category.child_list || (category.child_list && categoryId == category.child_list.length - 1))">已经到底了~</view>
<view class="end-tips" ref="endTips" v-else @tap.stop="switchCategory('next')">
<text class="iconfont icon-xiangshangzhanhang"></text>
上滑查看下一分类
</view> -->
</view>
<loading-cover ref="loadingCover" :init-show="loading"></loading-cover>
</scroll-view>
</view>
<block v-if="loadType == 'all' && goodsList.length">
<view class="goods-list" :class="{ 'double-column': !isList, 'single-column': isList }"
:data-template="value.template">
<view class="goods-item" v-for="(item, index) in goodsList" :key="index">
<view class="goods-img" @tap.stop="toDetail(item)">
<image :src="goodsImg(item.goods_image)" mode="widthFix" @error="imgError(index)"
:lazy-load="true" show-menu-by-longpress />
<view class="color-base-bg goods-tag" v-if="item.label_name">{{ item.label_name }}</view>
</view>
<!-- 模板3: 卡片式布局 - 上方名称, 下方价格+按钮 -->
<view class="info-wrap" v-if="value.template == 3">
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="info-bottom">
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="buy-now-btn color-base-bg" @tap.stop="toDetail(item)">
{{ $lang('components-diy.diy-category-item.buyNow') }}
</view>
</view>
</view>
<!-- 其他模板: 保留原结构 -->
<view class="info-wrap" v-else>
<view class="name-wrap" @tap.stop="toDetail(item)">
<view class="goods-name">{{ isEnEnv ? item.en_goods_name : item.goods_name }}</view>
</view>
<view class="price-wrap">
<view class="discount-price">
<block v-if="item.isinformation == 0">
<text class="unit price-style small">{{ $lang('common.currencySymbol') }}</text>
<text class="price price-style large">{{
parseFloat(showPrice(item)).toFixed(2).split('.')[0] }}</text>
<text class="unit price-style small">.{{
parseFloat(showPrice(item)).toFixed(2).split('.')[1] }}</text>
</block>
</view>
<view class="member-price-tag"
v-if="item.member_price && item.member_price == showPrice(item)">
<image :src="$util.img('public/uniapp/index/VIP.png')" mode="widthFix" show-menu-by-longpress />
</view>
<view class="member-price-tag" v-else-if="item.promotion_type == 1">
<image :src="$util.img('public/uniapp/index/discount.png')" mode="widthFix" show-menu-by-longpress />
</view>
</view>
<view class="footer-wrap">
<view class="pro-info">
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item)">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="consult-now-btn-wrap" v-if="item.isinformation >= 1">
<view class="consult-now-btn" @tap.stop="sendToAIChat(item)">
{{ $lang('components-diy.diy-category-item.consultNow') }}
</view>
</view>
<view class="right-wrap" v-if="(value.template == 2 || value.template == 4) && showAddCart && item.isinformation == 0">
<block v-if="item.is_virtual">
<view class="color-base-bg select-sku" @tap.stop="toDetail(item)">{{ $lang('components-diy.diy-category-item.buyNow') }}</view>
</block>
<block v-else>
<view v-if="item.goods_spec_format" class="color-base-bg select-sku"
@tap.stop="selectSku(item)">
<text>{{ $lang('components-diy.diy-category-item.selectSpecs') }}</text>
<text class="num-tag" v-if="item.num">{{ item.num }}</text>
</view>
<block v-else>
<block
v-if="cartList['goods_' + item.goods_id] && cartList['goods_' + item.goods_id]['sku_' + item.sku_id]">
<view class="num-action reduce" @tap.stop="reduce(item)">
<text class="iconfont icon-jian"></text>
</view>
<view class="num">{{ cartList['goods_' + item.goods_id]['sku_' +
item.sku_id].num }}</view>
<view class="num-action" :id="'cart-num-' + index"
@tap.stop="increase($event, item)">
<text class="iconfont icon-jia"></text>
<view class="click-event"></view>
</view>
</block>
<view class="num-action" v-else :id="'cart-num-' + index"
@tap.stop="increase($event, item, 0)">
<text class="iconfont icon-jia"></text>
<view class="click-event"></view>
</view>
</block>
</block>
</view>
</view>
</view>
</view>
</view>
</block>
</block>
</view>
</template>
<script>
import DiyMinx from './minx.js'
// 商品分类项
export default {
name: 'diy-category-item',
props: {
category: {
type: Object
},
value: {
type: Object,
default: () => {
return {};
}
},
index: {
type: Number,
default: 0
},
select: {
type: Number,
default: 0
},
oneCategorySelect: {
type: Number,
default: 0
},
last: {
type: Boolean,
default: false
},
isList: {
type: Boolean,
default: false
},
lastSecondCategoryName: {
type: String,
default: ''
},
currentOneCategory: {
type: Object,
default: null
},
},
mixins: [DiyMinx],
data() {
return {
level: 3,
categoryId: -1,
loading: false,
goodsList: [],
pageIndex: 0,
pageSize: 10,
// 已加载完成的分类ID, 用于避免重复拉取同一分类的商品
_loadedGoodsKey: null,
totalPage: 1,
touchstartPosition: 0,
scrollType: 'top',
contentWrapHeight: 0,
scrollIntoView: 'category-2--1',
scrollTop: 0,
loadType: '',
timeout: {},
cartEditRepeat: false,
selectedSecondCategory: null,
secondCategoryGoodsList: [],
// 模板1专用:选中的分类ID和商品列表
selectedCategoryId: null,
template1GoodsList: [],
template1Loading: false,
// 模板1专用(categoryLevel == 3): 按二级分类ID分别存储各自商品列表
// key: child.category_id, value: 商品数组
template1ChildGoodsList: {},
template1ChildLoading: {}
};
},
computed: {
// 后台开关:加购按钮 = cartBtn; 未下发 cartBtn 时兼容旧字段 quickBuy
// 后台开启就显示, 关闭就不显示
showAddCart() {
const t = this.getDiyToggle(['showAddCart', 'show_add_cart', 'cartBtn', 'cart_btn']);
if (t !== undefined) return t;
const legacy = this.getDiyToggle(['quickBuy', 'quick_buy']);
return legacy !== undefined ? legacy : false;
},
/**
* 当前分类层级 (参考 template 2 的 goodsLevel 模式)
* 优先级: 数据层级 > 后台配置 > 兜底
* 1. 先检测数据实际支持几级 (二级分类下是否还有 child_list)
* 2. 后台配置的 level 不能超过数据支持的层级
* (数据只有 2 级时,就算后台配 3 也强制用 2,避免出现空标题列表)
* 3. 都没配置就按数据层级来
*/
categoryLevel() {
// 先检测数据层级
let dataLevel = 2; // 兜底
if (this.category && this.category.child_list && this.category.child_list.length > 0) {
const hasThirdLevel = this.category.child_list.some(item =>
item.child_list && item.child_list.length > 0
);
dataLevel = hasThirdLevel ? 3 : 2;
}
// 后台配置: 不能超过数据支持的层级
if (this.value.level && this.value.level <= dataLevel) {
return this.value.level;
}
// 否则按数据层级
return dataLevel;
},
/**
* 模板1: 是否按子分类分组显示
* 后台"展示分类"配置:
* 二级分类 (value.level == 2) → false → 单网格显示所有商品
* 三级分类 (value.level == 3) → true → 按二级分类分别显示各自商品
*/
isGroupedDisplay() {
// 仅模板1 走"按二级分类分组显示商品"; 模板5 的层级由 value.level 直接决定(见下方 value.template == 5 分支)
return this.value.template == 1 && this.value.level == 3;
},
/**
* 模板1三级分类: 只渲染"有商品"的二级分类
* 无商品的二级分类整块(标题 + 网格)都不渲染; 加载中的二级分类也暂不渲染(避免闪烁)
* 判定时机: 该二级分类加载完成(template1ChildLoading[id] === false)且商品列表非空
*/
filteredGroupedChildren() {
if (!this.isGroupedDisplay) return [];
if (!this.category || !this.category.child_list || !this.category.child_list.length) return [];
return this.category.child_list.filter(one => {
// 还在加载中: 暂不渲染(避免闪烁)
if (this.template1ChildLoading[one.category_id]) return false;
// 加载完成: 只有该二级分类下有商品时才显示, 无商品则整块隐藏
const list = this.template1ChildGoodsList[one.category_id];
return !!(list && list.length);
});
},
/**
* 渲染类型: 模板1/99 用分类树卡片(category), 其它模板用商品卡片(goods)
* 用计算属性而非 created() 里的一次性赋值:
* 页面下拉刷新/装修数据更新时 value.template 可能变化, 组件被 Vue 复用不会重建,
* 若 type 不跟随更新, 模板1 会误走 goods 分支, 导致"资讯类商品=否"的商品不显示立即咨询按钮
*/
type() {
return this.value.template == 1 || this.value.template == 5 || this.value.template == 99 ? 'category' : 'goods';
},
},
created() {
if (this.index == this.select && this.pageIndex == 0) this.getGoodsList();
this.loadType = this.value.goodsLevel == 1 && this.value.loadType == 'all' ? 'all' : 'part';
if (this.value.template == 99 && this.index == this.select && this.category.child_list && this.category.child_list.length > 0) {
let targetCategory = null;
if (this.lastSecondCategoryName) {
targetCategory = this.category.child_list.find(item => item.category_name === this.lastSecondCategoryName);
}
if (!targetCategory) {
targetCategory = this.category.child_list[0];
}
this.selectedSecondCategory = targetCategory.category_id;
this.secondCategoryGoodsList = [];
// 用当前一级分类下同名二级分类的 id 查商品(而非全局按钮的 id)
this.refreshGoodsByCurrentCategory();
}
// 模板1:根据后台"展示分类"配置 + 数据层级决定加载方式
// 三级分类模式 (isGroupedDisplay) + 有二级分类时,按二级分类分别加载各自商品
// 其他情况(二级分类模式 / 无二级分类),直接加载当前一级分类下的所有商品
// 模板5 渲染规则:
// 二级分类(value.level == 2) → 商品网格(图2), 需要加载该一级分类下所有商品(合并其二级分类)
// 三级分类(value.level == 3) → 二级分类网格(图3), 静态网格无需加载商品
// 若该一级分类下没有子分类(兜底), 仍需要加载商品
const template5NeedGoods = this.value.template == 5 &&
(this.value.level == 2 ||
!(this.category && this.category.child_list && this.category.child_list.length));
if ((this.value.template == 1 || template5NeedGoods) && this.category && this.category.category_id) {
if (this.isGroupedDisplay && this.category.child_list && this.category.child_list.length) {
this.category.child_list.forEach(child => {
this.getTemplate1ChildGoods(child.category_id);
});
} else if (this.category.child_list && this.category.child_list.length) {
// 二级分类模式: 合并所有二级分类的商品, 避免只查一级分类ID时漏掉子分类商品
this.loadTemplate1Level2Goods();
} else {
this.getTemplate1Goods(this.category.category_id);
}
}
},
mounted() {
setTimeout(() => {
const query = uni.createSelectorQuery().in(this);
query.select('.scroll-goods-view')
.boundingClientRect(data => {
if (data) this.contentWrapHeight = data.height;
}).exec();
}, 1000);
if (this.value.template == 99 && this.category.child_list && this.category.child_list.length > 0) {
let targetCategory = null;
if (this.lastSecondCategoryName) {
targetCategory = this.category.child_list.find(item => item.category_name === this.lastSecondCategoryName);
}
if (!targetCategory) {
targetCategory = this.category.child_list[0];
}
this.selectedSecondCategory = targetCategory.category_id;
this.secondCategoryGoodsList = [];
this.getSecondCategoryGoods(targetCategory.category_id);
}
},
watch: {
oneCategorySelect: function () {
this.scrollTop = -1;
this.goodsList = [];
this.selectCategory(-1);
},
select: function () {
if (this.index == this.select) {
this.scrollTop = 0;
if (this.pageIndex == 0) {
this.getGoodsList();
} else if (this.$refs.loadingCover && this.loadType == 'part') {
this.$refs.loadingCover.show();
setTimeout(() => {
this.$refs.loadingCover.hide();
}, 300);
}
if (!this.contentWrapHeight) {
const query = uni.createSelectorQuery().in(this);
query.select('.scroll-goods-view')
.boundingClientRect(data => {
if (data) this.contentWrapHeight = data.height;
}).exec();
}
} else if (this.loadType == 'part') {
this.scrollTop = -1;
}
if (this.value.template == 99 && this.category.child_list && this.category.child_list.length > 0) {
let targetCategory = null;
if (this.lastSecondCategoryName) {
targetCategory = this.category.child_list.find(item => item.category_name === this.lastSecondCategoryName);
}
if (!targetCategory) {
targetCategory = this.category.child_list[0];
}
this.selectedSecondCategory = targetCategory.category_id;
this.secondCategoryGoodsList = [];
this.refreshGoodsByCurrentCategory();
}
// 模板1:切换一级分类时重新加载该分类下的商品
// 三级分类模式 + 有二级分类时,按二级分类分别重新加载,否则直接加载当前分类
// 注意: 模板5 是"全部分类纵向平铺", 商品由 created 首次加载 + 父级链式懒加载提供,
// 滚动时 select 会随 listenScroll 变化, 若此处也重载会导致滚动中反复清空/重复请求, 故排除 5
if (this.value.template == 1 && this.index == this.select && this.category && this.category.category_id) {
this.template1GoodsList = [];
this.template1ChildGoodsList = {};
this.template1ChildLoading = {};
if (this.isGroupedDisplay && this.category.child_list && this.category.child_list.length) {
this.category.child_list.forEach(child => {
this.getTemplate1ChildGoods(child.category_id);
});
} else if (this.category.child_list && this.category.child_list.length) {
// 二级分类模式: 切换一级分类时重新合并所有二级分类商品
this.loadTemplate1Level2Goods();
} else {
this.getTemplate1Goods(this.category.category_id);
}
}
},
// 切换一级分类时, 根据当前一级分类下同名二级分类重新加载商品
currentOneCategory: {
handler() {
if (this.value.template == 99) {
this.refreshGoodsByCurrentCategory();
}
},
deep: true
},
'globalStoreInfo.store_id': {
handler(nval, oval) {
if (nval != oval) {
if (this.index == this.select || this.loadType == 'all') {
this.pageIndex = 0;
this.categoryId = -1;
this.totalPage = 1;
this.getGoodsList();
}
}
},
deep: true
}
},
methods: {
/**
* 拉取某分类下的全部商品(按后台实际数量)
* 后端 pageByCategory 存在分页上限, 这里自动翻页直到最后一页再合并返回,
* 保证分类模板(1/2/3/4/99)显示该分类下的全部商品, 而不是只显示第一页
* @param {Number} categoryId 分类ID
* @param {String} order 排序字段
* @param {String} sort 排序方向
* @returns {Promise<Array>}
*/
getCategoryGoodsAll(categoryId, order = '', sort = '') {
return new Promise(resolve => {
const MAX_PAGE = 200; // 安全上限, 防止后台 page 参数失效时死循环
const all = [];
let prevLen = 0;
const fetchPage = page => {
if (page > MAX_PAGE) {
resolve(all);
return;
}
this.$api.sendRequest({
url: '/api/goodssku/pageByCategory',
data: {
category_id: categoryId,
page: page,
// 每次按较大 page_size 取, 减少请求次数; 实际每页条数由后台决定
page_size: 100,
order: order,
sort: sort
},
success: res => {
if (res.code == 0 && res.data && res.data.list && res.data.list.length) {
const list = res.data.list;
all.push(...list);
const pageCount = res.data.page_count;
// 后台返回了可靠的 page_count 时, 以它为准停止翻页
if (typeof pageCount === 'number' && pageCount >= 1 && page >= pageCount) {
resolve(all);
return;
}
// 自适应翻页: 不信任 page_count 时也按"上一页是否满页"判断
// 当前页条数 >= 上一页条数 → 可能还有下一页, 继续拉取
// 当前页比上一页少 → 已是末页(不满), 停止
if (page === 1 || list.length >= prevLen) {
prevLen = list.length;
fetchPage(page + 1);
} else {
resolve(all);
}
} else {
// 当前页无数据, 全部商品已取完
resolve(all);
}
},
fail: () => resolve(all)
});
};
fetchPage(1);
});
},
/**
* 拉取某分类及其所有二级子分类下的全部商品(自动翻页 + 合并去重)
* 解决后端 pageByCategory 只返回"直接"挂在当前分类的商品, 漏掉子分类商品的问题,
* 保证一级分类下的所有二级分类商品都能全部显示
* @param {Object} category 分类对象(含 child_list)
* @param {Number} categoryId 当前分类ID
* @param {String} order 排序字段
* @param {String} sort 排序方向
* @returns {Promise<Array>}
*/
getCategoryGoodsWithChildrenAll(category, categoryId, order = '', sort = '') {
const ids = [categoryId];
if (category && category.child_list && category.child_list.length) {
category.child_list.forEach(child => {
if (child.category_id) ids.push(child.category_id);
});
}
const promises = ids.map(id => this.getCategoryGoodsAll(id, order, sort));
return Promise.all(promises).then(lists => {
const all = [];
const seen = new Set();
lists.forEach(list => {
list.forEach(item => {
const key = item.sku_id || item.goods_id;
if (key && !seen.has(key)) {
seen.add(key);
all.push(item);
}
});
});
return all;
});
},
/**
* 查询商品列表(模板 2/3/4)
* 一次性拉取该分类下的全部商品, 不再依赖滚动分页
*/
getGoodsList() {
if (this.type != 'goods' || this.loading) return;
// 已加载过该分类时, 避免 scrolltolower 重复拉取
const categoryId = this.categoryId != -1 ? (this.value.template == 4 && this.currentOneCategory && this.currentOneCategory.child_list ? this.currentOneCategory.child_list[this.categoryId].category_id : this.category.child_list[this.categoryId].category_id)
: (this.value.template == 4 && this.currentOneCategory ? this.currentOneCategory.category_id : this.category.category_id);
if (this._loadedGoodsKey === categoryId) return;
this.loading = true;
this.goodsList = [];
let order = '';
let sort = '';
if (this.value.sortWay) {
if (this.value.sortWay == 'default') {
order = '';
sort = '';
} else if (this.value.sortWay == 'sales') {
order = 'sale_num';
sort = 'desc';
} else if (this.value.sortWay == 'price') {
order = 'discount_price';
sort = 'desc';
} else if (this.value.sortWay == 'news') {
order = 'create_time';
sort = 'desc';
}
}
if (this.$refs.loadingCover && this.loadType == 'part') {
setTimeout(() => {
this.$refs.loadingCover.show();
});
}
// 模板2/3/4: 未选中具体二级分类时, 后端 pageByCategory 只返回直接挂在一级分类的商品,
// 需合并其下所有二级子分类商品, 才能显示全; 选中具体二级分类时, 只查该分类
let goodsPromise;
if (this.value.template == 4 && this.categoryId == -1 && this.currentOneCategory && this.currentOneCategory.child_list && this.currentOneCategory.child_list.length) {
// 模板4: 当前一级分类在 currentOneCategory, 合并其下所有二级分类商品
goodsPromise = this.getCategoryGoodsWithChildrenAll(this.currentOneCategory, categoryId, order, sort);
} else if ((this.value.template == 2 || this.value.template == 3) && this.categoryId == -1 && this.category && this.category.child_list && this.category.child_list.length) {
goodsPromise = this.getCategoryGoodsWithChildrenAll(this.category, categoryId, order, sort);
} else {
goodsPromise = this.getCategoryGoodsAll(categoryId, order, sort);
}
goodsPromise.then(list => {
this.goodsList = list;
this._loadedGoodsKey = categoryId;
this.loadGoodsCartNum();
this.loading = false;
if (this.$refs.loadingCover && this.loadType == 'part') this.$refs.loadingCover.hide();
setTimeout(() => {
if (this.loadType == 'all') this.$emit('loadfinish', this.index);
const query = uni.createSelectorQuery().in(this);
query
.select('.goods-list')
.boundingClientRect(data => {
if (data && data.height < this.contentWrapHeight) this
.scrollType = 'none';
})
.exec();
}, 500);
});
},
/**
* 商品图片处理
* @param {Object} imgStr
*/
goodsImg(imgStr) {
let imgs = imgStr.split(',');
return imgs[0] ?
this.$util.img(imgs[0], {
size: this.value == 3 ? 'big' : 'mid'
}) :
this.$util.getDefaultImage().goods;
},
/**
* 分类图片处理
* @param {Object} imgStr
*/
categoryImg(imgStr) {
if (!imgStr) return this.$util.getDefaultImage().goods;
return this.$util.img(imgStr);
},
categoryImgError(level, oneIndex, twoIndex) {
// 分类图片加载失败处理
if (level == 2 && this.category.child_list && this.category.child_list[oneIndex]) {
this.category.child_list[oneIndex].image = '';
} else if (level == 3 && this.category.child_list && this.category.child_list[oneIndex]
&& this.category.child_list[oneIndex].child_list && this.category.child_list[oneIndex].child_list[twoIndex]) {
this.category.child_list[oneIndex].child_list[twoIndex].image = '';
}
},
imgError(index) {
this.goodsList[index].goods_image = this.$util.getDefaultImage().goods;
},
showPrice(data) {
let price = data.discount_price;
if (data.member_price && parseFloat(data.member_price) < parseFloat(price)) price = data.member_price;
return price;
},
showMarketPrice(item) {
if (item.market_price_show) {
let price = this.showPrice(item);
if (item.market_price > 0) {
return item.market_price;
} else if (parseFloat(item.price) > parseFloat(price)) {
return item.price;
}
}
return '';
},
/**
* 跳转商品详情
* @param {Object} item
*/
toDetail(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
/**
* 购物车数量增加
*/
increase(event, data) {
if (!this.storeToken) {
this.$emit('tologin');
return;
}
if (this.cartEditRepeat) return;
let cart =
this.cartList['goods_' + data.goods_id] && this.cartList['goods_' + data.goods_id]['sku_' + data
.sku_id
] ?
this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id] :
null;
let cartNum = cart ? cart.num : 0;
let api = cart && cart.cart_id ? '/api/cart/edit' : '/api/cart/add';
let minBuy = data.min_buy > 0 ? data.min_buy - 1 : 0;
let num = cartNum >= data.min_buy ? cartNum : minBuy;
let _num = num + 1;
let cart_id = cart ? cart.cart_id : 0;
if (_num > parseInt(data.stock)) {
this.$util.showToast({
title: this.$lang('components-diy.diy-category-item.insufficientStock')
});
return;
}
if (data.is_limit && data.max_buy && _num > parseInt(data.max_buy)) {
this.$util.showToast({
title: this.$lang('components-diy.diy-category-item.purchaseLimit', {
maxBuy: data.max_buy,
unit: data.unit || this.$lang('components-diy.diy-category-item.pieceUnit')
})
});
return;
}
const query = uni.createSelectorQuery().in(this);
query
.select('#' + event.currentTarget.id + ' .click-event')
.boundingClientRect(data => {
if (data) {
this.$emit('addCart', data.left, data.top);
}
})
.exec();
if (this.timeout[data.sku_id]) clearTimeout(this.timeout[data.sku_id]);
if (cart) {
this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id].num = _num;
} else {
// 如果商品第一次添加,则初始化数据
if (!this.cartList['goods_' + data.goods_id]) {
this.cartList['goods_' + data.goods_id] = {};
}
let discount_price = data.discount_price;
if (data.member_price > 0 && Number(data.member_price) <= Number(data.discount_price)) {
discount_price = data.member_price;
}
this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id] = {
cart_id,
goods_id: data.goods_id,
sku_id: data.sku_id,
num: _num,
discount_price
};
}
this.$forceUpdate();
// 立即同步购物车数量/金额到 store: 底部结算栏按 cartNumber 显隐,
// 若只等 800ms 防抖 + 接口回调才计算, 会出现"点了加购但结算栏没弹出"
this.$store.dispatch('cartCalculate');
this.timeout[data.sku_id] = setTimeout(() => {
this.cartEditRepeat = true;
this.$api.sendRequest({
url: api,
data: {
cart_id,
sku_id: data.sku_id,
num: _num
},
success: res => {
this.cartEditRepeat = false;
if (res.code == 0) {
if (cart_id == 0) {
this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id].cart_id = res.data;
}
this.$store.dispatch('cartCalculate');
} else {
this.$util.showToast({
title: res.message
});
}
}
});
}, 800);
},
/**
* 购物车数量减少
*/
reduce(data) {
if (this.cartEditRepeat) return;
let cart = this.cartList['goods_' + data.goods_id] && this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id] ? this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id] : null;
let num = cart.num > data.min_buy ? cart.num : 1;
let api = num > 1 ? '/api/cart/edit' : '/api/cart/delete';
let _num = num - 1;
if (this.timeout[data.sku_id]) clearTimeout(this.timeout[data.sku_id]);
if (_num) {
this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id].num = _num;
} else {
delete this.cartList['goods_' + data.goods_id]['sku_' + data.sku_id];
if (Object.keys(this.cartList['goods_' + data.goods_id]).length == 2) {
delete this.cartList['goods_' + data.goods_id];
}
}
this.$forceUpdate();
// 同上: 减购后立即重算, 减到 0 时结算栏同步消失
this.$store.dispatch('cartCalculate');
// 防止点击太快
if (cart && cart.cart_id == 0) {
return;
}
this.timeout[data.sku_id] = setTimeout(() => {
this.cartEditRepeat = true;
this.$api.sendRequest({
url: api,
data: {
cart_id: cart.cart_id,
sku_id: data.sku_id,
num: _num
},
success: res => {
this.cartEditRepeat = false;
if (res.code == 0) {
this.$store.dispatch('cartCalculate');
} else {
this.$util.showToast({
title: res.message
});
}
}
});
}, 800);
},
/**
* 选择规格
* @param {Object} data
*/
selectSku(data) {
if (!this.storeToken) {
this.$emit('tologin');
return;
}
this.$emit('selectsku', data);
},
selectCategory(index) {
this.categoryId = index;
this.pageIndex = 0;
this.totalPage = 1;
this.getGoodsList();
setTimeout(() => {
this.scrollIntoView = 'category-2-' + index;
});
},
/**
* 滚动到底部加载
*/
scrolltolower() {
this.getGoodsList();
},
/**
* 监听滚动
* @param {Object} e
*/
listenScroll(e) {
if (e.detail.scrollTop <= 10) {
this.scrollType = 'top';
} else if (parseInt(e.detail.scrollTop + this.contentWrapHeight + 30) >= e.detail.scrollHeight) {
this.scrollType = 'bottom';
} else {
this.scrollType = '';
}
},
/**
* 触摸开始
* @param {Object} e
*/
touchstart(e) {
this.touchstartPosition = e.changedTouches[0].clientY;
},
/**
* 触摸结束
* @param {Object} e
*/
touchend(e) {
let end = e.changedTouches[0].clientY;
if ((this.scrollType == 'top' || this.scrollType == 'none') && end - this.touchstartPosition > 100) {
this.switchCategory('prev');
} else if ((this.scrollType == 'bottom' || this.scrollType == 'none') && this.touchstartPosition - end >
100) {
this.switchCategory('next');
}
},
/**
* 切换分类
* @param {Object} action
*/
switchCategory(action) {
if (action == 'prev') {
if (this.categoryId == -1) {
let index = this.index - 1;
if (index == -1) return;
this.$emit('switch', index);
} else {
let index = this.categoryId - 1;
this.selectCategory(index);
}
} else {
if (this.categoryId == -1 || (this.category.child_list && this.categoryId == this.category.child_list.length - 1)) {
let index = this.index + 1;
this.$emit('switch', index);
} else {
let index = this.categoryId + 1;
this.selectCategory(index);
}
}
},
/**
* 跳转到商品详情
* @param {Object} item 商品信息
*/
sendToAIChat(item) {
this.$util.redirectTo('/pages_goods/detail', {
goods_id: item.goods_id
});
},
diyRedirectTo(link) {
if (!link) return;
this.$util.diyRedirectTo(JSON.parse(link));
},
/**
* 根据当前一级分类(currentOneCategory)下与选中按钮同名的二级分类, 加载对应商品
* 按钮是全局去重的(选中状态用全局 category_id), 但商品只显示当前一级分类下的
* 当前一级分类下若没有同名二级分类, 则商品清空(按钮保持选中)
*/
refreshGoodsByCurrentCategory() {
if (this.selectedSecondCategory == null) {
this.secondCategoryGoodsList = [];
return;
}
// 取当前选中按钮的名称
const cur = (this.category.child_list || []).find(c => c.category_id === this.selectedSecondCategory);
if (!cur) {
this.secondCategoryGoodsList = [];
return;
}
const one = this.currentOneCategory;
if (!one || !one.child_list || one.child_list.length === 0) {
this.secondCategoryGoodsList = [];
return;
}
const match = one.child_list.find(c => c.category_name === cur.category_name);
if (match) {
this.getSecondCategoryGoods(match.category_id);
} else {
// 当前一级分类下没有该二级分类, 商品清空(按钮仍保持选中)
this.secondCategoryGoodsList = [];
}
},
selectSecondCategory(category) {
if (this.selectedSecondCategory === category.category_id) {
this.selectedSecondCategory = null;
this.secondCategoryGoodsList = [];
} else {
this.selectedSecondCategory = category.category_id;
this.secondCategoryGoodsList = [];
this.$emit('saveSecondCategory', category.category_name);
// 用当前一级分类下同名 category_id 查商品
this.refreshGoodsByCurrentCategory();
}
},
getSecondCategoryGoods(categoryId) {
this.loading = true;
let order = '';
let sort = '';
if (this.value.sortWay) {
if (this.value.sortWay == 'default') {
order = '';
sort = '';
} else if (this.value.sortWay == 'sales') {
order = 'sales';
sort = 'desc';
} else if (this.value.sortWay == 'price') {
order = 'min_price';
sort = this.value.sortType == 'desc' ? 'desc' : 'asc';
}
}
this.getCategoryGoodsAll(categoryId, order, sort).then(list => {
this.loading = false;
this.secondCategoryGoodsList = list;
}).catch(() => {
this.loading = false;
this.secondCategoryGoodsList = [];
});
},
/**
* 模板1: 选择分类后加载该分类下的商品
* @param {Object} category 选中的分类对象
*/
selectCategoryForGoods(category) {
if (this.selectedCategoryId === category.category_id) {
return; // 已选中,不重复加载
}
this.selectedCategoryId = category.category_id;
this.template1GoodsList = [];
this.getTemplate1Goods(category.category_id);
},
/**
* 模板1: 根据分类ID获取商品列表
*/
getTemplate1Goods(categoryId) {
this.template1Loading = true;
this.getCategoryGoodsAll(categoryId).then(list => {
this.template1GoodsList = list;
this.template1Loading = false;
}).catch(() => {
this.template1GoodsList = [];
this.template1Loading = false;
});
},
/**
* 模板1二级分类模式: 加载当前一级分类下所有二级分类的商品并合并
* 避免只按一级分类ID查询时, 后端不返回子分类商品, 导致商品显示不全
*/
loadTemplate1Level2Goods() {
this.template1Loading = true;
const promises = this.category.child_list.map(child => this.getCategoryGoodsAll(child.category_id));
Promise.all(promises).then(lists => {
const all = [];
const seen = new Set();
lists.forEach(list => {
list.forEach(item => {
const key = item.sku_id || item.goods_id;
if (key && !seen.has(key)) {
seen.add(key);
all.push(item);
}
});
});
this.template1GoodsList = all;
this.template1Loading = false;
}).catch(() => {
this.template1GoodsList = [];
this.template1Loading = false;
});
},
/**
* 模板1: 根据二级分类ID分别加载各自的商品列表
* 用于 categoryLevel == 3 场景, 每个二级分类展示自己的商品
* @param {Number} categoryId 二级分类ID
*/
getTemplate1ChildGoods(categoryId) {
this.$set(this.template1ChildLoading, categoryId, true);
this.getCategoryGoodsAll(categoryId).then(list => {
this.$set(this.template1ChildLoading, categoryId, false);
this.$set(this.template1ChildGoodsList, categoryId, list);
}).catch(() => {
this.$set(this.template1ChildLoading, categoryId, false);
this.$set(this.template1ChildGoodsList, categoryId, []);
});
},
/**
* 模板1: 商品图片加载失败处理
* @param {Number} categoryId 二级分类ID(可选,用于按子分类定位)
* @param {Number} index 商品在列表中的索引
*/
template1ImgError(categoryId, index) {
// 兼容旧调用: 仅传 index 的情况(categoryLevel == 2)
if (categoryId === undefined || categoryId === null) {
if (this.template1GoodsList[index]) {
this.template1GoodsList[index].goods_image = this.$util.getDefaultImage().goods;
}
return;
}
const list = this.template1ChildGoodsList[categoryId];
if (list && list[index]) {
list[index].goods_image = this.$util.getDefaultImage().goods;
}
},
//加载商品在购物车的总数量
loadGoodsCartNum(isRefresh = false) {
this.goodsList.forEach((item) => {
item.num = this.cartList['goods_' + item.goods_id] ? this.cartList['goods_' + item.goods_id].num : 0;
})
if (isRefresh) this.$forceUpdate();
}
}
};
</script>
<style lang="scss" scoped>
.small {
font-size: 28rpx !important;
}
.item-wrap {
width: 100%;
box-sizing: border-box;
padding: 0 0rpx;
&.goods {
height: 100%;
}
}
.category-title {
padding: 20rpx 0;
font-size: var(--diy-category-item-title-size, 26rpx);
// font-weight: bold;
color: #222;
position: relative;
text-align: left;
padding-left: 30rpx;
// &::after,
// &::before {
// content: ' ';
// width: 46rpx;
// border-top: 2rpx solid #dfdfdf;
// position: absolute;
// top: 50%;
// }
&::after {
margin-left: 40rpx;
}
&::before {
margin-left: -80rpx;
}
}
// 模板1 专用: 标题居中 + 两侧横线
.category-title-with-lines {
display: flex !important;
align-items: center;
justify-content: center;
text-align: center;
padding: 30rpx 0 20rpx !important;
padding-left: 0 !important;
color: #333;
font-size: 30rpx;
font-weight: 500;
letter-spacing: 2rpx;
// 删除标题行整块的白色块, 背景设为透明, 让上层网格的 #f5f5f5 透出,
// 标题行与下方 .template1-goods-wrap 灰色融为一体, 只保留居中的文字与两侧短横线
background: transparent !important;
&::before,
&::after {
content: '';
flex: 0 0 60rpx;
height: 0;
border-top: 1rpx solid #d9d9d9;
margin: 0 24rpx;
}
}
.category-adv {
width: 100%;
overflow: hidden;
line-height: 1;
padding: 20rpx 0;
image {
width: 100%;
border-radius: 8rpx;
}
}
// 分类网格(模板5 / legacy 商品分类): 白底卡片 + 3 列 图片 + 名称
// 与 Branch_5cfee417 的商品分类保持一致
.category-list {
display: flex;
flex-wrap: wrap;
background: #fff;
margin: 0 20rpx;
border-radius: 10rpx;
padding: 10rpx 0;
.category-item {
width: 33.33%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16rpx;
box-sizing: border-box;
.img-box {
width: 80%;
height: 0;
padding-top: 80%;
margin: 0 auto;
overflow: hidden;
margin-bottom: 20rpx;
position: relative;
background: transparent;
image {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
transform: none;
}
}
.name {
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
text-align: center;
line-height: 1;
font-size: var(--diy-category-item-name-size, 24rpx);
}
&.selected {
.img-box {
border: 2rpx solid var(--base-color, #FF6B35);
border-radius: 8rpx;
}
.name {
color: var(--base-color, #FF6B35);
font-weight: bold;
}
}
}
}
// 模板1商品列表容器
.template1-goods-wrap {
padding: 20rpx;
background: #f5f5f5;
.goods-list {
margin: 0;
padding-top: 0;
display: flex;
.goods-item {
width: calc(50% - 10rpx);
background: #fff;
border-radius: 12rpx;
overflow: hidden;
margin-bottom: 20rpx;
// 划线价放在现价正下方(左边与现价对齐, 间距 6rpx)
.price-wrap {
.delete-price {
margin-left: 0;
margin-top: 6rpx;
width: 100%;
display: inline-flex;
align-items: baseline;
text-decoration: line-through;
}
}
.footer-wrap-template1 {
padding: 16rpx 4rpx 0rpx;
display: flex;
justify-content: flex-end;
.detail-btn {
display: flex;
align-items: center;
justify-content: center;
padding: 0 32rpx;
height: 55rpx;
background-color: #ff4545;
color: #fff;
font-size: 25rpx;
border-radius: 26rpx;
box-sizing: border-box;
white-space: nowrap;
}
}
}
}
}
.goods-list {
/*双列*/
display: none;
margin: 0 24rpx;
padding-top: $margin-updown;
position: relative;
flex-wrap: wrap;
justify-content: space-between;
&.show {
display: flex;
}
.goods-item {
display: flex;
flex-direction: column;
border-radius: $border-radius;
overflow: hidden;
background-color: #fff;
margin-bottom: 20rpx;
.name-wrap {
display: var(--diy-category-item-name-wrap-display, block);
flex: var(--diy-category-item-name-wrap-flex, auto);
overflow: var(--diy-category-item-name-wrap-overflow, visible);
}
.goods-img {
position: relative;
overflow: hidden;
padding-top: 100%;
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
image {
width: 100%;
position: absolute !important;
top: 50%;
left: 0;
transform: translateY(-50%);
}
}
.goods-tag {
color: #fff;
line-height: 1;
padding: 8rpx 16rpx;
position: absolute;
border-bottom-right-radius: $border-radius;
top: 0;
left: 0;
font-size: var(--diy-category-item-goods-tag-size, 22rpx);
}
.goods-tag-img {
position: absolute;
border-top-left-radius: $border-radius;
width: 80rpx;
height: 80rpx;
top: 0;
left: 0;
z-index: 5;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.info-wrap {
padding: var(--diy-category-item-info-wrap-padding, 0 20rpx);
display: flex;
flex-direction: column;
flex: 1;
}
.goods-name {
font-size: var(--diy-category-item-goods-name-size, 28rpx);
line-height: var(--diy-category-item-goods-name-line-height, 1.3);
margin-top: var(--diy-category-item-goods-name-margin-top, 20rpx);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.lineheight-clear {
margin-top: 16rpx;
}
.price-wrap {
// 原 SCSS 完全没有 .price-wrap 样式定义(元素是 block 布局,初始值)
// 保留 5 个属性的主题化能力,但所有 fallback 都设为 initial value,
// 不改变原渲染。远端可覆写为 flex / baseline / wrap / space-between / 8rpx
display: var(--diy-category-item-price-wrap-display, block);
align-items: var(--diy-category-item-price-wrap-align-items, normal);
flex-wrap: var(--diy-category-item-price-wrap-flex-wrap, nowrap);
justify-content: var(--diy-category-item-price-wrap-justify-content, normal);
margin-top: var(--diy-category-item-price-wrap-margin-top, 0);
}
.discount-price {
display: inline-block;
font-weight: var(--diy-category-item-discount-price-weight, bold);
line-height: var(--diy-category-item-discount-price-line-height, 1);
.unit {
margin-right: 6rpx;
color: var(--price-color);
}
.price {
color: var(--price-color);
}
}
.pro-info {
display: flex;
margin-top: auto;
align-items: center;
.block-wrap {
flex: 1;
line-height: 1;
margin-right: 20rpx;
.sale {
font-size: $font-size-tag !important;
}
}
}
.delete-price {
// display: inline-block;
// margin-left: 6rpx;
// float: right;
.unit {
margin-right: 6rpx;
}
text {
line-height: var(--diy-category-item-delete-price-line-height, 1);
font-size: var(--diy-category-item-delete-price-size, $font-size-tag) !important;
}
}
.member-price-tag {
display: inline-block;
width: var(--diy-category-item-member-price-tag-width, 60rpx);
line-height: var(--diy-category-item-member-price-tag-line-height, 1);
margin-left: var(--diy-category-item-member-price-tag-margin-left, 6rpx);
image {
width: 100%;
}
}
.footer-wrap {
display: var(--diy-category-item-footer-wrap-display, flex);
justify-content: var(--diy-category-item-footer-wrap-justify-content, space-between);
align-items: var(--diy-category-item-footer-wrap-align-items, normal);
margin-top: var(--diy-category-item-footer-wrap-margin-top, 0);
}
.consult-now-btn-wrap {
display: var(--diy-category-item-consult-now-wrap-display, flex);
justify-content: var(--diy-category-item-consult-now-wrap-justify-content, flex-end);
margin-top: var(--diy-category-item-consult-now-wrap-margin-top, auto);
padding-top: var(--diy-category-item-consult-now-wrap-padding-top, 10rpx);
}
.sell-out {
position: absolute;
z-index: 1;
width: 100%;
height: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
text {
color: #fff;
font-size: 250rpx;
}
}
}
/* 双列布局 */
&.double-column {
.goods-item {
width: calc(50% - 10rpx);
}
}
/* 单列布局(不包含模板2/4,模板2/4使用模板4卡片样式) */
&.single-column:not([data-template='2']):not([data-template='4']) {
flex-direction: column;
.goods-item {
width: 100%;
flex-direction: row;
align-items: center;
.goods-img {
width: 180rpx;
height: 180rpx;
padding-top: 0;
margin-right: 14rpx;
border-radius: $border-radius;
image {
border-radius: $border-radius;
}
}
.goods-name {
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
}
/**/
/*.goods-item {
padding: 0 0 26rpx 0;
background: #fff;
display: flex;
position: relative;
.goods-img {
width: 180rpx;
height: 180rpx;
overflow: hidden;
border-radius: $border-radius;
margin-right: 14rpx;
image {
width: 100%;
height: 100%;
}
}
.goods-tag {
color: #fff;
line-height: 1;
padding: 8rpx 12rpx;
position: absolute;
border-top-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
top: 0;
left: 0;
font-size: $font-size-goods-tag;
}
.goods-tag-img {
position: absolute;
border-top-left-radius: $border-radius;
width: 80rpx;
height: 80rpx;
top: 26rpx;
left: 26rpx;
z-index: 5;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
width: 0;
}
.name-wrap {
flex: 1;
}
.goods-name {
font-size: var(--diy-category-item-goods-name-size, $font-size-base);
line-height: var(--diy-category-item-goods-name-line-height, 1.3);
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.introduction {
line-height: 1;
margin-top: 10rpx;
}
.discount-price {
display: inline-block;
font-weight: var(--diy-category-item-discount-price-weight, bold);
line-height: var(--diy-category-item-discount-price-line-height, 1);
margin-top: var(--diy-category-item-discount-price-margin-top, 16rpx);
color: var(--price-color);
.unit {
margin-right: 6rpx;
}
}
.pro-info {
display: flex;
margin-top: var(--diy-category-item-pro-info-margin-top, 16rpx);
.delete-price {
text-decoration: var(--diy-category-item-delete-price-text-decoration, line-through);
flex: 1;
.unit {
margin-right: 0rpx;
}
}
&>view {
line-height: 1;
&:nth-child(2) {
text-align: right;
}
}
}
.member-price-tag {
display: inline-block;
width: var(--diy-category-item-member-price-tag-width, 60rpx);
line-height: var(--diy-category-item-member-price-tag-line-height, 1);
margin-left: var(--diy-category-item-member-price-tag-margin-left, 6rpx);
height: var(--diy-category-item-member-price-tag-height, 28rpx);
image {
width: 100%;
height: 100%;
}
}
.footer-wrap {
display: flex;
justify-content: space-between;
.right-wrap {
display: flex;
align-items: center;
justify-content: flex-end;
.num {
width: auto;
padding: 0 20rpx;
line-height: 1;
}
.num-action {
display: flex;
align-items: center;
justify-content: center;
width: 40rpx;
height: 40rpx;
background: $base-color;
border-radius: 50%;
position: relative;
.click-event {
position: absolute;
width: 2rpx;
height: 2rpx;
left: 0;
top: 0;
transform: translate(-50%, -50%);
z-index: 5;
}
&.reduce {
width: 38rpx;
height: 38rpx;
background-color: transparent;
border: 2rpx solid $base-color;
box-sizing: border-box;
.icon-jian {
color: $base-color;
}
}
}
.icon-jian,
.icon-jia {
color: var(--btn-text-color);
font-weight: bold;
font-size: 26rpx;
line-height: 1;
}
.select-sku {
font-weight: bold;
color: var(--btn-text-color);
font-size: 24rpx;
padding: 16rpx 24rpx;
position: relative;
// height: 40rpx;
line-height: 1;
text-align: center;
border-radius: 50rpx;
.num-tag {
position: absolute;
top: 0;
right: 0;
transform: translateY(-50%);
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 24rpx;
padding: 0 6rpx;
min-width: 32rpx;
border-radius: 16rpx;
background-color: $base-color;
border: 2rpx solid #fff;
}
}
}
}
}*/
// ===================== 模板3 商品分类样式 (卡片式布局: 图片顶部 + 名称上行 + 价格和按钮下行) =====================
&[data-template='3'] {
// 模板3 的商品列表容器: 灰色背景让卡片有层次
background: #f4f6fa;
// 重置 double-column 的影响, 强制单列布局
.goods-item {
display: block;
flex-direction: column;
align-items: stretch;
width: 100% !important;
max-width: 100%;
background: #fff;
border-radius: 12rpx;
overflow: hidden;
margin: 0 0 20rpx;
padding: 0;
box-sizing: border-box;
}
// 强制取消 single-column 的 row 布局(防止同时应用)
&.single-column .goods-item,
&.double-column .goods-item {
flex-direction: column;
align-items: stretch;
}
// 图片: 占满卡片宽度, 自适应高度(类似 banner)
.goods-img {
width: 100%;
height: 0;
padding-top: 50%;
position: relative;
margin-right: 0;
line-height: 1;
border-radius: 0;
overflow: hidden;
image {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
transform: none;
border-radius: 0;
}
}
// 信息区域: 上下结构 (名称一行 + 价格+按钮一行)
.info-wrap {
display: flex;
flex-direction: column;
width: 100%;
margin-top: 0;
padding: 24rpx;
box-sizing: border-box;
}
// 商品名称: 单行省略
.name-wrap {
width: 100%;
.goods-name {
font-size: 28rpx;
line-height: 1.4;
margin-top: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: #333;
}
}
// 底部行: 价格在左, 按钮在右
.info-bottom {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12rpx;
}
// 价格区域: 现价在上, 划线价在下方
.price-wrap {
display: flex;
flex-direction: column;
align-items: flex-start;
flex: 1;
min-width: 0;
.discount-price {
display: inline-flex;
align-items: baseline;
font-weight: bold;
line-height: 1;
font-size: 30rpx;
color: var(--price-color, #FF6B35);
.unit {
font-size: 22rpx;
margin-right: 2rpx;
color: var(--price-color, #FF6B35);
}
.price {
font-size: 30rpx;
color: var(--price-color, #FF6B35);
}
}
// 删除线价格: 在现价下方
.delete-price {
margin-left: 0;
margin-top: 6rpx;
display: inline-flex;
align-items: baseline;
text-decoration: line-through;
font-size: 24rpx;
color: #999;
.unit {
margin-right: 2rpx;
}
}
}
// 立即购买按钮: 在底部行右侧
.buy-now-btn {
flex-shrink: 0;
margin-left: 20rpx;
width: 160rpx;
height: 56rpx;
line-height: 56rpx;
text-align: center;
font-size: 26rpx;
font-weight: bold;
color: #fff;
border-radius: 28rpx;
}
}
// ===================== 模板2/4 商品分类样式(支持右上角视图切换) =====================
// 单列视图: 左图右文横向卡片 + 右侧+按钮
// 双列视图: 上下结构宫格卡片, 每行两个
&[data-template='2'],
&[data-template='4'] {
// 整体灰色背景让卡片有层次
background: #f4f6fa;
padding: 20rpx;
margin: 0;
.goods-item {
display: flex;
align-items: stretch;
max-width: 100%;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
margin: 0 0 20rpx;
box-sizing: border-box;
position: relative;
}
// 单列视图: 整行横向卡片
&.single-column .goods-item {
flex-direction: row;
width: 100% !important;
padding: 20rpx;
}
// 双列视图: 每行两个纵向卡片
&.double-column .goods-item {
flex-direction: column;
width: calc(50% - 10rpx) !important;
padding: 0;
.goods-img {
width: 100%;
height: 0;
padding-top: 100%;
margin-right: 0;
border-radius: 12rpx 12rpx 0 0;
image {
border-radius: 12rpx 12rpx 0 0;
}
}
.info-wrap {
padding: 16rpx 16rpx 20rpx;
}
// 竖卡片下价格不再给右侧按钮留位
.price-wrap {
margin-top: auto;
padding-right: 0;
}
// 加购/选规格按钮从绝对定位改为跟在价格下方右侧, 避免遮挡价格
.footer-wrap {
position: static;
right: auto;
bottom: auto;
display: flex;
justify-content: flex-end;
align-items: center;
margin-top: 12rpx;
padding: 0 16rpx;
}
}
// 商品图片: 左侧正方形
.goods-img {
width: 200rpx;
height: 200rpx;
padding-top: 0;
margin-right: 20rpx;
border-radius: 12rpx;
flex-shrink: 0;
position: relative;
overflow: hidden;
background: #fff;
image {
width: 100%;
height: 100%;
position: absolute !important;
top: 0;
left: 0;
transform: none;
border-radius: 12rpx;
}
// 标签(秒杀/爆款): 位于图片左上角
.goods-tag {
position: absolute;
top: 0;
left: 0;
padding: 6rpx 16rpx;
font-size: 22rpx;
color: #fff;
background: $base-color;
border-bottom-right-radius: 12rpx;
border-top-left-radius: 12rpx;
z-index: 2;
line-height: 1;
}
}
// 信息区域: 右侧占满
.info-wrap {
flex: 1;
display: flex;
flex-direction: column;
padding: 0;
min-width: 0;
position: relative;
}
// 商品名称: 2行省略
.name-wrap {
width: 100%;
.goods-name {
font-size: 26rpx;
line-height: 1.4;
margin-top: 0;
white-space: normal;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
color: #333;
}
}
// 价格区域: 当前价(红) + 删除价(灰色删除线)
.price-wrap {
margin-top: auto;
display: flex;
align-items: baseline;
flex-wrap: wrap;
padding-right: 90rpx; // 留出右侧+按钮的位置
.discount-price {
display: inline-flex;
align-items: baseline;
color: var(--price-color, #FF6B35);
font-weight: bold;
line-height: 1;
.unit {
font-size: 24rpx;
margin-right: 2rpx;
color: var(--price-color, #FF6B35);
}
.price {
font-size: 36rpx;
color: var(--price-color, #FF6B35);
}
}
// 删除线价格: 紧跟当前价,灰色
.delete-price {
margin-left: 16rpx;
display: inline-flex;
align-items: baseline;
text-decoration: line-through;
font-size: 24rpx;
color: #999;
line-height: 1;
.unit {
margin-right: 2rpx;
}
}
}
// 底部行: 仅在模板4下保留用于挂载 right-wrap
.footer-wrap {
position: absolute;
right: 20rpx;
bottom: 20rpx;
margin-top: 0;
padding: 0;
}
// 右侧+按钮 / 择规格按钮
.right-wrap {
display: flex;
align-items: center;
justify-content: flex-end;
margin-top: 0;
padding: 0;
.num {
width: auto;
padding: 0 16rpx;
line-height: 1;
color: #333;
font-size: 28rpx;
}
.num-action {
display: flex;
align-items: center;
justify-content: center;
width: 50rpx;
height: 50rpx;
background: $base-color;
border-radius: 8rpx;
position: relative;
box-shadow: 0 2rpx 6rpx rgba(255, 107, 53, 0.3);
.click-event {
position: absolute;
width: 2rpx;
height: 2rpx;
left: 0;
top: 0;
transform: translate(-50%, -50%);
z-index: 5;
}
&.reduce {
width: 48rpx;
height: 48rpx;
background-color: #fff;
border: 2rpx solid $base-color;
box-sizing: border-box;
.icon-jian {
color: $base-color;
}
}
}
.icon-jian,
.icon-jia {
color: var(--btn-text-color, #fff);
font-weight: bold;
font-size: 30rpx;
line-height: 1;
}
// 择规格按钮
.select-sku {
font-weight: bold;
color: var(--btn-text-color, #fff);
font-size: 24rpx;
padding: 12rpx 24rpx;
position: relative;
line-height: 1;
text-align: center;
border-radius: 30rpx;
background: $base-color;
box-shadow: 0 2rpx 6rpx rgba(255, 107, 53, 0.3);
.num-tag {
position: absolute;
top: -8rpx;
right: -8rpx;
display: inline-block;
box-sizing: border-box;
color: #fff;
line-height: 1.2;
text-align: center;
font-size: 22rpx;
padding: 0 6rpx;
min-width: 28rpx;
border-radius: 16rpx;
background-color: var(--price-color, #FF6B35);
border: 2rpx solid #fff;
}
}
}
}
// 模板2/4: 划线价放在现价正下方(price-wrap 为 flex wrap, 划线价占满一行即换到现价下一行, 左边与现价对齐)
&[data-template='2'],
&[data-template='4'] {
.price-wrap .delete-price {
margin-left: 0;
margin-top: 6rpx;
width: 100%;
}
}
}
.categoty-goods-wrap {
display: flex;
flex-direction: column;
height: 100%;
.scroll-goods-view {
flex: 1;
height: 0;
transform: translateX(0px);
}
}
.screen-category-wrap {
display: flex;
align-items: center;
position: relative;
.icon-unfold {
position: absolute;
right: 15rpx;
top: 0;
z-index: 2;
width: 70rpx;
height: 70rpx;
line-height: 62rpx;
text-align: center;
font-size: 36rpx;
color: #999;
padding: 0;
}
}
.screen-category {
flex: 1;
width: 0;
padding: 0 70rpx 20rpx 0;
white-space: nowrap;
height: 60rpx;
.item {
display: inline-block;
padding: 4rpx 24rpx;
background: #f5f5f5;
color: #666;
margin-right: 20rpx;
border-radius: 40rpx;
&.selected {
background-color: $base-color;
color: var(--btn-text-color);
}
}
}
::v-deep .uni-popup__wrapper-box {
border-radius: 0;
}
.screen-category-popup {
display: flex;
.screen-category {
white-space: break-spaces;
padding: 20rpx;
height: auto;
}
.title {
line-height: 1;
margin-bottom: 20rpx;
font-weight: bold;
}
.item {
margin-bottom: 20rpx;
}
}
.end-tips {
text-align: center;
color: #999;
font-size: 24rpx;
padding: 30rpx 0 30rpx 0;
display: flex;
align-items: center;
justify-content: center;
}
::v-deep .loading-layer {
background: #fff !important;
}
.category-empty {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding-top: 100rpx;
image {
width: 280rpx;
height: 252rpx;
}
.tips {
font-size: 26rpx;
font-weight: 500;
color: #999;
margin-top: 50rpx;
}
}
.screen-category-4 .item {
background-color: #f2f2f2 !important;
padding: 16rpx 40rpx;
line-height: 1;
font-size: $font-size-tag;
&.selected {
background-color: $base-color !important;
color: #fff;
}
}
.consult-now-btn-wrap {
display: var(--diy-category-item-consult-now-wrap-display, flex);
justify-content: var(--diy-category-item-consult-now-wrap-justify-content, flex-end);
margin-top: var(--diy-category-item-consult-now-wrap-margin-top, auto);
padding-top: var(--diy-category-item-consult-now-wrap-padding-top, 10rpx);
}
.consult-now-btn {
// 兼容: 微信小程序 / 鸿蒙元服务 — 所有 var() 属性无问题
// 10 个属性都用 --diy-category-item-consult-now-* 独立 token,
// 便于后台单独调整任何一项(配色 / 尺寸 / 字重 / 对齐 / 显示模式)
background-color: var(--diy-category-item-consult-now-bg, #FF4646);
color: var(--diy-category-item-consult-now-text, #FFFFFF);
padding: var(--diy-category-item-consult-now-padding, 12rpx 30rpx);
border-radius: var(--diy-category-item-consult-now-radius, 40rpx);
font-size: var(--diy-category-item-consult-now-size, 26rpx);
font-weight: var(--diy-category-item-consult-now-weight, 500);
line-height: var(--diy-category-item-consult-now-line-height,1.2);
text-align: var(--diy-category-item-consult-now-text-align, center);
white-space: var(--diy-category-item-consult-now-white-space,nowrap);
display: var(--diy-category-item-consult-now-display, inline-block);
}
.font-size-activity-tag {
font-size: var(--diy-category-item-delete-price-size, $font-size-activity-tag) !important;
}
.color-tip {
color: var(--diy-category-item-color-tip-color, $color-tip) !important;
}
.price-font {
font-weight: var(--diy-category-item-price-font-weight, 500) !important;
}
// ===================== 模板99 商品分类样式 =====================
.second-category-btns {
white-space: nowrap;
padding: 20rpx 20rpx 20rpx 24rpx;
height: 100rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
box-sizing: border-box;
.second-category-btn {
display: inline-block;
padding: 10rpx 24rpx;
background: #f5f5f5;
color: #666;
border-radius: 30rpx;
font-size: 20rpx;
margin: 0 14rpx 0rpx 0;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
&.selected {
background: #FF4646;
color: #FFFFFF;
}
}
}
.category-goods-list {
padding: 20rpx;
background: #f5f5f5;
width: 100%;
box-sizing: border-box;
.goods-list {
margin: 0;
padding-top: 0;
display: flex;
.goods-item {
width: calc(50% - 10rpx);
background: #fff;
border-radius: 12rpx;
overflow: hidden;
margin-bottom: 20rpx;
display: flex;
flex-direction: column;
box-sizing: border-box;
.goods-img {
width: 100%;
height: 0;
padding-top: 100%;
position: relative;
overflow: hidden;
background: #fff;
border-top-left-radius: 12rpx;
border-top-right-radius: 12rpx;
image {
width: 100%;
height: 100%;
position: absolute !important;
top: 0;
left: 0;
transform: none;
}
.goods-tag {
position: absolute;
top: 10rpx;
left: 10rpx;
padding: 4rpx 12rpx;
color: #fff;
font-size: 22rpx;
border-radius: 6rpx;
background: #FF6B35;
z-index: 2;
}
}
.info-wrap {
flex: 1;
padding: 16rpx;
display: flex;
flex-direction: column;
}
.name-wrap {
.goods-name {
font-size: 26rpx;
color: #333;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
margin-top: 0;
white-space: normal;
text-overflow: clip;
}
}
.price-wrap {
margin-top: 10rpx;
display: flex;
align-items: baseline;
justify-content: space-between;
// 划线价占满一行, 换行到现价正下方
flex-wrap: wrap;
.discount-price {
color: #FF6B35;
font-size: 30rpx;
font-weight: bold;
.unit {
font-size: 22rpx;
}
}
.delete-price {
margin-left: 0;
margin-top: 6rpx;
width: 100%;
display: inline-flex;
align-items: baseline;
text-decoration: line-through;
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
margin-top: 10rpx;
}
}
&.single-column {
flex-direction: column;
padding: 0 20rpx 0 8rpx;
gap: 20rpx;
.goods-item {
width: calc(100% + 20rpx);
flex-direction: row;
align-items: flex-start;
background: #fff;
border-radius: 12rpx;
margin-bottom: 0;
padding: 20rpx;
box-sizing: border-box;
height: 220rpx;
.goods-img {
width: 180rpx;
height: 180rpx;
padding-top: 0;
margin-right: 14rpx;
border-radius: 12rpx;
flex-shrink: 0;
image {
border-radius: 12rpx;
}
}
.info-wrap {
flex: 1;
padding: 0;
display: flex;
flex-direction: column;
height: 100%;
}
.name-wrap {
.goods-name {
font-size: 28rpx;
color: #333;
-webkit-line-clamp: 2;
display: -webkit-box;
-webkit-box-orient: vertical;
overflow: hidden;
line-height: 1.4;
margin-top: 0;
white-space: normal;
text-overflow: clip;
}
}
.price-wrap {
margin-top: 8rpx;
display: flex;
align-items: baseline;
justify-content: flex-start;
.discount-price {
color: #FF6B35;
font-size: 32rpx;
font-weight: bold;
.unit {
font-size: 24rpx;
}
}
}
.footer-wrap {
display: flex;
justify-content: flex-end;
margin-top: auto;
}
}
}
&.double-column {
.goods-item {
width: calc(50% - 10rpx);
flex-direction: column;
background: #fff;
border-radius: 12rpx;
margin-bottom: 20rpx;
}
}
}
}
.category-goods-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 80rpx 0;
background: #f5f5f5;
width: 100%;
image {
width: 280rpx;
height: 252rpx;
}
.tips {
font-size: 26rpx;
color: #999;
margin-top: 20rpx;
}
}
</style>