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

2530 lines
74 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">
<view class="second-category-btns">
<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>
</view>
<view class="category-goods-list" v-if="selectedSecondCategory">
<view class="goods-list double-column show" :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>
</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 样式) -->
<block v-else-if="isGroupedDisplay">
<block v-for="(one, oneIndex) in category.child_list" :key="oneIndex">
<view :class="['category-title', 'category-title-with-lines']">{{ one.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list double-column show" 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="one.category_id + '-' + 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>
</view>
</view>
</view>
<view class="category-goods-empty" v-else-if="!template1ChildLoading[one.category_id]">
<image :src="$util.img('public/uniapp/category/empty.png')" mode="widthFix" show-menu-by-longpress></image>
<view class="tips">暂无商品</view>
</view>
</view>
</block>
</block>
<!-- 模板1: 后台配置二级分类 + 有子分类时, 在一级分类标题下以双列网格显示所有商品(图 c2df0f3d29 样式) -->
<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 double-column show" 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>
</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>
<!-- 非模板1, 2级分类: 显示二级分类网格,点击跳转到列表页 -->
<block v-else-if="categoryLevel == 2">
<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 v-else-if="categoryLevel == 3">
<block v-for="(one, oneIndex) in category.child_list" :key="oneIndex">
<view :class="['category-title', '']">{{ one.category_name }}</view>
<view class="category-list">
<view class="category-item" v-for="(two, twoIndex) in one.child_list" :key="twoIndex"
@tap.stop="$util.redirectTo('/pages_goods/list', { category_id: two.category_id })">
<view class="img-box">
<image :src="categoryImg(two.image)" mode="aspectFit" @error="categoryImgError(3, oneIndex, twoIndex)" show-menu-by-longpress />
</view>
<view class="name">{{ two.category_name }}</view>
</view>
</view>
</block>
</block>
</block>
<!-- 模板1: 无子分类时(外层块不进入), 在一级分类标题下以双列网格显示所有商品(图 c2df0f3d29 样式) -->
<view v-else-if="value.template == 1 && type == 'category'">
<view :class="['category-title', 'category-title-with-lines']">{{ category.category_name }}</view>
<view class="template1-goods-wrap">
<view class="goods-list double-column show" 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>
</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.goodsLevel == 2 && (value.template == 4 ? (currentOneCategory && currentOneCategory.child_list) : category.child_list)">
<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>
<!-- 模板4: 删除线价格(图片样式) -->
<view class="delete-price font-size-activity-tag color-tip price-font"
v-if="showMarketPrice(item) && 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">
<text class="unit">{{ $lang('common.currencySymbol') }}</text>
<text>{{ showMarketPrice(item) }}</text>
</view>
</view>
<view class="consult-now-btn-wrap" v-if="item.isinformation == 1 && value.template != 4">
<view class="consult-now-btn" @tap.stop="sendToAIChat(item)">
{{ $lang('components-diy.diy-category-item.consultNow') }}
</view>
</view>
<!-- 模板4: 右侧+按钮/择规格按钮(图片样式) -->
<view class="right-wrap" v-if="value.template == 4">
<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 && value.template != 4">
<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">
<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 {
type: 'goods',
level: 3,
categoryId: -1,
loading: false,
goodsList: [],
pageIndex: 0,
pageSize: 10,
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: {
/**
* 当前分类层级 (参考 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() {
return this.value.template == 1 && this.value.level == 3;
}
},
created() {
this.type = this.value.template == 1 || this.value.template == 99 ? 'category' : 'goods';
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) + 有二级分类时,按二级分类分别加载各自商品
// 其他情况(二级分类模式 / 无二级分类),直接加载当前一级分类下的所有商品
if (this.value.template == 1 && 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 {
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 () {
if (this.index != this.select) return;
this.scrollTop = -1;
this.goodsList = [];
this.selectCategory(-1);
},
select: function () {
if (this.index == this.select) {
this.scrollTop = 0;
this.categoryId = -1;
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:切换一级分类时重新加载该分类下的商品
// 三级分类模式 + 有二级分类时,按二级分类分别重新加载,否则直接加载当前分类
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 {
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: {
/**
* 查询商品列表
*/
getGoodsList() {
if (this.type != 'goods' || this.loading || this.pageIndex >= this.totalPage) return;
this.loading = true;
this.pageIndex++;
if (this.$refs.loadingCover && this.pageIndex == 1 && this.loadType == 'part') {
setTimeout(() => {
this.$refs.loadingCover.show();
});
}
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';
}
}
const childList = this.value.template == 4 && this.currentOneCategory ? this.currentOneCategory.child_list : this.category.child_list;
const baseCategoryId = this.value.template == 4 && this.currentOneCategory ? this.currentOneCategory.category_id : this.category.category_id;
this.$api.sendRequest({
url: '/api/goodssku/pageByCategory',
data: {
page: this.pageIndex,
page_size: this.pageSize,
category_id: this.categoryId != -1 ? childList[this.categoryId].category_id : baseCategoryId,
order: order,
sort: sort
},
success: res => {
if (res.code == 0 && res.data) {
if (this.pageIndex == 1) this.goodsList = [];
//切换英文标题
if (uni.getStorageSync("lang") == 'en-us') {
// res.data.list.map(item => {
// item.goods_name = item.en_goods_name;
// return item;
// });
}
this.goodsList = this.goodsList.concat(res.data.list);
this.loadGoodsCartNum();
this.totalPage = res.data.page_count;
this.loading = false;
if (this.$refs.loadingCover && this.loadType == 'part') this.$refs.loadingCover.hide();
setTimeout(() => {
if (this.loadType == 'all') this.$emit('loadfinish', this.pageIndex == 1 ? this.index : -1);
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();
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();
// 防止点击太快
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) {
const childList = this.value.template == 4 && this.currentOneCategory ? this.currentOneCategory.child_list : this.category.child_list;
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 || (childList && this.categoryId == childList.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.$api.sendRequest({
url: '/api/goodssku/pageByCategory',
data: {
category_id: categoryId,
page: 1,
page_size: this.pageSize,
order: order,
sort: sort
},
success: res => {
this.loading = false;
if (res.code == 0 && res.data && res.data.list) {
this.secondCategoryGoodsList = res.data.list;
} else {
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.$api.sendRequest({
url: '/api/goodssku/pageByCategory',
data: {
category_id: categoryId,
page: 1,
page_size: 10
},
success: res => {
this.template1Loading = false;
if (res.code == 0 && res.data && res.data.list) {
this.template1GoodsList = res.data.list;
} else {
this.template1GoodsList = [];
}
},
fail: () => {
this.template1Loading = false;
this.template1GoodsList = [];
}
});
},
/**
* 模板1: 根据二级分类ID分别加载各自的商品列表
* 用于 categoryLevel == 3 场景, 每个二级分类展示自己的商品
* @param {Number} categoryId 二级分类ID
*/
getTemplate1ChildGoods(categoryId) {
this.$set(this.template1ChildLoading, categoryId, true);
this.$api.sendRequest({
url: '/api/goodssku/pageByCategory',
data: {
category_id: categoryId,
page: 1,
page_size: 10
},
success: res => {
this.$set(this.template1ChildLoading, categoryId, false);
if (res.code == 0 && res.data && res.data.list) {
this.$set(this.template1ChildGoodsList, categoryId, res.data.list);
} else {
this.$set(this.template1ChildGoodsList, categoryId, []);
}
},
fail: () => {
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;
&::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;
}
}
.category-list {
display: grid !important;
grid-template-columns: repeat(3, 1fr) !important;
background: transparent;
margin: 0 20rpx;
border-radius: 0;
padding: 0;
gap: 20rpx 0;
.category-item {
width: 100% !important;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0;
box-sizing: border-box;
.img-box {
width: 70% !important;
height: 0;
padding-bottom: 70%;
margin: 0 auto;
overflow: hidden;
margin-bottom: 16rpx;
position: relative;
background: transparent !important;
border-radius: 0 !important;
image {
position: absolute;
width: 100% !important;
height: 100% !important;
left: 0 !important;
top: 0 !important;
transform: none !important;
}
}
.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);
}
// 模板1选中状态
&.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;
}
}
}
.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);
}
}
/* 单列布局 */
&.single-column {
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;
align-items: baseline;
flex: 1;
min-width: 0;
flex-wrap: wrap;
.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: 16rpx;
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;
}
}
// ===================== 模板4 商品分类样式(单列卡片式: 左图右文 + 右侧+按钮) =====================
&[data-template='4'] {
// 整体灰色背景让卡片有层次
background: #f4f6fa;
padding: 20rpx;
margin: 0;
.goods-item {
display: flex;
flex-direction: row;
align-items: stretch;
width: 100% !important;
max-width: 100%;
background: #fff;
border-radius: 16rpx;
overflow: hidden;
margin: 0 0 20rpx;
padding: 20rpx;
box-sizing: border-box;
position: relative;
}
// 商品图片: 左侧正方形
.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;
}
}
}
}
}
.categoty-goods-wrap {
display: flex;
flex-direction: column;
height: 100%;
.scroll-goods-view {
flex: 1;
height: 0;
transform: translateX(0px);
margin-top: -50rpx;
}
}
.screen-category-wrap {
display: flex;
.icon-unfold {
font-size: 24rpx;
color: #999;
padding: 0 0 0 20rpx;
}
}
.screen-category {
flex: 1;
width: 0;
padding: 0 0 10rpx 0;
white-space: nowrap;
height: 60rpx;
.item {
display: inline-block;
padding: 4rpx 24rpx;
background: #f5f5f5;
color: #666;
margin-right: 20rpx;
border-radius: 8rpx;
&.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: #fff !important;
padding: 16rpx 40rpx;
line-height: 1;
font-size: 28rpx;
border: 1rpx solid #eee;
border-radius: 12rpx !important;
&.selected {
background-color: $base-color !important;
color: #fff;
border-color: $base-color;
}
}
.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 {
display: flex;
flex-wrap: wrap;
padding: 20rpx 20rpx 10rpx;
background: #fff;
border-bottom: 1rpx solid #f0f0f0;
.second-category-btn {
padding: 14rpx 32rpx;
background: #f5f5f5;
color: #666;
border-radius: 40rpx;
font-size: 26rpx;
margin: 0 16rpx 16rpx 0;
max-width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.selected {
background: #FF6B35;
color: #fff;
}
}
}
.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;
.discount-price {
color: #FF6B35;
font-size: 30rpx;
font-weight: bold;
.unit {
font-size: 22rpx;
}
}
}
}
}
}
.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>